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

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