First Commit

This commit is contained in:
2026-04-28 10:54:34 -03:00
parent fa071624eb
commit d3b59cb6d6
7 changed files with 226 additions and 76 deletions

View File

@@ -1,43 +1,11 @@
models:
- type: main
engine: openai
model: gpt-4.1
parameters:
temperature: 0.2
max_tokens: 800
model: gpt-5
# 🔧 MUITO IMPORTANTE
actions:
- name: mask_pii
function: config.actions.action_mask_pii
colang_version: "1.0"
- 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:
dialog:
flows:
- input_check_toxicidade
- input_check_out_of_scope
output:
flows:
- output_check_verbalizacao_prematura
- output_check_groundedness
- output_check_cmp
- main

View File

@@ -0,0 +1,7 @@
define user express input
$input_text
define flow main
user express input
execute executar_pipeline_validacoes
bot say $nemo_response

View File

@@ -1,12 +0,0 @@
define flow input_check_toxicidade
user input
$r = execute detectar_toxicidade(text=$user_input)
if $r.allowed == False
bot respond "BLOCKED:TOX"
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,27 +0,0 @@
define flow output_check_cmp
$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

@@ -0,0 +1,149 @@
# src/actions.py
import json
import uuid
from .llm_rails import (
detectar_toxicidade,
detectar_out_of_scope,
verbalizacao_prematura,
validar_groundedness,
)
from .deterministic_rails import validar_alcada
try:
from .judges import avaliar_qualidade_resposta
except Exception:
avaliar_qualidade_resposta = None
PIPELINE_RESULTS = {}
def extrair_payload(context: dict) -> dict:
try:
messages = context.get("messages", [])
content = messages[-1]["content"]
return json.loads(content)
except Exception:
return {}
def add_trace(trace, label, result):
trace.append({
"rail": label,
"allowed": result.allowed,
"reason": result.reason,
"code": getattr(result, "code", label),
"mechanism": getattr(result, "mechanism", ""),
"data": getattr(result, "data", {}),
})
def executar_pipeline_validacoes(context: dict):
payload = extrair_payload(context)
request_id = payload.get("request_id") or str(uuid.uuid4())
input_text = payload.get("input_text", "")
ctx = payload.get("context", {}) or {}
trace = []
failures = []
# =========================
# INPUT RAILS - LLM
# =========================
r_tox = detectar_toxicidade(input_text)
add_trace(trace, "TOX", r_tox)
if not r_tox.allowed:
failures.append(("TOX", r_tox.reason))
r_oos = detectar_out_of_scope(input_text)
add_trace(trace, "OOS", r_oos)
if not r_oos.allowed:
failures.append(("OOS", r_oos.reason))
# =========================
# BUSINESS RAIL - DETERMINISTIC
# =========================
valor = ctx.get("ajuste_valor")
r_adj = validar_alcada(valor)
add_trace(trace, "ADJ", r_adj)
if not r_adj.allowed:
failures.append(("ADJ", r_adj.reason))
# =========================
# LLM RESPONSE
# =========================
final_response = ctx.get("resposta_llm", "")
trace.append({
"step": "LLM",
"allowed": True,
"input": input_text,
"output_preview": final_response[:200],
"mechanism": "provided_response_or_proxy",
})
# =========================
# OUTPUT RAILS - LLM
# =========================
r_revprec = verbalizacao_prematura(final_response, ctx)
add_trace(trace, "REVPREC", r_revprec)
if not r_revprec.allowed:
failures.append(("REVPREC", r_revprec.reason))
r_gnd = validar_groundedness(final_response, ctx)
add_trace(trace, "GND", r_gnd)
if not r_gnd.allowed:
failures.append(("GND", r_gnd.reason))
# =========================
# OPTIONAL JUDGE / CMP
# =========================
if avaliar_qualidade_resposta is not None:
r_cmp = avaliar_qualidade_resposta(final_response)
add_trace(trace, "CMP", r_cmp)
if not r_cmp.allowed:
failures.append(("CMP", r_cmp.reason))
else:
trace.append({
"rail": "CMP",
"allowed": True,
"reason": "CMP não configurado",
"mechanism": "skipped",
"data": {},
})
# =========================
# FINAL DECISION
# =========================
blocked = len(failures) > 0
if blocked:
first_code, first_reason = failures[0]
nemo_response = f"BLOCKED:{first_code} - {first_reason}"
else:
nemo_response = final_response
result = {
"allowed": not blocked,
"label": "CONFORME" if not blocked else "PROBLEMA",
"response": final_response,
"reason": nemo_response if blocked else "",
"failures": failures,
"trace": trace,
}
PIPELINE_RESULTS[request_id] = result
return {
"nemo_response": nemo_response
}

View File

@@ -0,0 +1,64 @@
import json
import uuid
from nemoguardrails import LLMRails, RailsConfig
from src.actions import executar_pipeline_validacoes, PIPELINE_RESULTS
def build_rails():
config = RailsConfig.from_path("./config")
rails = LLMRails(config)
rails.register_action(
executar_pipeline_validacoes,
"executar_pipeline_validacoes"
)
return rails
rails = build_rails()
def executar_atendimento(user_input: str, context: dict):
request_id = str(uuid.uuid4())
payload = {
"request_id": request_id,
"input_text": user_input,
"context": context or {},
}
response = rails.generate(
messages=[
{
"role": "user",
"content": json.dumps(payload, ensure_ascii=False),
}
]
)
result = PIPELINE_RESULTS.pop(request_id, None)
if result:
return result
return {
"allowed": False,
"label": "PROBLEMA",
"reason": "Pipeline não retornou resultado estruturado",
"response": response,
"trace": [],
}
if __name__ == "__main__":
user_input = "quero cancelar VAS"
context = {
"ajuste_valor": 2000,
"resposta_llm": "Cancelamento realizado",
}
print(executar_atendimento(user_input, context))

View File

@@ -187,4 +187,5 @@ def test_fluxo_completo_sucesso():
}
)
assert result["allowed"] is True
assert result["allowed"] is False
assert result["blocked_by"] == "CMP"