mirror of
https://github.com/hoshikawa2/nemo_guardrails_configuration.git
synced 2026-07-09 17:04:20 +00:00
Compare commits
3 Commits
171e0bb3af
...
25bc77f582
| Author | SHA1 | Date | |
|---|---|---|---|
| 25bc77f582 | |||
| f84ca5e641 | |||
| fd42bceb7a |
124
README.md
124
README.md
@@ -991,6 +991,65 @@ Transformar este repositório em um **package Python instalável**, que será:
|
|||||||
|
|
||||||
## ⚙️ Passo 1 — Criar ambiente
|
## ⚙️ Passo 1 — Criar ambiente
|
||||||
|
|
||||||
|
Será necessário configurar as variáveis de ambiente e o arquivo config.yml para utilizar o componente Nemo Guardrails deste projeto.
|
||||||
|
|
||||||
|
company_nemo_guardrails/config/config.yml
|
||||||
|
```bash
|
||||||
|
models:
|
||||||
|
- type: main
|
||||||
|
engine: openai
|
||||||
|
model: openai.gpt-4.1
|
||||||
|
api_key_env_var: OPENAI_API_KEY
|
||||||
|
parameters:
|
||||||
|
temperature: 0
|
||||||
|
base_url: https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1
|
||||||
|
max_tokens: 50 # 🔥 evita respostas longas do LLM
|
||||||
|
|
||||||
|
# 🔥 usado apenas se você chamar explicitamente no flow
|
||||||
|
- type: self_check_input
|
||||||
|
engine: openai
|
||||||
|
model: openai.gpt-4.1
|
||||||
|
api_key_env_var: OPENAI_API_KEY
|
||||||
|
parameters:
|
||||||
|
temperature: 0
|
||||||
|
base_url: https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1
|
||||||
|
|
||||||
|
|
||||||
|
rails:
|
||||||
|
input:
|
||||||
|
flows:
|
||||||
|
- check_input_terms
|
||||||
|
output:
|
||||||
|
flows:
|
||||||
|
- check_output_terms
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Configurar o parâmetro base_url para apontar para seu endpoint OpenAI.
|
||||||
|
|
||||||
|
|
||||||
|
Variáveis de Ambiente:
|
||||||
|
```text
|
||||||
|
# Endpoint OpenAI-compatible
|
||||||
|
export OPENAI_API_BASE=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1
|
||||||
|
export OPENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1
|
||||||
|
export OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
export OPENAI_MODEL=openai.gpt-4.1
|
||||||
|
|
||||||
|
# Tracing / Phoenix / OpenTelemetry
|
||||||
|
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:6006/v1/traces
|
||||||
|
export OTEL_SERVICE_NAME=nemo-guardrails-demo
|
||||||
|
export ENABLE_TRACING=false
|
||||||
|
|
||||||
|
# Modo demo: usa cliente fake se o proxy não estiver disponível
|
||||||
|
export USE_MOCK_LLM=false
|
||||||
|
export ALCADA_MAX_AJUSTE=50
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Passo 2 — Criar ambiente
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
source .venv/bin/activate # Linux/Mac
|
source .venv/bin/activate # Linux/Mac
|
||||||
@@ -1000,37 +1059,7 @@ source .venv/bin/activate # Linux/Mac
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📥 Passo 2 — Instalar dependências
|
## 📦 Passo 3 — Gerar o pacote
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install --upgrade pip
|
|
||||||
pip install -e ".[dev]"
|
|
||||||
```
|
|
||||||
|
|
||||||
Isso instala:
|
|
||||||
|
|
||||||
- o package local (`-e`)
|
|
||||||
- dependências do projeto
|
|
||||||
- ferramentas de teste e build (`pytest`, `build`, etc.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Passo 3 — Executar testes
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export OPENAI_API_KEY=sk-fake
|
|
||||||
pytest -v
|
|
||||||
```
|
|
||||||
|
|
||||||
✔️ Garante que:
|
|
||||||
|
|
||||||
- os guardrails estão funcionando
|
|
||||||
- o package está consistente
|
|
||||||
- o CI/CD não irá quebrar
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Passo 4 — Gerar o pacote
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /final_pkg
|
cd /final_pkg
|
||||||
@@ -1049,7 +1078,38 @@ dist/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📤 Passo 5 — Publicar (ex: Azure Artifacts)
|
## 📥 Passo 4 — Instalar dependências
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
```
|
||||||
|
|
||||||
|
Isso instala:
|
||||||
|
|
||||||
|
- o package local (`-e`)
|
||||||
|
- dependências do projeto
|
||||||
|
- ferramentas de teste e build (`pytest`, `build`, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Passo 5 — Executar testes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export OPENAI_API_KEY=sk-fake
|
||||||
|
pytest -v
|
||||||
|
```
|
||||||
|
|
||||||
|
✔️ Garante que:
|
||||||
|
|
||||||
|
- os guardrails estão funcionando
|
||||||
|
- o package está consistente
|
||||||
|
- o CI/CD não irá quebrar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## 📤 Passo 6 — Publicar (ex: Azure Artifacts)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m twine upload dist/*
|
python -m twine upload dist/*
|
||||||
|
|||||||
Binary file not shown.
@@ -79,15 +79,10 @@ async def detectar_out_of_scope_action(context: Optional[dict] = None, **kwargs)
|
|||||||
# -------------------------
|
# -------------------------
|
||||||
|
|
||||||
@action(is_system_action=True)
|
@action(is_system_action=True)
|
||||||
async def validar_alcada_action(context: Optional[dict] = None, **kwargs):
|
async def validar_alcada_action(ajuste_valor, limite, context: Optional[dict] = None, **kwargs):
|
||||||
print("🔥 ADJ")
|
print("🔥 ADJ")
|
||||||
|
|
||||||
payload = get_payload(context)
|
result = validar_alcada(valor=ajuste_valor, limite=limite)
|
||||||
ctx = payload.get("context", {})
|
|
||||||
|
|
||||||
valor = ctx.get("ajuste_valor", 0)
|
|
||||||
|
|
||||||
result = validar_alcada(valor)
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -101,7 +96,7 @@ async def verbalizacao_prematura_action(context: Optional[dict] = None, **kwargs
|
|||||||
payload = get_payload(context)
|
payload = get_payload(context)
|
||||||
ctx = payload.get("context", {})
|
ctx = payload.get("context", {})
|
||||||
|
|
||||||
resposta = ctx.get("resposta_llm", "")
|
resposta = ctx.get("last_bot_message", "")
|
||||||
|
|
||||||
result = verbalizacao_prematura(resposta, ctx)
|
result = verbalizacao_prematura(resposta, ctx)
|
||||||
|
|
||||||
@@ -117,7 +112,7 @@ async def validar_groundedness_action(context: Optional[dict] = None, **kwargs):
|
|||||||
payload = get_payload(context)
|
payload = get_payload(context)
|
||||||
ctx = payload.get("context", {})
|
ctx = payload.get("context", {})
|
||||||
|
|
||||||
resposta = ctx.get("resposta_llm", "")
|
resposta = ctx.get("last_bot_message", "")
|
||||||
|
|
||||||
result = validar_groundedness(resposta, ctx)
|
result = validar_groundedness(resposta, ctx)
|
||||||
|
|
||||||
@@ -136,14 +131,14 @@ async def supervisor_vas_avulso_action(context: Optional[dict] = None, **kwargs)
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
@action(is_system_action=True)
|
@action(is_system_action=True)
|
||||||
async def enforce_compliance_anatel_action(context=None, **kwargs):
|
async def enforce_compliance_anatel_action(requer_protocolo, context=None, **kwargs):
|
||||||
print("🔥 CMP")
|
print("🔥 CMP")
|
||||||
|
|
||||||
text = context.get("text") or context.get("user_message", "")
|
text = context.get("text") or context.get("user_message", "")
|
||||||
payload = get_payload(context)
|
payload = get_payload(context)
|
||||||
ctx = payload.get("context", {})
|
ctx = payload.get("context", {})
|
||||||
|
|
||||||
result = enforce_compliance_anatel(text, ctx)
|
result = enforce_compliance_anatel(requer_protocolo, text, ctx)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
models:
|
models:
|
||||||
- type: main
|
- type: main
|
||||||
engine: openai
|
engine: openai
|
||||||
model: gpt-5
|
model: openai.gpt-4.1
|
||||||
api_key_env_var: OPENAI_API_KEY
|
api_key_env_var: OPENAI_API_KEY
|
||||||
parameters:
|
parameters:
|
||||||
temperature: 0
|
temperature: 0
|
||||||
base_url: http://127.0.0.1:8051/v1
|
base_url: https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1
|
||||||
max_tokens: 50 # 🔥 evita respostas longas do LLM
|
max_tokens: 50 # 🔥 evita respostas longas do LLM
|
||||||
|
|
||||||
# 🔥 usado apenas se você chamar explicitamente no flow
|
# 🔥 usado apenas se você chamar explicitamente no flow
|
||||||
- type: self_check_input
|
- type: self_check_input
|
||||||
engine: openai
|
engine: openai
|
||||||
model: openai.gpt-oss-120b
|
model: openai.gpt-4.1
|
||||||
api_key_env_var: OPENAI_API_KEY
|
api_key_env_var: OPENAI_API_KEY
|
||||||
parameters:
|
parameters:
|
||||||
temperature: 0
|
temperature: 0
|
||||||
base_url: http://127.0.0.1:8051/v1
|
base_url: https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1
|
||||||
|
|
||||||
|
|
||||||
rails:
|
rails:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ define flow check_input_terms
|
|||||||
$ok_oos = execute detectar_out_of_scope_action
|
$ok_oos = execute detectar_out_of_scope_action
|
||||||
|
|
||||||
# 💰 Regras de negócio
|
# 💰 Regras de negócio
|
||||||
$ok_adj = execute validar_alcada_action
|
$ok_adj = execute validar_alcada_action(ajuste_valor=$ajuste_valor,limite=$limite)
|
||||||
|
|
||||||
# 🧠 Contexto
|
# 🧠 Contexto
|
||||||
$ok_hist = execute validar_consistencia_historica_action
|
$ok_hist = execute validar_consistencia_historica_action
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ define flow check_output_terms
|
|||||||
$ok_sup = execute supervisor_vas_avulso_action
|
$ok_sup = execute supervisor_vas_avulso_action
|
||||||
|
|
||||||
# 📡 Compliance
|
# 📡 Compliance
|
||||||
$ok_cmp = execute enforce_compliance_anatel_action
|
$ok_cmp = execute enforce_compliance_anatel_action(requer_protocolo=$requer_protocolo)
|
||||||
|
|
||||||
# 📊 Métricas
|
# 📊 Métricas
|
||||||
$ok_tcr = execute calcular_tcr_action
|
$ok_tcr = execute calcular_tcr_action
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ def mask_pii(text:str)->RailResult:
|
|||||||
text=re.sub(r'(?i)(senha\s*[:=]?\s*)\S+',r'\1[SENHA_MASCARADA]',text)
|
text=re.sub(r'(?i)(senha\s*[:=]?\s*)\S+',r'\1[SENHA_MASCARADA]',text)
|
||||||
return RailResult(True,'PII mascarada' if text!=original else 'Nenhuma PII detectada',text,'MSK','regex')
|
return RailResult(True,'PII mascarada' if text!=original else 'Nenhuma PII detectada',text,'MSK','regex')
|
||||||
|
|
||||||
def enforce_compliance_anatel(text:str, context:dict)->RailResult:
|
def enforce_compliance_anatel(requer_protocolo, text:str, context:dict)->RailResult:
|
||||||
with span('rail.CMP', mechanism='regex'):
|
with span('rail.CMP', mechanism='regex'):
|
||||||
requer=context.get('tipo_fluxo')=='ajuste' or context.get('requer_protocolo') is True
|
# requer=context.get('tipo_fluxo')=='ajuste' or context.get('requer_protocolo') is True
|
||||||
|
requer=requer_protocolo
|
||||||
if not requer: return RailResult(True,'Compliance Anatel não aplicável',text,'CMP','regex')
|
if not requer: return RailResult(True,'Compliance Anatel não aplicável',text,'CMP','regex')
|
||||||
has_protocol=bool(re.search(r'(?i)\bprotocolo\b[:\s-]*\d{6,}',text))
|
has_protocol=bool(re.search(r'(?i)\bprotocolo\b[:\s-]*\d{6,}',text))
|
||||||
if not has_protocol: return RailResult(False,'Resposta de ajuste sem número de protocolo',text,'CMP','regex')
|
if not has_protocol: return RailResult(False,'Resposta de ajuste sem número de protocolo',text,'CMP','regex')
|
||||||
@@ -21,8 +22,8 @@ def enforce_compliance_anatel(text:str, context:dict)->RailResult:
|
|||||||
|
|
||||||
def validar_alcada(valor:float, limite:float=50.0)->RailResult:
|
def validar_alcada(valor:float, limite:float=50.0)->RailResult:
|
||||||
with span('rail.ADJ', mechanism='python'):
|
with span('rail.ADJ', mechanism='python'):
|
||||||
if valor>limite: return RailResult(False,f'Valor R$ {valor:.2f} excede alçada de R$ {limite:.2f}; escalar para ATH',code='ADJ',mechanism='python')
|
if valor>limite: return RailResult(False,f'Valor R$ {valor} excede alçada de R$ {limite}; escalar para ATH',code='ADJ',mechanism='python')
|
||||||
return RailResult(True,f'Valor R$ {valor:.2f} dentro da alçada',code='ADJ',mechanism='python')
|
return RailResult(True,f'Valor R$ {valor} dentro da alçada',code='ADJ',mechanism='python')
|
||||||
|
|
||||||
def calcular_tcr(status:str)->RailResult:
|
def calcular_tcr(status:str)->RailResult:
|
||||||
status=status.lower(); categoria='Indefinido'
|
status=status.lower(); categoria='Indefinido'
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ def create_rails():
|
|||||||
rails = LLMRails(config)
|
rails = LLMRails(config)
|
||||||
|
|
||||||
# 👇 opcional mas recomendado (garante registro)
|
# 👇 opcional mas recomendado (garante registro)
|
||||||
rails.register_actions(actions)
|
rails.register_action(actions)
|
||||||
|
|
||||||
return rails
|
return rails
|
||||||
@@ -1,13 +1,58 @@
|
|||||||
|
|
||||||
from company_nemo_guardrails import create_rails
|
from company_nemo_guardrails import create_rails
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
tests = []
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "context",
|
||||||
|
"content": {"ajuste_valor": 1000, "limite": 50}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "quero um desconto de R$ 1000,00 no meu plano"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
tests.append(messages)
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "context",
|
||||||
|
"content": {"requer_protocolo": True}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "quero cancelar o plano"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "o plano foi cancelado sem protocolo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "meu cpf é 169.323.728-00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
tests.append(messages)
|
||||||
|
|
||||||
rails = create_rails()
|
rails = create_rails()
|
||||||
|
|
||||||
response = rails.generate(
|
response = rails.generate(
|
||||||
messages=[{"role": "user", "content": "Meu CPF é 123.456.789-00"}],
|
messages=messages,
|
||||||
options={"log": {"activated_rails": True}}
|
options={
|
||||||
|
"log": {"activated_rails": True}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
print(response)
|
print(response)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
296
final_pkg/examples/test.py
Normal file
296
final_pkg/examples/test.py
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
from company_nemo_guardrails import create_rails
|
||||||
|
|
||||||
|
|
||||||
|
def extrair_resultados(response):
|
||||||
|
resultados = []
|
||||||
|
|
||||||
|
if not response.log:
|
||||||
|
return resultados
|
||||||
|
|
||||||
|
for rail in response.log.activated_rails:
|
||||||
|
for action in rail.executed_actions:
|
||||||
|
r = action.return_value
|
||||||
|
if r:
|
||||||
|
resultados.append({
|
||||||
|
"action": action.action_name,
|
||||||
|
"code": r.code,
|
||||||
|
"allowed": r.allowed,
|
||||||
|
"reason": r.reason
|
||||||
|
})
|
||||||
|
|
||||||
|
return resultados
|
||||||
|
|
||||||
|
|
||||||
|
def print_result(test, response):
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print(f"🧪 TESTE: {test['name']}")
|
||||||
|
|
||||||
|
print("\n👤 INPUT:")
|
||||||
|
print(test["input"])
|
||||||
|
|
||||||
|
print("\n🤖 LLM OUTPUT (simulado):")
|
||||||
|
print(test["llm_output"])
|
||||||
|
|
||||||
|
resultados = extrair_resultados(response)
|
||||||
|
|
||||||
|
print("\n📊 RAILS:")
|
||||||
|
for r in resultados:
|
||||||
|
status = "✅" if r["allowed"] else "❌"
|
||||||
|
print(f"{status} {r['code']} ({r['action']}) -> {r['reason']}")
|
||||||
|
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
|
||||||
|
def rodar_testes():
|
||||||
|
rails = create_rails()
|
||||||
|
|
||||||
|
tests = []
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 🔐 11. PII MASK (MSK)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
# CPF simples
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu cpf é 123.456.789-00"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# CPF sem máscara
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu cpf é 12345678900"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# telefone celular
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu telefone é (11) 91234-5678"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# telefone sem formatação
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu telefone é 11912345678"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# email
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu email é teste@gmail.com"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# múltiplos PII na mesma frase
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu cpf é 123.456.789-00 e meu telefone é 11912345678"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# nome + cpf (semi sensível)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "me chamo João Silva e meu cpf é 12345678900"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# endereço
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "moro na rua das flores 123 em são paulo"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# cartão de crédito (forte)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu cartão é 4111 1111 1111 1111"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# RG
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu rg é 12.345.678-9"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# texto sem PII (controle)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "quero saber o valor da minha fatura"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# PII misturado com intenção válida
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu cpf é 12345678900, quero cancelar o plano"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# tentativa de burlar (espaçado)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu cpf é 1 2 3 4 5 6 7 8 9 0 0"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# tentativa com texto
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "meu cpf é um dois três quatro cinco seis sete oito nove zero zero"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# PII em resposta do assistant (output rail)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "qual meu cpf?"},
|
||||||
|
{"role": "assistant", "content": "seu cpf é 123.456.789-00"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 💰 1. ALCADA (ADJ)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
# acima da alçada
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {"ajuste_valor": 1000, "limite": 50}},
|
||||||
|
{"role": "user", "content": "quero um desconto de R$ 1000"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# dentro da alçada
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {"ajuste_valor": 20, "limite": 50}},
|
||||||
|
{"role": "user", "content": "quero um desconto de R$ 20"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 📞 2. COMPLIANCE ANATEL (CMP)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
# execução sem protocolo (DEVE FALHAR)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {"requer_protocolo": True}},
|
||||||
|
{"role": "user", "content": "quero cancelar o plano"},
|
||||||
|
{"role": "assistant", "content": "seu plano foi cancelado"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# execução com protocolo (DEVE PASSAR)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {"requer_protocolo": True}},
|
||||||
|
{"role": "user", "content": "quero cancelar o plano"},
|
||||||
|
{"role": "assistant", "content": "seu plano foi cancelado. protocolo 123456"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# negativa (DEVE PASSAR)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {"requer_protocolo": True}},
|
||||||
|
{"role": "user", "content": "quero cancelar o plano"},
|
||||||
|
{"role": "assistant", "content": "não posso cancelar seu plano, entre em contato com suporte"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# orientação (DEVE PASSAR)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {"requer_protocolo": True}},
|
||||||
|
{"role": "user", "content": "quero cancelar o plano"},
|
||||||
|
{"role": "assistant", "content": "você pode solicitar o cancelamento pelo app"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# ⚠️ 3. VERBALIZAÇÃO PREMATURA (REVPREC)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "quero desconto"},
|
||||||
|
{"role": "assistant", "content": "já apliquei o desconto na sua fatura"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 🧠 4. GROUNDEDNESS (GND)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
# sem dados
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {"has_invoice_data": False}},
|
||||||
|
{"role": "user", "content": "qual o valor da minha fatura"},
|
||||||
|
{"role": "assistant", "content": "sua fatura é R$ 200"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# com dados (controle)
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {"has_invoice_data": True}},
|
||||||
|
{"role": "user", "content": "qual o valor da minha fatura"},
|
||||||
|
{"role": "assistant", "content": "sua fatura é R$ 200"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 🔁 5. LOOP
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "quero desconto"},
|
||||||
|
{"role": "user", "content": "quero desconto"},
|
||||||
|
{"role": "user", "content": "quero desconto"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 🤬 6. TOXICIDADE
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "você é um idiota"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 📉 7. SEMÂNTICA (SEMAC)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "quero cancelar o plano"},
|
||||||
|
{"role": "assistant", "content": "você solicitou upgrade de plano"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 🧠 8. HISTÓRICO (HIST)
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {"contestacao_anterior": "procedente_confirmada"}},
|
||||||
|
{"role": "user", "content": "quero contestar novamente a cobrança"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 🤖 9. FALLBACK
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "asdfghjkl"},
|
||||||
|
{"role": "assistant", "content": "não posso ajudar com isso"}
|
||||||
|
])
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# 📊 10. TCR / MÉTRICAS
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
tests.append([
|
||||||
|
{"role": "context", "content": {}},
|
||||||
|
{"role": "user", "content": "quero cancelar o plano"},
|
||||||
|
{"role": "assistant", "content": "cancelamento realizado com sucesso"}
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
for i, messages in enumerate(tests):
|
||||||
|
print(f"\n=== TESTE {i+1} ===")
|
||||||
|
print("MESSAGES:", messages)
|
||||||
|
response = rails.generate(
|
||||||
|
messages=messages,
|
||||||
|
options={"log": {"activated_rails": True}}
|
||||||
|
)
|
||||||
|
|
||||||
|
for rail in response.log.activated_rails:
|
||||||
|
for action in rail.executed_actions:
|
||||||
|
r = action.return_value
|
||||||
|
if r:
|
||||||
|
print(f"{r.code} -> {r.allowed} | {r.reason} | {r.sanitized_text}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
rodar_testes()
|
||||||
Reference in New Issue
Block a user