mirror of
https://github.com/hoshikawa2/nemo_guardrails_configuration.git
synced 2026-07-09 17:04:20 +00:00
First Commit
This commit is contained in:
155
README.md
155
README.md
@@ -485,7 +485,18 @@ ALCADA_MAX_AJUSTE=50
|
|||||||
|
|
||||||
## 7. Configuração do NeMo Guardrails
|
## 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
|
```yaml
|
||||||
models:
|
models:
|
||||||
@@ -508,7 +519,147 @@ rails:
|
|||||||
- self check output
|
- 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.
|
Segue abaixo a estruturação dos exemplos para o entendimento do funcionamento da tecnologia.
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,42 @@ models:
|
|||||||
- type: main
|
- type: main
|
||||||
engine: openai
|
engine: openai
|
||||||
model: gpt-4.1
|
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:
|
rails:
|
||||||
input:
|
input:
|
||||||
flows: [check toxicidade, check out of scope]
|
flows:
|
||||||
|
- input_check_toxicidade
|
||||||
|
- input_check_out_of_scope
|
||||||
|
|
||||||
output:
|
output:
|
||||||
flows: [check verbalizacao prematura, check groundedness]
|
flows:
|
||||||
|
- output_check_verbalizacao_prematura
|
||||||
|
- output_check_groundedness
|
||||||
|
- output_check_cmp
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
define flow check toxicidade
|
define flow input_check_toxicidade
|
||||||
pass
|
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"
|
||||||
@@ -1,5 +1,27 @@
|
|||||||
define flow check verbalizacao prematura
|
define flow output_check_cmp
|
||||||
pass
|
|
||||||
|
|
||||||
define flow check groundedness
|
$r = execute cmp(resposta="dummy")
|
||||||
pass
|
|
||||||
|
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"
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
from .deterministic_rails import mask_pii, validar_alcada, enforce_compliance_anatel
|
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
|
from .judges import avaliar_qualidade_resposta
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -7,7 +13,9 @@ import json
|
|||||||
def executar_atendimento(user_input: str, context: dict):
|
def executar_atendimento(user_input: str, context: dict):
|
||||||
steps = []
|
steps = []
|
||||||
|
|
||||||
# INPUT RAILS
|
# =========================
|
||||||
|
# 🔹 INPUT RAILS
|
||||||
|
# =========================
|
||||||
r = mask_pii(user_input)
|
r = mask_pii(user_input)
|
||||||
steps.append(r)
|
steps.append(r)
|
||||||
text = r.sanitized_text or user_input
|
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]:
|
for rail in [detectar_toxicidade, detectar_out_of_scope]:
|
||||||
r = rail(text)
|
r = rail(text)
|
||||||
steps.append(r)
|
steps.append(r)
|
||||||
|
|
||||||
if not r.allowed:
|
if not r.allowed:
|
||||||
return {
|
return {
|
||||||
"allowed": False,
|
"allowed": False,
|
||||||
"blocked_by": r.code,
|
"blocked_by": r.code,
|
||||||
|
"response": None,
|
||||||
"steps": steps
|
"steps": steps
|
||||||
}
|
}
|
||||||
|
|
||||||
# PYTHON RULE
|
# =========================
|
||||||
|
# 🔹 PYTHON RULE (CRÍTICA)
|
||||||
|
# =========================
|
||||||
if "ajuste_valor" in context:
|
if "ajuste_valor" in context:
|
||||||
r = validar_alcada(context["ajuste_valor"])
|
r = validar_alcada(context["ajuste_valor"])
|
||||||
steps.append(r)
|
steps.append(r)
|
||||||
|
|
||||||
if not r.allowed:
|
if not r.allowed:
|
||||||
return {
|
return {
|
||||||
"allowed": False,
|
"allowed": False,
|
||||||
"blocked_by": r.code,
|
"blocked_by": r.code,
|
||||||
|
"response": None,
|
||||||
"steps": steps
|
"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 [
|
# 🔹 OUTPUT RAILS (BLOQUEANTES)
|
||||||
verbalizacao_prematura(resposta, context),
|
# =========================
|
||||||
|
output_rails = [
|
||||||
enforce_compliance_anatel(resposta, context),
|
enforce_compliance_anatel(resposta, context),
|
||||||
|
verbalizacao_prematura(resposta, context),
|
||||||
validar_groundedness(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)
|
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 {
|
return {
|
||||||
"allowed": all(s.allowed for s in steps if s.code != "RQLT"),
|
"allowed": allowed,
|
||||||
"response": resposta,
|
"response": resposta,
|
||||||
"steps": steps
|
"steps": steps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# =========================
|
# =========================
|
||||||
# 🔥 EXECUÇÃO DIRETA
|
# 🔥 PRINT FORMATADO
|
||||||
# =========================
|
# =========================
|
||||||
def print_result(result):
|
def print_result(result):
|
||||||
print("\n" + "=" * 80)
|
print("\n" + "=" * 80)
|
||||||
@@ -81,7 +136,6 @@ def print_result(result):
|
|||||||
|
|
||||||
print("=" * 80)
|
print("=" * 80)
|
||||||
|
|
||||||
# JSON final (para integração)
|
|
||||||
print("\n📦 JSON OUTPUT:")
|
print("\n📦 JSON OUTPUT:")
|
||||||
print(json.dumps({
|
print(json.dumps({
|
||||||
"allowed": result["allowed"],
|
"allowed": result["allowed"],
|
||||||
@@ -99,8 +153,10 @@ def print_result(result):
|
|||||||
}, indent=2, ensure_ascii=False))
|
}, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# 🔥 EXECUÇÃO DIRETA
|
||||||
|
# =========================
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 🔧 EXEMPLO DE EXECUÇÃO
|
|
||||||
|
|
||||||
user_input = "Meu CPF é 123.456.789-00 e quero ajuste de 20 reais"
|
user_input = "Meu CPF é 123.456.789-00 e quero ajuste de 20 reais"
|
||||||
|
|
||||||
|
|||||||
@@ -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.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.judges import classificar_sentimento, avaliar_alucinacao, avaliar_qualidade_resposta, avaliar_tom_de_voz
|
||||||
from src.registry import load_guardrail_registry
|
from src.registry import load_guardrail_registry
|
||||||
|
from src.app import executar_atendimento
|
||||||
|
|
||||||
def log_rail(codigo,item,entrada,result):
|
def log_rail(codigo,item,entrada,result):
|
||||||
print('\n'+'='*90)
|
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
|
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():
|
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
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user