Funcional

This commit is contained in:
2026-04-28 20:31:06 -03:00
parent ad6f9d4c0c
commit 387a662a39
9 changed files with 347 additions and 250 deletions

View File

@@ -1,150 +1,284 @@
# src/actions.py
import json
import uuid
from typing import Optional
from nemoguardrails.actions import action
from .deterministic_rails import (
mask_pii,
validar_alcada,
enforce_compliance_anatel,
calcular_tcr,
detectar_fallback,
registrar_violacao,
validar_consistencia_historica,
contabilizar_tokens,
calcular_eficiencia_nlu,
detectar_no_match_rag,
detectar_loop,
medir_tamanho_mensagem,
calcular_precisao_revocacao,
avaliar_acuracia_semantica,
)
from .llm_rails import (
detectar_toxicidade,
detectar_out_of_scope,
verbalizacao_prematura,
validar_groundedness,
supervisor_vas_avulso,
)
from .deterministic_rails import validar_alcada
# =========================
# HELPERS
# =========================
try:
from .judges import avaliar_qualidade_resposta
except Exception:
avaliar_qualidade_resposta = None
def get_payload(context: Optional[dict]) -> dict:
return (context or {}).get("payload", {})
# =========================
# ACTIONS
# =========================
@action(is_system_action=True)
async def mask_pii_action(context: Optional[dict] = None, **kwargs):
print("🔥 MSK")
payload = get_payload(context)
input_text = payload.get("input_text") or context.get("user_message", "")
result = mask_pii(input_text)
if context is not None:
context["text"] = getattr(result, "sanitized_text", input_text)
return result
PIPELINE_RESULTS = {}
# -------------------------
@action(is_system_action=True)
async def detectar_toxicidade_action(context: Optional[dict] = None, **kwargs):
print("🔥 TOX")
text = context.get("text") or context.get("user_message", "")
result = detectar_toxicidade(text)
return result
def extrair_payload(context: dict) -> dict:
try:
messages = context.get("messages", [])
content = messages[-1]["content"]
return json.loads(content)
except Exception:
return {}
# -------------------------
@action(is_system_action=True)
async def detectar_out_of_scope_action(context: Optional[dict] = None, **kwargs):
print("🔥 OOS")
text = context.get("text") or context.get("user_message", "")
result = detectar_out_of_scope(text)
return result
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", {}),
})
# -------------------------
@action(is_system_action=True)
async def validar_alcada_action(context: Optional[dict] = None, **kwargs):
print("🔥 ADJ")
payload = get_payload(context)
ctx = payload.get("context", {})
valor = ctx.get("ajuste_valor", 0)
result = validar_alcada(valor)
return result
def executar_pipeline_validacoes(context: dict):
print("🔥🔥🔥 ACTION FOI EXECUTADA")
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 {}
@action(is_system_action=True)
async def verbalizacao_prematura_action(context: Optional[dict] = None, **kwargs):
print("🔥 REVPREC")
trace = []
failures = []
payload = get_payload(context)
ctx = payload.get("context", {})
# =========================
# INPUT RAILS - LLM
# =========================
resposta = ctx.get("resposta_llm", "")
r_tox = detectar_toxicidade(input_text)
add_trace(trace, "TOX", r_tox)
if not r_tox.allowed:
failures.append(("TOX", r_tox.reason))
result = verbalizacao_prematura(resposta, ctx)
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))
return result
# =========================
# 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
# =========================
@action(is_system_action=True)
async def validar_groundedness_action(context: Optional[dict] = None, **kwargs):
print("🔥 GND")
final_response = ctx.get("resposta_llm", "")
payload = get_payload(context)
ctx = payload.get("context", {})
trace.append({
"step": "LLM",
"allowed": True,
"input": input_text,
"output_preview": final_response[:200],
"mechanism": "provided_response_or_proxy",
})
resposta = ctx.get("resposta_llm", "")
# =========================
# OUTPUT RAILS - LLM
# =========================
result = validar_groundedness(resposta, ctx)
r_revprec = verbalizacao_prematura(final_response, ctx)
add_trace(trace, "REVPREC", r_revprec)
if not r_revprec.allowed:
failures.append(("REVPREC", r_revprec.reason))
return result
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
# =========================
@action(is_system_action=True)
async def supervisor_vas_avulso_action(context: Optional[dict] = None, **kwargs):
print("🔥 REVPREC_SUP")
if avaliar_qualidade_resposta is not None:
r_cmp = avaliar_qualidade_resposta(input_text, 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": {},
})
payload = get_payload(context)
# =========================
# FINAL DECISION
# =========================
result = supervisor_vas_avulso(payload)
blocked = len(failures) > 0
return result
if blocked:
first_code, first_reason = failures[0]
nemo_response = f"BLOCKED:{first_code} - {first_reason}"
else:
nemo_response = final_response
@action(is_system_action=True)
async def enforce_compliance_anatel_action(context=None, **kwargs):
print("🔥 CMP")
text = context.get("text") or context.get("user_message", "")
payload = get_payload(context)
ctx = payload.get("context", {})
result = enforce_compliance_anatel(text, ctx)
return result
@action(is_system_action=True)
async def calcular_tcr_action(context=None, **kwargs):
print("🔥 TCR")
payload = get_payload(context)
status = payload.get("context", {}).get("status", "")
result = calcular_tcr(status)
return result
@action(is_system_action=True)
async def detectar_fallback_action(context=None, **kwargs):
print("🔥 FALLBACK")
text = context.get("text") or context.get("user_message", "")
result = detectar_fallback(text)
return result
@action(is_system_action=True)
async def registrar_violacao_action(context=None, **kwargs):
print("🔥 VIOL")
payload = get_payload(context)
agent_id = payload.get("agent_id", "unknown")
code = payload.get("violation_code", "UNKNOWN")
result = registrar_violacao(agent_id, code)
return result
@action(is_system_action=True)
async def validar_consistencia_historica_action(context=None, **kwargs):
print("🔥 HIST")
payload = get_payload(context)
ctx = payload.get("context", {})
result = validar_consistencia_historica(ctx)
return result
@action(is_system_action=True)
async def contabilizar_tokens_action(context=None, **kwargs):
print("🔥 PMPTK")
payload = get_payload(context)
prompt = payload.get("prompt_tokens", 0)
completion = payload.get("completion_tokens", 0)
result = contabilizar_tokens(prompt, completion)
return result
@action(is_system_action=True)
async def calcular_eficiencia_nlu_action(context=None, **kwargs):
print("🔥 EFIC")
payload = get_payload(context)
ctx = payload.get("context", {})
result = calcular_eficiencia_nlu(
ctx.get("chunks_retornados", 0),
ctx.get("chunks_utilizados", 0)
)
return result
@action(is_system_action=True)
async def detectar_no_match_rag_action(context=None, **kwargs):
print("🔥 NO-M")
payload = get_payload(context)
ctx = payload.get("context", {})
result = detectar_no_match_rag(
ctx.get("chunks", []),
ctx.get("resposta_llm", "")
)
return result
@action(is_system_action=True)
async def detectar_loop_action(context=None, **kwargs):
print("🔥 VLOOP")
payload = get_payload(context)
mensagens = payload.get("context", {}).get("mensagens", [])
result = detectar_loop(mensagens)
return result
@action(is_system_action=True)
async def medir_tamanho_mensagem_action(context=None, **kwargs):
print("🔥 MSIZE")
text = context.get("text") or context.get("user_message", "")
result = medir_tamanho_mensagem(text)
return result
@action(is_system_action=True)
async def calcular_precisao_revocacao_action(context=None, **kwargs):
print("🔥 REVPREC_METRIC")
payload = get_payload(context)
ctx = payload.get("context", {})
result = calcular_precisao_revocacao(
ctx.get("y_true", []),
ctx.get("y_pred", [])
)
return result
@action(is_system_action=True)
async def avaliar_acuracia_semantica_action(context=None, **kwargs):
print("🔥 SEMAC")
payload = get_payload(context)
ctx = payload.get("context", {})
result = avaliar_acuracia_semantica(
ctx.get("audio_transcrito", ""),
ctx.get("referencia_humana", "")
)
return result
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
}