First Commit

This commit is contained in:
2026-04-28 09:30:23 -03:00
parent 6c574c8929
commit fa071624eb
6 changed files with 325 additions and 27 deletions

155
README.md
View File

@@ -485,7 +485,18 @@ ALCADA_MAX_AJUSTE=50
## 7. Configuração do NeMo Guardrails
Arquivo: `config/config.yml`
A camada de configuração no NeMo Guardrails é responsável por definir o comportamento do sistema sem alterar código Python.
Ela é composta por dois arquivos principais:
| Arquivo | Função |
|------------|----------------------------------------------------------------|
| config.yml | Configuração estrutural (modelo, instruções, bindings) |
| rails.co | Definição de fluxos, regras e comportamento conversacional |
### Arquivo: `config/config.yml`
```yaml
models:
@@ -508,7 +519,147 @@ rails:
- self check output
```
## 8. Rails criados a partir da planilha
O config.yml é o arquivo central de configuração do NeMo Guardrails.
Ele define:
qual modelo será utilizado
como o agente deve se comportar
quais fluxos (rails) serão executados
como o sistema se conecta ao motor de LLM
>**Nota:** Em termos simples, o config.yml define a estrutura e as regras globais do agente, antes mesmo da execução dos fluxos do .co
Dentro do seu fluxo:
User Input
config.yml ← (define regras globais)
rails.co ← (define comportamento dinâmico)
LLM / Python / Supervisor
### Arquivo: `config/guardrails.yaml`
```yaml
guardrails:
- {codigo: MSK, item: "PII Masking", mecanismo: regex}
- {codigo: CMP, item: "Compliance Anatel", mecanismo: regex}
- {codigo: ADJ, item: "Alçada de Ajuste", mecanismo: python}
- {codigo: REVPREC_SUP, item: "Supervisor VAS Avulso", mecanismo: llm_supervisor}
- {codigo: TCR, item: "Conclusão de Tarefa", mecanismo: python}
- {codigo: REVPREC, item: "Verbalização Prematura", mecanismo: llm_rail}
- {codigo: FALLBACK, item: "Fallback Não Entendi", mecanismo: python}
- {codigo: VIOL, item: "Violação de Guardrails", mecanismo: python}
- {codigo: TOX, item: "Toxicidade", mecanismo: llm_rail}
- {codigo: OOS, item: "Out-of-Scope", mecanismo: llm_rail}
- {codigo: GND, item: "Groundedness", mecanismo: llm_rail}
- {codigo: HIST, item: "Consistência Histórica", mecanismo: python}
- {codigo: PMPTK, item: "Prompt Tokens", mecanismo: python}
- {codigo: EFIC, item: "Eficiência NLU", mecanismo: python}
- {codigo: NO-M, item: "No-Match RAG", mecanismo: python}
- {codigo: CSI, item: "Sentimento", mecanismo: llm_judge}
- {codigo: ALUC, item: "Taxa de Alucinação", mecanismo: llm_judge}
- {codigo: VLOOP, item: "Volume de Loops", mecanismo: python}
- {codigo: MSIZE, item: "Tamanho de Mensagem", mecanismo: python}
- {codigo: RQLT, item: "Qualidade da Resposta", mecanismo: llm_judge}
- {codigo: VCTN, item: "Tom de Voz", mecanismo: llm_judge}
- {codigo: REVPREC_METRIC, item: "Precisão e Revocação", mecanismo: python}
- {codigo: SEMAC, item: "Acurácia Semântica STT", mecanismo: python}
```
### Arquivo: `rails/input.co`
```yaml
define flow check toxicidade
user input
bot evaluate toxicity
if not allowed
bot refuse
define flow check out of scope
user input
bot evaluate out_of_scope
if not allowed
bot respond "Posso ajudar apenas com assuntos relacionados a telecom."
```
### Arquivo: `rails/output.co`
```yaml
define flow check verbalizacao prematura
bot message
if contains "já fiz" or contains "já apliquei"
bot revise
define flow check groundedness
bot message
bot evaluate groundedness
if not grounded
bot revise
define flow check compliance anatel
bot message
# CMP real tratado em Python
bot continue
```
## Definições
### define flow
Cria um fluxo de execução.
define flow self check input
Representa:
- um guardrail
- uma sequência de validações
### Eventos
Captura a entrada do usuário.
- user input
### bot message
Captura a resposta do LLM.
- bot message
### Ações com LLM
Avaliação semântica
- bot evaluate toxicity
Função:
chamar LLM para classificar conteúdo
Exemplo:
- toxicidade
- out-of-scope
- groundedness
### Condições
if not allowed
bot refuse
Função:
- bloquear fluxo
- redirecionar resposta
## 8. Exemplos de rails criados a partir da planilha
Segue abaixo a estruturação dos exemplos para o entendimento do funcionamento da tecnologia.

