diff --git a/final_nemo_package_ready.zip b/final_nemo_package_ready.zip index d0e9457..65f81a5 100644 Binary files a/final_nemo_package_ready.zip and b/final_nemo_package_ready.zip differ diff --git a/final_pkg/company_nemo_guardrails/actions.py b/final_pkg/company_nemo_guardrails/actions.py index e7f3eed..11cabd3 100644 --- a/final_pkg/company_nemo_guardrails/actions.py +++ b/final_pkg/company_nemo_guardrails/actions.py @@ -79,15 +79,10 @@ async def detectar_out_of_scope_action(context: Optional[dict] = None, **kwargs) # ------------------------- @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") - payload = get_payload(context) - ctx = payload.get("context", {}) - - valor = ctx.get("ajuste_valor", 0) - - result = validar_alcada(valor) + result = validar_alcada(valor=ajuste_valor, limite=limite) return result @@ -101,7 +96,7 @@ async def verbalizacao_prematura_action(context: Optional[dict] = None, **kwargs payload = get_payload(context) ctx = payload.get("context", {}) - resposta = ctx.get("resposta_llm", "") + resposta = ctx.get("last_bot_message", "") result = verbalizacao_prematura(resposta, ctx) @@ -117,7 +112,7 @@ async def validar_groundedness_action(context: Optional[dict] = None, **kwargs): payload = get_payload(context) ctx = payload.get("context", {}) - resposta = ctx.get("resposta_llm", "") + resposta = ctx.get("last_bot_message", "") result = validar_groundedness(resposta, ctx) @@ -136,14 +131,14 @@ async def supervisor_vas_avulso_action(context: Optional[dict] = None, **kwargs) return result @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") text = context.get("text") or context.get("user_message", "") payload = get_payload(context) ctx = payload.get("context", {}) - result = enforce_compliance_anatel(text, ctx) + result = enforce_compliance_anatel(requer_protocolo, text, ctx) return result diff --git a/final_pkg/company_nemo_guardrails/config/config.yml b/final_pkg/company_nemo_guardrails/config/config.yml index 7a21a9f..8ba1075 100644 --- a/final_pkg/company_nemo_guardrails/config/config.yml +++ b/final_pkg/company_nemo_guardrails/config/config.yml @@ -1,21 +1,21 @@ models: - type: main engine: openai - model: gpt-5 + model: openai.gpt-4.1 api_key_env_var: OPENAI_API_KEY parameters: 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 # 🔥 usado apenas se você chamar explicitamente no flow - type: self_check_input engine: openai - model: openai.gpt-oss-120b + model: openai.gpt-4.1 api_key_env_var: OPENAI_API_KEY parameters: 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: diff --git a/final_pkg/company_nemo_guardrails/config/rails/input.co b/final_pkg/company_nemo_guardrails/config/rails/input.co index 3803157..5fc76d5 100644 --- a/final_pkg/company_nemo_guardrails/config/rails/input.co +++ b/final_pkg/company_nemo_guardrails/config/rails/input.co @@ -9,7 +9,7 @@ define flow check_input_terms $ok_oos = execute detectar_out_of_scope_action # 💰 Regras de negócio - $ok_adj = execute validar_alcada_action + $ok_adj = execute validar_alcada_action(ajuste_valor=$ajuste_valor,limite=$limite) # 🧠 Contexto $ok_hist = execute validar_consistencia_historica_action diff --git a/final_pkg/company_nemo_guardrails/config/rails/output.co b/final_pkg/company_nemo_guardrails/config/rails/output.co index cceb0a2..6e01670 100644 --- a/final_pkg/company_nemo_guardrails/config/rails/output.co +++ b/final_pkg/company_nemo_guardrails/config/rails/output.co @@ -6,7 +6,7 @@ define flow check_output_terms $ok_sup = execute supervisor_vas_avulso_action # 📡 Compliance - $ok_cmp = execute enforce_compliance_anatel_action + $ok_cmp = execute enforce_compliance_anatel_action(requer_protocolo=$requer_protocolo) # 📊 Métricas $ok_tcr = execute calcular_tcr_action diff --git a/final_pkg/company_nemo_guardrails/deterministic_rails.py b/final_pkg/company_nemo_guardrails/deterministic_rails.py index cd00e1b..acb02a5 100644 --- a/final_pkg/company_nemo_guardrails/deterministic_rails.py +++ b/final_pkg/company_nemo_guardrails/deterministic_rails.py @@ -11,9 +11,10 @@ def mask_pii(text:str)->RailResult: 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') -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'): - 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') 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') @@ -21,8 +22,8 @@ def enforce_compliance_anatel(text:str, context:dict)->RailResult: def validar_alcada(valor:float, limite:float=50.0)->RailResult: 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') - return RailResult(True,f'Valor R$ {valor:.2f} dentro da alçada',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} dentro da alçada',code='ADJ',mechanism='python') def calcular_tcr(status:str)->RailResult: status=status.lower(); categoria='Indefinido' diff --git a/final_pkg/company_nemo_guardrails/factory.py b/final_pkg/company_nemo_guardrails/factory.py index e0be208..b186f9b 100644 --- a/final_pkg/company_nemo_guardrails/factory.py +++ b/final_pkg/company_nemo_guardrails/factory.py @@ -13,6 +13,6 @@ def create_rails(): rails = LLMRails(config) # 👇 opcional mas recomendado (garante registro) - rails.register_actions(actions) + rails.register_action(actions) return rails \ No newline at end of file diff --git a/final_pkg/examples/app_nemo.py b/final_pkg/examples/app_nemo.py index 872307a..3e38529 100644 --- a/final_pkg/examples/app_nemo.py +++ b/final_pkg/examples/app_nemo.py @@ -1,13 +1,48 @@ - from company_nemo_guardrails import create_rails 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() + response = rails.generate( - messages=[{"role": "user", "content": "Meu CPF é 123.456.789-00"}], - options={"log": {"activated_rails": True}} + messages=messages, + options={ + "log": {"activated_rails": True} + } ) + print(response) + if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/final_pkg/examples/test.py b/final_pkg/examples/test.py new file mode 100644 index 0000000..e8d4780 --- /dev/null +++ b/final_pkg/examples/test.py @@ -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() \ No newline at end of file