diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..da7ea03c Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/main.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 00000000..5b6ab51e Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 00000000..77accaf2 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/state.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/state.cpython-313.pyc new file mode 100644 index 00000000..8e74de72 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/__pycache__/state.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc new file mode 100644 index 00000000..cf679934 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc new file mode 100644 index 00000000..5c3f4a04 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 00000000..0e8cc297 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 00000000..00a5707f Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc new file mode 100644 index 00000000..26cc262c Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 00000000..f56e53d6 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/billing_agent.py b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/billing_agent.py +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/orders_agent.py b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/orders_agent.py +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/product_agent.py b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/product_agent.py +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/support_agent.py b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/support_agent.py +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..97bc5803 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc new file mode 100644 index 00000000..823a1a39 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc new file mode 100644 index 00000000..a725202b Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc new file mode 100644 index 00000000..19296b0a Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc new file mode 100644 index 00000000..fce919c0 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc new file mode 100644 index 00000000..bf35ba7a Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..47fe1272 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc new file mode 100644 index 00000000..05612aee Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..c39a0a89 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc index 009b7c12..79a8274a 100644 Binary files a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/tool_renderers.py b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/tool_renderers.py index f77c47a4..cb442023 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/tool_renderers.py +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/presentation/tool_renderers.py @@ -13,7 +13,42 @@ def _money_brl(value: Any) -> str: def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: - return f"[{agent_label}] Fatura consultada: {result}." + """Renderiza somente campos de negócio seguros da fatura. + + Identificadores técnicos/PII presentes no payload MCP (por exemplo msisdn, + customer_id, document e business keys) não devem ser propagados ao usuário. + """ + lines = [f"[{agent_label}] Dados da sua fatura:"] + total = result.get("valor_total") + vencimento = result.get("vencimento") + status = result.get("status") + if total is not None: + lines.append(f"Valor total: R$ {_money_brl(total)}.") + if vencimento not in (None, ""): + lines.append(f"Vencimento: {vencimento}.") + if status not in (None, ""): + lines.append(f"Situação: {status}.") + + items = result.get("itens") or [] + rendered_items: list[str] = [] + if isinstance(items, list): + for item in items: + if not isinstance(item, dict): + continue + description = item.get("descricao") or item.get("nome") + value = item.get("valor") + if description in (None, ""): + continue + if value is None: + rendered_items.append(str(description)) + else: + rendered_items.append(f"{description}: R$ {_money_brl(value)}") + if rendered_items: + lines.append("Itens: " + "; ".join(rendered_items) + ".") + + # Se não houver nenhum campo de negócio seguro além do cabeçalho, deixe a + # composição pela LLM/guardrails em vez de despejar o payload bruto. + return " ".join(lines) if len(lines) > 1 else None def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..8accd287 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc new file mode 100644 index 00000000..0b970980 Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 00000000..192132da Binary files /dev/null and b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py index b8ed7bcb..303bf510 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/app/workflows/agent_graph.py @@ -494,6 +494,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -509,15 +622,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -603,7 +716,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..21303451 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/main.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 00000000..005a6d8a Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 00000000..2f423392 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/state.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/state.cpython-313.pyc new file mode 100644 index 00000000..8ee7f900 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/__pycache__/state.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc new file mode 100644 index 00000000..7214e11a Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc new file mode 100644 index 00000000..f1bf0dfe Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 00000000..af63de6a Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 00000000..bc4a8147 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc new file mode 100644 index 00000000..52afdf36 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 00000000..a44cfd9c Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/billing_agent.py b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/billing_agent.py +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/orders_agent.py b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/orders_agent.py +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/product_agent.py b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/product_agent.py +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/support_agent.py b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/support_agent.py +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..e9246dd6 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc new file mode 100644 index 00000000..5961c07a Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc new file mode 100644 index 00000000..6674af30 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc new file mode 100644 index 00000000..37c8d76c Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc new file mode 100644 index 00000000..3e4fe4b8 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc new file mode 100644 index 00000000..a9c17ae6 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/extensions/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/extensions/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..a1382b06 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/extensions/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/extensions/__pycache__/example_guardrails.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/extensions/__pycache__/example_guardrails.cpython-313.pyc new file mode 100644 index 00000000..4a27a18a Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/extensions/__pycache__/example_guardrails.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/extensions/__pycache__/example_judges.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/extensions/__pycache__/example_judges.cpython-313.pyc new file mode 100644 index 00000000..d82ff088 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/extensions/__pycache__/example_judges.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..14323bf2 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc new file mode 100644 index 00000000..15b78667 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..e34e5416 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc new file mode 100644 index 00000000..28859532 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/tool_renderers.py b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/tool_renderers.py index f77c47a4..cb442023 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/tool_renderers.py +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/presentation/tool_renderers.py @@ -13,7 +13,42 @@ def _money_brl(value: Any) -> str: def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: - return f"[{agent_label}] Fatura consultada: {result}." + """Renderiza somente campos de negócio seguros da fatura. + + Identificadores técnicos/PII presentes no payload MCP (por exemplo msisdn, + customer_id, document e business keys) não devem ser propagados ao usuário. + """ + lines = [f"[{agent_label}] Dados da sua fatura:"] + total = result.get("valor_total") + vencimento = result.get("vencimento") + status = result.get("status") + if total is not None: + lines.append(f"Valor total: R$ {_money_brl(total)}.") + if vencimento not in (None, ""): + lines.append(f"Vencimento: {vencimento}.") + if status not in (None, ""): + lines.append(f"Situação: {status}.") + + items = result.get("itens") or [] + rendered_items: list[str] = [] + if isinstance(items, list): + for item in items: + if not isinstance(item, dict): + continue + description = item.get("descricao") or item.get("nome") + value = item.get("valor") + if description in (None, ""): + continue + if value is None: + rendered_items.append(str(description)) + else: + rendered_items.append(f"{description}: R$ {_money_brl(value)}") + if rendered_items: + lines.append("Itens: " + "; ".join(rendered_items) + ".") + + # Se não houver nenhum campo de negócio seguro além do cabeçalho, deixe a + # composição pela LLM/guardrails em vez de despejar o payload bruto. + return " ".join(lines) if len(lines) > 1 else None def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 00000000..7e370465 Binary files /dev/null and b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py index 0a12c4be..2cb86e12 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/app/workflows/agent_graph.py @@ -492,6 +492,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -507,15 +620,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -601,7 +714,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc index 884409ed..823d2841 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc index 7f097b78..baf57767 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc index d6621835..71b13395 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc index e5055fe3..7d103e65 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc index 0e9f1e1c..abd23f2b 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc index f05ef2c6..3a5ff2f4 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc index 2b363379..cfd8819c 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc index 27c5dd67..0ab1a52b 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc index 321fc089..10892912 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc index bcd05596..66abc541 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/billing_agent.py b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/billing_agent.py +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/orders_agent.py b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/orders_agent.py +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/product_agent.py b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/product_agent.py +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/support_agent.py b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/support_agent.py +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc index e011e2c1..82f6849c 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc index 417fdeaf..5ce24803 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc index 43c28410..636a41ab 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc index 8684f23b..0f860a4a 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc index 19cee2f2..4e5f533a 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc index 5919f9d0..063a9b9c 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc index 60f4328a..e06cfd22 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc index a01c7f1e..70a39579 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..91dabf0d Binary files /dev/null and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc index 32cbdf80..2c6e20ef 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/tool_renderers.py b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/tool_renderers.py index f77c47a4..cb442023 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/tool_renderers.py +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/presentation/tool_renderers.py @@ -13,7 +13,42 @@ def _money_brl(value: Any) -> str: def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: - return f"[{agent_label}] Fatura consultada: {result}." + """Renderiza somente campos de negócio seguros da fatura. + + Identificadores técnicos/PII presentes no payload MCP (por exemplo msisdn, + customer_id, document e business keys) não devem ser propagados ao usuário. + """ + lines = [f"[{agent_label}] Dados da sua fatura:"] + total = result.get("valor_total") + vencimento = result.get("vencimento") + status = result.get("status") + if total is not None: + lines.append(f"Valor total: R$ {_money_brl(total)}.") + if vencimento not in (None, ""): + lines.append(f"Vencimento: {vencimento}.") + if status not in (None, ""): + lines.append(f"Situação: {status}.") + + items = result.get("itens") or [] + rendered_items: list[str] = [] + if isinstance(items, list): + for item in items: + if not isinstance(item, dict): + continue + description = item.get("descricao") or item.get("nome") + value = item.get("valor") + if description in (None, ""): + continue + if value is None: + rendered_items.append(str(description)) + else: + rendered_items.append(f"{description}: R$ {_money_brl(value)}") + if rendered_items: + lines.append("Itens: " + "; ".join(rendered_items) + ".") + + # Se não houver nenhum campo de negócio seguro além do cabeçalho, deixe a + # composição pela LLM/guardrails em vez de despejar o payload bruto. + return " ".join(lines) if len(lines) > 1 else None def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc index 17e9714a..8adf49f3 100644 Binary files a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc and b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py index b8ed7bcb..303bf510 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/app/workflows/agent_graph.py @@ -494,6 +494,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -509,15 +622,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -603,7 +716,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..33197e54 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 00000000..b834e5a5 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 00000000..3f0f59de Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc new file mode 100644 index 00000000..66a88310 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc new file mode 100644 index 00000000..dd8907fd Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc new file mode 100644 index 00000000..d93873a6 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 00000000..e6a45d17 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 00000000..da03ff12 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc new file mode 100644 index 00000000..e5072a2b Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 00000000..e38f7072 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/billing_agent.py b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/billing_agent.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/orders_agent.py b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/orders_agent.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/product_agent.py b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/product_agent.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/support_agent.py b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/support_agent.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..ef32d167 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc new file mode 100644 index 00000000..e21d30a5 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc new file mode 100644 index 00000000..04492435 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc new file mode 100644 index 00000000..02ff434c Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc new file mode 100644 index 00000000..502913f3 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc new file mode 100644 index 00000000..84f7d61e Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..be993d0a Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc new file mode 100644 index 00000000..da0b1635 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..d5802e05 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc index 23b52e13..970b809f 100644 Binary files a/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc and b/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/tool_renderers.py b/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/tool_renderers.py index f77c47a4..cb442023 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/tool_renderers.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend/app/presentation/tool_renderers.py @@ -13,7 +13,42 @@ def _money_brl(value: Any) -> str: def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: - return f"[{agent_label}] Fatura consultada: {result}." + """Renderiza somente campos de negócio seguros da fatura. + + Identificadores técnicos/PII presentes no payload MCP (por exemplo msisdn, + customer_id, document e business keys) não devem ser propagados ao usuário. + """ + lines = [f"[{agent_label}] Dados da sua fatura:"] + total = result.get("valor_total") + vencimento = result.get("vencimento") + status = result.get("status") + if total is not None: + lines.append(f"Valor total: R$ {_money_brl(total)}.") + if vencimento not in (None, ""): + lines.append(f"Vencimento: {vencimento}.") + if status not in (None, ""): + lines.append(f"Situação: {status}.") + + items = result.get("itens") or [] + rendered_items: list[str] = [] + if isinstance(items, list): + for item in items: + if not isinstance(item, dict): + continue + description = item.get("descricao") or item.get("nome") + value = item.get("valor") + if description in (None, ""): + continue + if value is None: + rendered_items.append(str(description)) + else: + rendered_items.append(f"{description}: R$ {_money_brl(value)}") + if rendered_items: + lines.append("Itens: " + "; ".join(rendered_items) + ".") + + # Se não houver nenhum campo de negócio seguro além do cabeçalho, deixe a + # composição pela LLM/guardrails em vez de despejar o payload bruto. + return " ".join(lines) if len(lines) > 1 else None def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 00000000..114625a7 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py index 0a12c4be..2cb86e12 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend/app/workflows/agent_graph.py @@ -492,6 +492,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -507,15 +620,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -601,7 +714,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..f58a0656 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 00000000..ffb3aacc Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 00000000..2c08e830 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc new file mode 100644 index 00000000..633c69ba Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc new file mode 100644 index 00000000..b054c868 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc new file mode 100644 index 00000000..710b3183 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 00000000..a06a87f0 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 00000000..71ad8ec6 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc new file mode 100644 index 00000000..e10fbd85 Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 00000000..1a49580a Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/billing_agent.py b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/billing_agent.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/orders_agent.py b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/orders_agent.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/product_agent.py b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/product_agent.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/support_agent.py b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/support_agent.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..42d368bd Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc new file mode 100644 index 00000000..b99954da Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 00000000..3c19c45a Binary files /dev/null and b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index 0a12c4be..2cb86e12 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -492,6 +492,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -507,15 +620,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -601,7 +714,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..f8eb5411 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 00000000..dd7251b0 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 00000000..2e100295 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc new file mode 100644 index 00000000..af7100e9 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc new file mode 100644 index 00000000..f5a9d475 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc new file mode 100644 index 00000000..9fefbf7d Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 00000000..7ebb5c30 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 00000000..e81fba32 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc new file mode 100644 index 00000000..0302053e Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 00000000..956fee11 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/billing_agent.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/billing_agent.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/orders_agent.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/orders_agent.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/product_agent.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/product_agent.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/support_agent.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/support_agent.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..fda75cab Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc new file mode 100644 index 00000000..9d7cc49e Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc new file mode 100644 index 00000000..e1fe21e2 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc new file mode 100644 index 00000000..b69294dc Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc new file mode 100644 index 00000000..85c19945 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc new file mode 100644 index 00000000..3a2600e2 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..f201aed9 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc new file mode 100644 index 00000000..7c9b983b Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..ed945c8d Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc index 567eb4dc..8c88b043 100644 Binary files a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/tool_renderers.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/tool_renderers.py index f77c47a4..cb442023 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/tool_renderers.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/presentation/tool_renderers.py @@ -13,7 +13,42 @@ def _money_brl(value: Any) -> str: def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: - return f"[{agent_label}] Fatura consultada: {result}." + """Renderiza somente campos de negócio seguros da fatura. + + Identificadores técnicos/PII presentes no payload MCP (por exemplo msisdn, + customer_id, document e business keys) não devem ser propagados ao usuário. + """ + lines = [f"[{agent_label}] Dados da sua fatura:"] + total = result.get("valor_total") + vencimento = result.get("vencimento") + status = result.get("status") + if total is not None: + lines.append(f"Valor total: R$ {_money_brl(total)}.") + if vencimento not in (None, ""): + lines.append(f"Vencimento: {vencimento}.") + if status not in (None, ""): + lines.append(f"Situação: {status}.") + + items = result.get("itens") or [] + rendered_items: list[str] = [] + if isinstance(items, list): + for item in items: + if not isinstance(item, dict): + continue + description = item.get("descricao") or item.get("nome") + value = item.get("valor") + if description in (None, ""): + continue + if value is None: + rendered_items.append(str(description)) + else: + rendered_items.append(f"{description}: R$ {_money_brl(value)}") + if rendered_items: + lines.append("Itens: " + "; ".join(rendered_items) + ".") + + # Se não houver nenhum campo de negócio seguro além do cabeçalho, deixe a + # composição pela LLM/guardrails em vez de despejar o payload bruto. + return " ".join(lines) if len(lines) > 1 else None def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 00000000..91f49fc4 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py index 0a12c4be..2cb86e12 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/app/workflows/agent_graph.py @@ -492,6 +492,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -507,15 +620,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -601,7 +714,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..f0d4c0e1 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 00000000..ca99d568 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 00000000..ffaf183b Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc new file mode 100644 index 00000000..8a0915dc Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc new file mode 100644 index 00000000..455b1753 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc new file mode 100644 index 00000000..bb03d128 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 00000000..44aa6f58 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 00000000..7d146604 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc new file mode 100644 index 00000000..340ff14d Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 00000000..c047c8d5 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/billing_agent.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/billing_agent.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/orders_agent.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/orders_agent.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/product_agent.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/product_agent.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/support_agent.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/support_agent.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..e757f931 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc new file mode 100644 index 00000000..65b0a28a Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 00000000..d268a633 Binary files /dev/null and b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index 0a12c4be..2cb86e12 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -492,6 +492,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -507,15 +620,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -601,7 +714,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..41099f9b Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/main.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 00000000..7a89633b Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 00000000..c84d77fa Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/state.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/state.cpython-313.pyc new file mode 100644 index 00000000..9c38b483 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/__pycache__/state.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc new file mode 100644 index 00000000..b970a9fe Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc new file mode 100644 index 00000000..9ca64f20 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 00000000..1ebd6c50 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 00000000..2a94b99f Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc new file mode 100644 index 00000000..0014d62e Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 00000000..1d1ee676 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/billing_agent.py b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/billing_agent.py +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/orders_agent.py b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/orders_agent.py +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/product_agent.py b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/product_agent.py +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/support_agent.py b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/support_agent.py +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..deb38e5d Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc new file mode 100644 index 00000000..505e62bd Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc new file mode 100644 index 00000000..2fcb362f Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc new file mode 100644 index 00000000..d78ba9c1 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc new file mode 100644 index 00000000..50ae968a Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc new file mode 100644 index 00000000..ffc9f3f3 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..dd8600fb Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc new file mode 100644 index 00000000..d6f43df7 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..8048b8f5 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc new file mode 100644 index 00000000..defbfd70 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/tool_renderers.py b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/tool_renderers.py index f77c47a4..cb442023 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/tool_renderers.py +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/presentation/tool_renderers.py @@ -13,7 +13,42 @@ def _money_brl(value: Any) -> str: def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: - return f"[{agent_label}] Fatura consultada: {result}." + """Renderiza somente campos de negócio seguros da fatura. + + Identificadores técnicos/PII presentes no payload MCP (por exemplo msisdn, + customer_id, document e business keys) não devem ser propagados ao usuário. + """ + lines = [f"[{agent_label}] Dados da sua fatura:"] + total = result.get("valor_total") + vencimento = result.get("vencimento") + status = result.get("status") + if total is not None: + lines.append(f"Valor total: R$ {_money_brl(total)}.") + if vencimento not in (None, ""): + lines.append(f"Vencimento: {vencimento}.") + if status not in (None, ""): + lines.append(f"Situação: {status}.") + + items = result.get("itens") or [] + rendered_items: list[str] = [] + if isinstance(items, list): + for item in items: + if not isinstance(item, dict): + continue + description = item.get("descricao") or item.get("nome") + value = item.get("valor") + if description in (None, ""): + continue + if value is None: + rendered_items.append(str(description)) + else: + rendered_items.append(f"{description}: R$ {_money_brl(value)}") + if rendered_items: + lines.append("Itens: " + "; ".join(rendered_items) + ".") + + # Se não houver nenhum campo de negócio seguro além do cabeçalho, deixe a + # composição pela LLM/guardrails em vez de despejar o payload bruto. + return " ".join(lines) if len(lines) > 1 else None def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..50d3c241 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc new file mode 100644 index 00000000..40704f84 Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 00000000..515e583d Binary files /dev/null and b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py index e6605a48..fef0791c 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/app/workflows/agent_graph.py @@ -494,6 +494,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -509,15 +622,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -603,7 +716,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..f942810b Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/main.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 00000000..886a86f3 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 00000000..accca3bf Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/state.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/state.cpython-313.pyc new file mode 100644 index 00000000..103d670b Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/__pycache__/state.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc new file mode 100644 index 00000000..c09150b1 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc new file mode 100644 index 00000000..2d784a42 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 00000000..5f7bfa18 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 00000000..942b2401 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc new file mode 100644 index 00000000..c06c1248 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 00000000..ba05a3eb Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/billing_agent.py b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/billing_agent.py +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/orders_agent.py b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/orders_agent.py +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/product_agent.py b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/product_agent.py +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/support_agent.py b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/support_agent.py +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..317ae4d6 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc new file mode 100644 index 00000000..0cf9b024 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc new file mode 100644 index 00000000..479518dc Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc new file mode 100644 index 00000000..2baa2d5e Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc new file mode 100644 index 00000000..912d680d Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc new file mode 100644 index 00000000..bc15baf2 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..515f8338 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc new file mode 100644 index 00000000..24d81969 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..15e8e717 Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc new file mode 100644 index 00000000..fde7777a Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/tool_renderers.py b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/tool_renderers.py index f77c47a4..cb442023 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/tool_renderers.py +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/presentation/tool_renderers.py @@ -13,7 +13,42 @@ def _money_brl(value: Any) -> str: def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: - return f"[{agent_label}] Fatura consultada: {result}." + """Renderiza somente campos de negócio seguros da fatura. + + Identificadores técnicos/PII presentes no payload MCP (por exemplo msisdn, + customer_id, document e business keys) não devem ser propagados ao usuário. + """ + lines = [f"[{agent_label}] Dados da sua fatura:"] + total = result.get("valor_total") + vencimento = result.get("vencimento") + status = result.get("status") + if total is not None: + lines.append(f"Valor total: R$ {_money_brl(total)}.") + if vencimento not in (None, ""): + lines.append(f"Vencimento: {vencimento}.") + if status not in (None, ""): + lines.append(f"Situação: {status}.") + + items = result.get("itens") or [] + rendered_items: list[str] = [] + if isinstance(items, list): + for item in items: + if not isinstance(item, dict): + continue + description = item.get("descricao") or item.get("nome") + value = item.get("valor") + if description in (None, ""): + continue + if value is None: + rendered_items.append(str(description)) + else: + rendered_items.append(f"{description}: R$ {_money_brl(value)}") + if rendered_items: + lines.append("Itens: " + "; ".join(rendered_items) + ".") + + # Se não houver nenhum campo de negócio seguro além do cabeçalho, deixe a + # composição pela LLM/guardrails em vez de despejar o payload bruto. + return " ".join(lines) if len(lines) > 1 else None def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..6003863c Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc new file mode 100644 index 00000000..e179f6df Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 00000000..8604cd4c Binary files /dev/null and b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py index e6605a48..fef0791c 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/app/workflows/agent_graph.py @@ -494,6 +494,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -509,15 +622,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -603,7 +716,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc index f6a2b79f..ee504c1e 100644 Binary files a/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc and b/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc b/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc index 08f8cb6c..521accba 100644 Binary files a/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc and b/templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc index 8d7093df..0e8f5864 100644 Binary files a/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc and b/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc b/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc index 0e7d19e3..50e1f65a 100644 Binary files a/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc and b/templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc b/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc index 4e27b205..7a1b3b5b 100644 Binary files a/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc and b/templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc b/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc index 803273eb..92838379 100644 Binary files a/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc and b/templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc b/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc index 104fb209..cec44484 100644 Binary files a/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc and b/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc b/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc index eda48f56..f4cf1727 100644 Binary files a/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc and b/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc b/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc index df7bf154..a2df5a5f 100644 Binary files a/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc and b/templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc b/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc index 7fbc59f0..de82b0f7 100644 Binary files a/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc and b/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/agents/billing_agent.py b/templates/agent_template_backend/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/templates/agent_template_backend/app/agents/billing_agent.py +++ b/templates/agent_template_backend/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/templates/agent_template_backend/app/agents/orders_agent.py b/templates/agent_template_backend/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/templates/agent_template_backend/app/agents/orders_agent.py +++ b/templates/agent_template_backend/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/templates/agent_template_backend/app/agents/product_agent.py b/templates/agent_template_backend/app/agents/product_agent.py index 113bd8e9..6c691d24 100644 --- a/templates/agent_template_backend/app/agents/product_agent.py +++ b/templates/agent_template_backend/app/agents/product_agent.py @@ -110,7 +110,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/templates/agent_template_backend/app/agents/support_agent.py b/templates/agent_template_backend/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/templates/agent_template_backend/app/agents/support_agent.py +++ b/templates/agent_template_backend/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc index a6540ec7..53216fc2 100644 Binary files a/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc and b/templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc index 8fc575e9..5a027786 100644 Binary files a/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc and b/templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc index 8253000a..fa305eab 100644 Binary files a/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc and b/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc index 41a72082..761f5f79 100644 Binary files a/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc and b/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc index a8f3f887..4f9483cf 100644 Binary files a/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc and b/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc index 3b7d6a83..ccfb7552 100644 Binary files a/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc and b/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc index 9144c5b2..288f277b 100644 Binary files a/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc and b/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc index 9495cf23..51251828 100644 Binary files a/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc and b/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..9191fadd Binary files /dev/null and b/templates/agent_template_backend/app/presentation/__pycache__/__init__.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc b/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc index 32e74b95..69de758a 100644 Binary files a/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc and b/templates/agent_template_backend/app/presentation/__pycache__/tool_renderers.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/presentation/tool_renderers.py b/templates/agent_template_backend/app/presentation/tool_renderers.py index f77c47a4..cb442023 100644 --- a/templates/agent_template_backend/app/presentation/tool_renderers.py +++ b/templates/agent_template_backend/app/presentation/tool_renderers.py @@ -13,7 +13,42 @@ def _money_brl(value: Any) -> str: def render_telecom_invoice(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: - return f"[{agent_label}] Fatura consultada: {result}." + """Renderiza somente campos de negócio seguros da fatura. + + Identificadores técnicos/PII presentes no payload MCP (por exemplo msisdn, + customer_id, document e business keys) não devem ser propagados ao usuário. + """ + lines = [f"[{agent_label}] Dados da sua fatura:"] + total = result.get("valor_total") + vencimento = result.get("vencimento") + status = result.get("status") + if total is not None: + lines.append(f"Valor total: R$ {_money_brl(total)}.") + if vencimento not in (None, ""): + lines.append(f"Vencimento: {vencimento}.") + if status not in (None, ""): + lines.append(f"Situação: {status}.") + + items = result.get("itens") or [] + rendered_items: list[str] = [] + if isinstance(items, list): + for item in items: + if not isinstance(item, dict): + continue + description = item.get("descricao") or item.get("nome") + value = item.get("valor") + if description in (None, ""): + continue + if value is None: + rendered_items.append(str(description)) + else: + rendered_items.append(f"{description}: R$ {_money_brl(value)}") + if rendered_items: + lines.append("Itens: " + "; ".join(rendered_items) + ".") + + # Se não houver nenhum campo de negócio seguro além do cabeçalho, deixe a + # composição pela LLM/guardrails em vez de despejar o payload bruto. + return " ".join(lines) if len(lines) > 1 else None def render_telecom_plan(*, tool_name: str, result: dict[str, Any], state: dict[str, Any], agent_label: str) -> str | None: diff --git a/templates/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc index d2173760..32363647 100644 Binary files a/templates/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc and b/templates/agent_template_backend/app/workflow_actions/__pycache__/__init__.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc b/templates/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc index 9ec9aa1b..001fdff4 100644 Binary files a/templates/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc and b/templates/agent_template_backend/app/workflow_actions/__pycache__/devolucao.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc index c5a35819..720b535e 100644 Binary files a/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc and b/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/templates/agent_template_backend/app/workflows/agent_graph.py b/templates/agent_template_backend/app/workflows/agent_graph.py index e6605a48..fef0791c 100644 --- a/templates/agent_template_backend/app/workflows/agent_graph.py +++ b/templates/agent_template_backend/app/workflows/agent_graph.py @@ -494,6 +494,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -509,15 +622,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -603,7 +716,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc index eafedeeb..6a9d2090 100644 Binary files a/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc index c13b42c4..7e6bd82b 100644 Binary files a/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc index a8f0280d..a18a16d7 100644 Binary files a/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc index f856d0c4..4dc5fa79 100644 Binary files a/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc index 406206df..fa947b3c 100644 Binary files a/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc index cbe2d5df..50916c38 100644 Binary files a/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc index d394a5bf..4e1d80ea 100644 Binary files a/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc index d96c6e4c..ac2905a7 100644 Binary files a/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc index 0d395862..f1619c00 100644 Binary files a/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc index 9dee7059..fa42c73e 100644 Binary files a/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/agents/billing_agent.py b/templates/agent_template_backend_day_zero/app/agents/billing_agent.py index aa600999..05254d17 100644 --- a/templates/agent_template_backend_day_zero/app/agents/billing_agent.py +++ b/templates/agent_template_backend_day_zero/app/agents/billing_agent.py @@ -95,7 +95,7 @@ class BillingAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em faturas. Responda com clareza, objetividade e sem sugerir ações não solicitadas. Use dados MCP quando disponíveis.", + "Você é um agente especialista em faturas.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade para responder somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente, como “contract_key”, “customer_key” ou “MSISDN”.\nPara consultas informativas de fatura, apresente somente dados de negócio necessários, como valor, vencimento, situação e itens cobrados.\nNão acrescente canais, telefones, códigos USSD, URLs, aplicativos, lojas, relatórios adicionais, procedimentos alternativos ou próximos passos que não tenham sido explicitamente retornados pela tool/RAG e solicitados pelo usuário.\nNão ofereça espontaneamente outras ações ou detalhamentos.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/templates/agent_template_backend_day_zero/app/agents/orders_agent.py b/templates/agent_template_backend_day_zero/app/agents/orders_agent.py index f557bed4..b793c260 100644 --- a/templates/agent_template_backend_day_zero/app/agents/orders_agent.py +++ b/templates/agent_template_backend_day_zero/app/agents/orders_agent.py @@ -95,7 +95,7 @@ class OrdersAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de pedidos de varejo. Use dados de tools quando disponíveis.", + "Você é um agente de pedidos de varejo.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso, alteração, troca, cancelamento ou qualquer mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/templates/agent_template_backend_day_zero/app/agents/product_agent.py b/templates/agent_template_backend_day_zero/app/agents/product_agent.py index 34433f57..998dad66 100644 --- a/templates/agent_template_backend_day_zero/app/agents/product_agent.py +++ b/templates/agent_template_backend_day_zero/app/agents/product_agent.py @@ -95,7 +95,7 @@ class ProductAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente especialista em produtos, planos e serviços. Explique sem fazer oferta proativa e sem executar ações sem confirmação. Use dados MCP quando disponíveis.", + "Você é um agente especialista em produtos, planos e serviços.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou MSISDN/telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nEm consultas meramente informativas, não exponha flags ou capacidades transacionais internas como can.cancel e não informe espontaneamente que algo pode ser cancelado, alterado, contratado, removido ou trocado. Só mencione capacidade transacional quando o usuário tiver solicitado essa ação.\nNão faça oferta proativa e não execute nem simule mutações sem a confirmação exigida pelo framework.\nNão acrescente canais, procedimentos ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não declare sucesso, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/templates/agent_template_backend_day_zero/app/agents/support_agent.py b/templates/agent_template_backend_day_zero/app/agents/support_agent.py index b4f0244f..b15a0e42 100644 --- a/templates/agent_template_backend_day_zero/app/agents/support_agent.py +++ b/templates/agent_template_backend_day_zero/app/agents/support_agent.py @@ -95,7 +95,7 @@ class SupportAgent(AgentRuntimeMixin): state, system_prompt=apply_agent_profile_prompt( state, - "Você é um agente de suporte de varejo para troca, devolução e garantia.", + "Você é um agente de suporte de varejo para troca, devolução e garantia.\n\nUse dados de tools/MCP e RAG autorizados como fonte de verdade e responda somente à solicitação atual.\nNunca exponha identificadores técnicos ou de identidade presentes no estado, contexto ou MCP, incluindo customer_key, contract_key, account_key, resource_key, session_key, customer_id, document, message_id, ura_call_id ou telefone completo.\nNão transforme nomes internos de campos em rótulos para o cliente.\nNão declare sucesso nem simule troca, devolução, garantia ou outra mutação se a tool não tiver confirmado a execução.\nNão acrescente canais, procedimentos, ofertas ou próximos passos não solicitados.\nSe uma tool retornar BLOCKED, OUT_OF_SCOPE, NOT_ALLOWED, FAILED ou outro resultado terminal, explique somente o motivo retornado, não invente alternativa e encerre a resposta.", ), mcp_results=tool_context, rag_context=rag_context, diff --git a/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc index 359dee6e..029d41d1 100644 Binary files a/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc index 76a03bbd..2d327ac3 100644 Binary files a/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc index a39b5852..09d9e25f 100644 Binary files a/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc and b/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc differ diff --git a/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index 8612e4f3..aa1eb63b 100644 --- a/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -492,6 +492,119 @@ class AgentWorkflow: "next_state": "SESSION_ENDED", } + @staticmethod + def _output_guardrail_context(state: dict) -> dict: + """Monta o contexto operacional do turno para os guardrails de saída. + + Mantém evidências/protocolos necessários aos rails, mas impede que uma + transação encerrada ou semanticamente interrompida governe o novo turno. + O histórico completo permanece no state/checkpoint para auditoria. + """ + ctx = dict(state.get("context", {}) or {}) + mcp_results = state.get("mcp_results") or [] + ctx["evidence"] = mcp_results or ctx.get("evidence") + ctx["tool_result"] = mcp_results or ctx.get("tool_result") + ctx["tool_executed"] = any(isinstance(r, dict) and r.get("ok") for r in mcp_results) + + history = list(state.get("history") or []) + current_user_text = str(state.get("user_text") or "").strip() + if current_user_text: + if ( + not history + or not isinstance(history[-1], dict) + or str(history[-1].get("content") or "") != current_user_text + or str(history[-1].get("role") or "") != "user" + ): + history.append({"role": "user", "content": current_user_text}) + + route_decision = state.get("route_decision") or {} + route_metadata = route_decision.get("metadata") if isinstance(route_decision, dict) else {} + route_metadata = route_metadata if isinstance(route_metadata, dict) else {} + pre_validation = state.get("transaction_pre_validation") or {} + pre_validation = pre_validation if isinstance(pre_validation, dict) else {} + tx_status = str( + state.get("transaction_status") or pre_validation.get("status") or "" + ).strip().upper() + terminal_tx = bool(pre_validation.get("terminal")) or tx_status in { + "COMPLETED", "FAILED", "CANCELLED", "BLOCKED", "OUT_OF_SCOPE" + } + semantic_intent_shift = ( + str(route_metadata.get("transaction_interruption") or "").strip().lower() + == "intent_shift" + ) + stickiness_intent_shift = bool(route_metadata.get("route_stickiness_preempted")) + should_isolate_history = semantic_intent_shift or (terminal_tx and stickiness_intent_shift) + + current_route = str( + state.get("route") + or (route_decision.get("route") if isinstance(route_decision, dict) else "") + or "" + ).strip() + current_intent = str( + state.get("intent") + or (route_decision.get("intent") if isinstance(route_decision, dict) else "") + or "" + ).strip() + ctx["current_user_message"] = current_user_text + ctx["current_route"] = current_route + ctx["current_intent"] = current_intent + + if should_isolate_history: + operational_history = ( + [{"role": "user", "content": current_user_text}] + if current_user_text else [] + ) + ctx["historical_transaction_ignored"] = True + ctx["historical_transaction_status"] = tx_status or ( + "INTERRUPTED" if semantic_intent_shift else "TERMINAL" + ) + if semantic_intent_shift: + ctx["historical_transaction_interruption"] = "intent_shift" + for stale_key in ( + "transaction_pre_validation", + "transaction_status", + "active_transaction", + "transaction", + ): + ctx.pop(stale_key, None) + else: + operational_history = history + + ctx["conversation_history"] = operational_history + ctx["history_texts"] = [ + str(item.get("content") or "") + for item in operational_history + if isinstance(item, dict) and item.get("content") not in (None, "") + ] + + protocols: list[str] = [] + seen: set[str] = set() + protocol_keys = { + "protocol_number", "protocolo_id", "interactionProtocol", + "protocolNumber", "finalizacao_protocol", + } + + def walk(value): + if isinstance(value, dict): + for key, item in value.items(): + if key in protocol_keys and item not in (None, ""): + text = str(item).strip() + if text and text not in seen: + seen.add(text) + protocols.append(text) + elif isinstance(item, (dict, list, tuple)): + walk(item) + elif isinstance(value, (list, tuple)): + for item in value: + walk(item) + + walk(mcp_results) + if protocols: + ctx["expected_protocols"] = protocols + ctx["requer_protocolo"] = True + ctx.setdefault("tipo_fluxo", "ajuste") + return ctx + async def output_supervisor(self, state): """Valida a resposta candidata com o OutputSupervisor corporativo. @@ -507,15 +620,15 @@ class AgentWorkflow: } candidate = state.get("answer") or "" - context = { - **(state.get("context") or {}), + context = self._output_guardrail_context(state) + context.update({ "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": state.get("conversation_key") or state.get("session_id"), "route": state.get("route"), "intent": state.get("intent"), "supervisor_attempt": int(state.get("supervisor_attempt", 0)), - } + }) async with self.telemetry.span( "workflow.output_supervisor", session_id=state.get("conversation_key") or state.get("session_id"), @@ -601,7 +714,7 @@ class AgentWorkflow: component="workflow.output_guardrails.start", ) final, decisions = await self.guardrails.run_output( - state["answer"], state.get("context", {}) + state["answer"], self._output_guardrail_context(state) ) for _decision in decisions: await self.guardrail_telemetry.evaluated("output", _decision) diff --git a/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc index 17572f2e..2e7e76e5 100644 Binary files a/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_agent_template_backend_safety_consistency.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_agent_template_backend_safety_consistency.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 00000000..203c1aaa Binary files /dev/null and b/tests/__pycache__/test_agent_template_backend_safety_consistency.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_generic_tool_response_presentation.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_generic_tool_response_presentation.cpython-313-pytest-9.0.2.pyc index 4b2fda1c..0e408dc1 100644 Binary files a/tests/__pycache__/test_generic_tool_response_presentation.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_generic_tool_response_presentation.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc index a56a7c0d..479cc369 100644 Binary files a/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc index 289bd4f5..64c4baab 100644 Binary files a/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc index 2bba895f..89a99916 100644 Binary files a/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc index 6300ab3a..25a6167d 100644 Binary files a/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc and b/tests/__pycache__/test_transactional_tool_flow.cpython-313-pytest-9.0.2.pyc differ diff --git a/tests/test_agent_template_backend_safety_consistency.py b/tests/test_agent_template_backend_safety_consistency.py new file mode 100644 index 00000000..9c2788a3 --- /dev/null +++ b/tests/test_agent_template_backend_safety_consistency.py @@ -0,0 +1,70 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def _template_dirs(): + return sorted( + p + for p in ROOT.rglob("*") + if p.is_dir() and p.name in {"agent_template_backend", "agent_template_backend_day_zero"} + ) + + +def test_all_agent_template_backend_copies_have_safe_agent_prompts(): + targets = _template_dirs() + assert targets, "Nenhum template backend encontrado" + for base in targets: + checks = { + "billing_agent.py": ( + "Nunca exponha identificadores técnicos", + "Não acrescente canais", + "Se uma tool retornar BLOCKED", + ), + "product_agent.py": ( + "Nunca exponha identificadores técnicos", + "can.cancel", + "Se uma tool retornar BLOCKED", + ), + "orders_agent.py": ( + "Nunca exponha identificadores técnicos", + "Não declare sucesso", + "Se uma tool retornar BLOCKED", + ), + "support_agent.py": ( + "Nunca exponha identificadores técnicos", + "Não declare sucesso", + "Se uma tool retornar BLOCKED", + ), + } + for filename, required in checks.items(): + text = (base / "app" / "agents" / filename).read_text(encoding="utf-8") + for marker in required: + assert marker in text, f"{base.relative_to(ROOT)}/{filename} sem {marker!r}" + + +def test_all_agent_template_backend_copies_isolate_interrupted_transaction_context(): + for base in _template_dirs(): + graph = (base / "app" / "workflows" / "agent_graph.py").read_text(encoding="utf-8") + assert 'route_metadata.get("transaction_interruption")' in graph + assert '== "intent_shift"' in graph + assert 'ctx["historical_transaction_ignored"] = True' in graph + assert 'ctx["expected_protocols"] = protocols' in graph + assert 'self._output_guardrail_context(state)' in graph + + +def test_full_templates_do_not_dump_raw_invoice_payload(): + checked = 0 + for base in _template_dirs(): + renderer = base / "app" / "presentation" / "tool_renderers.py" + if not renderer.exists(): + continue + checked += 1 + text = renderer.read_text(encoding="utf-8") + assert 'Fatura consultada: {result}' not in text + assert 'result.get("valor_total")' in text + assert 'result.get("vencimento")' in text + assert 'result.get("status")' in text + assert 'result.get("itens")' in text + assert checked > 0