View File

@@ -2,8 +2,42 @@ models:
- type: main
engine: openai
model: gpt-4.1
parameters:
temperature: 0.2
max_tokens: 800
# 🔧 MUITO IMPORTANTE
actions:
- name: mask_pii
function: config.actions.action_mask_pii
- name: detectar_toxicidade
function: config.actions.action_detectar_toxicidade
- name: detectar_out_of_scope
function: config.actions.action_detectar_out_of_scope
- name: validar_alcada
function: config.actions.action_validar_alcada
- name: validar_groundedness
function: config.actions.action_validar_groundedness
- name: verbalizacao_prematura
function: config.actions.action_verbalizacao_prematura
- name: cmp
function: config.actions.action_cmp
# 🧠 Organização dos rails
rails:
input:
flows: [check toxicidade, check out of scope]
flows:
- input_check_toxicidade
- input_check_out_of_scope
output:
flows: [check verbalizacao prematura, check groundedness]
flows:
- output_check_verbalizacao_prematura
- output_check_groundedness
- output_check_cmp

View File

@@ -1,5 +1,12 @@
define flow check toxicidade
pass
define flow input_check_toxicidade
user input
$r = execute detectar_toxicidade(text=$user_input)
if $r.allowed == False
bot respond "BLOCKED:TOX"
define flow check out of scope
pass
define flow input_check_out_of_scope
user input
$r = execute detectar_out_of_scope(text=$user_input)
if $r.allowed == False
bot respond "BLOCKED:OOS"

View File

@@ -1,5 +1,27 @@
define flow check verbalizacao prematura
pass
define flow output_check_cmp
define flow check groundedness
pass
$r = execute cmp(resposta="dummy")
if $r.allowed == False
bot respond "BLOCKED:CMP"
bot respond "OK"
define flow output_check_verbalizacao_prematura
$r = execute verbalizacao_prematura(resposta="dummy")
if $r.allowed == False
bot respond "BLOCKED:REVPREC"
bot respond "OK"
define flow output_check_groundedness
$r = execute validar_groundedness(resposta="dummy")
if $r.allowed == False
bot respond "BLOCKED:GND"
bot respond "OK"

View File

