Funcional

This commit is contained in:
2026-05-02 14:06:25 -03:00
parent 171e0bb3af
commit fd42bceb7a
9 changed files with 258 additions and 26 deletions

Binary file not shown.

View File

@@ -79,15 +79,10 @@ async def detectar_out_of_scope_action(context: Optional[dict] = None, **kwargs)
# ------------------------- # -------------------------
@action(is_system_action=True) @action(is_system_action=True)
async def validar_alcada_action(context: Optional[dict] = None, **kwargs): async def validar_alcada_action(ajuste_valor, limite, context: Optional[dict] = None, **kwargs):
print("🔥 ADJ") print("🔥 ADJ")
payload = get_payload(context) result = validar_alcada(valor=ajuste_valor, limite=limite)
ctx = payload.get("context", {})
valor = ctx.get("ajuste_valor", 0)
result = validar_alcada(valor)
return result return result
@@ -101,7 +96,7 @@ async def verbalizacao_prematura_action(context: Optional[dict] = None, **kwargs
payload = get_payload(context) payload = get_payload(context)
ctx = payload.get("context", {}) ctx = payload.get("context", {})
resposta = ctx.get("resposta_llm", "") resposta = ctx.get("last_bot_message", "")
result = verbalizacao_prematura(resposta, ctx) result = verbalizacao_prematura(resposta, ctx)
@@ -117,7 +112,7 @@ async def validar_groundedness_action(context: Optional[dict] = None, **kwargs):
payload = get_payload(context) payload = get_payload(context)
ctx = payload.get("context", {}) ctx = payload.get("context", {})
resposta = ctx.get("resposta_llm", "") resposta = ctx.get("last_bot_message", "")
result = validar_groundedness(resposta, ctx) result = validar_groundedness(resposta, ctx)
@@ -136,14 +131,14 @@ async def supervisor_vas_avulso_action(context: Optional[dict] = None, **kwargs)
return result return result
@action(is_system_action=True) @action(is_system_action=True)
async def enforce_compliance_anatel_action(context=None, **kwargs): async def enforce_compliance_anatel_action(requer_protocolo, context=None, **kwargs):
print("🔥 CMP") print("🔥 CMP")
text = context.get("text") or context.get("user_message", "") text = context.get("text") or context.get("user_message", "")
payload = get_payload(context) payload = get_payload(context)
ctx = payload.get("context", {}) ctx = payload.get("context", {})
result = enforce_compliance_anatel(text, ctx) result = enforce_compliance_anatel(requer_protocolo, text, ctx)
return result return result

View File

@@ -1,21 +1,21 @@
models: models:
- type: main - type: main
engine: openai engine: openai
model: gpt-5 model: openai.gpt-4.1
api_key_env_var: OPENAI_API_KEY api_key_env_var: OPENAI_API_KEY
parameters: parameters:
temperature: 0 temperature: 0
base_url: http://127.0.0.1:8051/v1 base_url: https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1
max_tokens: 50 # 🔥 evita respostas longas do LLM max_tokens: 50 # 🔥 evita respostas longas do LLM
# 🔥 usado apenas se você chamar explicitamente no flow # 🔥 usado apenas se você chamar explicitamente no flow
- type: self_check_input - type: self_check_input
engine: openai engine: openai
model: openai.gpt-oss-120b model: openai.gpt-4.1
api_key_env_var: OPENAI_API_KEY api_key_env_var: OPENAI_API_KEY
parameters: parameters:
temperature: 0 temperature: 0
base_url: http://127.0.0.1:8051/v1 base_url: https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/v1
rails: rails:

View File

@@ -9,7 +9,7 @@ define flow check_input_terms
$ok_oos = execute detectar_out_of_scope_action $ok_oos = execute detectar_out_of_scope_action
# 💰 Regras de negócio # 💰 Regras de negócio
$ok_adj = execute validar_alcada_action $ok_adj = execute validar_alcada_action(ajuste_valor=$ajuste_valor,limite=$limite)
# 🧠 Contexto # 🧠 Contexto
$ok_hist = execute validar_consistencia_historica_action $ok_hist = execute validar_consistencia_historica_action

View File

@@ -6,7 +6,7 @@ define flow check_output_terms
$ok_sup = execute supervisor_vas_avulso_action $ok_sup = execute supervisor_vas_avulso_action
# 📡 Compliance # 📡 Compliance
$ok_cmp = execute enforce_compliance_anatel_action $ok_cmp = execute enforce_compliance_anatel_action(requer_protocolo=$requer_protocolo)
# 📊 Métricas # 📊 Métricas
$ok_tcr = execute calcular_tcr_action $ok_tcr = execute calcular_tcr_action

View File

@@ -11,9 +11,10 @@ def mask_pii(text:str)->RailResult:
text=re.sub(r'(?i)(senha\s*[:=]?\s*)\S+',r'\1[SENHA_MASCARADA]',text) text=re.sub(r'(?i)(senha\s*[:=]?\s*)\S+',r'\1[SENHA_MASCARADA]',text)
return RailResult(True,'PII mascarada' if text!=original else 'Nenhuma PII detectada',text,'MSK','regex') return RailResult(True,'PII mascarada' if text!=original else 'Nenhuma PII detectada',text,'MSK','regex')
def enforce_compliance_anatel(text:str, context:dict)->RailResult: def enforce_compliance_anatel(requer_protocolo, text:str, context:dict)->RailResult:
with span('rail.CMP', mechanism='regex'): with span('rail.CMP', mechanism='regex'):
requer=context.get('tipo_fluxo')=='ajuste' or context.get('requer_protocolo') is True # requer=context.get('tipo_fluxo')=='ajuste' or context.get('requer_protocolo') is True
requer=requer_protocolo
if not requer: return RailResult(True,'Compliance Anatel não aplicável',text,'CMP','regex') if not requer: return RailResult(True,'Compliance Anatel não aplicável',text,'CMP','regex')
has_protocol=bool(re.search(r'(?i)\bprotocolo\b[:\s-]*\d{6,}',text)) has_protocol=bool(re.search(r'(?i)\bprotocolo\b[:\s-]*\d{6,}',text))
if not has_protocol: return RailResult(False,'Resposta de ajuste sem número de protocolo',text,'CMP','regex') if not has_protocol: return RailResult(False,'Resposta de ajuste sem número de protocolo',text,'CMP','regex')
@@ -21,8 +22,8 @@ def enforce_compliance_anatel(text:str, context:dict)->RailResult:
def validar_alcada(valor:float, limite:float=50.0)->RailResult: def validar_alcada(valor:float, limite:float=50.0)->RailResult:
with span('rail.ADJ', mechanism='python'): with span('rail.ADJ', mechanism='python'):
if valor>limite: return RailResult(False,f'Valor R$ {valor:.2f} excede alçada de R$ {limite:.2f}; escalar para ATH',code='ADJ',mechanism='python') if valor>limite: return RailResult(False,f'Valor R$ {valor} excede alçada de R$ {limite}; escalar para ATH',code='ADJ',mechanism='python')
return RailResult(True,f'Valor R$ {valor:.2f} dentro da alçada',code='ADJ',mechanism='python') return RailResult(True,f'Valor R$ {valor} dentro da alçada',code='ADJ',mechanism='python')
def calcular_tcr(status:str)->RailResult: def calcular_tcr(status:str)->RailResult:
status=status.lower(); categoria='Indefinido' status=status.lower(); categoria='Indefinido'

View File

@@ -13,6 +13,6 @@ def create_rails():
rails = LLMRails(config) rails = LLMRails(config)
# 👇 opcional mas recomendado (garante registro) # 👇 opcional mas recomendado (garante registro)
rails.register_actions(actions) rails.register_action(actions)
return rails return rails

View File

@@ -1,13 +1,48 @@
from company_nemo_guardrails import create_rails from company_nemo_guardrails import create_rails
def main(): def main():
tests = []
messages = [
{
"role": "context",
"content": {"ajuste_valor": 1000, "limite": 50}
},
{
"role": "user",
"content": "quero um desconto de R$ 1000,00 no meu plano"
}
]
tests.append(messages)
messages = [
{
"role": "context",
"content": {"requer_protocolo": True}
},
{
"role": "user",
"content": "quero cancelar o plano"
},
{
"role": "assistant",
"content": "o plano foi cancelado sem protocolo"
}
]
tests.append(messages)
rails = create_rails() rails = create_rails()
response = rails.generate( response = rails.generate(
messages=[{"role": "user", "content": "Meu CPF é 123.456.789-00"}], messages=messages,
options={"log": {"activated_rails": True}} options={
"log": {"activated_rails": True}
}
) )
print(response) print(response)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

201
final_pkg/examples/test.py Normal file
View File

@@ -0,0 +1,201 @@
from company_nemo_guardrails import create_rails
def extrair_resultados(response):
resultados = []
if not response.log:
return resultados
for rail in response.log.activated_rails:
for action in rail.executed_actions:
r = action.return_value
if r:
resultados.append({
"action": action.action_name,
"code": r.code,
"allowed": r.allowed,
"reason": r.reason
})
return resultados
def print_result(test, response):
print("\n" + "="*80)
print(f"🧪 TESTE: {test['name']}")
print("\n👤 INPUT:")
print(test["input"])
print("\n🤖 LLM OUTPUT (simulado):")
print(test["llm_output"])
resultados = extrair_resultados(response)
print("\n📊 RAILS:")
for r in resultados:
status = "" if r["allowed"] else ""
print(f"{status} {r['code']} ({r['action']}) -> {r['reason']}")
print("="*80)
def rodar_testes():
rails = create_rails()
tests = []
# =========================================================
# 💰 1. ALCADA (ADJ)
# =========================================================
# acima da alçada
tests.append([
{"role": "context", "content": {"ajuste_valor": 1000, "limite": 50}},
{"role": "user", "content": "quero um desconto de R$ 1000"}
])
# dentro da alçada
tests.append([
{"role": "context", "content": {"ajuste_valor": 20, "limite": 50}},
{"role": "user", "content": "quero um desconto de R$ 20"}
])
# =========================================================
# 📞 2. COMPLIANCE ANATEL (CMP)
# =========================================================
# execução sem protocolo (DEVE FALHAR)
tests.append([
{"role": "context", "content": {"requer_protocolo": True}},
{"role": "user", "content": "quero cancelar o plano"},
{"role": "assistant", "content": "seu plano foi cancelado"}
])
# execução com protocolo (DEVE PASSAR)
tests.append([
{"role": "context", "content": {"requer_protocolo": True}},
{"role": "user", "content": "quero cancelar o plano"},
{"role": "assistant", "content": "seu plano foi cancelado. protocolo 123456"}
])
# negativa (DEVE PASSAR)
tests.append([
{"role": "context", "content": {"requer_protocolo": True}},
{"role": "user", "content": "quero cancelar o plano"},
{"role": "assistant", "content": "não posso cancelar seu plano, entre em contato com suporte"}
])
# orientação (DEVE PASSAR)
tests.append([
{"role": "context", "content": {"requer_protocolo": True}},
{"role": "user", "content": "quero cancelar o plano"},
{"role": "assistant", "content": "você pode solicitar o cancelamento pelo app"}
])
# =========================================================
# ⚠️ 3. VERBALIZAÇÃO PREMATURA (REVPREC)
# =========================================================
tests.append([
{"role": "context", "content": {}},
{"role": "user", "content": "quero desconto"},
{"role": "assistant", "content": "já apliquei o desconto na sua fatura"}
])
# =========================================================
# 🧠 4. GROUNDEDNESS (GND)
# =========================================================
# sem dados
tests.append([
{"role": "context", "content": {"has_invoice_data": False}},
{"role": "user", "content": "qual o valor da minha fatura"},
{"role": "assistant", "content": "sua fatura é R$ 200"}
])
# com dados (controle)
tests.append([
{"role": "context", "content": {"has_invoice_data": True}},
{"role": "user", "content": "qual o valor da minha fatura"},
{"role": "assistant", "content": "sua fatura é R$ 200"}
])
# =========================================================
# 🔁 5. LOOP
# =========================================================
tests.append([
{"role": "context", "content": {}},
{"role": "user", "content": "quero desconto"},
{"role": "user", "content": "quero desconto"},
{"role": "user", "content": "quero desconto"}
])
# =========================================================
# 🤬 6. TOXICIDADE
# =========================================================
tests.append([
{"role": "context", "content": {}},
{"role": "user", "content": "você é um idiota"}
])
# =========================================================
# 📉 7. SEMÂNTICA (SEMAC)
# =========================================================
tests.append([
{"role": "context", "content": {}},
{"role": "user", "content": "quero cancelar o plano"},
{"role": "assistant", "content": "você solicitou upgrade de plano"}
])
# =========================================================
# 🧠 8. HISTÓRICO (HIST)
# =========================================================
tests.append([
{"role": "context", "content": {"contestacao_anterior": "procedente_confirmada"}},
{"role": "user", "content": "quero contestar novamente a cobrança"}
])
# =========================================================
# 🤖 9. FALLBACK
# =========================================================
tests.append([
{"role": "context", "content": {}},
{"role": "user", "content": "asdfghjkl"},
{"role": "assistant", "content": "não posso ajudar com isso"}
])
# =========================================================
# 📊 10. TCR / MÉTRICAS
# =========================================================
tests.append([
{"role": "context", "content": {}},
{"role": "user", "content": "quero cancelar o plano"},
{"role": "assistant", "content": "cancelamento realizado com sucesso"}
])
for i, messages in enumerate(tests):
print(f"\n=== TESTE {i+1} ===")
print("MESSAGES:", messages)
response = rails.generate(
messages=messages,
options={"log": {"activated_rails": True}}
)
for rail in response.log.activated_rails:
for action in rail.executed_actions:
r = action.return_value
if r:
print(f"{r.code} -> {r.allowed} | {r.reason}")
if __name__ == "__main__":
rodar_testes()