@@ -1,5 +1,11 @@
from .deterministic_rails import mask_pii, validar_alcada, enforce_compliance_anatel
from .llm_rails import detectar_toxicidade, detectar_out_of_scope, validar_groundedness, verbalizacao_prematura, supervisor_vas_avulso
from .llm_rails import (
detectar_toxicidade,
detectar_out_of_scope,
validar_groundedness,
verbalizacao_prematura,
supervisor_vas_avulso
)
from .judges import avaliar_qualidade_resposta
import json
@@ -7,7 +13,9 @@ import json
def executar_atendimento(user_input: str, context: dict):
steps = []
# INPUT RAILS
# =========================
# 🔹 INPUT RAILS
# =========================
r = mask_pii(user_input)
steps.append(r)
text = r.sanitized_text or user_input
@@ -15,46 +23,93 @@ def executar_atendimento(user_input: str, context: dict):
for rail in [detectar_toxicidade, detectar_out_of_scope]:
r = rail(text)
steps.append(r)
if not r.allowed:
return {
"allowed": False,
"blocked_by": r.code,
"response": None,
"steps": steps
}
# PYTHON RULE
# =========================
# 🔹 PYTHON RULE (CRÍTICA)
# =========================
if "ajuste_valor" in context:
r = validar_alcada(context["ajuste_valor"])
steps.append(r)
if not r.allowed:
return {
"allowed": False,
"blocked_by": r.code,
"response": None,
"steps": steps
}
# LLM RESPONSE (simulada ou real)
resposta = context.get("resposta_llm", "Resposta simulada do agente.")
# =========================
# 🔹 LLM RESPONSE
# =========================
resposta = context.get(
"resposta_llm",
"Resposta simulada do agente."
)
# OUTPUT + JUDGES + SUPERVISOR
for r in [
verbalizacao_prematura(resposta, context),
# =========================
# 🔹 OUTPUT RAILS (BLOQUEANTES)
# =========================
output_rails = [
enforce_compliance_anatel(resposta, context),
verbalizacao_prematura(resposta, context),
validar_groundedness(resposta, context),
avaliar_qualidade_resposta(user_input, resposta),
supervisor_vas_avulso(context.get("supervisor_payload", {}))
]:
]
for r in output_rails:
steps.append(r)
# 🔥 NÃO bloquear groundedness automaticamente
if not r.allowed and r.code != "GND":
return {
"allowed": False,
"blocked_by": r.code,
"response": None,
"steps": steps
}
# =========================
# 🔹 JUDGE (NÃO BLOQUEIA)
# =========================
r_quality = avaliar_qualidade_resposta(user_input, resposta)
steps.append(r_quality)
# =========================
# 🔹 SUPERVISOR (AUDITORIA)
# =========================
r_supervisor = supervisor_vas_avulso(
context.get("supervisor_payload", {})
)
steps.append(r_supervisor)
# =========================
# 🔹 RESULTADO FINAL
# =========================
BLOCKING_CODES = {"CMP", "ADJ", "REVPREC"}
allowed = all(
s.allowed for s in steps
if s.code in BLOCKING_CODES
)
return {
"allowed": all(s.allowed for s in steps if s.code != "RQLT"),
"allowed": allowed,
"response": resposta,
"steps": steps
}
# =========================
# 🔥 EXECUÇÃO DIRETA
# 🔥 PRINT FORMATADO
# =========================
def print_result(result):
print("\n" + "=" * 80)
@@ -81,7 +136,6 @@ def print_result(result):
print("=" * 80)
# JSON final (para integração)
print("\n📦 JSON OUTPUT:")
print(json.dumps({
"allowed": result["allowed"],
@@ -99,8 +153,10 @@ def print_result(result):
}, indent=2, ensure_ascii=False))
# =========================
# 🔥 EXECUÇÃO DIRETA
# =========================
if __name__ == "__main__":
# 🔧 EXEMPLO DE EXECUÇÃO
user_input = "Meu CPF é 123.456.789-00 e quero ajuste de 20 reais"

View File

@@ -6,6 +6,7 @@ from src.deterministic_rails import *
from src.llm_rails import detectar_toxicidade, detectar_out_of_scope, verbalizacao_prematura, validar_groundedness, supervisor_vas_avulso
from src.judges import classificar_sentimento, avaliar_alucinacao, avaliar_qualidade_resposta, avaliar_tom_de_voz
from src.registry import load_guardrail_registry
from src.app import executar_atendimento
def log_rail(codigo,item,entrada,result):
print('\n'+'='*90)
@@ -160,3 +161,30 @@ def test_semac_acuracia_ok():
r=avaliar_acuracia_semantica('cancelar serviço','cancelar serviço'); log_rail('SEMAC','Acurácia STT - ok','cancelar serviço',r); assert r.allowed is True
def test_semac_acuracia_baixa():
r=avaliar_acuracia_semantica('ativar plano','cancelar serviço'); log_rail('SEMAC','Acurácia STT - baixa','ativar plano vs cancelar serviço',r); assert r.allowed is False
def test_fluxo_completo_bloqueia_cmp():
result = executar_atendimento(
"Quero ajuste",
{
"tipo_fluxo": "ajuste",
"requer_protocolo": True,
"resposta_llm": "Ajuste realizado na sua fatura.", # ❌ sem protocolo
}
)
assert result["allowed"] is False
assert result["blocked_by"] == "CMP"
def test_fluxo_completo_sucesso():
result = executar_atendimento(
"Quero ajuste",
{
"tipo_fluxo": "ajuste",
"requer_protocolo": True,
"resposta_llm": "Ajuste realizado. Protocolo: 123",
"chunks_rag": ["ajuste realizado protocolo"],
"supervisor_payload": {}
}
)
assert result["allowed"] is True