Ajustes conforme relatorio de testes 2026-08-27

This commit is contained in:
2026-08-29 09:53:32 -03:00
parent 0ecff719b7
commit 88e1f070d7
791 changed files with 27040 additions and 29038 deletions

View File

@@ -18,6 +18,27 @@ O objetivo é que cada novo agente implemente apenas sua lógica de domínio —
>**Note: Se deseja ir direto e testar a DEMO, vá até a Seção 17 e 18.**
## Índice de Desenvolvimento — Agent Framework OCI
### Outros idiomas
- [Developer documentation in English](README_en.md)
- [Índice técnico detalhado em Português](docs/developer/pt/INDEX_DEVELOPER_GUIDE.md)
- [Detailed technical index in English](docs/developer/en/INDEX_DEVELOPER_GUIDE.md)
### Como usar esta documentação
A documentação possui três níveis:
1. **Tutorial principal:** este [`README.md`](README.md) — criação, configuração, execução e teste de um agente do início ao fim.
2. **Arquitetura:** [01 — Arquitetura e Conceitos](docs/developer/pt/01_architecture_and_concepts.md) — componentes, responsabilidades e onde implementar cada coisa.
3. **Referências especializadas:** manuais `02` a `12` — implementação profunda e troubleshooting por capacidade.
Se você está começando um novo agente, siga este `README.md` desde o início. Para aprofundamento ou troubleshooting, use os links abaixo.
Se algo não está funcionando ou se deseja entender melhor funcionalidades da arquitetura do Agent Framework OCI, vá até [34. Funcionalidades Avançadas](#34-funcionalidades-avançadas). Você vai encontrar detalhamento sobre funcionalidades avançadas, como conceitos, exemplos e manuais de utilização.
## SPECs / SDDs da Agent Platform OCI
@@ -11177,6 +11198,121 @@ A adoção das funcionalidades do `Tuning-Performance` pode proporcionar:
O conteúdo desta pasta deve ser tratado como uma extensão adicional do framework. Sua utilização requer implementação, configuração, testes funcionais e validação das regras de negócio antes da implantação em produção.
### RAG provider alternativo (KBDB Enterprise)
### Buscar pelo problema
| Problema / dúvida | O que normalmente está envolvido | Onde procurar |
|---|---|---|
| O framework não encontra o agente/intenção correta | routing, intents, threshold, modo determinístico/LLM | [Routing e Stickiness](docs/developer/pt/02_routing_stickiness_and_intent_shift.md) |
| O agente fica preso no mesmo assunto e não troca de intent | route stickiness, intent shift, handoff | [Routing e Stickiness](docs/developer/pt/02_routing_stickiness_and_intent_shift.md) |
| Uma resposta que deveria preencher parâmetro é interpretada como novo intent | precedência transacional, parameter extraction | [Workflows Transacionais](docs/developer/pt/03_transaction_workflows_and_state.md) |
| A transação fica pedindo o mesmo parâmetro | estado transacional, extractor, schema | [Workflows Transacionais](docs/developer/pt/03_transaction_workflows_and_state.md) e [MCP/Tools](docs/developer/pt/04_mcp_integration_tools_and_policies.md) |
| A confirmação “sim/não” não continua o fluxo | confirmation state, transaction state | [Workflows Transacionais](docs/developer/pt/03_transaction_workflows_and_state.md) |
| Uma transação encerrada reaparece | checkpoint antigo versus estado transacional ativo | [Workflows Transacionais](docs/developer/pt/03_transaction_workflows_and_state.md) e [LTM/Checkpoint](docs/developer/pt/08_long_term_memory_and_checkpoint.md) |
| O sistema diz que executou algo, mas não existe evidência | MCP result, estado `COMPLETED`, judges transacionais | [Workflows Transacionais](docs/developer/pt/03_transaction_workflows_and_state.md) e [Guardrails/Judges](docs/developer/pt/06_guardrails_judges_and_transaction_evaluation.md) |
| Uma tool não aparece ou não é encontrada | `tools.yaml`, catálogo MCP, discovery | [MCP/Tools](docs/developer/pt/04_mcp_integration_tools_and_policies.md) |
| MCP Server não aparece no catálogo | registration, manifest/discovery, MCP Gateway | [MCP/Tools](docs/developer/pt/04_mcp_integration_tools_and_policies.md) e [Gateways](docs/developer/pt/05_agent_gateway_mcp_gateway_and_auth.md) |
| Parâmetros enviados à tool estão errados | schema, mapping, BusinessContext, extractor | [MCP/Tools](docs/developer/pt/04_mcp_integration_tools_and_policies.md) |
| Uma operação transacional executa sem confirmação | tool policy, `require_confirmation` | [MCP/Tools](docs/developer/pt/04_mcp_integration_tools_and_policies.md) |
| Uma busca por nome exige correspondência exata demais | extração/mapeamento de parâmetros e lógica do agente | [MCP/Tools](docs/developer/pt/04_mcp_integration_tools_and_policies.md) |
| Recebo 401 entre gateway/backend/MCP | Basic Auth, credenciais por hop | [Gateways e Auth](docs/developer/pt/05_agent_gateway_mcp_gateway_and_auth.md) |
| Preciso decidir se algo pertence ao framework ou ao agente | boundary core/agente | [Arquitetura e Conceitos](docs/developer/pt/01_architecture_and_concepts.md) |
| Guardrail específico de um agente está quebrando outro | extensibilidade, imports de domínio no core | [Guardrails e Judges](docs/developer/pt/06_guardrails_judges_and_transaction_evaluation.md) |
| Uma frase incompleta recebe mensagem genérica de “regra de segurança” | feedback de input guardrail, `COER`, blocked-turn state | [Feedback de Guardrails de Entrada](docs/developer/pt/12_input_guardrail_feedback_and_blocked_turns.md) |
| `route=blocked` aparece junto com tools/resultados de outro turno | limpeza de estado do turno bloqueado | [Feedback de Guardrails de Entrada](docs/developer/pt/12_input_guardrail_feedback_and_blocked_turns.md) |
| Workflow conclui e gera protocolo, mas a resposta final vira mensagem de segurança | `expected_protocols`, `CMP`, `DLEX_OUT`, ordem de `output_guardrails` | [Guardrails e Judges](docs/developer/pt/06_guardrails_judges_and_transaction_evaluation.md) |
| Judge não roda em uma transação | sampling, `always_run_for_transactional`, sinais transacionais | [Guardrails e Judges](docs/developer/pt/06_guardrails_judges_and_transaction_evaluation.md) |
| Groundedness está avaliando sem contexto correto | RAG context, MCP evidence, judge inputs | [RAG/Grounding](docs/developer/pt/07_rag_business_context_and_grounding.md) |
| RAG não encontra conteúdo | provider, ingestão, embeddings, configuração | [RAG/Grounding](docs/developer/pt/07_rag_business_context_and_grounding.md) |
| Não sei se usar RAG, memória ou tool | separação de responsabilidades | [Arquitetura e Conceitos](docs/developer/pt/01_architecture_and_concepts.md) e [RAG/Grounding](docs/developer/pt/07_rag_business_context_and_grounding.md) |
| Memória desaparece ao trocar de sessão | LTM versus conversation memory | [LTM e Checkpoint](docs/developer/pt/08_long_term_memory_and_checkpoint.md) |
| Memória de um cliente/agente aparece em outro | identity key, tenant/agent/customer isolation | [LTM e Checkpoint](docs/developer/pt/08_long_term_memory_and_checkpoint.md) |
| Preciso recuperar `reasoning_content` | `ainvoke_response()` | [LLM Rich Response](docs/developer/pt/09_llm_rich_response_reasoning.md) |
| `reasoning_content` vem `None` | provider/model não expõe o campo | [LLM Rich Response](docs/developer/pt/09_llm_rich_response_reasoning.md) |
| Há chamadas LLM desnecessárias | routing determinístico, concorrência, cache | [Performance](docs/developer/pt/10_performance_cache_and_async_runtime.md) |
| Há deadlock ou espera entre event loops | cross-loop sequence/runtime | [Performance](docs/developer/pt/10_performance_cache_and_async_runtime.md) |
| Logs/traces não correlacionam o mesmo agente | labels, IDs e mapeamento de observabilidade | [Observabilidade](docs/developer/pt/11_observability_persistence_and_operational_readiness.md) |
| Sequence está interferindo no processamento | implementação assíncrona de sequência | [Observabilidade](docs/developer/pt/11_observability_persistence_and_operational_readiness.md) e [Performance](docs/developer/pt/10_performance_cache_and_async_runtime.md) |
| Um exemplo antigo não compila | documentação histórica versus API atual | [Validação README x Código](docs/developer/pt/VALIDATION_README_ALIGNMENT.md) |
| Preciso criar um agente novo do zero | fluxo completo | [`README.md`](README.md) |
| Preciso saber onde colocar uma nova feature | arquitetura e boundaries | [Arquitetura e Conceitos](docs/developer/pt/01_architecture_and_concepts.md) |
### 34. Funcionalidades Avançadas
### [01 — Arquitetura e Conceitos](docs/developer/pt/01_architecture_and_concepts.md)
**O que é:** visão dos componentes, contratos e limites de responsabilidade.
**Use quando:** precisar entender a plataforma, decidir onde implementar algo ou evitar acoplamento entre core e agente.
### [02 — Routing, Route Stickiness e Intent Shift](docs/developer/pt/02_routing_stickiness_and_intent_shift.md)
**O que é:** referência completa de descoberta de agente/intent, stickiness, handoff e mudança de intenção.
**Use quando:** a mensagem cai no agente errado, não troca de intent ou perde continuidade.
### [03 — Workflows Transacionais e Estado](docs/developer/pt/03_transaction_workflows_and_state.md)
**O que é:** ciclo transacional multi-turno, estados, confirmação, pausa/retomada e evidência operacional.
**Use quando:** há loops, confirmações incorretas, retomadas erradas ou operações críticas.
### [04 — MCP, Tools, Policies e Extração de Parâmetros](docs/developer/pt/04_mcp_integration_tools_and_policies.md)
**O que é:** referência de tools, MCP Servers, mappings, policies e parameter extraction.
**Use quando:** integração/execução de tool está incorreta ou precisa ser criada.
### [05 — Agent Gateway, MCP Gateway e Autenticação](docs/developer/pt/05_agent_gateway_mcp_gateway_and_auth.md)
**O que é:** responsabilidades dos gateways, governança e autenticação entre componentes.
**Use quando:** houver problema de entrada, catálogo, autorização, 401 ou deployment dos gateways.
### [06 — Guardrails, Judges e Avaliação Transacional](docs/developer/pt/06_guardrails_judges_and_transaction_evaluation.md)
**O que é:** validações nativas/externas, judges, grounding e regras para turnos transacionais.
**Use quando:** uma validação bloqueia, não roda ou produz avaliação incorreta.
### [07 — RAG, BusinessContext e Grounding](docs/developer/pt/07_rag_business_context_and_grounding.md)
**O que é:** providers de RAG, contexto recuperado, BusinessContext e grounding.
**Use quando:** conhecimento recuperado não chega corretamente ao agente/judge.
### [08 — Long-Term Memory e Checkpoint](docs/developer/pt/08_long_term_memory_and_checkpoint.md)
**O que é:** memória durável, memória conversacional, identidade e snapshots de estado.
**Use quando:** contexto some, vaza ou workflow retoma do lugar errado.
### [09 — LLM Rich Response e reasoning_content](docs/developer/pt/09_llm_rich_response_reasoning.md)
**O que é:** resposta estruturada de inferência além do `str` retornado por `ainvoke()`.
**Use quando:** consumidores precisam de metadados, usage ou reasoning disponibilizado pelo provider.
### [10 — Performance, Cache e Runtime Assíncrono](docs/developer/pt/10_performance_cache_and_async_runtime.md)
**O que é:** otimizações de concorrência, cache, LLM e event loops.
**Use quando:** houver latência evitável, processamento serial ou deadlock.
### [11 — Observabilidade, Persistência e Prontidão Operacional](docs/developer/pt/11_observability_persistence_and_operational_readiness.md)
**O que é:** correlação, eventos, labels, sequence, persistência e diagnóstico.
**Use quando:** for necessário provar o caminho executado ou diagnosticar produção.
### [12 — Feedback de Guardrails de Entrada e Turnos Bloqueados](docs/developer/pt/12_input_guardrail_feedback_and_blocked_turns.md)
**O que é:** semântica de mensagens públicas para bloqueios de input, limpeza do estado do turno e passagem da resposta pelos guardrails de saída.
**Use quando:** um `COER`/guardrail de entrada gera mensagem genérica, `route=blocked` carrega resultados antigos ou há dúvida sobre a precedência entre input guardrails, routing e tools.
### Tutorial principal
[`README.md`](README.md) continua sendo a referência para o passo a passo completo:
`arquitetura → configuração → criação do agente → registro → estado → routing → tools → MCP → identidade → execução → testes → gateways → memória → RAG`.
O RAG original continua sendo o default (`RAG_PROVIDER=standard`). Para usar a arquitetura KBDB enterprise como alternativa de serving, configure `RAG_PROVIDER=kbdb`. Os agentes continuam usando o mesmo `RagService`/`_retrieve_rag_context()` e os dois backends não são executados simultaneamente. Consulte `docs/RAG_PROVIDER_KBDB.md`.

View File

@@ -18,6 +18,28 @@ The goal is for each new agent to implement only its domain logic — prompts, b
>**Note: If you want to test the DEMO, go to the Section 17 and 18.**
## Developer Index — Agent Framework OCI
### Other languages
- [Documentação de desenvolvimento em Português](README.md)
- [Detailed technical index in English](docs/developer/en/INDEX_DEVELOPER_GUIDE.md)
- [Índice técnico detalhado em Português](docs/developer/pt/INDEX_DEVELOPER_GUIDE.md)
### How to use this documentation
The documentation has three clear levels:
1. **Main tutorial:** this [`README_en.md`](README_en.md) — build, configure, run and test an agent end to end.
2. **Architecture:** [01 — Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) — components, boundaries and implementation placement.
3. **Specialized references:** manuals `02` through `12` — deep implementation and troubleshooting by capability.
If you are creating a new agent, follow this `README_en.md` from the beginning. For deeper implementation details or troubleshooting, use the links below.
If something is not working or if you want to understand the features of the Agent Framework OCI architecture, go to [34. Advanced Features](#34-advanced-features)
## SPECs / SDDs of the Agent Platform OCI
The Agent Platform OCI documentation is organized into numbered SPECs/SDDs, each covering an architectural, operational, or governance area of the platform. The objective is to standardize the construction, evolution, operation, and certification of enterprise agents based on the Agent Framework OCI.
@@ -11082,3 +11104,115 @@ Adopting the `Tuning-Performance` capabilities can provide:
* consistent behavior across agents and projects.
The content of this folder should be treated as an additional framework extension. Its use requires implementation, configuration, functional testing, and business-rule validation before production deployment.
### Search by problem
| Problem / question | Usually involves | Go to |
|---|---|---|
| Framework selects the wrong agent/intent | routing, intents, thresholds, deterministic/LLM mode | [Routing and Stickiness](docs/developer/en/02_routing_stickiness_and_intent_shift.md) |
| Agent stays stuck on the same subject | route stickiness, intent shift, handoff | [Routing and Stickiness](docs/developer/en/02_routing_stickiness_and_intent_shift.md) |
| A parameter answer is mistaken for a new intent | transaction precedence, parameter extraction | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) |
| Transaction keeps asking for the same parameter | transaction state, extractor, schema | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) and [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
| “yes/no” confirmation does not continue the flow | confirmation state | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) |
| A closed transaction reappears | old checkpoint vs active transaction | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) and [LTM/Checkpoint](docs/developer/en/08_long_term_memory_and_checkpoint.md) |
| System claims an operation ran but there is no evidence | MCP results, `COMPLETED`, transaction judges | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) and [Guardrails/Judges](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md) |
| A tool is missing | tools config, MCP catalog/discovery | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
| MCP Server is missing from catalog | registration, manifest/discovery, MCP Gateway | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) and [Gateways](docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md) |
| Tool parameters are wrong | schema, mapping, BusinessContext, extraction | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
| Transactional tool executes without confirmation | policy, `require_confirmation` | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
| 401 between gateway/backend/MCP | Basic Auth, hop credentials | [Gateways and Auth](docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md) |
| Need to decide framework vs agent ownership | core/agent boundary | [Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) |
| Agent-specific guardrail breaks another agent | extension model, domain imports | [Guardrails and Judges](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md) |
| Judge does not run for a transaction | sampling, transaction signals | [Guardrails and Judges](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md) |
| Groundedness gets the wrong context | RAG context, MCP evidence, judge inputs | [RAG/Grounding](docs/developer/en/07_rag_business_context_and_grounding.md) |
| RAG returns no useful content | provider, ingestion, embeddings | [RAG/Grounding](docs/developer/en/07_rag_business_context_and_grounding.md) |
| Unsure whether to use RAG, memory or a tool | responsibility separation | [Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) |
| Memory disappears across sessions | LTM vs conversation memory | [LTM and Checkpoint](docs/developer/en/08_long_term_memory_and_checkpoint.md) |
| Memory leaks across customer/agent | identity isolation | [LTM and Checkpoint](docs/developer/en/08_long_term_memory_and_checkpoint.md) |
| Need `reasoning_content` | `ainvoke_response()` | [LLM Rich Response](docs/developer/en/09_llm_rich_response_reasoning.md) |
| `reasoning_content` is `None` | provider/model does not expose it | [LLM Rich Response](docs/developer/en/09_llm_rich_response_reasoning.md) |
| Too many LLM calls | deterministic routing, concurrency, cache | [Performance](docs/developer/en/10_performance_cache_and_async_runtime.md) |
| Deadlock across event loops | cross-loop runtime/sequence | [Performance](docs/developer/en/10_performance_cache_and_async_runtime.md) |
| Logs/traces do not correlate the same agent | labels, IDs, observability mapping | [Observability](docs/developer/en/11_observability_persistence_and_operational_readiness.md) |
| Historical example no longer compiles | stale docs vs current API | [README Alignment Validation](docs/developer/en/VALIDATION_README_ALIGNMENT.md) |
| Need to create a new agent from scratch | complete flow | [`README_en.md`](README_en.md) |
### 34. Advanced Features
### [01 — Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md)
**What it is:** component, contract and responsibility-boundary reference.
**Use it when:** understanding the platform or deciding where a feature belongs.
### [02 — Routing, Route Stickiness and Intent Shift](docs/developer/en/02_routing_stickiness_and_intent_shift.md)
**What it is:** agent/intent discovery, stickiness, handoff and intent-shift reference.
**Use it when:** routing is wrong or session continuity behaves incorrectly.
### [03 — Transactional Workflows and State](docs/developer/en/03_transaction_workflows_and_state.md)
**What it is:** multi-turn transaction lifecycle, states, confirmation, resume and execution evidence.
**Use it when:** transactions loop, resume incorrectly or perform critical operations.
### [04 — MCP, Tools, Policies and Parameter Extraction](docs/developer/en/04_mcp_integration_tools_and_policies.md)
**What it is:** tools, MCP Servers, mappings, policies and extraction reference.
**Use it when:** building or troubleshooting tool integration.
### [05 — Agent Gateway, MCP Gateway and Authentication](docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md)
**What it is:** gateway responsibilities, governance and component authentication.
**Use it when:** troubleshooting ingress, catalog, authorization or gateway deployment.
### [06 — Guardrails, Judges and Transaction Evaluation](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md)
**What it is:** native/external validation, judges, grounding and transaction evaluation.
**Use it when:** validation blocks, skips or evaluates incorrectly.
### [07 — RAG, BusinessContext and Grounding](docs/developer/en/07_rag_business_context_and_grounding.md)
**What it is:** RAG providers, retrieved context, BusinessContext and grounding.
**Use it when:** retrieved knowledge does not reach the runtime/judge correctly.
### [08 — Long-Term Memory and Checkpoint](docs/developer/en/08_long_term_memory_and_checkpoint.md)
**What it is:** durable memory, conversational memory, identity and state snapshots.
**Use it when:** context disappears, leaks or resumes incorrectly.
### [09 — LLM Rich Response and reasoning_content](docs/developer/en/09_llm_rich_response_reasoning.md)
**What it is:** structured inference output beyond the `str` returned by `ainvoke()`.
**Use it when:** consumers require provider metadata, usage or reasoning exposed by the provider.
### [10 — Performance, Cache and Async Runtime](docs/developer/en/10_performance_cache_and_async_runtime.md)
**What it is:** concurrency, caching, LLM and event-loop optimization reference.
**Use it when:** reducing avoidable latency or diagnosing deadlocks.
### [11 — Observability, Persistence and Operational Readiness](docs/developer/en/11_observability_persistence_and_operational_readiness.md)
**What it is:** correlation, events, labels, sequencing, persistence and production diagnostics.
**Use it when:** proving execution paths or diagnosing production behavior.
### [12 — Input Guardrail Feedback and Blocked-Turn Semantics](docs/developer/en/12_input_guardrail_feedback_and_blocked_turns.md)
**What it is:** user-facing semantics for input blocks, blocked-turn state cleanup, and output-guardrail validation of the generated feedback.
**Use it when:** `COER`/input guardrails generate generic messages, `route=blocked` carries stale results, or you need to reason about precedence between input guardrails, routing, and tools.
### Main tutorial
[`README_en.md`](README_en.md) remains the complete step-by-step guide.
| Workflow completes and generates a protocol, but the final response becomes a safety message | `expected_protocols`, `CMP`, `DLEX_OUT`, `output_guardrails` ordering | [Guardrails and Judges](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md) |

View File

@@ -160,7 +160,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "load_long_term_memory"},
{"blocked": "output_guardrails", "continue": "load_long_term_memory"},
)
builder.add_edge("load_long_term_memory", "routing_decision")
builder.add_conditional_edges(
@@ -197,6 +197,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -281,12 +306,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -160,7 +160,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "load_long_term_memory"},
{"blocked": "output_guardrails", "continue": "load_long_term_memory"},
)
builder.add_edge("load_long_term_memory", "routing_decision")
builder.add_conditional_edges(
@@ -197,6 +197,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -281,12 +306,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -159,7 +159,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "routing_decision"},
{"blocked": "output_guardrails", "continue": "routing_decision"},
)
builder.add_conditional_edges(
"routing_decision",
@@ -195,6 +195,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -279,12 +304,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -160,7 +160,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "load_long_term_memory"},
{"blocked": "output_guardrails", "continue": "load_long_term_memory"},
)
builder.add_edge("load_long_term_memory", "routing_decision")
builder.add_conditional_edges(
@@ -197,6 +197,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -281,12 +306,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -159,7 +159,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "routing_decision"},
{"blocked": "output_guardrails", "continue": "routing_decision"},
)
builder.add_conditional_edges(
"routing_decision",
@@ -195,6 +195,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -279,12 +304,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -159,7 +159,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "routing_decision"},
{"blocked": "output_guardrails", "continue": "routing_decision"},
)
builder.add_conditional_edges(
"routing_decision",
@@ -195,6 +195,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -279,12 +304,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -13,3 +13,46 @@ O `WorkflowRuntime` também preserva o último snapshot persistido do LangGraph
Isso é necessário para workflows transacionais: por exemplo, se um protocolo foi criado e uma chamada posterior falha, o chamador ainda recebe o `protocol_number` persistido e pode executar recuperação/idempotência sem repetir o primeiro side effect.
O runtime não transforma falha em sucesso e não reexecuta automaticamente a action; ele apenas preserva a evidência durável já existente no checkpointer.
## Tratamento genérico de entrada fora das opções (`unmatched`)
`expected_input` mantém compatibilidade com o comportamento anterior.
Sem `semantic_classifier`, qualquer entrada que não pertença literalmente a
`allowed_values` permanece no workflow e recebe o `reprompt`.
Quando o agente precisa aceitar linguagem natural, ele declara um prompt
classificatório cujo resultado deve ser uma das próprias opções dinâmicas:
```yaml
expected_input:
key: resposta_usuario
allowed_values: [SIM, NAO]
normalize: upper_strip
reprompt: "Não entendi. Responda sim ou não."
semantic_classifier:
enabled: true
prompt: |
Classifique a fala em exatamente uma opção de {{ allowed_values }}.
Pergunta pendente: {{ pending_prompt }}
Fala do usuário: {{ user_input }}
Retorne somente uma opção de {{ allowed_values }}.
```
O framework não possui classes fixas. `allowed_values` pode conter duas, três ou
mais opções; o prompt do agente define a semântica de cada uma. O framework
renderiza os placeholders, chama a LLM e rejeita qualquer saída que não pertença
à allowlist, usando `reprompt` nesse caso. O texto original do usuário é mantido
nos metadados da decisão para auditoria.
Nesse modo, `COER` delega a interpretação semântica ao classificador configurado.
Rails de segurança independentes — por exemplo PINJ, toxicidade, PII e limites
de tamanho — continuam podendo bloquear o turno normalmente.
O exemplo executável está em `agent_template_backend/` e, por compatibilidade
com a estrutura histórica desta feature, também em
`agent_template_backend_pause_resume/`.
### Reentrada contextual por opção
Uma opção do `semantic_classifier` pode declarar `option_actions.<OPCAO>.action: contextual_reentry`. Nesse caso o workflow pausado não é retomado: o framework libera a pausa e reexecuta o roteamento usando somente o contexto conversacional ancorado que originou a decisão mais a fala atual. A fala original é preservada para auditoria e o contexto reconstruído não vira evidência de negócio; parâmetros candidatos continuam sujeitos a validação e confirmação normais.

View File

@@ -32,3 +32,49 @@ O exemplo usa `MemorySaver` apenas para ser autocontido. Em aplicações reais u
## Regra arquitetural
Código de domínio não deve importar `langgraph.graph.StateGraph`. Para grafos de agentes use `FrameworkStateGraph`; para workflows determinísticos de negócio use `WorkflowRuntime`.
## Entrada enumerada, reprompt e `semantic_classifier`
O contrato `expected_input` pode declarar qualquer conjunto de opções em
`allowed_values`. O match literal continua determinístico; quando a resposta não
coincide literalmente com uma opção, o agente pode habilitar um classificador
semântico com prompt próprio.
```yaml
expected_input:
key: resposta_usuario
allowed_values: [SIM, NAO]
normalize: upper_strip
reprompt: "Não entendi. Responda sim ou não."
semantic_classifier:
enabled: true
prompt: |
Classifique {{ user_input }} em exatamente uma opção de {{ allowed_values }}.
Para este workflow, aceitação/entendimento => SIM; negação, nova pergunta
ou hipótese factual a validar => NAO.
Retorne somente uma opção de {{ allowed_values }}.
```
O framework não conhece o significado de `SIM`, `NAO` nem de nenhuma outra
opção. Ele apenas injeta `allowed_values`, `pending_prompt` e `user_input`, chama
a LLM e valida estritamente se a saída pertence à lista declarada. Uma saída
fora da lista usa o `reprompt`.
O mesmo mecanismo funciona sem alteração do framework para, por exemplo,
`[CONFIRMAR, ALTERAR, CANCELAR]` ou qualquer outra lista configurada pelo agente.
O rail `COER` delega a semântica ao `semantic_classifier` nesse modo; PINJ,
toxicidade, PII e os demais rails de segurança continuam independentes.
Há dois diretórios equivalentes para facilitar comparação com os demais
cenários de Tuning-Performance:
- `agent_template_backend/` — nome padrão de template;
- `agent_template_backend_pause_resume/` — nome histórico deste exemplo.
Ambos contêm o mesmo workflow `confirmacao.v1.yaml`.
### Reentrada contextual por opção
Uma opção do `semantic_classifier` pode declarar `option_actions.<OPCAO>.action: contextual_reentry`. Nesse caso o workflow pausado não é retomado: o framework libera a pausa e reexecuta o roteamento usando somente o contexto conversacional ancorado que originou a decisão mais a fala atual. A fala original é preservada para auditoria e o contexto reconstruído não vira evidência de negócio; parâmetros candidatos continuam sujeitos a validação e confirmação normais.

View File

@@ -0,0 +1,26 @@
# Agent Template Backend — Pause/Resume Workflow
Exemplo autocontido de um agente que usa o motor genérico de workflows do
`agent_framework_oci`.
O arquivo `workflows/confirmacao.v1.yaml` demonstra:
- `pause`;
- `expected_input`;
- `allowed_values`;
- `normalize`;
- `reprompt`;
- `semantic_classifier`;
- `resume_from`.
O framework não conhece `SIM`, `NAO` nem a regra de negócio. O agente declara
os valores e o prompt no YAML. Quando a fala não corresponde literalmente a uma
opção, `semantic_classifier` classifica usando o prompt do agente e o framework
aceita somente uma saída presente em `allowed_values`; qualquer outra saída usa
o `reprompt`. O mesmo mecanismo funciona com qualquer quantidade de opções.
Execute os testes a partir desta pasta:
```bash
pytest -q
```

View File

@@ -0,0 +1,56 @@
from __future__ import annotations
import asyncio
from pathlib import Path
from agent_framework.workflows import FileWorkflowRepository, WorkflowActionRegistry, WorkflowRuntime
ROOT = Path(__file__).resolve().parents[1]
def build_runtime(*, offline_test_fallback: bool = False) -> WorkflowRuntime:
actions = WorkflowActionRegistry()
async def preparar(params, state):
return {"assunto": params.get("assunto") or "operação"}
async def perguntar(params, state):
return {"mensagem": f"Deseja confirmar {params['assunto']}?"}
async def decidir(params, state):
return {
"mensagem": "Operação confirmada." if params["resposta"] == "SIM" else "Operação cancelada.",
"confirmado": params["resposta"] == "SIM",
}
actions.register("preparar_operacao", preparar)
actions.register("montar_pergunta", perguntar)
actions.register("registrar_decisao", decidir)
checkpointer = None
if not offline_test_fallback:
# Produção/exemplo real continua usando LangGraph + checkpointer. O import
# fica aqui para que a regressão offline do repositório não dependa de rede.
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
return WorkflowRuntime(
FileWorkflowRepository(ROOT / "workflows"),
actions=actions,
checkpointer=checkpointer,
allow_deterministic_fallback=offline_test_fallback,
)
async def main() -> None:
runtime = build_runtime()
first = await runtime.arun("confirmacao", {"assunto": "a alteração do plano"})
print(first.model_dump(mode="json"))
assert first.status == "PAUSED"
resumed = await runtime.aresume("confirmacao", first.execution_id, {"resposta_usuario": "sim"})
print(resumed.model_dump(mode="json"))
assert resumed.status == "COMPLETED"
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -0,0 +1,35 @@
from __future__ import annotations
import pytest
from app.demo import build_runtime
@pytest.mark.asyncio
async def test_pause_resume_does_not_repeat_previous_action():
# Regressão offline: exercita a mesma DSL/WorkflowRuntime sem exigir download
# de LangGraph no builder. Produção continua usando build_runtime() default.
runtime = build_runtime(offline_test_fallback=True)
first = await runtime.arun("confirmacao", {"assunto": "o cancelamento"})
assert first.status == "PAUSED"
assert first.pause["expected_input"]["key"] == "resposta_usuario"
before = [item for item in first.trace if item.get("action") == "preparar_operacao"]
assert len(before) == 1
resumed = await runtime.aresume("confirmacao", first.execution_id, {"resposta_usuario": "SIM"})
assert resumed.status == "COMPLETED"
after = [item for item in resumed.trace if item.get("action") == "preparar_operacao"]
assert len(after) == 1
assert resumed.state["vars"]["decidir"]["confirmado"] is True
def test_pause_resume_example_documents_semantic_classifier():
from pathlib import Path
import yaml
project = Path(__file__).resolve().parents[1]
data = yaml.safe_load((project / "workflows" / "confirmacao.v1.yaml").read_text(encoding="utf-8"))
perguntar = next(node for node in data["nodes"] if node["id"] == "perguntar")
expected = perguntar["pause"]["expected_input"]
assert expected["reprompt"] == "Não entendi. Responda sim ou não."
assert expected["semantic_classifier"]["enabled"] is True
assert "{{ allowed_values }}" in expected["semantic_classifier"]["prompt"]

View File

@@ -0,0 +1,45 @@
name: confirmacao
version: 1
start: preparar
nodes:
- id: preparar
action: preparar_operacao
input:
assunto: $.input.assunto
- id: perguntar
action: montar_pergunta
input:
assunto: $.vars.preparar.assunto
pause:
enabled: true
return_from: $.output.mensagem
expected_input:
key: resposta_usuario
allowed_values: [SIM, NAO]
normalize: upper_strip
reprompt: "Não entendi. Responda sim ou não."
semantic_classifier:
include_relevant_context: true
enabled: true
prompt: |
Classifique a resposta em exatamente uma opção de {{ allowed_values }}.
Pergunta pendente: {{ pending_prompt }}
Contexto relevante:
{{ relevant_conversation_context }}
Resposta do usuário: {{ user_input }}
Neste exemplo, concordância/aceitação corresponde a SIM e recusa, dúvida
adicional ou nova condição corresponde a NAO.
Retorne somente uma opção de {{ allowed_values }}.
resume_from: decidir
- id: decidir
action: registrar_decisao
input:
resposta: $.input.resposta_usuario
assunto: $.vars.preparar.assunto
edges:
- from: preparar
to: perguntar
- from: perguntar
to: END
- from: decidir
to: END

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -20,3 +20,16 @@ async def test_pause_resume_does_not_repeat_previous_action():
after = [item for item in resumed.trace if item.get("action") == "preparar_operacao"]
assert len(after) == 1
assert resumed.state["vars"]["decidir"]["confirmado"] is True
def test_pause_resume_example_documents_semantic_classifier():
from pathlib import Path
import yaml
project = Path(__file__).resolve().parents[1]
data = yaml.safe_load((project / "workflows" / "confirmacao.v1.yaml").read_text(encoding="utf-8"))
perguntar = next(node for node in data["nodes"] if node["id"] == "perguntar")
expected = perguntar["pause"]["expected_input"]
assert expected["reprompt"] == "Não entendi. Responda sim ou não."
assert expected["semantic_classifier"]["enabled"] is True
assert "{{ allowed_values }}" in expected["semantic_classifier"]["prompt"]

View File

@@ -17,6 +17,19 @@ nodes:
key: resposta_usuario
allowed_values: [SIM, NAO]
normalize: upper_strip
reprompt: "Não entendi. Responda sim ou não."
semantic_classifier:
include_relevant_context: true
enabled: true
prompt: |
Classifique a resposta em exatamente uma opção de {{ allowed_values }}.
Pergunta pendente: {{ pending_prompt }}
Contexto relevante:
{{ relevant_conversation_context }}
Resposta do usuário: {{ user_input }}
Neste exemplo, concordância/aceitação corresponde a SIM e recusa, dúvida
adicional ou nova condição corresponde a NAO.
Retorne somente uma opção de {{ allowed_values }}.
resume_from: decidir
- id: decidir
action: registrar_decisao

View File

@@ -159,7 +159,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "routing_decision"},
{"blocked": "output_guardrails", "continue": "routing_decision"},
)
builder.add_conditional_edges(
"routing_decision",
@@ -195,6 +195,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -279,12 +304,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -159,7 +159,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "routing_decision"},
{"blocked": "output_guardrails", "continue": "routing_decision"},
)
builder.add_conditional_edges(
"routing_decision",
@@ -195,6 +195,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -279,12 +304,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -14,45 +14,38 @@ CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
# LLM - OCI Generative AI como provider principal
###############################################################################
# Opções: mock, oci_openai, oci_sdk, openai_compatible
LLM_PROVIDER=oci_sdk
LLM_PROVIDER=oci_openai
LLM_TEMPERATURE=0.2
LLM_MAX_TOKENS=2048
LLM_TIMEOUT_SECONDS=120
# OCI OpenAI-compatible endpoint
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1
OCI_GENAI_MODEL=openai.gpt-4.1
OCI_GENAI_API_KEY=sk-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS
OCI_GENAI_API_KEY=sk-ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6
OCI_GENAI_PROJECT_OCID=
#OCI_GENAI_BASE_URL=https://pegruagntaiatenddev.pe.inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com
#OCI_GENAI_MODEL=openai.gpt-4.1
#OCI_GENAI_API_KEY=
#OCI_GENAI_PROJECT_OCID=
# OCI_AUTH_MODE=config_file|instance_principal|resource_principal
OCI_AUTH_MODE=config_file
# OCI SDK / signer / profiles
OCI_CONFIG_FILE=~/.oci/config
OCI_PROFILE=LATINOAMERICA-Chicago
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q
OCI_PROFILE=DEFAULT
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
OCI_REGION=us-chicago-1
###############################################################################
# Persistência
###############################################################################
# Opções: memory, autonomous, mongodb
SESSION_REPOSITORY_PROVIDER=autonomous
MEMORY_REPOSITORY_PROVIDER=autonomous
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
SESSION_REPOSITORY_PROVIDER=sqlite
MEMORY_REPOSITORY_PROVIDER=sqlite
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
SQLITE_DB_PATH=./data/agent_framework.db
# Autonomous Database
ADB_USER=admin
ADB_PASSWORD=Moniquinha19721972
ADB_DSN=oradb23ai_high
ADB_WALLET_LOCATION=/mnt/d/Dropbox/ORACLE/LatinoAmerica/Wallet_ORADB23ai
ADB_WALLET_PASSWORD=Moniquinha1972
ADB_PASSWORD=fjhsdf04954hf
ADB_DSN=oradb23aidev_high
ADB_WALLET_LOCATION=/ORACLE/DEFAULT/Wallet_ORADB23aiDev
ADB_WALLET_PASSWORD=fjhsdf04954hf
ADB_TABLE_PREFIX=AGENTFW
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
@@ -66,10 +59,10 @@ ENABLE_REDIS_CACHE=false
###############################################################################
# RAG / Vector / Graph
###############################################################################
VECTOR_STORE_PROVIDER=autonomous
GRAPH_STORE_PROVIDER=autonomous
VECTOR_STORE_PROVIDER=sqlite
GRAPH_STORE_PROVIDER=sqlite
RAG_TOP_K=5
EMBEDDING_PROVIDER=oci
EMBEDDING_PROVIDER=mock
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
@@ -77,21 +70,14 @@ RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
# Observabilidade
###############################################################################
ENABLE_LANGFUSE=true
# Opcional: verbose, compact
LANGFUSE_TRACE_MODE=compact
# Nome customizado do trace pai, ex.: backoffice.checklist.workflow ou backoffice.emulador.workflow
LANGFUSE_COMPACT_VISIBLE_EVENT_PREFIXES=AGA.,NOC., IC.
LANGFUSE_COMPACT_SUPPRESSED_PREFIXES=llm.chat_completion
LANGFUSE_IGNORE_HEALTHCHECKS=true
LANGFUSE_IGNORED_PATHS=/health,/ready,/metrics
LANGFUSE_PUBLIC_KEY=pk-lf-4a1e3921-5158-4fd3-a16d-7a77549fb312
LANGFUSE_SECRET_KEY=sk-lf-efc6fd59-c5ec-4858-b6ec-4aa129734915
LANGFUSE_TRACE_MODE=compact # Opcional: verbose, compact
LANGFUSE_PUBLIC_KEY=pk-lf-2f9da109-5b0f-4c78-b61d-9598ed787eba
LANGFUSE_SECRET_KEY=sk-lf-a4cb0cdd-f2ea-4468-9911-cebeb91ba944
LANGFUSE_HOST=http://localhost:3005
ENABLE_OTEL=false
OTEL_EXPORTER_OTLP_ENDPOINT=
OTEL_SERVICE_NAME=ai-agent-template
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
###############################################################################
# Analytics / Observer corporativo
@@ -99,7 +85,7 @@ ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
ENABLE_ANALYTICS=false
# Providers aceitos: oci_streaming,pubsub,noop
ANALYTICS_PROVIDERS=oci_streaming
ANALYTICS_PROVIDERS=pubsub
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
AGENT_PUBSUB_TOPIC=
GCP_PUBSUB_TOPIC_PATH=
@@ -162,6 +148,7 @@ ROUTE_STICKINESS_HISTORY_TURNS=2
ROUTE_STICKINESS_MAX_TOKENS=80
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
SESSION_ALREADY_ENDED_MESSAGE=Este atendimento já foi encerrado. Inicie uma nova sessão para continuar.
###############################################################################
# MCP / Tools
@@ -169,13 +156,14 @@ END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
ENABLE_MCP_TOOLS=true
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
TOOLS_CONFIG_PATH=./config/tools.yaml
TOOL_POLICIES_PATH=./config/tool_policies.yaml
MCP_TOOL_TIMEOUT_SECONDS=30
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
ROUTING_MODE=router
# Usage/cost accounting
USAGE_REPOSITORY_PROVIDER=autonomous
USAGE_REPOSITORY_PROVIDER=sqlite
IDENTITY_CONFIG_PATH=./config/identity.yaml
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
@@ -192,6 +180,18 @@ MEMORY_SUMMARY_USE_LLM=true
MEMORY_INJECT_RECENT_MESSAGES=true
MEMORY_INJECT_SUMMARY=true
###############################################################################
# MCP Gateway
###############################################################################
# true = framework routes tool calls to the dedicated MCP Gateway.
# false = framework calls MCP servers directly from mcp_servers.yaml.
MCP_GATEWAY_ENABLED=true
MCP_GATEWAY_URL=http://localhost:8300
MCP_GATEWAY_TIMEOUT_SECONDS=60
# MCP_GATEWAY_TOKEN=
MCP_GATEWAY_AGENT_ID=telecom_contas
MCP_GATEWAY_TENANT_ID=default
###############################################################################
# LONG-TERM MEMORY
###############################################################################

View File

@@ -160,7 +160,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "load_long_term_memory"},
{"blocked": "output_guardrails", "continue": "load_long_term_memory"},
)
builder.add_edge("load_long_term_memory", "routing_decision")
builder.add_conditional_edges(
@@ -197,6 +197,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -281,12 +306,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -160,7 +160,7 @@ class AgentWorkflow:
builder.add_conditional_edges(
"input_guardrails",
self._after_input_guardrails,
{"blocked": "persist", "continue": "load_long_term_memory"},
{"blocked": "output_guardrails", "continue": "load_long_term_memory"},
)
builder.add_edge("load_long_term_memory", "routing_decision")
builder.add_conditional_edges(
@@ -197,6 +197,31 @@ class AgentWorkflow:
def _after_input_guardrails(self, state):
return "blocked" if state.get("blocked") else "continue"
@staticmethod
def _input_guardrail_user_message(decisions, state, sanitized_text):
# Keep the technical guardrail reason in telemetry, but expose only a
# safe, actionable message to the end user. The message is intentionally
# routed through output_guardrails before persistence/delivery.
blocked = [d for d in decisions if not getattr(d, "allowed", True)]
first = blocked[0] if blocked else None
code = str(getattr(first, "code", "") or "").upper()
if code == "COER":
return (
"Não consegui entender sua última mensagem porque ela parece "
"incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
)
if code == "INPUT_SIZE":
return "Sua mensagem ficou muito longa para eu processar de uma vez. Pode resumir ou dividir em partes?"
if code == "DLEX_IN":
return "Não posso usar essa informação da forma solicitada. Reformule o pedido sem incluir dados ou conteúdo restrito."
if code == "PINJ":
return "Não posso seguir instruções que tentem alterar as regras do atendimento. Posso continuar ajudando com a sua solicitação."
if code == "TOX":
return "Não consegui prosseguir com essa mensagem. Pode reformular o pedido para continuarmos o atendimento?"
if code == "CMP":
return "Não posso prosseguir com essa solicitação dessa forma. Posso ajudar com uma alternativa permitida."
return "Não consegui processar essa mensagem. Pode reformular para eu continuar o atendimento?"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
@@ -281,12 +306,33 @@ class AgentWorkflow:
component="workflow.input_guardrails.final",
)
if any(not d.allowed for d in decisions):
# A blocking input guardrail stops the turn before routing/tools.
# Clear turn-local routing/tool state so stale data from a prior
# turn cannot appear as if it was executed after the block.
user_message = self._input_guardrail_user_message(decisions, state, sanitized)
return {
"sanitized_input": sanitized,
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
"answer": user_message,
"final_answer": None,
"guardrail_decisions": [d.model_dump() for d in decisions],
"route": "blocked",
"intent": "input_guardrail_blocked",
"route_decision": {
"route": "blocked",
"agent": None,
"intent": "input_guardrail_blocked",
"confidence": 1.0,
"reason": "Entrada interrompida por guardrail antes do roteamento.",
"method": "guardrail",
"next_state": state.get("next_state"),
"handoff": False,
"metadata": {},
"domain": state.get("domain"),
"mcp_tools": [],
},
"mcp_tools": [],
"mcp_results": [],
"judge_results": [],
"blocked": True,
}
return {

View File

@@ -7,6 +7,35 @@ router:
confidence_threshold: 0.65
allow_handoff: true
transaction_confirmation:
# Explicit yes/no stays deterministic. Only inconclusive replies use this LLM fallback.
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Você classifica a resposta do cliente a uma confirmação transacional pendente.
Considere a pergunta pendente, somente o histórico recente relacionado ao mesmo tema e a fala atual.
Não execute a ação e não invente fatos.
Classes permitidas: {{ allowed_values }}
- SIM: confirmação/aceite inequívoco, inclusive equivalentes como "isso mesmo", "pode confirmar", "é isso" quando o contexto tornar o aceite claro.
- NAO: recusa/cancelamento inequívoco da ação pendente.
- CONTINUAR: qualquer resposta que não confirme nem rejeite inequivocamente, incluindo pergunta adicional, correção, novo dado, ambiguidade ou possível mudança de assunto.
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual do cliente:
{{ user_input }}
state_policies:
- state: WAITING_BILLING_CONFIRMATION
agent: billing_agent

View File

@@ -0,0 +1,11 @@
# Confirmação Transacional Semântica
Este template suporta confirmação transacional em duas camadas: primeiro um parser determinístico para `sim`/`não` e equivalentes explícitos; somente quando ele não consegue decidir, o framework usa um classificador semântico configurado em `config/routing.yaml`.
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.
Consulte `docs/developer/pt/03_transaction_workflows_and_state.md` do framework para o contrato completo e exemplos.

View File

@@ -0,0 +1,49 @@
# Correção: precedência de handoff sobre workflow pausado
## Problema
Quando um workflow conversacional estava em `WORKFLOW_PAUSED` com `expected_input`
enumerado e `semantic_classifier`, uma solicitação explícita de atendimento humano podia
ser absorvida pelo classificador local do workflow (por exemplo `SIM/NAO/CONTINUAR`).
Exemplo de regressão:
1. cliente pede explicação de fatura;
2. workflow pausa perguntando se a dúvida foi resolvida;
3. cliente diz `quero falar com um atendente`;
4. a frase era classificada como valor do `expected_input`, em vez de acionar handoff.
## Regra de precedência corrigida
A ordem passa a ser:
1. `expected_input` determinístico continua com precedência absoluta (`sim`, `não`, etc.);
2. se não houver match determinístico, o framework verifica exclusivamente o controle global
`HUMAN_HANDOFF` usando o classificador semântico de continuidade já existente;
3. se não houver handoff, o `semantic_classifier` declarativo do workflow continua sendo a
autoridade sobre a mensagem;
4. `CONTINUE`, `ROUTE` e `END_SESSION` encontrados no probe global são ignorados nessa etapa;
5. as regras normais de transação e intent shift permanecem inalteradas.
Assim, `quero falar com um atendente` não é tratado como `intent_shift`: é um comando global
de controle de sessão. A correção não cria lista de palavras nem regex de handoff.
## Observabilidade
Quando o handoff preempta um workflow pausado, a decisão contém:
- `session_control=HUMAN_HANDOFF`;
- `global_control_preempted_workflow=true`;
- `workflow_interruption=human_handoff`;
- `interrupted_workflow_name`;
- `interrupted_workflow_execution_id`.
## Testes
Foram adicionados testes para garantir que:
- pedido explícito de atendente preempta `expected_input.semantic_classifier`;
- resposta determinística `sim` continua retomando o workflow e não é roubada pelo probe global.
Também foram executadas as suítes de regressão de transação/intent shift para confirmar que a
mudança não altera a precedência existente de coleta de parâmetros e confirmação transacional.

View File

@@ -0,0 +1,15 @@
# Soft reset operacional após finalização de workflow
Quando um workflow de domínio termina, a sessão conversacional permanece a mesma, mas o próximo turno deve iniciar uma nova interação operacional.
A correção introduz um marcador `operational_context_boundary_pending` no fechamento do workflow. No primeiro turno subsequente, o marcador é consumido e o framework:
- mantém `session_id`, `session_key`, `conversation_key`, identidade e BusinessContext;
- preserva o histórico durável/checkpoint para auditoria;
- mantém Long-Term Memory;
- limpa `pending_domain_workflow`, `pending_tool_clarification`, `active_transaction`, tool calls, parâmetros pendentes, confirmação, pre-validation, route/intent/active_agent e demais latches operacionais;
- não executa route continuity do fluxo encerrado;
- não injeta ConversationSummaryMemory nem mensagens recentes do fluxo encerrado no primeiro turno após a fronteira;
- entrega apenas a nova mensagem ao contexto operacional desse turno.
O marcador de reset é de uso único e é desligado no `persist` do novo turno. A partir do turno seguinte, a nova interação pode novamente acumular seu próprio contexto curto, usando o mesmo identificador de sessão.

View File

@@ -0,0 +1,7 @@
# Workflow final status normalization
A resumed domain workflow may be returned by a legacy adapter with `status=PAUSED` even after its terminal node has emitted `workflow_response_final=true`.
The framework now treats `workflow_response_final=true` as the authoritative interaction-lifecycle signal and normalizes that stale adapter status to `COMPLETED` before capturing the workflow latch. This clears the paused workflow/expected-input state, persists an operational-context boundary for the next user turn, and keeps the same session identifiers.
Contextual re-entry routing now also degrades safely to the configured fallback when an LLM router response cannot be parsed, rather than propagating a structured-output exception to the HTTP endpoint.

View File

@@ -0,0 +1,25 @@
# Fix: terminal workflow lifecycle in the same conversation session
## Problem
A conversational workflow could return `status=COMPLETED` and a final response (`workflow_response_final=true`), while a stale `pending_domain_workflow` / `expected_input` latch remained durable in LangGraph state. A later user message in the same `session_id` could therefore be interpreted as a resume of the already completed workflow.
Scenario 22 reproduces the issue: after invoice explanation is accepted and protocol is returned, `ah espera` must be treated as a new interaction in the same conversation session, not as SIM/NAO/CONTINUAR for the old workflow.
## Semantics after the fix
- Conversation identifiers are preserved (`session_id`, `session_key`, `conversation_key`, `user_id`, `msisdn`, customer/contract keys).
- The completed workflow is terminal only as an interaction/workflow, not as the user session.
- `pending_domain_workflow`, `pending_tool_clarification`, `workflow_input_reprompt`, active transaction latches and `next_state` are cleared.
- `transaction_status` is materialized as `COMPLETED` (or `FAILED`) so terminal state wins over stale checkpoints.
- The next message is routed as a new interaction in the same session.
- Router and input-guardrail layers defensively ignore stale paused-workflow contracts when transaction status is already terminal.
## Main files
- `libs/agent_framework/src/agent_framework/runtime/agent_runtime.py`
- `libs/agent_framework/src/agent_framework/routing/enterprise_router.py`
- `../app/workflows/agent_graph.py`
- `tests/test_paused_workflow_resume_precedence.py`
## Validation
- Framework transactional/routing suites: 119 passed.
- Focused migration suites: 12 passed.
- Full `tests/migration`: 789 passed.

View File

@@ -0,0 +1,31 @@
# Correção: snapshot terminal não deve virar PAUSED sem interrupt real
## Problema
O `WorkflowRuntime` tratava qualquer `snapshot.next` truthy do LangGraph como evidência de pausa. Em alguns snapshots/checkpointers, o último nó de ação já havia terminado e sua transição ativa apontava para `END`, porém `snapshot.next` ainda continha trabalho estrutural interno. Como não havia `interrupt()`, o runtime fabricava um `pause={"node": current_node}` e devolvia `PAUSED`.
Efeito observado em `contestacao_tool`: `atualizar_status_sr` estava `COMPLETED`, mas o workflow era exposto como `PAUSED`, com `resume_tool=retomar_workflow`, impedindo o fechamento normal da evidência transacional.
## Regra corrigida
A precedência agora é:
1. Se existem payloads reais de `interrupt()` no snapshot: `PAUSED`.
2. Se não há interrupt e o `current_node` possui uma transição ativa para `END`: `COMPLETED`, mesmo que `snapshot.next` esteja truthy.
3. Se não há interrupt, o estado não é estruturalmente terminal e ainda existe `snapshot.next`: fail-closed (`FAILED`) com diagnóstico, em vez de inventar uma pausa.
4. Sem interrupt, sem pending work e sem anomalia: conclusão normal.
A mesma regra foi aplicada em `WorkflowRuntime.arun()` e `WorkflowRuntime.aresume()`.
## Por que não há hardcode
A detecção terminal usa exclusivamente a `WorkflowDefinition`, o `current_node` e as condições das edges. Não conhece `contestacao_tool`, `atualizar_status_sr`, TIM ou qualquer agente específico.
## Testes de regressão
`tests/unit/test_workflow_terminal_snapshot_semantics.py` cobre:
- `arun`: `snapshot.next` truthy + sem interrupt + edge ativa para `END` => `COMPLETED`;
- `aresume`: mesma condição => `COMPLETED`;
- interrupt real tem precedência e continua retornando `PAUSED`;
- pending work não terminal sem interrupt retorna `FAILED`, nunca uma pausa falsa.

View File

@@ -0,0 +1,264 @@
### Agent Framework OCI Architecture and Concepts
### Purpose of this document
This document **does not replace the root `README_en.md`** and does not repeat the agent-creation tutorial.
Use:
- [`README_en.md`](../../../README_en.md) to develop, configure, run, and test an agent end to end;
- this document to understand the architecture, responsibility boundaries, components, and where each type of implementation belongs;
- the other manuals in this folder to deepen a specific capability or solve a problem.
The separation is intentional: there is **one main tutorial** and several **specialized reference manuals**.
### Source of truth
When documentation diverges, use this order:
1. code for the version in use;
2. `README.md` / `README_en.md` from the same version;
3. normative SPECs/SDDs;
4. specialized manuals in this folder;
5. release notes and `README_old*` only as history.
### Platform mental model
Agent Framework OCI should be understood as a layered platform.
The **framework core** provides reusable, domain-neutral mechanisms: runtime, state, memory, routing, tool integration, guardrails, judges, persistence, observability, and common contracts.
The **agent** contains what is specific to the use case: intents, prompts, domain rules, specific policies, business workflow, mappings, integrations, and external components that belong to that agent.
**Gateways** handle cross-cutting ingress, governance, and integration responsibilities. They should not absorb the agent's business logic.
**MCP Servers** encapsulate tools and integrations with domain or legacy services. The **MCP Gateway** provides centralized catalog and governance for these tools.
### Main components
| Component | Main responsibility | Must not contain |
|---|---|---|
| `libs/agent_framework/` | Generic runtime, contracts, state, memory, routing, guardrails, judges, common integrations | Rule specific to a company or agent |
| `templates/agent_template_backend/` | Executable reference for creating agents | Permanent fork of the core |
| `apps/agent_gateway/` | Governed ingress, cross-cutting policies, rate limit, authentication, metadata | Business workflow |
| `apps/channel_gateway/` | Channel adaptation to the canonical contract | Agent business rule |
| `apps/mcp_gateway/` | Catalog, authorization, and centralized tool execution | Conversational logic |
| `mcp/servers/` | Integrations and tools by domain | Global agent orchestration |
| `evals/` | Certification and regression | Production logic |
| `deploy/` | Containers and Kubernetes | Functional rules |
### Conceptual request flow
A typical request goes through the following responsibilities:
```text
Canal
|
v
Channel Gateway
|
v
Agent Gateway
| governança / autenticação / rate limit / metadata
v
Backend do agente
|
+--> Routing / stickiness / intent
|
+--> Estado / memória / checkpoint
|
+--> Guardrails / judges
|
+--> Workflow / políticas transacionais
|
+--> MCP Gateway
|
+--> MCP Server A --> sistema legado
+--> MCP Server B --> serviço externo
+--> MCP Server C --> API de domínio
```
Not every deployment needs to use all components. Composition should follow the agent's needs and the platform contracts.
### Agent runtime
The current runtime is based on `AgentRuntimeMixin` and `RuntimeContext`.
The template imports the runtime through `app.agents.runtime`, which re-exports the framework's official implementation. The goal is to prevent each agent from maintaining its own divergent copy of the runtime.
Current APIs confirmed in the code include:
```python
AgentRuntimeMixin.get_runtime_context()
AgentRuntimeMixin.normalize_tools_by_intent()
AgentRuntimeMixin.build_tool_arguments()
AgentRuntimeMixin.execute_tools_for_intent()
AgentRuntimeMixin.prepare_memory_context()
AgentRuntimeMixin.build_messages()
AgentRuntimeMixin.transaction_state_patch()
AgentRuntimeMixin.transaction_clarification_message()
AgentRuntimeMixin.transaction_confirmation_message()
AgentRuntimeMixin.build_direct_mcp_answer()
```
These APIs represent runtime capabilities. Developers should prefer them over manually rebuilding the same logic inside each agent.
### Configuration versus code
A central framework guideline is that configurable behavior should remain in configuration.
Examples:
- agents and metadata: `config/agents.yaml`;
- routing: `config/routing.yaml`;
- tools: `config/tools.yaml`;
- MCP Servers and mappings: corresponding MCP configuration;
- LLM profiles: `llm_profiles.yaml`;
- policies and extensions: capability-specific configuration files.
Code should implement mechanisms. YAML/config should select behavior whenever that can be done without compromising security or contracts.
### Separation between framework and agent
A change belongs to the **framework** when it introduces a mechanism reusable by different agents.
Examples:
- new guardrail SPI;
- new rich LLM response contract;
- new generic checkpoint capability;
- new configurable tool-policy mechanism;
- new generic routing strategy.
A change belongs to the **agent** when it expresses a rule from a domain or company.
Examples:
- which charges can be disputed;
- a telecom-specific prompt;
- VAS rules;
- internal company codes;
- legacy-service mapping;
- specific phraseology.
If the core needs to import a concrete agent module in order to work, this separation has probably been broken.
### State, memory, and checkpoint are different concepts
**Execution state** represents what is happening in the turn and workflow.
**Conversation memory** preserves conversational context.
**Long-Term Memory** stores durable facts associated with a business identity.
**Checkpoint** persists LangGraph state snapshots for resume.
An old checkpoint must not, by itself, determine which transaction is active. The functional decision must use canonical transaction state.
### Routing and execution are different responsibilities
Routing answers: **which agent/intent should handle this message?**
Execution answers: **what should that agent do now?**
Route stickiness preserves continuity, but it must not prevent an explicit intent change. During a transaction, expected parameters and valid confirmation take precedence to avoid false intent shifts.
Full details: [Routing, Stickiness, and Intent Shift](./02_routing_stickiness_and_intent_shift.md).
### Tools and MCP
A tool represents an invokable capability.
The MCP Server implements or exposes that capability.
The MCP Gateway organizes catalog, authorization, mapping, and centralized execution.
The agent decides **when** a tool should be used in its flow; the tool/MCP decides **how** to access the corresponding service.
Full details: [MCP, Tools, Policies, and Parameter Extraction](./04_mcp_integration_tools_and_policies.md).
### Transactions
Operations with side effects require different handling from queries.
The framework provides state, confirmation, policy, and deterministic-workflow mechanisms. Concrete rules remain in the agent.
The LLM may participate in interpretation and composition, but it must not be the only source of truth for claiming that a critical operation was executed.
Full details: [Transactional Workflows and State](./03_transaction_workflows_and_state.md).
### Guardrails and Judges
Guardrails control or validate behavior during processing.
Judges evaluate quality, grounding, and other criteria.
The core provides native mechanisms and extension points. Domain-specific guardrails/judges should be loaded by the agent through configuration, avoiding specific imports inside the framework.
Full details: [Guardrails, Judges, and Transaction Evaluation](./06_guardrails_judges_and_transaction_evaluation.md).
### RAG, memory, and tools are not equivalent
- **RAG** retrieves knowledge.
- **Memory** preserves context/facts.
- **Tool** executes or queries an external capability.
Choosing the wrong mechanism creates bugs that are difficult to diagnose. Information that needs to be updated in a system should not be solved only through RAG; a durable customer fact should not depend only on prompt history.
### Observability as a cross-cutting contract
Routing, agent, transaction, tool, guardrail, judge, and failure must be correlatable.
Observability should record what happened, but it must not control business state. Sequence, trace IDs, and labels are diagnostic and audit infrastructure.
Full details: [Observability, Persistence, and Operational Readiness](./11_observability_persistence_and_operational_readiness.md).
### Where to place a new feature
Before implementing, ask these questions:
1. Is the capability reusable by different agents?
2. Is there a domain-specific rule?
3. Does it need state across turns?
4. Does it produce side effects?
5. Does it depend on an external system?
6. Should it be configurable?
7. Does it need to appear in observability?
8. Does it need to be evaluated by a guardrail/judge?
A reusable feature normally starts in the core and is enabled/configured by the agent. A business rule normally starts in the agent and uses core interfaces.
### Anti-patterns
Avoid:
- importing a concrete agent package inside the core;
- duplicating `AgentRuntimeMixin` in every agent;
- hardcoding agent, intent, tool, or company names in the runtime;
- using an LLM response as proof that an operation was executed;
- confusing an old checkpoint with the active transaction;
- executing a transactional operation without policy/confirmation when it is required;
- coupling an agent directly to dozens of services when MCP Gateway is the intended layer;
- creating a new functional document for every bug fix instead of updating the feature manual.
### Recommended path for a new developer
1. Read the architectural overview in this document.
2. Follow [`README_en.md`](../../../README_en.md) from beginning to end to create and run an agent.
3. When you reach a specific capability, use the corresponding specialized manual.
4. For failures, start with the [Developer Index](./INDEX_DEVELOPER_GUIDE.md), in the **Search by problem** section.
5. Before copying old code, confirm the API/import in the current template and core.
### Related documents
- [Main tutorial — README.md](../../../README.md)
- [Routing, Stickiness, and Intent Shift](./02_routing_stickiness_and_intent_shift.md)
- [Transactional Workflows and State](./03_transaction_workflows_and_state.md)
- [MCP, Tools, Policies, and Parameters](./04_mcp_integration_tools_and_policies.md)
- [Gateways and Authentication](./05_agent_gateway_mcp_gateway_and_auth.md)
- [Guardrails and Judges](./06_guardrails_judges_and_transaction_evaluation.md)
- [RAG and BusinessContext](./07_rag_business_context_and_grounding.md)
- [Long-Term Memory and Checkpoint](./08_long_term_memory_and_checkpoint.md)
- [LLM Rich Response](./09_llm_rich_response_reasoning.md)
- [Performance, Cache, and Async Runtime](./10_performance_cache_and_async_runtime.md)
- [Observability and Operational Readiness](./11_observability_persistence_and_operational_readiness.md)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,638 @@
### Transactional Workflows and State
### How to use this manual
This is a **specialized reference manual**. It does not replace the main tutorial.
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
- Use this document when you need to implement, deepen, or diagnose **transaction state, parameter collection, confirmation, pause/resume, and operational evidence**.
- Historical examples consolidated here should be read in light of the framework's current API.
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
### Relationship with the main tutorial
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
### Scope
Transaction state, parameter collection, confirmation, pause/resume, and operational evidence.
### Consolidated technical content
### Transactional Workflows, Multi-turn State, and Resume
Implementation guide for multi-step operations, canonical transaction-state source, confirmation, parameter merge, pause/resume, operational evidence, and routing interaction.
### How to use this document
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
### Multi-turn transaction-state guide
> Content consolidated from `docs/TRANSACTION_STATE_DEVELOPER_GUIDE.md`.
This document defines the operational contract for multi-turn transactions in Agent Framework OCI. It is normative for hosts and templates that use `AgentRuntime`, LangGraph checkpointing, and transactional tools.
### 1. Goal
A transaction can span several turns. Example:
```text
Usuário: quero cancelar o pedido
Framework: informe o número do pedido
Usuário: PED-1001
Framework: confirma o cancelamento?
Usuário: sim
Framework: executa a tool
```
The framework must preserve the transaction across all these turns without depending on LLM reclassification, keyword routing, or re-extraction of parameters that have already been obtained.
### 2. Canonical transaction-state source
The canonical state for the in-progress transaction is `active_transaction`.
```python
active_transaction: dict[str, Any]
last_transaction: dict[str, Any]
```
Every `AgentState` used by a host that enables multi-turn transactions **MUST** declare both fields. Because LangGraph uses the state schema for persistence/checkpointing, a field created only dynamically by the runtime is not a safe durable contract.
Minimum example:
```python
from typing import Any, TypedDict
class AgentState(TypedDict, total=False):
# ...campos normais...
selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any]
active_transaction: dict[str, Any]
last_transaction: dict[str, Any]
transaction_status: str
missing_parameters: list[str]
confirmation_required: bool
confirmation_received: bool
```
### 3. Role of each field
| Field | Role | Rule |
|---|---|---|
| `active_transaction` | Canonical source of the active transaction | Must survive checkpoint/resume while the transaction is active. |
| `last_transaction` | Snapshot of the last terminal transaction | Used for audit, evidence, and controlled continuity; it does not automatically reactivate the transaction. |
| `transaction_status` | Current logical state | E.g. `COLLECTING_PARAMETERS`, `AWAITING_CONFIRMATION`, `COMPLETED`, `CANCELLED`, `OUT_OF_SCOPE`. |
| `missing_parameters` | Parameters still required | Must reflect canonical transaction state, not only the current message. |
| `selected_tool_call` | Auxiliary/compatibility state | Must not replace `active_transaction` as the canonical source. |
| `pending_tool_call` | Auxiliary/compatibility state | May be used for compatibility, but not as the primary latch. |
| `next_state` | Workflow routing guidance | Helps keep the correct node/agent during collection/confirmation. |
| `transaction_pre_validation` | Pre-validation evidence | Preserves validation results before confirmation/execution. |
| `transaction_evidence` | Execution evidence | Preserves transaction results and execution trail. |
### 4. Recommended lifecycle
```text
IDLE
↓ intenção transacional
COLLECTING_PARAMETERS
↓ parâmetros completos
PRE_VALIDATION (quando configurado)
↓ elegível
AWAITING_CONFIRMATION
↓ confirmação positiva
EXECUTING
COMPLETED
```
Alternative terminal outcomes:
```text
CANCELLED
OUT_OF_SCOPE
FAILED
```
The runtime may represent some phases internally without a separate public `transaction_status`. The requirement is to preserve the latch and not lose arguments already collected.
### 5. Incremental parameter merge
A later response must complement the existing transaction, never recreate it only from the current text.
```python
existing = dict((state.get("active_transaction") or {}).get("arguments") or {})
new_values = {"valor": "71.99"}
arguments = {**existing, **new_values}
```
Expected example:
```text
Turno 1: subject = "TIM CTRL Redes Sociais 8.0"
Turno 2: valor = "71.99"
Resultado: subject + valor permanecem disponíveis
```
### 6. Routing precedence during a transaction
When an `active_transaction` exists in `COLLECTING_PARAMETERS`, the message must first be evaluated as a possible answer to the pending parameters.
Normative precedence:
1. pending parameter clearly filled → continue the transaction;
2. explicit cancellation/abandonment → cancel the transaction;
3. unequivocal new intent → interrupt the transaction and route;
4. generic keyword from the same domain/agent → **do not** interrupt the transaction;
5. ambiguous message → keep the transaction and clarify.
Examples:
| Current state | Message | Correct result |
|---|---|---|
| `retail_order_cancel`, missing `order_id` | `PED-1001` | Continue cancellation and fill `order_id`. |
| `retail_order_cancel`, missing `order_id` | `o pedido é o PED-1001` | Continue cancellation; `pedido` must not become tracking. |
| dispute, missing `valor` | `R$ 71,99` | Continue dispute and fill `valor`. |
| cancellation pending | `esquece, quero ver minha fatura` | Explicit interruption allowed. |
| cancellation pending | `quero rastrear pedido` | Unequivocal shift to tracking allowed. |
### 7. Checkpoint and resume
Before running normal routing, the host must restore the checkpoint using the same conversation identity (`tenant_id`, `agent_id`, `session_id`/`conversation_key` according to the host contract).
After restoration:
```text
active_transaction existe
status ativo?
↓ sim
retomar a transação antes de keyword routing / continuity LLM
```
A `COLLECTING_PARAMETERS` state without `active_transaction` must be treated as a state inconsistency and observed/diagnosed; it must not silently restart the tool from the current message.
### 8. What belongs to the framework and what belongs to the agent
Framework:
- latch persistence;
- argument merge;
- collection/confirmation states;
- resume precedence;
- deterministic confirmation;
- idempotency and evidence;
- checkpoint/resume.
Agent:
- domain-tool definitions;
- required parameters and domain messages;
- domain-specific eligibility rules;
- domain-specific pre-validation, when applicable;
- final customer response.
The agent must not implement a second transactional engine in parallel with `AgentRuntime`.
### 9. Checklist for new hosts/templates
- [ ] `AgentState` declares `active_transaction`.
- [ ] `AgentState` declares `last_transaction`.
- [ ] `transaction_status` and `missing_parameters` are part of state when used.
- [ ] The host uses checkpointing compatible with the state schema.
- [ ] The same `conversation_key` is used across turns of the same conversation.
- [ ] Previously collected parameters are merged with new values.
- [ ] Parameter answers take precedence over generic keyword routing.
- [ ] Explicit intent changes remain possible.
- [ ] The agent uses `transaction_state_patch(state)` when returning transactional responses if the template requires it.
- [ ] Multi-turn tests exist for collection, confirmation, interruption, and resume.
### 10. Minimum regression tests
```text
A. cancelamento de pedido
1. "quero cancelar pedido"
2. "o pedido é o PED-1001"
Esperado: continua retail_order_cancel; não vira retail_order_tracking.
B. contestação
1. "não contratei TIM CTRL Redes Sociais 8.0"
2. "R$ 71,99"
Esperado: subject e valor chegam juntos à pre-validation.
C. interrupção explícita
1. iniciar transação e deixar parâmetro pendente
2. "esquece, quero ver minha fatura"
Esperado: transação é interrompida e nova intenção é roteada.
D. checkpoint/resume
1. iniciar transação
2. persistir/checkpoint
3. reconstruir execução usando a mesma conversation_key
4. fornecer o parâmetro faltante
Esperado: active_transaction é restaurado e concluído sem reiniciar a tool.
```
### 11. Anti-patterns
- rebuilding the transaction only from the last message;
- using `selected_tool_call` as the only latch source;
- removing `active_transaction` from `AgentState` because it appears redundant;
- allowing a generic keyword such as `pedido` to interrupt `order_id` collection;
- storing parameters only in local node variables;
- duplicating transactional confirmation in the agent prompt;
- clearing the latch before terminal state.
### 12. Project references
- `specs/SPEC-002-Agent-Runtime.md`
- `specs/SPEC-010-Agent-Development.md`
- `templates/agent_template_backend/app/state.py`
- `libs/agent_framework/src/agent_framework/runtime/agent_runtime.py`
- `libs/agent_framework/src/agent_framework/routing/enterprise_router.py`
- `Tuning-Performance/Deterministic_Transactional_Workflow/`
- `Tuning-Performance/Transaction_Pre_Validation/`
- `Tuning-Performance/Transaction_Evidence/`
### Transactional workflow engine architectural decision
> Content consolidated from `docs/ADR_TRANSACTIONAL_WORKFLOW_ENGINE.md`.
### Decision
Add an optional deterministic execution capability based on LangGraph to the framework. The engine is generic; YAML definitions and domain actions remain in the agents.
### Rationale
Multi-step operations with side effects must not depend on the LLM to select the critical sequence. The solution reduces tokens, latency, and variability, while improving auditability, testing, and versioning.
### Compatibility
`execution.mode` defaults to `direct_tool`. Existing projects continue to use MCP directly. Workflow adoption is explicit per tool and may be controlled through `ENABLE_TRANSACTIONAL_WORKFLOWS`.
### Scope limits of this delivery
The foundation includes validation, file-based versioning, registry, sync/async execution, conditions, per-node retry, graph cache, and a policy adapter. Enterprise execution-record persistence, compensation/Saga, scope authorization, and workflow-specific IC/NOC emission must be connected to the abstractions available in each deployment before use in critical financial transactions.
### Deterministic workflow implementation
> Content consolidated from `Documentacao/IMPLEMENTACAO_WORKFLOWS_TRANSACIONAIS.md`.
### Delivery
An optional capability was added to `agent_framework_oci` to execute multi-step transactions as deterministic workflows compiled into LangGraph.
### New module
`libs/agent_framework/src/agent_framework/workflows/`
- `models.py`: Pydantic contracts and structural validation;
- `repository.py`: active-version resolution and immutable YAML reading;
- `registry.py`: decoupled registration of sync/async actions;
- `runtime.py`: StateGraph compilation, cache, and execution;
- `tool_executor.py`: integration with tool policy;
- `__init__.py`: public API.
### Expanded policy
`ToolPolicy` now accepts:
```yaml
execution:
mode: direct_tool | workflow | agent
workflow: nome_do_workflow
version: active | 1
```
The default remains `direct_tool`, preserving compatibility.
### Configuration
The following were added:
- `ENABLE_TRANSACTIONAL_WORKFLOWS=false`;
- `WORKFLOWS_PATH=./workflows`.
### Template
Includes a complete order-return example with:
- confirmation and required fields from policy;
- versioned workflow YAML;
- domain actions in the backend;
- deterministic branching based on validation result.
### Validation performed
- `tests/unit/test_tool_policies.py`: 4 tests passed;
- Python compilation for framework, template, and new tests: passed;
- the new LangGraph functional test was created but could not be run in this container because `langgraph` is not installed in the environment. The dependency is already declared in the framework `pyproject.toml`.
### Scope and safety
This delivery creates the engine and policy integration. For critical production operations, it is still necessary to connect:
- persistent execution store;
- business idempotency in actions/APIs;
- scope authorization;
- workflow-specific IC/NOC telemetry;
- compensation/Saga where applicable;
- enterprise timeout and retry strategy.
These items are explicitly documented to avoid the false impression that retry by itself guarantees transactional safety.
### Parameter-collection precedence
> Content consolidated from `FIX_TRANSACTION_PARAMETER_PRECEDENCE.md`.
This correction removes hardcoded textual extraction of transactional parameters and collects `policy.requires` through a generic LLM extractor.
### Precedence rule
While an active transaction exists, the framework handles the turn in this order:
```text
ACTIVE_TRANSACTION
|
+-- COLLECTING_PARAMETERS
| |
| +-- LLM tenta extrair SOMENTE os parâmetros ainda pendentes
| |
| +-- extraiu >= 1 ?
| |
| +-- SIM -> continua a transação; NÃO avalia intent_shift
| |
| +-- NÃO -> libera EnterpriseRouter para avaliar intent_shift
|
+-- AWAITING_CONFIRMATION
|
+-- reconhece confirmação/rejeição explícita
|
+-- reconheceu ?
|
+-- SIM -> continua/cancela a transação; NÃO avalia intent_shift
|
+-- NÃO -> libera EnterpriseRouter para avaliar intent_shift
```
### TransactionParameterExtractor
New component:
`libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py`
Textual extraction of business parameters is performed exclusively by the LLM. The component receives:
- name of the active tool/transaction;
- currently pending parameters;
- already known arguments;
- schema/types declared in `tools.yaml` when available;
- tool description;
- current user message.
It does not know domain names such as `order_id`, `reason`, `subject`, `valor`, TIM, or retail. There is no regex for business entities.
The LLM can interpret, for example:
- `PED-1001` when only one compatible parameter is pending;
- `o pedido é PED-1001`;
- `PED-1001, desisti da compra`, filling two parameters in the same turn;
- answers with the parameter name followed by the value;
- answers containing only the value, when semantically unequivocal.
When in doubt, the prompt instructs the model to return `null`. A new request must not be transformed into a parameter value.
### Separation of responsibilities
`tool_policies.yaml` remains the source of truth for `requires`.
`tools.yaml` may provide types through `args_schema` and the tool description to improve interpretation without introducing domain-specific code.
`mcp_parameter_mapping.yaml` remains responsible for auxiliary parameters/MCP contract. Mapper strategies are explicitly excluded for fields present in `policy.requires`, so MCP extraction is not mixed with transactional collection.
The `EnterpriseRouter` uses the same LLM extractor only as a precedence *probe*. If at least one pending parameter is found, the turn remains in the transactional state. Extracted values are placed in decision metadata and reused by the runtime, avoiding a second LLM call in the same turn.
### LLM profile
The following was added to the templates:
```yaml
transaction_parameter_extraction:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 500
timeout_seconds: 8
```
Generation/component:
- `llm.transaction_parameter_extraction`
- `transaction_parameter_extraction`
### State cleanup
On `intent_shift`, the abandoned transaction's `transaction_pre_validation` is removed so it does not contaminate the new transaction. The pre-validation result remains preserved while it belongs to its own transaction for audit purposes.
### Tests added
`tests/test_transaction_parameter_llm_precedence.py`
Coverage:
1. two parameters extracted in the same turn;
2. one filled parameter takes precedence over a keyword that would indicate another intent;
3. no parameter found releases `intent_shift`;
4. absence of the old hardcoded `_extract_action_arguments()`;
5. `sim` confirmation takes precedence over intent shift.
### Transaction/intent loop fix
> Content consolidated from `FIX_TRANSACTION_INTENT_LOOP.md`.
Correction applied on 2026-08-20 to prevent a session from getting stuck in `COLLECTING_PARAMETERS` or `AWAITING_CONFIRMATION` when the user explicitly changes subject.
### Corrected behavior
Before:
1. a transaction entered `COLLECTING_PARAMETERS`;
2. `next_state` forced the same agent through `state_policies`;
3. every following message was treated as an attempt to fill the missing parameter;
4. a new intent such as `quais sao meus servicos` remained trapped in the previous flow.
Now:
- the `EnterpriseRouter` checks for an explicit intent change before applying the state lock;
- explicit keyword has priority;
- when necessary, the LLM router can detect a change with confidence >= `router.confidence_threshold`;
- the decision receives `metadata.transaction_interruption=intent_shift`;
- the runtime closes the pending transaction as `CANCELLED`, clears `next_state`, parameters, and latches, and proceeds with the new intent;
- explicit cancellations such as `cancele essa operação anterior` also work during `COLLECTING_PARAMETERS`.
### Tests added
- intent change during `COLLECTING_PARAMETERS`;
- short/low-confidence answer remains in the transaction;
- explicit cancellation during parameter collection;
- cleanup of transactional state before executing the new intent.
Focused tests: 19 passed.
### Operational execution evidence
> Content consolidated from `docs/TRANSACTION_OPERATIONAL_EVIDENCE_FIX.md`.
### Problem
A confirmed transactional tool result was available only in the execution turn. On a later read-only turn, conversational memory could still mention the prior transaction (for example, a cancellation protocol), while the groundedness judge received only the current MCP results. This could classify a factually correct follow-up as unsupported.
### Fix
The framework now records completed/failed transactional tool outcomes as bounded operational evidence in LangGraph state/checkpoint (`transaction_evidence`). This is operational state, not Long Term Memory.
For each new turn, the runtime correlates previous transaction evidence with the current resource using generic identifiers (`*_id`, `order_id`, `invoice_id`, `asset_id`, `resource_key`, etc.). Only relevant evidence is materialized as `relevant_transaction_evidence`.
The same relevant evidence is:
- injected into the answering LLM prompt;
- merged with current MCP results for groundedness judges;
- exposed in response metadata as `transaction_evidence` for diagnostics;
- emitted with the completion telemetry event.
The history is bounded to the 10 most recent transaction outcomes, and at most 5 correlated entries are injected for a turn.
### Expected retail example
1. `cancelar_pedido(PED-1001)` returns protocol `CANCEL-2026-001`.
2. The result is persisted as transaction evidence.
3. The next `consultar_pedido(PED-1001)` returns `EM_TRANSPORTE`.
4. The answering agent and groundedness judge receive both the current order result and the prior cancellation evidence.
5. A response that mentions `CANCEL-2026-001` is grounded rather than treated as an unsupported claim.
### Integrated Backend/MCP validation
> Content consolidated from `Documentacao/VALIDACAO_TRANSACIONAL_BACKEND_MCP.md`.
### Implemented corrections
- `mcp_tools` is treated as an allowlist, not as an automatic execution list.
- `read_only` tools remain available for context enrichment.
- Only one transactional tool compatible with the request is selected.
- `require_confirmation: true` creates `pending_tool_call` and `AWAITING_CONFIRMATION`.
- The confirmation turn executes the pending call with `confirmed: true`.
- State exposes `selected_tool_call`, `tool_policy_result`, `confirmation_required`, `confirmation_received`, and `transaction_status`.
- `reason` was standardized across catalog, mapping, and Retail FastMCP.
- Orders `123` and `PED-ENTREGUE` return status `ENTREGUE` for positive tests.
- The generic keyword `produto` was removed from the Telecom intent so it does not capture Retail returns.
- `Normal` and `Route_Stickness` templates in `Tuning-Performance` were updated.
### Recommended test
1. `Quero devolver o pedido 123 porque me arrependi da compra.`
2. Expected: `transaction_status=AWAITING_CONFIRMATION`, without executing `solicitar_devolucao`.
3. `Sim, confirmo a devolução.`
4. Expected: `transaction_status=COMPLETED` and a single execution of `solicitar_devolucao`.
### Automated result
```text
7 passed
```
### Source files
The files below were consolidated into this manual:
- `docs/TRANSACTION_STATE_DEVELOPER_GUIDE.md`
- `docs/ADR_TRANSACTIONAL_WORKFLOW_ENGINE.md`
- `Documentacao/IMPLEMENTACAO_WORKFLOWS_TRANSACIONAIS.md`
- `FIX_TRANSACTION_PARAMETER_PRECEDENCE.md`
- `FIX_TRANSACTION_INTENT_LOOP.md`
- `docs/TRANSACTION_OPERATIONAL_EVIDENCE_FIX.md`
- `Documentacao/VALIDACAO_TRANSACIONAL_BACKEND_MCP.md`
### Maintenance rule
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.
## Canonical resolution and domain revalidation before execution
When pre-validation resolves a user reference to a canonical entity, the framework **must not blindly overwrite the parameter and execute the originally selected tool**. The contract keeps requested, resolved and execution values distinct.
A domain validator may return `transaction_decision` with `resolved_arguments`, `target_tool`, `action_changed`, `requires_reconfirmation`, and an optional customer-facing `confirmation_message`.
Responsibilities:
- **Framework:** preserve the requested arguments, apply only canonical arguments declared by the validator, update the transaction to the effective `target_tool`, honor reconfirmation, and retain the decision in pre-validation evidence.
- **Agent/domain:** decide business class, policy and effective tool. The framework must not know rules such as “Youtube Premium is strategic”.
- **MCP/backend:** execute the final operation chosen by the domain.
If canonicalization does not change the action, the current tool may remain valid. If entity resolution changes business class/policy/tool, domain revalidation must happen **before confirmation and execution**. Ambiguous or low-confidence resolution must request clarification instead of silently promoting a candidate.
### Troubleshooting: resolved_subject is correct but execution receives the original text
If pre-validation records `resolved_subject="Youtube Premium"` while execution still receives `subject="youtube"`, verify that the validator returns `transaction_decision.resolved_arguments` and that the runtime applies the decision before freezing `pending_tool_call` / `confirmation_snapshot`. If the canonical entity is correct but the final tool is wrong, inspect `transaction_decision.target_tool`; that business reclassification belongs to the domain validator, not to the framework.
For domains that expose an authoritative business classification in backend detail, revalidation should use that evidence before aggregated categories. In Contas, for example, `invoice_detail.parsed_content` preserves `classe=avulso|estrategico|bundle`, while `billing_analysis` may group the same item into broader sections such as `streaming` or partner services. Canonical entity discovery may use any authorized evidence, but the **business decision** should prioritize the source that preserves the domain classification. If classification evidence conflicts, do not silently change the action; preserve the current operation or request clarification according to the agent policy.
## Semantic transactional confirmation: SIM / NAO / CONTINUAR
Transactions in `AWAITING_CONFIRMATION` use two layers, in this order:
1. **Deterministic parser** for explicit confirmations/rejections (`sim`, `não`, `confirmo`, `pode fazer`, etc.). This remains the cheapest and safest path and **does not call an LLM**.
2. **LLM semantic fallback** only when the deterministic parser is inconclusive. The fallback reuses the same declarative semantic-classifier engine used by paused workflow `expected_input`, injecting the pending prompt, recent context related to the same topic, and the current user utterance.
Configuration lives in `config/routing.yaml` under `router.transaction_confirmation.semantic_fallback`:
```yaml
router:
transaction_confirmation:
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Allowed classes: {{ allowed_values }}
Pending prompt:
{{ pending_prompt }}
Relevant context:
{{ relevant_conversation_context }}
Current user input:
{{ user_input }}
```
`SIM` means an unambiguous acceptance, `NAO` an unambiguous rejection, and `CONTINUAR` means the utterance does not safely confirm or reject the pending action. Example: after `Você confirma o cancelamento do serviço Tamboro Mensal?`, the reply `isso mesmo, pode confirmar` can be classified as `SIM` without hardcoding that exact sentence.
When semantic confirmation succeeds, the router records:
```json
{
"transaction_turn_consumed": true,
"transaction_confirmation_decision": "confirm",
"transaction_confirmation_source": "semantic"
}
```
`AgentRuntime` reuses this routed decision instead of re-running the deterministic parser. The change is additive: existing explicit yes/no inputs continue through the deterministic path with no extra LLM call. Semantic generations are named `transaction.confirmation.semantic_classifier` for observability.
### Durable interrupt compatibility in pause/resume
The runtime does not use `snapshot.next` alone to decide whether a workflow is paused. A truthy `next` may represent LangGraph helper work, including framework-generated synthetic nodes such as `__pause` and `__continue`.
A pause is recognized only from a real interrupt. Depending on the LangGraph/checkpointer version, that interrupt may be exposed through `task.interrupts` or persisted in `snapshot.values["__interrupt__"]`. The runtime supports both shapes and deduplicates the payload when both are present.
This prevents two false diagnoses:
- treating `snapshot.next` as `PAUSED` when no real interrupt exists;
- treating `next=("<node>__pause",)` as invalid pending work when the real interrupt is persisted under `__interrupt__`.
For workflows using `expected_input.semantic_classifier`, internal tokens such as `SIM`, `NAO`, and `CONTINUAR` remain resume control values and must not be confused with customer-facing output.

View File

@@ -0,0 +1,821 @@
### MCP, Tools, Policies, and Parameter Extraction
### How to use this manual
This is a **specialized reference manual**. It does not replace the main tutorial.
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
- Use this document when you need to implement, deepen, or diagnose **tools, MCP Servers, mappings, read-only/transactional policies, and parameter extraction**.
- Historical examples consolidated here should be read in light of the framework's current API.
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
### Relationship with the main tutorial
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
### Scope
Tools, MCP servers, mappings, read-only/transactional policies, and parameter extraction.
### Consolidated technical content
### MCP Integration, Tools, Policies, and Parameter Extraction
Development manual for integrating MCP Servers, registering tools, isolating tools by agent, configuring read-only/transactional policies, confirmation, and contextual parameter extraction.
### How to use this document
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
### Complete MCP Server integration manual
> Content consolidated from `Documentacao/Manual_Integracao_MCP_Servers_Agent_Framework.docx`.
MCP Server Integration Manual
Multi-Agent Framework - Router, Supervisor, Tools, and External Servers
This document explains MCP concepts, how the current project integrates MCP servers, how to start the example Telecom and Retail servers, how to configure tools per agent, and how to evolve the implementation toward a solution that more closely follows the official MCP standard. The goal is to serve as a development, local-operations, and container/OCI deployment guide.
### MCP concepts
MCP stands for Model Context Protocol. It defines a standardized way for AI applications to access external context, tools, and capabilities from systems outside the model. Instead of putting integrations directly into the prompt or agent, MCP separates responsibilities: the agent decides what it needs, and an MCP server exposes tools, resources, and prompts in a controlled way.
In the official standard, MCP uses JSON-RPC messages and defines transports such as stdio and Streamable HTTP. The current project uses a simplified HTTP implementation to make understanding and local testing easier, with REST endpoints `/mcp/tools/list` and `/mcp/tools/call`. This is appropriate for tutorials and prototyping, but it can later evolve to an official MCP client.
### How the current project organizes MCP
The relevant project structure is:
```
projeto_multi_agent_isolado/
agent_framework/
src/agent_framework/mcp/
client.py
models.py
registry.py
tool_router.py
agent_template_backend/
config/
mcp_servers.yaml
mcp_servers.docker.yaml
tools.yaml
mcp_parameter_mapping.yaml
app/
main.py
workflows/agent_graph.py
mcp_servers/
telecom_mcp_server/
main.py
requirements.txt
Dockerfile
retail_mcp_server/
main.py
requirements.txt
Dockerfile
scripts/
run_mcp_servers.sh
docker-compose.yml
```
### Main components
### Simplified HTTP contract used by the project
```
GET /mcp/tools/list
POST /mcp/tools/call
Payload de chamada:
{
"tool_name": "consultar_fatura",
"arguments": {
"msisdn": "11999999999",
"invoice_id": "INV-001"
}
}
Resposta esperada:
{
"ok": true,
"result": { ... },
"metadata": {
"server": "telecom",
"tool": "consultar_fatura"
}
}
```
### How to start the example MCP servers
The project includes two example MCP servers: Telecom and Retail. They are independent FastAPI apps. The Telecom server runs on port 8100 and exposes tools such as `consultar_fatura`, `consultar_pagamentos`, `consultar_plano`, and `listar_servicos`. The Retail server runs on port 8200 and exposes tools such as `consultar_pedido`, `consultar_entrega`, `solicitar_troca`, and `solicitar_devolucao`.
### Local startup through the script
```
cd projeto_multi_agent_isolado
bash ./scripts/run_mcp_servers.sh
```
The script creates a venv in the root directory, installs the MCP-server dependencies, and starts both uvicorn processes in the background:
```
Telecom MCP: http://localhost:8100
Retail MCP: http://localhost:8200
```
### Manual startup of Telecom MCP
```
cd projeto_multi_agent_isolado
python -m venv .venv
source .venv/bin/activate
pip install -r mcp_servers/telecom_mcp_server/requirements.txt
uvicorn --app-dir mcp_servers/telecom_mcp_server main:app --host 0.0.0.0 --port 8100
```
### Manual startup of Retail MCP
```
cd projeto_multi_agent_isolado
source .venv/bin/activate
pip install -r mcp_servers/retail_mcp_server/requirements.txt
uvicorn --app-dir mcp_servers/retail_mcp_server main:app --host 0.0.0.0 --port 8200
```
### Startup with Docker Compose
```
cd projeto_multi_agent_isolado
docker compose up --build
```
In Docker Compose, the backend uses `mcp_servers.docker.yaml` because, inside the compose network, localhost would point to the backend container itself. Therefore the endpoints use service names: `telecom-mcp` and `retail-mcp`.
```
services:
telecom-mcp:
ports:
- "8100:8100"
retail-mcp:
ports:
- "8200:8200"
backend:
environment:
MCP_SERVERS_CONFIG_PATH: /app/config/mcp_servers.docker.yaml
depends_on:
- telecom-mcp
- retail-mcp
```
### How to test MCP tools
### Direct health checks on the servers
```
curl http://localhost:8100/health
curl http://localhost:8200/health
```
### List tools directly from Telecom MCP
```
curl http://localhost:8100/mcp/tools/list
```
### Call a tool directly on Telecom MCP
```
curl -X POST http://localhost:8100/mcp/tools/call -H 'Content-Type: application/json' -d '{
"tool_name": "consultar_fatura",
"arguments": {
"msisdn": "11999999999",
"invoice_id": "INV-001"
}
}'
```
### Call a tool directly on Retail MCP
```
curl -X POST http://localhost:8200/mcp/tools/call -H 'Content-Type: application/json' -d '{
"tool_name": "consultar_pedido",
"arguments": {
"order_id": "PED-1001",
"customer_id": "C-001"
}
}'
```
### Test through the agent backend
After starting the MCP servers and backend, the backend provides debug endpoints to list and call tools through `MCPToolRouter`.
```
cd agent_template_backend
python -m venv .venv
source .venv/bin/activate
pip install -e ../agent_framework
pip install -r requirements.txt
uvicorn app.main:app --reload --reload-dir app --reload-dir config --port 8000
curl http://localhost:8000/debug/mcp/tools
curl -X POST http://localhost:8000/debug/mcp/call/consultar_fatura -H 'Content-Type: application/json' -d '{"msisdn":"11999999999","invoice_id":"INV-001"}'
```
### How the agent calls MCP in the flow
The agent does not need to know the server URL. It calls a logical tool through `MCPToolRouter`. The expected flow is:
```
Usuário
-> FastAPI /gateway/message
-> Guardrails de input
-> Router ou Supervisor escolhe o agente
-> LangGraph executa o agent graph
-> Agent decide usar uma tool
-> MCPToolRouter.call("consultar_fatura", {...})
-> MCPRegistry resolve servidor telecom
-> MCPHttpClient chama http://localhost:8100/mcp/tools/call
-> Resultado volta ao agent graph
-> Guardrails de output
-> Judges
-> Resposta final
```
### Conceptual Python example
```
result = await tool_router.call(
"consultar_fatura",
{
"msisdn": context.get("msisdn"),
"invoice_id": context.get("invoice_id"),
},
)
if result.ok:
dados_fatura = result.result
else:
# fallback controlado, telemetria e resposta segura
erro = result.error
```
### Example through a gateway message
```
curl -X POST http://localhost:8000/gateway/message -H 'Content-Type: application/json' -d '{
"channel": "web",
"payload": {
"session_id": "sess-tel-1",
"message": "Minha fatura veio alta",
"context": {
"msisdn": "11999999999",
"invoice_id": "INV-001"
}
}
}'
curl -X POST http://localhost:8000/gateway/message -H 'Content-Type: application/json' -d '{
"channel": "web",
"payload": {
"session_id": "sess-ret-1",
"message": "Meu pedido não chegou",
"context": {
"order_id": "PED-1001",
"customer_id": "C-001"
}
}
}'
```
### How to configure new servers and tools
### Add a new MCP Server
Edit `agent_template_backend/config/mcp_servers.yaml` for local execution:
```
servers:
crm:
transport: http
endpoint: http://localhost:8300/mcp
enabled: true
description: MCP Server de CRM.
```
Edit `agent_template_backend/config/mcp_servers.docker.yaml` for Docker execution:
```
servers:
crm:
transport: http
endpoint: http://crm-mcp:8300/mcp
enabled: true
description: MCP Server de CRM via docker-compose.
```
### Register a new tool
Edit `agent_template_backend/config/tools.yaml`:
```
tools:
consultar_cliente:
description: Consulta dados cadastrais resumidos do cliente.
mcp_server: crm
enabled: true
args_schema:
customer_id: string
document_id: string
```
### Implement the endpoint in the MCP server
```
TOOLS = {
"consultar_cliente": {
"description": "Consulta dados cadastrais resumidos do cliente.",
"input_schema": {
"customer_id": "string",
"document_id": "string"
},
},
}
@app.post("/mcp/tools/call")
async def call_tool(call: ToolCall):
if call.tool_name == "consultar_cliente":
return {
"ok": True,
"result": {
"customer_id": call.arguments.get("customer_id"),
"status": "ATIVO",
"segmento": "PREMIUM"
},
"metadata": {"server": "crm", "tool": "consultar_cliente"}
}
```
### How to isolate MCP by agent
In a multi-agent architecture, not every agent should see every tool. The orders agent may use `consultar_pedido` and `consultar_entrega`. The billing agent may use `consultar_fatura` and `consultar_pagamentos`. This isolation reduces operational risk, improves governance, and simplifies each agent's prompt.
### Simple option: allowlist per agent
```
agents:
- agent_id: billing_agent
allowed_tools:
- consultar_fatura
- consultar_pagamentos
- consultar_plano
- listar_servicos
- agent_id: orders_agent
allowed_tools:
- consultar_pedido
- consultar_entrega
- solicitar_troca
- solicitar_devolucao
```
### Recommended option: tools by configuration file
For large projects, each agent can have its own `tools.yaml`, `guardrails.yaml`, and `judges.yaml`. This maintains real isolation by agent and makes versioning easier.
```
config/agents/telecom_contas/
prompt_policy.yaml
guardrails.yaml
judges.yaml
tools.yaml
config/agents/retail_orders/
prompt_policy.yaml
guardrails.yaml
judges.yaml
tools.yaml
```
### How to deploy with Docker and OCI
### Local deployment with Docker Compose
The current `docker-compose.yml` already has separate services for `telecom-mcp`, `retail-mcp`, backend, and frontend. This separation is correct because MCP Servers should be independently scalable and versionable from the agent backend.
```
docker compose up --build
# URLs externas para teste local:
http://localhost:8100/health
http://localhost:8200/health
http://localhost:8000/debug/mcp/tools
http://localhost:5173
```
### Deployment on OCI/OKE
In Kubernetes/OKE, each MCP Server should be deployed as a Deployment + Service. The agent backend points to the Service's internal DNS. Conceptual example:
```
apiVersion: v1
kind: Service
metadata:
name: telecom-mcp
spec:
selector:
app: telecom-mcp
ports:
- port: 8100
targetPort: 8100
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: telecom-mcp
spec:
replicas: 2
selector:
matchLabels:
app: telecom-mcp
template:
metadata:
labels:
app: telecom-mcp
spec:
containers:
- name: telecom-mcp
image: <registry>/telecom-mcp:1.0.0
ports:
- containerPort: 8100
```
### Backend configuration in Kubernetes
```
servers:
telecom:
transport: http
endpoint: http://telecom-mcp.default.svc.cluster.local:8100/mcp
enabled: true
retail:
transport: http
endpoint: http://retail-mcp.default.svc.cluster.local:8200/mcp
enabled: true
```
### Security, guardrails, and observability
MCP greatly increases agent capability, but also increases the attack and operational-risk surface. A tool can query sensitive data, open protocols/cases, cancel services, generate credits, or execute business actions. Therefore, the integration must be protected before, during, and after the call.
### Minimum security checklist
- Every tool must have a clear description and argument schema.
- Every action tool must require explicit user confirmation before execution.
- Each agent must have a tool allowlist.
- Sensitive data returned by MCP must pass through masking/sanitization before the final response.
- Every MCP call must generate a trace/span/event in Langfuse or OpenTelemetry.
- Timeouts and retry limits must be configured per tool or per server.
- Do not expose MCP Servers directly to the internet without authentication, TLS, and network controls.
- Separate read-only tools from transactional tools.
### Recommended telemetry
```
span: mcp.tool_call
attributes:
tenant_id
agent_id
session_id
tool_name
mcp_server
latency_ms
ok
error
input_argument_keys
result_size
event: mcp.tool_call.completed
metadata:
tool_name
server
ok
error
```
### Evolution toward official MCP
The current project uses a simplified HTTP contract. For enterprise production there are two options. The first is to keep this internal contract for simplicity, provided it is well documented, secure, and versioned. The second is to evolve to an official MCP client/server with JSON-RPC, stdio, or Streamable HTTP.
### Complete developer step-by-step
```
# 1. Baixar e abrir o projeto
cd projeto_multi_agent_isolado
# 2. Subir servidores MCP de exemplo
bash ./scripts/run_mcp_servers.sh
# 3. Em outro terminal, subir backend
cd agent_template_backend
python -m venv .venv
source .venv/bin/activate
pip install -e ../agent_framework
pip install -r requirements.txt
uvicorn app.main:app --reload --reload-dir app --reload-dir config --port 8000
# 4. Validar tools carregadas pelo backend
curl http://localhost:8000/debug/mcp/tools
# 5. Chamar tool Telecom
curl -X POST http://localhost:8000/debug/mcp/call/consultar_fatura -H 'Content-Type: application/json' -d '{"msisdn":"11999999999","invoice_id":"INV-001"}'
# 6. Chamar tool Retail
curl -X POST http://localhost:8000/debug/mcp/call/consultar_pedido -H 'Content-Type: application/json' -d '{"order_id":"PED-1001","customer_id":"C-001"}'
# 7. Testar pelo gateway conversacional
curl -X POST http://localhost:8000/gateway/message -H 'Content-Type: application/json' -d '{"channel":"web","payload":{"session_id":"sess-ret-1","message":"Meu pedido não chegou","context":{"order_id":"PED-1001","customer_id":"C-001"}}}'
```
### Troubleshooting
### References
- Model Context Protocol Specification: https://modelcontextprotocol.io/specification
- MCP Transports: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports
- MCP Resources: https://modelcontextprotocol.io/specification/2025-06-18/server/resources
- Reference MCP Servers: https://github.com/modelcontextprotocol/servers
- LangChain MCP Adapters: https://docs.langchain.com/oss/python/langchain/mcp
- Project files: `agent_framework/src/agent_framework/mcp/*`, `agent_template_backend/config/mcp_servers.yaml`, `agent_template_backend/config/tools.yaml`, `mcp_servers/*`
### Read-only and transactional policies
The framework applies a minimal conversational policy immediately before the MCP call. `read_only` classification identifies queries; `transactional` identifies operations that change state. Authorization, idempotency, validation, and atomicity remain the responsibility of the MCP Server.
### Backend configuration
The configuration is optional and lives in `config/tool_policies.yaml` in `agent_template_backend`. The path can be set through `TOOL_POLICIES_PATH`. Do not place domain policies inside the shared library.
Example:
defaults:
operation_type: read_only
require_confirmation: false
tool_policies:
alterar_plano:
operation_type: transactional
require_confirmation: true
requires: [new_plan_id]
### Execution and compatibility
- Confirmation must arrive as `confirmed: true` or `confirmation: true`; text with value `true` is not sufficient.
- If `tool_policies.yaml` does not exist, `tool_type`, `requires`, `confirmation_required`, and `execution_policy` from `tools.yaml` remain valid.
- Old tools without policy continue to work without behavior changes.
- A blocked call does not reach MCP and returns metadata `blocked_by_policy`, `operation_type`, and `policy_source`.
### Read-only and transactional policies
> Content consolidated from `Documentacao/README_TOOL_POLICIES.md`.
### Goal
The framework distinguishes query operations (`read_only`) from operations that change state (`transactional`) immediately before the MCP call. This classification does not replace authorization, idempotency, or MCP-server business rules; it only adds minimal conversational protection, especially explicit confirmation.
### Where to configure
Configuration belongs to the application backend:
```text
templates/agent_template_backend/config/tool_policies.yaml
```
The shared library contains only the loader and validation. The path is optional:
```dotenv
TOOL_POLICIES_PATH=./config/tool_policies.yaml
```
### Example
```yaml
version: 1
defaults:
operation_type: read_only
require_confirmation: false
tool_policies:
consultar_plano:
operation_type: read_only
alterar_plano:
operation_type: transactional
require_confirmation: true
requires: [new_plan_id]
```
To execute `alterar_plano`, the arguments must contain `new_plan_id` and a literal boolean confirmation:
```json
{"new_plan_id": "CONTROLE_100", "confirmed": true}
```
`"confirmation": true` is also accepted. Strings such as `"true"` are not accepted as confirmation.
### Compatibility
- If `tool_policies.yaml` does not exist, the framework continues to use `tool_type`, `requires`, `confirmation_required`, and `execution_policy` from `tools.yaml`.
- Old tools without policy continue to execute as before.
- An explicit policy in the new file takes precedence for that tool's `operation_type` and confirmation.
- The `tools.yaml` catalog remains the source for endpoint, schema, enablement, and cache.
- The new file must not be placed in `libs/agent_framework`, because decisions vary by application and domain.
### Execution flow
```text
agente -> MCPToolRouter -> validação da política -> mapeamento de parâmetros -> MCP Gateway/Server
```
A blocked call returns `ok=false`, `metadata.blocked_by_policy=true`, the operation type, and the policy source. The MCP server remains the final authority for authentication, authorization, validation, idempotency, and business transaction.
### Recommended migration
1. Update the library without creating the file: legacy behavior remains.
2. Create `config/tool_policies.yaml` in the backend.
3. Initially register only transactional operations that require confirmation.
4. Test calls without confirmation, with boolean confirmation, and with missing required fields.
5. Gradually remove duplicate confirmation settings from `tools.yaml` when all consuming templates already use the new configuration.
### Minimum transactional runtime (binding fix)
The routing `mcp_tools` list is an **allowlist**, not an instruction to execute every tool. The runtime now:
1. automatically executes only `read_only` tools;
2. selects at most one transactional action compatible with the user's request;
3. when `require_confirmation: true`, persists `pending_tool_call` and `transaction_status: AWAITING_CONFIRMATION`;
4. on the confirmation turn, reuses the same call and executes it with `confirmed: true`;
5. publishes `available_mcp_tools`, `selected_tool_call`, `tool_policy_result`, `confirmation_required`, and `confirmation_received` in state.
For the example scenario, order `123` (or `PED-ENTREGUE`) returns `ENTREGUE` in Retail MCP. Use:
```text
Quero devolver o pedido 123 porque me arrependi da compra.
Sim, confirmo a devolução.
```
The MCP contract was standardized to use `reason` in both the catalog and FastMCP server. `tool_policies.yaml` takes precedence over legacy fields in `tools.yaml`; these remain aligned in the templates for compatibility.
### Tool-policy integration and compatibility
> Content consolidated from `Documentacao/RELEASE_NOTES_TOOL_POLICIES.md`.
### Changes
- New optional `ToolPolicyRegistry` in the shared library.
- Central validation in `MCPToolRouter`, including direct calls.
- Minimum types `read_only` and `transactional`.
- Strict confirmation through `confirmed: true` or `confirmation: true`.
- Optional support for required fields per policy.
- Automatic fallback to `tool_type`, `requires`, `confirmation_required`, and `execution_policy` from `tools.yaml`.
- `config/tool_policies.yaml` and the `TOOL_POLICIES_PATH` variable in the main templates, Day Zero, and `Tuning-Performance/Normal` and `Tuning-Performance/Route_Stickness` variants.
- Unit policy and compatibility tests added in `tests/unit/test_tool_policies.py`.
### Checks performed
- Compilation of `libs`, `templates`, `Tuning-Performance`, and `tests`: passed.
- Structural validation of the six YAML files: passed.
- Isolated loader cases (transactional policy, confirmation, missing file, and missing registration): passed.
- Rendering of both updated Word manuals: passed, with no clipping or overlap on the added pages.
### Validation-environment limitation
The `pytest` suite was prepared but could not be fully executed in this environment because `pytest` and project runtime dependencies were not installed and access to the package index timed out. To reproduce in a project environment:
```bash
PYTHONPATH=libs/agent_framework/src:templates/agent_template_backend python -m pytest -q
```
### Backend/MCP integration correction
- `mcp_tools` is now treated as an allowlist.
- Actions are no longer automatically executed together with queries.
- Transactional confirmation is persisted and resumed on the next turn.
- `reason`/`motivo` incompatibility in Retail MCP was corrected.
- A deterministic delivered order was added for tests (`123`).
- The generic keyword `produto` was removed from the Telecom intent to avoid collisions with Retail returns.
- `Normal` and `Route_Stickness` templates in `Tuning-Performance` were synchronized.
### Contextual MCP parameter extraction
> Content consolidated from `Documentacao/RELEASE_NOTES_MCP_PARAMETER_EXTRACTION_FIX.md`.
### Problem fixed
The `extract` block in `mcp_parameter_mapping.yaml` existed in configuration and documentation, but it was not executed by the runtime. In addition, Business Context values could overwrite explicit arguments, causing `contract_key` to replace the `order_id` provided by the user.
### Fixes
- implementation of generic `strategy: llm` extraction after tool selection;
- preserved support for `strategy: month_name_pt`;
- dedicated `mcp_parameter_extraction` profile;
- `llm.mcp_parameter_extraction` telemetry;
- `extract` is no longer interpreted as simple mapping;
- explicit/extracted arguments take precedence over Business Context;
- removal of `contract_key: order_id` from templates;
- `order_id` configured as `string`;
- update of `Tuning-Performance` variants.
### Expected result
For the message `consultar pedido 123`, the MCP call must receive `order_id=123`, even when Business Context contains a different `contract_key`.
### Local use of MCP tools
> Content consolidated from `Documentacao/README_MCP.md`.
This version adds an MCP layer to the framework:
- `agent_framework.mcp.MCPToolRouter`
- `agent_template_backend/config/mcp_servers.yaml`
- `agent_template_backend/config/tools.yaml`
- `mcp_servers/telecom_mcp_server`
- `mcp_servers/retail_mcp_server`
### Start locally
Terminal 1:
```bash
bash ./scripts/run_mcp_servers.sh
```
Terminal 2:
```bash
cd agent_template_backend
python -m venv .venv
source .venv/bin/activate
pip install -e ../agent_framework
pip install -r requirements.txt
uvicorn app.main:app --reload --reload-dir app --reload-dir config --port 8000
```
Terminal 3:
```bash
cd agent_frontend
python -m http.server 5173
```
### Quick tests
List MCP tools loaded by the backend:
```bash
curl http://localhost:8000/debug/mcp/tools
```
Call a tool directly through the backend:
```bash
curl -X POST http://localhost:8000/debug/mcp/call/consultar_fatura \
-H 'Content-Type: application/json' \
-d '{"msisdn":"11999999999","invoice_id":"INV-001"}'
```
Telecom routing + MCP:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"session_id":"sess-tel-1","message":"Minha fatura veio alta","context":{"msisdn":"11999999999","invoice_id":"INV-001"}}}'
```
Retail routing + MCP:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"session_id":"sess-ret-1","message":"Meu pedido não chegou","context":{"order_id":"PED-1001","customer_id":"C-001"}}}'
```
### Docker Compose
```bash
docker compose up --build
```
In compose, the backend uses `config/mcp_servers.docker.yaml` to point to `telecom-mcp` and `retail-mcp`.
### Read-only and transactional operations
Use `config/tool_policies.yaml` in the backend to classify only operations that need additional handling. Validation is applied in the central router before the MCP Gateway/Server. The file is optional and older templates continue using the policies already present in `tools.yaml`. Full configuration and the migration procedure are in [README_TOOL_POLICIES.md](README_TOOL_POLICIES.md).
### Source files
The files below were consolidated into this manual:
- `Documentacao/Manual_Integracao_MCP_Servers_Agent_Framework.docx`
- `Documentacao/README_TOOL_POLICIES.md`
- `Documentacao/RELEASE_NOTES_TOOL_POLICIES.md`
- `Documentacao/RELEASE_NOTES_MCP_PARAMETER_EXTRACTION_FIX.md`
- `Documentacao/README_MCP.md`
### Maintenance rule
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,286 @@
### Guardrails, Judges, and Transaction Evaluation
### How to use this manual
This is a **specialized reference manual**. It does not replace the main tutorial.
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
- Use this document when you need to implement, deepen, or diagnose **native/external guardrails, judges, transactional sampling, and grounding**.
- Historical examples consolidated here should be read in light of the framework's current API.
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
### Relationship with the main tutorial
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
### Scope
Native/external guardrails, judges, transactional sampling, and grounding.
### Consolidated technical content
### Guardrails, Judges, and Transaction Evaluation
Manual for input/output guardrails, agent-specific extensions, external judges, mandatory execution on transactions, and the signals/evidence used during evaluation.
### How to use this document
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
### Guardrails implemented in the framework
> Content consolidated from `Documentacao/README_GUARDRAILS_IMPLEMENTADOS.md`.
This version adds a pragmatic guardrail layer to `agent_framework`, inspired by separating rails by stage: input, output, retrieval, and execution/tool.
### Input rails
- `MSIZE` — blocks excessively large messages.
- `MSK` — masks CPF, CNPJ, phone, e-mail, card, postal code, RG, tokens, and keys.
- `TOX` — detects toxicity and records severity without blocking by default.
- `PINJ` — detects prompt injection and records a score.
- `JBRK` — detects jailbreak/bypass roleplay and records a score.
- `VLOOP` — blocks repetitive conversational loops.
### Output rails
- `PII_OUT` — masks PII in the agent response.
- `CMP` — softens absolute promises and excessive guarantee language.
- `REVPREC` — blocks verbalization of an operational action without tool confirmation.
- `GND` — signals grounding/risk when there is a specific answer without evidence.
- `ALUC_RISK` — marks hallucination risk for telemetry and judges.
### Optional rails
- `RET_REL` — validates retrieval-chunk relevance using a minimum score.
- `TOOL_VAL` — validates MCP/tool name, required arguments, negative values, and allowlist.
### Contract for authorized protocols in output guardrails
When a workflow or tool produces a **protocol/reference number that must be shown to the same customer**, the agent integration code must register that value in the output context before output guardrails run:
```python
ctx["expected_protocols"] = [protocol_number]
```
This field is a **framework contract**. It declares that those exact values were produced or validated by the current flow and may therefore be used by output guardrails as authorization evidence.
Expected flow:
```text
workflow/tool produces protocol
agent registers it in expected_protocols
CMP validates that the displayed protocol belongs to the expected values
DLEX_OUT does not block that protocol merely because it is an identifier
response may disclose the protocol to the customer
```
Important rules:
- `expected_protocols` must contain **only protocols actually produced/expected in the current turn or transaction**.
- Do not use `expected_protocols` to allow tokens, credentials, arbitrary internal IDs, or third-party data.
- Authorization applies only to listed values; any other identifier remains subject to normal `DLEX_OUT` rules.
- The value must be propagated **before `output_guardrails`**. Adding it later has no effect.
- For transactional responses, keep protocol evidence in the tool/workflow result so `CMP`, `GND`, and observability can correlate the value.
Example:
```python
result = await execute_workflow(...)
protocol_number = result.get("protocol_number") or result.get("protocolo_id")
if protocol_number:
ctx["expected_protocols"] = [str(protocol_number)]
```
#### Troubleshooting: workflow completed but the response was replaced by a safety message
Typical symptom:
```text
workflow = COMPLETED
CMP = allowed
DLEX_OUT = blocked because of "internal protocol"
final response = "I could not safely validate this response..."
```
Check, in this order:
1. Is the generated protocol present in the tool/workflow result or evidence?
2. Did the agent propagate the same value in `ctx["expected_protocols"]`?
3. Was `expected_protocols` populated before `output_guardrails`?
4. Is the protocol shown in the response exactly one of the expected values?
5. Is `DLEX_OUT` actually blocking another real issue such as a secret, token, or third-party data?
If `expected_protocols` is absent, the framework must not assume that an arbitrary textual identifier is safe to disclose.
### Files changed
- `agent_framework/src/agent_framework/guardrails/rails.py`
- `agent_framework/src/agent_framework/guardrails/pipeline.py`
- `agent_framework/src/agent_framework/guardrails/__init__.py`
### Quick use
```python
from agent_framework.guardrails.pipeline import GuardrailPipeline
pipeline = GuardrailPipeline()
sanitized_input, input_decisions = await pipeline.run_input(
user_text,
{"history_texts": history_texts},
)
final_answer, output_decisions = await pipeline.run_output(
answer,
context,
)
```
For tools/MCP:
```python
_, decisions = await pipeline.run_tool(
"cancelar_produto",
{"produto": "VAS", "valor": 0},
{
"required_args": ["produto"],
"allowed_tools": ["cancelar_produto", "consultar_fatura"],
},
)
```
### SPI for external guardrails and judges
> Content consolidated from `docs/EXTERNAL_GUARDRAILS_JUDGES.md`.
`agent_framework_oci` supports agent-owned guardrails and judges without importing domain code into the core.
```yaml
output:
- code: ACME_POLICY
type: external
class: app.extensions.guardrails:AcmePolicyRail
```
```yaml
judges:
- name: acme_quality
type: external
class: app.extensions.judges:AcmeQualityJudge
threshold: 0.7
```
Native entries remain unchanged. External synchronous `evaluate()` methods execute in worker threads via `asyncio.to_thread`; asynchronous methods execute concurrently on the framework event loop. Judges run concurrently with `asyncio.gather`, preserving YAML result order. Agent plugins should reuse the LLM supplied by the framework rather than instantiate a separate provider.
The core must not reference a concrete agent package, company, product, telecom identifier, or domain-specific policy. Domain-specific variants belong to the agent and should receive distinct public codes/names.
### Compatibility rule
Domain policies must not be replaced by cosmetically generic text inside the core while losing the original policy. The generic core implementation and the agent-specific implementation may coexist; the embedding agent explicitly selects its own code/name in YAML.
Legacy business validators should migrate to the agent domain. A temporary compatibility shim is acceptable for old imports, but new application code must import the agent-owned implementation.
### Mandatory judge execution for transactions
> Content consolidated from `docs/JUDGES_TRANSACTIONAL_SAMPLING_FIX.md`.
### Problem
Even with `always_run_for_transactional: true`, judges could be skipped by sampling because the `judge` node sent only `context`, `route`, `intent`, and `mcp_results`. Transactional fields produced by the runtime did not reach `JudgePipeline`.
### Fix
The `judge` node now passes:
- `transaction_status`
- `confirmation_required`
- `confirmation_received`
- `tool_policy_result`
- `selected_tool_call`
- `pending_tool_call`
- `mcp_results` as evidence
`JudgePipeline` detects transactions through multiple signals and evaluates `always_run_for_transactional` before applying `sample_rate`.
With the configuration below, common queries continue to be sampled at 25%, but `AWAITING_CONFIRMATION`, `COMPLETED`, `FAILED`, or `CANCELLED` turns always run the judges.
```yaml
enabled: true
sample_rate: 0.25
always_run_for_transactional: true
```
### Global Supervisor validation
> Content consolidated from `docs/docs_GLOBAL_SUPERVISOR_VALIDATION.txt`.
VALIDATION - GLOBAL SUPERVISOR
Implemented changes:
1. Framework
- agent_framework.global_supervisor.models
- agent_framework.global_supervisor.config
- agent_framework.global_supervisor.session_store
- agent_framework.global_supervisor.router
- agent_framework.global_supervisor.client
2. New service
- agent_gateway/app/main.py
- agent_gateway/app/settings.py
- agent_gateway/config/backends.yaml
- agent_gateway/README.md
- agent_gateway/Dockerfile
- agent_gateway/docs/ARQUITETURA_GLOBAL_SUPERVISOR.md
3. Docker Compose
- agent-gateway service added on port 8010.
Validations performed:
- python3 -m compileall -q agent_framework/src/agent_framework/global_supervisor agent_gateway/app
Result: OK
- Hybrid-routing smoke test:
Input 1: "My bill is too high" -> billing
Input 2: "and this amount?" on the same session_id -> billing via active_backend
Result: OK
- FastAPI app import smoke test:
from app.main import app, registry, router
Result: OK
Note:
- The gateway SSE proxy was left as a future step. The `/gateway/message/sse` endpoint already routes and forwards as a normal message; for end-to-end SSE, a proxy from `/gateway/events/{session_id}` to the active backend can be implemented.
### Guardrail event validation
> Content consolidated from `docs/docs_VALIDATION_GUARDRAILS_IC.txt`.
VALIDATION REPORT - guardrails parallel fail-fast + observer IC
Date: 2026-06-03
compileall: OK
smoke-tests: OK
### Source files
The files below were consolidated into this manual:
- `Documentacao/README_GUARDRAILS_IMPLEMENTADOS.md`
- `docs/EXTERNAL_GUARDRAILS_JUDGES.md`
- `docs/JUDGES_TRANSACTIONAL_SAMPLING_FIX.md`
- `docs/docs_GLOBAL_SUPERVISOR_VALIDATION.txt`
- `docs/docs_VALIDATION_GUARDRAILS_IC.txt`
### Maintenance rule
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.

View File

@@ -0,0 +1,387 @@
### RAG, BusinessContext, and Grounding
### How to use this manual
This is a **specialized reference manual**. It does not replace the main tutorial.
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
- Use this document when you need to implement, deepen, or diagnose **RAG, providers, BusinessContext, retrieved context, and grounding**.
- Historical examples consolidated here should be read in light of the framework's current API.
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
### Relationship with the main tutorial
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
### Scope
RAG, providers, BusinessContext, retrieved context, and grounding.
### Consolidated technical content
### RAG, Enterprise Providers, BusinessContext, and Grounding
Guide for integrating retrieved knowledge, selecting between RAG providers, configuring KBDB, using samples, MCP sufficiency, and using BusinessContext as a data contract.
### How to use this document
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
### Standard RAG Provider versus KBDB Enterprise
> Content consolidated from `docs/RAG_PROVIDER_KBDB.md`.
The framework now supports two retrieval backends through the same `RagService` contract, without changing agents or `_retrieve_rag_context()`.
### Selection
```env
RAG_PROVIDER=standard # default: comportamento anterior
# ou
RAG_PROVIDER=kbdb # KBDB enterprise
```
Selection is exclusive per process. The two RAG implementations do not run together and do not share vector store, graph store, or ingestion.
### `standard`
Fully preserves the existing RAG in `agent_framework_oci`: `VECTOR_STORE_PROVIDER`, `GRAPH_STORE_PROVIDER`, embedding, query rewrite, compression, retrieval guardrails, and generation remain valid.
### `kbdb`
The framework integrates only the stable serving port of the KBDB project:
`PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE`
The enterprise pipeline remains external to the agent runtime and preserves its own RAW → SILVER → GOLD architecture, HVI/hybrid search, property graph, publishing, lifecycle, audit, and observability.
The KBDB envelope is adapted to `RagResult`/`VectorDocument`; therefore existing agents continue calling `_retrieve_rag_context()` and the framework's retrieval guardrails continue after retrieval.
### Configuration
```env
RAG_PROVIDER=kbdb
RAG_TOP_K=5
KBDB_DB_USER=KB_USER
KBDB_DB_PASSWORD=...
KBDB_DB_DSN=...
KBDB_DB_WALLET_LOCATION=...
KBDB_DB_WALLET_PASSWORD=...
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
```
When `RAG_PROVIDER=kbdb`, `KBDB_DB_USER`, `KBDB_DB_PASSWORD`, and `KBDB_DB_DSN` are required. KBDB uses an isolated connection because it may reside in another Autonomous database. `KBDB_DB_DSN` follows the same semantics as `ADB_DSN`: use the existing TNS alias in the `tnsnames.ora` from the wallet indicated by `KBDB_DB_WALLET_LOCATION`, not a `tcps://...` URL.
### Isolation and compatibility
- `RAG_PROVIDER=standard` does not import or connect to KBDB.
- `RAG_PROVIDER=kbdb` does not instantiate the standard RAG vector/graph stores.
- Ingestion through `RagService.add_documents()` is not allowed in KBDB mode: it must go through the KBDB pipeline/publishing process.
- Query rewrite and context compression remain optional and are applied by the framework's common layer.
- `AgentRuntimeMixin._retrieve_rag_context()` and agents remain unchanged.
- KBDB failures follow the framework's existing semantics: retrieval is auxiliary evidence and the exception is converted into technical metadata without breaking the user journey.
### Direct tool response and RAG
The framework no longer considers a structured MCP result, by itself, to be a sufficient user response.
A `response.renderer` policy defines only **how** to present the result. It does not terminate the flow before RAG/LLM. For a tool to deliberately produce a direct final response, the application must explicitly declare:
```yaml
response:
mode: renderer
renderer: meu.renderer
direct: true
```
Without `direct: true`, the tool result remains MCP evidence and the flow continues to `_retrieve_rag_context()` and LLM composition. This allows, for example, an operational plan query to be combined with KBDB documentary knowledge when the question asks for rules, policies, or explanations.
The framework core has no fallback by tool name (`consultar_plano`, `consultar_pedido`, etc.). Presentation rules belong to the application/domain.
### MCP sufficiency and grounding
A successful MCP result **does not** make the framework skip RAG automatically.
The domain may declare documentary sufficiency only explicitly in the payload with `rag_sufficient=true` or `knowledge_sufficient=true`. This decision is generic and does not depend on the tool name or telecom/retail keywords.
For the `kbdb` provider, `KBDB_GROUNDED_ONLY=true` is the default. When KBDB search returns empty, blocked, or error, LLM composition may use facts proven by MCP/business context, but it must not fill the documentary portion using parametric model knowledge. It must state that there is insufficient evidence in the knowledge base.
ProductAgent events record `IC.PRODUCT_RAG_CONTEXT_EVALUATED` for every attempt/decision and `IC.PRODUCT_RAG_CONTEXT_RETRIEVED` only when context was retrieved. Metadata includes `provider`, `status`, `document_count`, `reason`, `error`, `query`, `namespace`, and `latency_ms`.
### RAG samples and tests
> Content consolidated from `docs/README_rag_samples.md`.
These PDF files are synthetic, searchable sample documents created to validate the RAG embedding and retrieval flow of `agent_template_backend`.
### Files
- `01_billing_agent_invoice_policy.pdf` - sample knowledge for `billing_agent`
- `02_orders_agent_lifecycle_policy.pdf` - sample knowledge for `orders_agent`
- `03_product_agent_catalog_policy.pdf` - sample knowledge for `product_agent`
- `04_support_agent_sla_policy.pdf` - sample knowledge for `support_agent`
- `05_business_context_rag_flow.pdf` - sample knowledge about BusinessContext, identity.yaml and MCP parameter mapping
### How to use
Copy the PDF files to the backend documentation directory:
```bash
mkdir -p agent_template_backend/docs/rag_samples
cp *.pdf agent_template_backend/docs/rag_samples/
```
For a local smoke test, use:
```env
VECTOR_STORE_PROVIDER=sqlite
EMBEDDING_PROVIDER=mock
SQLITE_DB_PATH=./data/agent_framework.db
RAG_TOP_K=4
```
Then run:
```bash
python scripts/generate_rag_embeddings.py \
--docs-dir ./agent_template_backend/docs/rag_samples \
--namespace default
```
For production-like semantic embeddings with OCI Generative AI, use:
```env
VECTOR_STORE_PROVIDER=autonomous
EMBEDDING_PROVIDER=oci
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..xxxx
OCI_REGION=us-chicago-1
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
```
### Suggested retrieval test questions
- What is a prorated charge?
- When can the OrdersAgent open an exchange request?
- Which SKU represents the AI Agents book?
- What is the target response for a critical support ticket?
- How does BusinessContext map customer_key to MCP tool parameters?
### BusinessContext v2
> Content consolidated from `Documentacao/README_TEMPLATE_BUSINESS_CONTEXT_V2.md`.
This package updates `agent_template_backend` and `agent_frontend` to reflect the new framework, where keys coming from the channel/front end are resolved once into canonical keys and propagated through the layers to the MCP Server.
### Implemented flow
1. The front end sends `tenant_id`, `agent_id`, `session_id`, and `business_context`.
2. The backend normalizes the message through `ChannelGateway`, preserving the full payload in `context`.
3. The backend uses `IdentityResolver` with `config/identity.yaml` to generate `BusinessContext`:
- `customer_key`
- `contract_key`
- `interaction_key`
- `account_key`
- `resource_key`
- `session_key`
4. The workflow receives `context.business_context`.
5. Example agents no longer build specific arguments such as `msisdn`, `invoice_id`, or `order_id` directly.
6. `MCPToolRouter` uses `config/mcp_parameter_mapping.yaml` to convert canonical keys into the actual parameters of each MCP tool.
### Main files adjusted
- `agent_template_backend/app/main.py`
- loads `IdentityResolver`;
- resolves `BusinessContext` per message;
- persists keys in session/memory/metadata/SSE;
- adds `/debug/identity`.
- `agent_template_backend/app/agents/runtime.py`
- adds centralized `_collect_mcp_context()`;
- forwards `business_context` and `original_context` to the MCP Router.
- `agent_template_backend/app/agents/*_agent.py`
- agents now use `_collect_mcp_context()` instead of building specific arguments.
- `agent_template_backend/config/identity.yaml`
- defines how channel/front-end fields feed canonical keys.
- `agent_template_backend/config/mcp_parameter_mapping.yaml`
- defines how canonical keys become real parameters per MCP tool.
- `agent_frontend/index.html` and `agent_frontend/app.js`
- add `tenant`, `agent`, and canonical-key fields;
- send `business_context` in the payload;
- retain domain aliases for compatibility (`msisdn`, `invoice_id`, `order_id`, etc.).
### Quick test
Start backend, frontend, and MCP servers. Then test:
```bash
curl -s http://localhost:8000/health | jq
curl -s -X POST http://localhost:8000/debug/identity \
-H 'Content-Type: application/json' \
-d '{
"channel":"web",
"tenant_id":"default",
"agent_id":"telecom_contas",
"payload":{
"message":"Minha fatura veio alta",
"session_id":"teste-001",
"msisdn":"11999999999",
"invoice_id":"3000131180",
"ura_call_id":"URA-123",
"business_context":{
"customer_key":"11999999999",
"contract_key":"3000131180",
"interaction_key":"URA-123",
"session_key":"teste-001"
}
}
}' | jq
curl -s -X POST http://localhost:8000/debug/mcp/call/consultar_fatura \
-H 'Content-Type: application/json' \
-d '{
"business_context": {
"customer_key":"11999999999",
"contract_key":"3000131180",
"interaction_key":"URA-123",
"session_key":"teste-001"
}
}' | jq
```
In the backend log, look for `mcp.tool.mapped`. It should indicate the mapped keys and `has_msisdn=true`, `has_invoice_id=true` for the telecom domain.
### Operational RAG and cache integration
> Content consolidated from `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`.
This version fixes the gaps identified in the comparison against FIRST.
### Applied fixes
### 1. Operational LangGraph checkpoint
The workflow no longer compiles directly with `MemorySaver()`. The following adapter was created:
```text
agent_framework/checkpoints/langgraph_saver.py
```
It connects LangGraph to the framework's configured repository:
- `memory`
- `sqlite`
- `oracle` / `autonomous`
In the workflow:
```python
builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
```
### 2. LangGraph telemetry wrapping actual execution
A node wrapper was added to the workflow:
```python
self._node("billing_agent", self.billing_agent)
```
This way the `langgraph.node.*` span/event wraps actual node execution, not just an empty block.
Events emitted:
- `langgraph.node.started`
- `langgraph.node.completed`
- `langgraph.node.failed`
- `langgraph.edge.selected`
### 3. RAG integrated into agents
Agents now receive `RagService` and use retrieved context in the prompt:
- BillingAgent
- ProductAgent
- OrdersAgent
- SupportAgent
RAG uses:
- `VECTOR_STORE_PROVIDER=memory|sqlite|oracle|autonomous`
- `GRAPH_STORE_PROVIDER=memory|oracle|autonomous`
- `RAG_TOP_K`
### 4. Cache integrated into agent runtime
The following mixin was created:
```text
agent_template_backend/app/agents/runtime.py
```
It adds:
- standardized RAG retrieval;
- cache key for LLM calls;
- hit/miss with telemetry;
- distributed cache through `create_cache(settings)`.
### 5. Unit tests
The following directory was created:
```text
tests/unit
```
Initial coverage:
- cache;
- SSE;
- RAG;
- checkpoint saver;
- LangGraph telemetry;
- agent runtime;
- static workflow verification;
- main imports.
Local validation performed:
```text
12 passed
```
### How to test
```bash
cd projeto_agent_framework_first_ready
pip install -r agent_template_backend/requirements.txt
pytest -q tests/unit
```
### Source files
The files below were consolidated into this manual:
- `docs/RAG_PROVIDER_KBDB.md`
- `docs/README_rag_samples.md`
- `Documentacao/README_TEMPLATE_BUSINESS_CONTEXT_V2.md`
- `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`
### Maintenance rule
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.

View File

@@ -0,0 +1,635 @@
### Long-Term Memory and Checkpoint
### How to use this manual
This is a **specialized reference manual**. It does not replace the main tutorial.
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
- Use this document when you need to implement, deepen, or diagnose **LTM, conversation memory, identity-based isolation, and state persistence**.
- Historical examples consolidated here should be read in light of the framework's current API.
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
### Relationship with the main tutorial
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
### Scope
LTM, conversation memory, identity-based isolation, and state persistence.
### Consolidated technical content
### Long-Term Memory and Enterprise Checkpointing
Implementation manual for durable memory, identity isolation, stores, extraction, LangGraph integration, persistence testing, and the differences among LTM, history, summary, and checkpoint.
### How to use this document
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
### Complete Long-Term Memory implementation
> Content consolidated from `Documentacao/Manual_Long_Term_Memory_PT.md`.
### Concept
Long-Term Memory (LTM) is the `agent_framework` capability to store and retrieve durable facts beyond the lifetime of a conversation session.
Unlike message history, which is normally associated with a `session_id`, long-term memory is associated with the business identity of the user or customer. In the current implementation, this identity is composed of:
```text
tenant_id
agent_id
customer_key
```
This allows an agent to retrieve preferences, identity information, projects, and constraints even when a new session is created.
### What it is for
Long-Term Memory is used to:
- maintain continuity across sessions;
- personalize responses;
- avoid making the user repeat information already provided;
- reduce the need to send the entire history to the model;
- store preferences, current projects, preferred names, and constraints;
- isolate memory across tenants, agents, and customers.
Example:
```text
Sessão A:
"Me chame de Cris. Minha linguagem preferida é Python."
Sessão B, com outro session_id e o mesmo customer_key:
"O que você lembra sobre mim?"
Resposta esperada:
"Seu nome preferido é Cris e sua linguagem preferida é Python."
```
### Difference among memory types
### Conversation Memory
Maintains messages from the current conversation and is normally associated with `session_id`.
### Summary Memory
Maintains a conversation summary to reduce the amount of context sent to the model.
### Long-Term Memory
Maintains durable facts across sessions and is associated with business identity, especially `customer_key`.
### Feature components
### LongTermMemoryManager
Responsible for coordinating:
- memory loading;
- retrieval by identity;
- context rendering;
- extraction of new facts;
- persistence of facts;
- deduplication and updates.
### LongTermMemoryStore
Persistence interface used by the manager.
### SQLiteLongTermMemoryStore
Reference implementation based on SQLite.
It is appropriate for:
- local development;
- tests;
- demonstrations;
- low-scale environments.
### InMemoryLongTermMemoryStore
In-memory implementation used for quick tests.
Its content is lost when the backend process terminates.
### LongTermMemoryExtractor
Responsible for identifying durable facts in messages.
Examples of facts:
```text
preferred_name = Cris
preferred_language = Python
current_project = Atlas
```
### LongTermMemoryItem
Model representing a persisted item, including identity, key, value, category, confidence, and metadata.
### AgentRuntime
Loads memory before agent execution and injects the context into the prompt.
### `persist_long_term_memory` node
LangGraph node responsible for persisting facts after final-response generation and validation.
### File structure
```text
libs/
└── agent_framework/
└── src/
└── agent_framework/
└── memory/
├── __init__.py
├── long_term_extractor.py
├── long_term_memory.py
├── long_term_models.py
└── long_term_store.py
```
### Execution flow
```text
Mensagem do usuário
AgentRuntime.prepare_memory_context()
├── Conversation Memory
├── Summary Memory
└── Long-Term Memory
long_term_memory_context
Prompt do agente
Agente
Guardrails / Judges / Supervisor
persist_long_term_memory
LongTermMemoryExtractor
LongTermMemoryStore
```
### Framework configuration
### New modules
Copy the files:
```text
libs/agent_framework/src/agent_framework/memory/long_term_extractor.py
libs/agent_framework/src/agent_framework/memory/long_term_memory.py
libs/agent_framework/src/agent_framework/memory/long_term_models.py
libs/agent_framework/src/agent_framework/memory/long_term_store.py
```
### Updating `memory/__init__.py`
Export the Long-Term Memory components:
```python
from agent_framework.memory.long_term_memory import (
LongTermMemoryManager,
create_long_term_memory_manager,
)
from agent_framework.memory.long_term_models import LongTermMemoryItem
from agent_framework.memory.long_term_store import (
InMemoryLongTermMemoryStore,
LongTermMemoryStore,
SQLiteLongTermMemoryStore,
create_long_term_memory_store,
)
```
### Updating `settings.py`
Add the configurations:
```python
ENABLE_LONG_TERM_MEMORY: bool = False
LONG_TERM_MEMORY_PROVIDER: str = "sqlite"
LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db"
LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory"
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20
LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70
LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True
LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True
```
### Integration with AgentRuntime
The runtime must:
1. check whether the feature is enabled;
2. create the manager when necessary;
3. retrieve facts by identity;
4. populate state;
5. inject context into the prompt.
Fields added to state:
```python
long_term_memories: list[dict]
long_term_memory_context: str
long_term_memory_write_result: dict
```
### Initialization in AgentWorkflow
The manager must be created in `AgentWorkflow`:
```python
self.long_term_memory_manager = create_long_term_memory_manager(
settings,
telemetry=telemetry,
)
```
### Correct agent initialization
`long_term_memory_manager` must not be passed through `agent_kwargs` if the constructors of `BillingAgent`, `ProductAgent`, `OrdersAgent`, and `SupportAgent` do not declare that parameter.
This initialization causes an error:
```python
agent_kwargs = {
"telemetry": telemetry,
"settings": settings,
"memory": memory,
"summary_memory": summary_memory,
"long_term_memory_manager": self.long_term_memory_manager,
}
self.billing = BillingAgent(llm, **agent_kwargs)
```
Resulting error:
```text
TypeError: BillingAgent.__init__() got an unexpected keyword argument
'long_term_memory_manager'
```
The recommended form is to create agents using the existing signature and inject the manager as an attribute after initialization:
```python
agent_kwargs = {
"telemetry": telemetry,
"tool_router": getattr(self, "tool_router", None),
"rag_service": self.rag_service,
"cache": self.cache,
"settings": settings,
"observer": self.observer,
"memory": memory,
"summary_memory": summary_memory,
}
self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs)
self.orders = OrdersAgent(llm, **agent_kwargs)
self.support = SupportAgent(llm, **agent_kwargs)
for agent in (
self.billing,
self.product,
self.orders,
self.support,
):
agent.long_term_memory_manager = self.long_term_memory_manager
```
This approach avoids changing every agent constructor and keeps the capability encapsulated in the framework.
### LangGraph configuration
Register the node:
```python
builder.add_node(
"persist_long_term_memory",
self._node(
"persist_long_term_memory",
self.persist_long_term_memory,
),
)
```
Change the flow:
```python
builder.add_edge(
"supervisor_review",
"persist_long_term_memory",
)
builder.add_edge(
"persist_long_term_memory",
"persist",
)
```
Implement the method:
```python
async def persist_long_term_memory(
self,
state: AgentState,
) -> dict[str, object]:
result = await self.long_term_memory_manager.persist_turn(state)
return {
"long_term_memory_write_result": result,
}
```
Final flow:
```text
supervisor_review
persist_long_term_memory
persist
```
### Environment variables
```env
ENABLE_LONG_TERM_MEMORY=true
LONG_TERM_MEMORY_PROVIDER=sqlite
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
LONG_TERM_MEMORY_AUTO_EXTRACT=true
LONG_TERM_MEMORY_INJECT_CONTEXT=true
```
### SQLite database path
The relative path is resolved from the directory where the backend is started.
To avoid accidentally creating different databases, prefer an absolute path in development environments:
```env
LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db
```
Create the directory before starting:
```bash
mkdir -p data
```
### How to test
### Test 1 — Write
Send:
```json
{
"session_id": "default:telecom_contas:memory-session-a",
"customer_key": "11999999999",
"message": "Me chame de Cris. Minha linguagem preferida é Python e meu projeto atual se chama Atlas."
}
```
### Test 2 — Retrieval in another session
Use another `session_id`, keeping the same `customer_key`:
```json
{
"session_id": "default:telecom_contas:memory-session-b",
"customer_key": "11999999999",
"message": "O que você lembra sobre mim, minhas preferências e meu projeto?"
}
```
Expected result:
```text
Seu nome preferido é Cris.
Sua linguagem preferida é Python.
Seu projeto atual se chama Atlas.
```
### Test 3 — Isolation
Use another customer:
```json
{
"session_id": "default:telecom_contas:memory-session-c",
"customer_key": "outro-cliente",
"message": "Qual é meu nome preferido e qual é meu projeto atual?"
}
```
The data for `11999999999` must not appear.
### Test 4 — Frontend restart
Restart or reset the frontend and confirm that it continues sending the same `customer_key`.
Memory must survive the `session_id` change. Resetting the frontend does not erase SQLite.
### Test 5 — Backend restart
Restart Uvicorn and repeat the query.
With:
```env
LONG_TERM_MEMORY_PROVIDER=sqlite
```
memory must remain available.
With:
```env
LONG_TERM_MEMORY_PROVIDER=memory
```
memory will be lost when the process terminates.
### Direct verification in SQLite
Locate the database:
```bash
find . -name "agent_framework.db" -type f
```
Open it:
```bash
sqlite3 ./data/agent_framework.db
```
Query it:
```sql
SELECT
tenant_id,
agent_id,
customer_key,
memory_type,
memory_key,
memory_value,
confidence,
created_at,
updated_at
FROM agentfw_long_term_memory
ORDER BY updated_at DESC;
```
### Success criteria
The implementation is working when:
- memory is retrieved with another `session_id`;
- the same `customer_key` retrieves previous facts;
- another `customer_key` cannot access those facts;
- restarting the frontend does not erase memory;
- restarting the backend does not erase memory when the provider is SQLite;
- the `persist_long_term_memory` node executes;
- the prompt receives `long_term_memory_context`.
### Best practices
- Persist only durable facts.
- Do not store the full conversation as Long-Term Memory.
- Isolate data by `tenant_id`, `agent_id`, and `customer_key`.
- Do not use `session_id` as the user's permanent identity.
- Persist only after final validations.
- Avoid storing temporary tool results.
- Record read, write, update, and failure telemetry.
- Define retention and deletion policies.
- Use an absolute SQLite path in environments with multiple execution directories.
- Migrate to an enterprise database for production and high-availability environments.
### Reference-implementation limitations
The current implementation uses rule-based extraction and SQLite as the reference provider.
Recommended evolutions:
- fact extraction with LLM;
- semantic memory with vectors;
- episodic memory;
- expiration and versioning;
- semantic deduplication;
- consent policy;
- query and deletion API;
- Oracle Autonomous Database provider;
- encryption and sensitive-data classification.
### Enterprise Checkpointing in LangGraph
> Content consolidated from `Documentacao/README_CHECKPOINT_ENTERPRISE.md`.
This version adds four capabilities to the LangGraph checkpointer used by the framework:
1. **Checkpoint Integrity**: each checkpoint is stored inside an envelope containing `schema_version`, `checkpoint_id`, SHA-256 `payload_hash`, and `created_at`. On read, the hash is recalculated. If the payload was truncated, changed, or corrupted, the checkpoint is ignored during recovery.
2. **Checkpoint Compaction**: old checkpoints are automatically removed according to `CHECKPOINT_COMPACT_EVERY` and `CHECKPOINT_KEEP_LAST`. This prevents unbounded growth of the `workflow_checkpoints` table.
3. **Resilient Checkpointer**: writes and reads use retry with backoff and jitter. The resilient layer works over memory, SQLite, and Oracle/Autonomous Database.
4. **Checkpoint Recovery**: when restoring state, the framework scans recent checkpoints and returns the newest valid one, skipping corrupted checkpoints.
### Configuration
In `.env`:
```env
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
ENABLE_RESILIENT_CHECKPOINTER=true
ENABLE_CHECKPOINT_INTEGRITY=true
ENABLE_CHECKPOINT_COMPACTION=true
CHECKPOINT_COMPACT_EVERY=50
CHECKPOINT_KEEP_LAST=20
CHECKPOINT_RECOVERY_SCAN_LIMIT=25
CHECKPOINT_RETRY_MAX_ATTEMPTS=3
CHECKPOINT_RETRY_BASE_DELAY_SECONDS=0.05
CHECKPOINT_RETRY_MAX_DELAY_SECONDS=1.0
CHECKPOINT_RETRY_JITTER_SECONDS=0.05
```
For production with multiple pods, prefer:
```env
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
ADB_USER=...
ADB_PASSWORD=...
ADB_DSN=...
ADB_WALLET_LOCATION=...
ADB_TABLE_PREFIX=AGENTFW
```
### Use in LangGraph
```python
from agent_framework.checkpoints import create_langgraph_checkpointer
checkpointer = create_langgraph_checkpointer(settings)
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": session_id}}
result = graph.invoke(input_state, config=config)
```
`thread_id` remains the conversation-recovery key. In an environment with a Load Balancer, any pod can resume execution if it uses the same persistent repository.
### Files changed
- `agent_framework/src/agent_framework/checkpoints/checkpoint_repository.py`
- `agent_framework/src/agent_framework/checkpoints/langgraph_saver.py`
- `agent_framework/src/agent_framework/checkpoints/__init__.py`
- `agent_framework/src/agent_framework/config/settings.py`
- `tests/unit/test_resilient_checkpointer.py`
### Important note
The `memory` provider now also uses `RepositoryCheckpointSaver` when `ENABLE_RESILIENT_CHECKPOINTER=true`. To return to LangGraph's pure `MemorySaver` for local tests, configure:
```env
ENABLE_RESILIENT_CHECKPOINTER=false
CHECKPOINT_REPOSITORY_PROVIDER=memory
```
### Source files
The files below were consolidated into this manual:
- `Documentacao/Manual_Long_Term_Memory_PT.md`
- `Documentacao/README_CHECKPOINT_ENTERPRISE.md`
### Maintenance rule
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.

View File

@@ -0,0 +1,117 @@
### LLM Rich Response and reasoning_content
### How to use this manual
This is a **specialized reference manual**. It does not replace the main tutorial.
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
- Use this document when you need to implement, deepen, or diagnose **`ainvoke_response()`, inference metadata, and optional `reasoning_content`**.
- Historical examples consolidated here should be read in light of the framework's current API.
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
### Relationship with the main tutorial
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
### Scope
`ainvoke_response()`, inference metadata, and optional `reasoning_content`.
### Consolidated technical content
### LLM Rich Response and reasoning_content
Guide for using the opt-in structured LLM response API without breaking the legacy `ainvoke()` contract, including `reasoning_content`, usage, model, provider, fallback, and tests.
### How to use this document
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
### Rich LLM response API
> Content consolidated from `docs/LLM_RICH_RESPONSE.md`.
### Goal
The framework keeps `ainvoke()` as the backward-compatible API, returning only `str`, and adds `ainvoke_response()` for consumers that need additional inference metadata, including `reasoning_content` when the model/provider/API makes it available.
### APIs
### Legacy API — unchanged
```python
answer = await llm.ainvoke(messages)
assert isinstance(answer, str)
```
No existing agent needs to be changed.
### New rich API — opt-in
```python
response = await llm.ainvoke_response(messages)
answer = response.content
reasoning = response.reasoning_content
usage = response.usage
model = response.model
provider = response.provider
```
`reasoning_content` is `str | None`. `None` is the expected behavior when the model, provider, or API does not expose textual reasoning.
### Backoffice
A consumer that previously did:
```python
answer = await llm.ainvoke(messages)
template = extract_response(answer)
```
can instead do:
```python
response = await llm.ainvoke_response(messages)
template = extract_response(response.content)
reasoning_content = response.reasoning_content
```
Logic that expects text continues to receive `response.content`; reasoning remains separate and does not contaminate response, cache, memory, judges, or guardrails.
### Custom-provider compatibility
`LLMProvider.ainvoke_response()` has a fallback. An external provider that implements only `ainvoke()` continues to work and automatically receives `LLMResponse(content=<texto>)`, with `reasoning_content=None`.
Native providers (`mock`, OpenAI-compatible/OCI OpenAI, and OCI SDK) implement the rich response and attempt to preserve reasoning when present.
### Compatibility guarantees
- `ainvoke()` continues to return `str`.
- No existing router, judge, RAG, memory, cache, or runtime has been migrated to the new API.
- `reasoning_content` is never fabricated by the framework.
- Missing reasoning does not generate an error.
- Existing telemetry output continues to be the final content, without automatically appending reasoning.
### Tests
Specific tests are in `tests/unit/test_llm_rich_response.py` and verify:
1. a legacy provider that implements only `ainvoke()`;
2. preservation of the `str` return from `ainvoke()`;
3. `LLMResponse` return from `ainvoke_response()`;
4. reasoning through a direct attribute;
5. reasoning through `model_extra`;
6. missing reasoning and extraction in OCI SDK format.
### Source files
The files below were consolidated into this manual:
- `docs/LLM_RICH_RESPONSE.md`
### Maintenance rule
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.

View File

@@ -0,0 +1,360 @@
### Performance, Cache, and Async Runtime
### How to use this manual
This is a **specialized reference manual**. It does not replace the main tutorial.
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
- Use this document when you need to implement, deepen, or diagnose **concurrency, cache, reduction of LLM calls, and cross-loop fixes**.
- Historical examples consolidated here should be read in light of the framework's current API.
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
### Relationship with the main tutorial
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
### Scope
Concurrency, cache, reduction of LLM calls, and cross-loop fixes.
### Consolidated technical content
### Performance, Cache, Concurrency, and Async Runtime
Manual for optimizations on the critical MCP, RAG, and Judge path, reduction of LLM calls, deterministic preemption, and cross-loop deadlock correction in sequencing.
### How to use this document
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
### MCP, RAG, and Judge optimizations
> Content consolidated from `docs/PERFORMANCE_OPTIMIZATIONS_MCP_JUDGES_RAG.md`.
- `mcp_tools` remains an allowlist; only the query selected through `selection_keywords` is executed.
- `strategy: hybrid` extraction tries a regex `pattern` before the LLM profile.
- RAG is skipped when successful MCP evidence is sufficient, except for policy/rule questions.
- `mcp_results` is provided as evidence to the groundedness judge.
- `judges.yaml` accepts `sample_rate` and `always_run_for_transactional`.
- Simple structured queries can return a deterministic response without invoking the agent LLM.
### Shift from query to transactional action
Route stickiness is preempted when an explicit keyword configured in `routing.yaml` identifies another intent/agent. Thus, a session in `retail_order_tracking` moves to `retail_support_exchange_return` when it receives requests such as “return order”. In addition, direct responses from read-only tools are blocked when the message contains `selection_keywords` from any registered transactional tool.
Action words remain in `config/tools.yaml`; the runtime does not maintain hardcoded domain aliases.
### Deterministic preemption for an explicit intent change
Stickiness does not call a second LLM when the message contains an explicit change that can be recognized deterministically. Multi-token keywords configured in `routing.yaml` accept up to three intermediate tokens while preserving order. Therefore, `cancelar pedido` recognizes `quero cancelar meu pedido`, `cancelar o meu pedido`, and `pode cancelar esse pedido`. In this case the new intent preempts stickiness and the `keyword_match_strategy=ordered_tokens` metadata makes the decision auditable. Messages with no explicit signal continue using route stickiness normally.
### Cross-loop deadlock fix
> Content consolidated from `Documentacao/FIX_DEADLOCK_SEQUENCE_CROSS_LOOP.md`.
### Problem
The synchronous `agent_framework.observer.event()` API could be called from a worker thread with no active event loop. In that case, the previous implementation ran `asyncio.run(aevent(...))`, creating a temporary new event loop. At the same time, `analytics/tim_sequence.py` shared global `asyncio.Lock` instances (`_mongo_index_lock` and `_memory_lock`) across calls that could come from different event loops.
On the first Mongo operation, `_ensure_mongo_ttl_index_once()` held `_mongo_index_lock` while creating the TTL index. Contention from another loop could leave the second call waiting indefinitely.
### Applied changes
1. `observer.py`
- removed `asyncio.run()` from the synchronous `event()` path;
- added a dedicated reusable event loop for synchronous calls;
- cross-thread submission uses `asyncio.run_coroutine_threadsafe()`;
- best-effort loop shutdown when the process terminates.
2. `analytics/tim_sequence.py`
- `_mongo_index_lock`: `asyncio.Lock` -> `threading.Lock`;
- `_memory_lock`: `asyncio.Lock` -> `threading.Lock`;
- TTL-index initialization moved to a synchronous function protected by a thread lock and called through `asyncio.to_thread()`;
- the in-memory fallback counter uses a short thread-safe critical section.
3. Tests
- `tests/test_observer_cross_loop_deadlock_fix.py` validates:
- multiple worker threads using `event()` share the same synchronous observer loop;
- in-memory sequence remains monotonic across independent event loops;
- TTL-index creation happens only once under cross-loop contention.
### Validation performed
```bash
PYTHONPATH=libs/agent_framework/src pytest -q tests/test_observer_cross_loop_deadlock_fix.py
```
Result: `3 passed`.
The full repository suite has pre-existing/independent failures unrelated to this change, including collection conflicts for `test_long_term_memory.py`, static template paths, and checkpoint/workflow tests. Those items were not changed by this fix.
### Operational performance features
> Content consolidated from `Documentacao/README_MAX_OPERACIONAL.md`.
This version adds the operational adjustments that were missing to bring the framework closer to the FIRST production standard.
### Adjustments included in this version
### 1. Langfuse Enterprise Adapter
New module:
```text
agent_framework/observability/langfuse_enterprise.py
```
Includes an adapter compatible with Langfuse SDKs v2/v3 for:
- trace updates;
- trace scoring/evaluation;
- prompt registry when supported by the SDK;
- isolation of Langfuse API differences.
### 2. Persistent Token and Cost Accounting
New package:
```text
agent_framework/billing/
```
Includes:
- `UsageRecord`
- `SQLiteUsageRepository`
- `OracleUsageRepository`
- `create_usage_repository(settings)`
The LLM provider now records automatically:
- `prompt_tokens`
- `completion_tokens`
- `cached_tokens`
- `total_tokens`
- `cost_usd`
- `cost_brl`
- `tenant_id`
- `agent_id`
- `session_id`
- `message_id`
New endpoint:
```http
GET /debug/usage
GET /debug/usage?tenant_id=default
GET /debug/usage?session_id=<id>
```
### 3. Operational RAG Service
New module:
```text
agent_framework/rag/rag_service.py
```
Includes:
- `RagService.add_documents()`
- `RagService.retrieve()`
- `RagResult.as_prompt_context()`
- telemetry for latency, document count, top scores, and graph.
### 4. New configuration
Variable added:
```env
USAGE_REPOSITORY_PROVIDER=sqlite
```
Values:
```text
sqlite
oracle
autonomous
```
### 5. Local operational compatibility
By default, usage accounting uses SQLite even when everything else is in memory. This makes local testing possible without Oracle.
### Quick test
```bash
cd agent_template_backend
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Test a message:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"text":"teste","user_id":"u1","session_id":"s1"}}'
```
Check usage/cost:
```bash
curl http://localhost:8000/debug/usage
```
### To run closer to a production pattern
```env
SESSION_REPOSITORY_PROVIDER=sqlite
MEMORY_REPOSITORY_PROVIDER=sqlite
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
USAGE_REPOSITORY_PROVIDER=sqlite
CACHE_BACKEND_PROVIDER=sqlite
VECTOR_STORE_PROVIDER=sqlite
ENABLE_LANGFUSE=true
LANGFUSE_HOST=http://localhost:3000
LANGFUSE_PUBLIC_KEY=...
LANGFUSE_SECRET_KEY=...
```
For Autonomous Database:
```env
SESSION_REPOSITORY_PROVIDER=oracle
MEMORY_REPOSITORY_PROVIDER=oracle
CHECKPOINT_REPOSITORY_PROVIDER=oracle
USAGE_REPOSITORY_PROVIDER=oracle
CACHE_BACKEND_PROVIDER=oracle
VECTOR_STORE_PROVIDER=oracle
GRAPH_STORE_PROVIDER=oracle
ADB_USER=...
ADB_PASSWORD=...
ADB_DSN=...
ADB_WALLET_LOCATION=...
ADB_TABLE_PREFIX=AGENTFW
```
### Final cache, RAG, and telemetry adjustments
> Content consolidated from `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`.
This version fixes the gaps identified in the comparison against FIRST.
### Applied fixes
### 1. Operational LangGraph checkpoint
The workflow no longer compiles directly with `MemorySaver()`. The following adapter was created:
```text
agent_framework/checkpoints/langgraph_saver.py
```
It connects LangGraph to the framework's configured repository:
- `memory`
- `sqlite`
- `oracle` / `autonomous`
In the workflow:
```python
builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
```
### 2. LangGraph telemetry wrapping actual execution
A node wrapper was added to the workflow:
```python
self._node("billing_agent", self.billing_agent)
```
This way the `langgraph.node.*` span/event wraps actual node execution, not just an empty block.
Events emitted:
- `langgraph.node.started`
- `langgraph.node.completed`
- `langgraph.node.failed`
- `langgraph.edge.selected`
### 3. RAG integrated into agents
Agents now receive `RagService` and use retrieved context in the prompt:
- BillingAgent
- ProductAgent
- OrdersAgent
- SupportAgent
RAG uses:
- `VECTOR_STORE_PROVIDER=memory|sqlite|oracle|autonomous`
- `GRAPH_STORE_PROVIDER=memory|oracle|autonomous`
- `RAG_TOP_K`
### 4. Cache integrated into agent runtime
The following mixin was created:
```text
agent_template_backend/app/agents/runtime.py
```
It adds:
- standardized RAG retrieval;
- cache key for LLM calls;
- hit/miss with telemetry;
- distributed cache through `create_cache(settings)`.
### 5. Unit tests
The following directory was created:
```text
tests/unit
```
Initial coverage:
- cache;
- SSE;
- RAG;
- checkpoint saver;
- LangGraph telemetry;
- agent runtime;
- static workflow verification;
- main imports.
Local validation performed:
```text
12 passed
```
### How to test
```bash
cd projeto_agent_framework_first_ready
pip install -r agent_template_backend/requirements.txt
pytest -q tests/unit
```
### Source files
The files below were consolidated into this manual:
- `docs/PERFORMANCE_OPTIMIZATIONS_MCP_JUDGES_RAG.md`
- `Documentacao/FIX_DEADLOCK_SEQUENCE_CROSS_LOOP.md`
- `Documentacao/README_MAX_OPERACIONAL.md`
- `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`
### Maintenance rule
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.

View File

@@ -0,0 +1,689 @@
### Observability, Persistence, and Operational Readiness
### How to use this manual
This is a **specialized reference manual**. It does not replace the main tutorial.
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
- Use this document when you need to implement, deepen, or diagnose **telemetry, IC/NOC/GRL, correlation, sequencing, persistence, and operational diagnostics**.
- Historical examples consolidated here should be read in light of the framework's current API.
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
### Relationship with the main tutorial
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
### Scope
Telemetry, IC/NOC/GRL, correlation, sequencing, persistence, and operational diagnostics.
### Consolidated technical content
### Observability, Persistence, and Operational Readiness
Consolidated guide to FIRST-ready capabilities: end-to-end correlation, Langfuse, OpenTelemetry, observable SSE, Oracle persistence, token/cost accounting, cache, and LangGraph telemetry.
### How to use this document
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
### FIRST-ready foundation and observability
> Content consolidated from `Documentacao/README_FIRST_READY.md`.
This version preserves the `meu_projeto_agent_framework` architecture and adds the operational patterns found in the FIRST project.
### Added features
1. **SSE following the FIRST pattern**
- `GET /gateway/events/{session_id}` for a `text/event-stream`.
- `POST /gateway/message/sse` to process a message while emitting SSE events.
- Events: `connected`, `flow.start`, `session.upserted`, `message.received`, `workflow.started`, `workflow.completed`, `message.responded`, `flow.end`.
- Keepalive configurable through `SSE_KEEPALIVE_SECONDS`.
- Per-session lock to prevent concurrency within the same conversation.
- Event replay through `Last-Event-ID` or the `last_event_id` query parameter.
2. **Session and message persistence**
- Implemented `sqlite` provider, runnable locally.
- `SESSION_REPOSITORY_PROVIDER=sqlite`.
- `MEMORY_REPOSITORY_PROVIDER=sqlite`.
- Local tables: `agent_sessions`, `agent_messages`.
- Idempotency by `message_id`.
3. **Persistent checkpoint**
- Implemented `sqlite` provider for the workflow's final checkpoint.
- `CHECKPOINT_REPOSITORY_PROVIDER=sqlite`.
- Read endpoint: `GET /sessions/{session_id}/checkpoint`.
4. **Message history**
- Endpoint: `GET /sessions/{session_id}/messages`.
- History is used as conversational memory before invoking LangGraph.
5. **Cache**
- New module `agent_framework.cache.cache`.
- Supports local in-memory cache and Redis when `ENABLE_REDIS_CACHE=true`.
6. **RAG / Vector Store**
- `agent_framework.rag.vector_store` now includes `InMemoryVectorStore`, `SQLiteVectorStore`, and the `AutonomousVectorStore` contract.
- The SQLite version uses local lexical search for development.
- The contract allows replacement by Oracle Vector Search without changing the application layer.
7. **Observability**
- Preserves existing Langfuse integration.
- Adds gateway/SSE/workflow events with `session_id`, `agent_id`, `tenant_id`, `message_id`, route, and intent.
### Resulting architecture
```text
Browser
|-- POST /gateway/message/sse
|-- GET /gateway/events/{session_id}
|
FastAPI Template Backend
|
ChannelGateway
|
SessionRepository + MessageHistory + CheckpointRepository
|
LangGraph AgentWorkflow
|
Guardrails -> Router/Supervisor -> Agent -> Output Guardrails -> Judges
|
Telemetry / Langfuse / OCI Streaming
```
### How to run locally
```bash
cd agent_template_backend
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e ../agent_framework
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Frontend:
```bash
cd agent_frontend
python -m http.server 3000
```
Open:
```text
http://localhost:3000
```
### Main variables
```env
SESSION_REPOSITORY_PROVIDER=sqlite
MEMORY_REPOSITORY_PROVIDER=sqlite
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
VECTOR_STORE_PROVIDER=sqlite
SQLITE_DB_PATH=./data/agent_framework.db
ENABLE_SSE=true
SSE_KEEPALIVE_SECONDS=15
ENABLE_MESSAGE_IDEMPOTENCY=true
```
### Test with curl
Normal message:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"text":"teste","message":"teste","session_id":"s1","user_id":"u1","message_id":"m1"}}'
```
Message with SSE:
```bash
curl -N http://localhost:8000/gateway/events/s1
```
In another terminal:
```bash
curl -X POST http://localhost:8000/gateway/message/sse \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"text":"teste","message":"teste","session_id":"s1","user_id":"u1","message_id":"m2"}}'
```
History:
```bash
curl http://localhost:8000/sessions/s1/messages
```
Checkpoint:
```bash
curl http://localhost:8000/sessions/s1/checkpoint
```
### Important note
The added version is locally executable with SQLite. The `AutonomousSessionRepository`, `DatabaseMessageHistory`, `AutonomousCheckpointRepository`, and `AutonomousVectorStore` classes preserve the Oracle Autonomous Database contract, but in this delivery they use SQLite as the local backend so the project can run and be tested without Oracle infrastructure.
### FIRST-style Observability evolution
This version adds an enterprise observability layer to the framework while keeping reusable components inside `agent_framework`.
### Added components
```text
agent_framework/observability/
├── context.py # ContextVar: request_id, session_id, user_id, tenant_id, agent_id, channel, ura_call_id, workflow_id, message_id
├── telemetry.py # Facade central: span, event, generation, rag_event, cache_event, checkpoint_event
├── event_bus.py # Event bus interno para plugar logs, SSE, OCI Streaming, Elastic, Phoenix etc.
├── otel.py # OpenTelemetry opcional via OTLP
├── workflow_events.py # workflow.started, node.started, node.completed, edge.selected, workflow.failed
├── guardrail_events.py # guardrail.<CODE>.evaluated e guardrail.<CODE>.blocked
├── judge_events.py # judge.<NAME>.evaluated
├── streaming_events.py # sse.connected, sse.keepalive, sse.event.emitted
└── decorators.py # decorator @traced para classes do framework
```
### End-to-end correlation
Each HTTP call creates or propagates `x-request-id`, and the message flow links:
```text
request_id → tenant_id → agent_id → session_id → user_id → channel → message_id → workflow_id
```
The context uses `ContextVar`, so it works across async calls, FastAPI, LangGraph, and LLM providers.
### Langfuse
Enable in `.env`:
```env
ENABLE_LANGFUSE=true
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=http://localhost:3000
```
The framework records:
```text
Trace de conversa
├── http.request
├── agent.gateway_message
├── workflow.langgraph.ainvoke
├── workflow.input_guardrails
│ └── guardrail.<CODE>.evaluated / blocked
├── workflow.routing_decision
├── workflow.agent.<agent>
│ └── generation.<model>
├── workflow.output_guardrails
├── workflow.judge
│ └── judge.<NAME>.evaluated
├── workflow.supervisor_review
├── workflow.persist
└── sse.event.emitted / sse.keepalive
```
### OpenTelemetry
Enable in `.env`:
```env
ENABLE_OTEL=true
OTEL_SERVICE_NAME=agent-framework-template
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
```
With this configuration, the same spans are exported through OTLP to Elastic, Grafana Tempo, Jaeger, Collector, or another compatible backend.
### Observable SSE
`SSEHub` now records events for:
- opened connection;
- event replay;
- emitted event;
- keepalive;
- per-session lock during message processing.
### Guardrails and Judges
In addition to aggregate events (`guardrails.input.completed`, `judges.completed`), each individual decision generates its own telemetry:
```text
guardrail.MSK.evaluated
guardrail.OOS.blocked
judge.response_quality.evaluated
judge.groundedness.evaluated
```
### Extension to other backends
The `Telemetry.event_bus` class allows new handlers to be plugged in without changing the workflow. Example:
```python
async def enviar_para_elastic(event):
...
telemetry.event_bus.subscribe(enviar_para_elastic)
```
---
### Complete FIRST Enterprise evolution
This version received the components that were missing to bring the framework closer to the operational standard of the FIRST project:
### Oracle Autonomous Database persistence
Real Oracle providers were added:
- `OracleSessionRepository`
- `OracleMessageHistory`
- `OracleCheckpointRepository`
- `OracleCache`
- `OracleVectorStore`
- `OracleGraphStore`
- `OracleStore`
Tables are created automatically with configurable `ADB_TABLE_PREFIX`:
- `<PREFIX>_AGENT_SESSION`
- `<PREFIX>_AGENT_MESSAGE`
- `<PREFIX>_WORKFLOW_CHECKPOINT`
- `<PREFIX>_WORKFLOW_CHECKPOINT_WRITE`
- `<PREFIX>_WORKFLOW_CHECKPOINT_BLOB`
- `<PREFIX>_SSE_EVENT`
- `<PREFIX>_CACHE_ENTRY`
- `<PREFIX>_RAG_DOCUMENT`
- `<PREFIX>_GRAPH_EDGE`
### Oracle configuration
```env
SESSION_REPOSITORY_PROVIDER=oracle
MEMORY_REPOSITORY_PROVIDER=oracle
CHECKPOINT_REPOSITORY_PROVIDER=oracle
CACHE_BACKEND_PROVIDER=oracle
VECTOR_STORE_PROVIDER=oracle
GRAPH_STORE_PROVIDER=oracle
SSE_STORE_PROVIDER=oracle
ADB_USER=ADMIN
ADB_PASSWORD=***
ADB_DSN=meu_adb_high
ADB_WALLET_LOCATION=/path/wallet
ADB_WALLET_PASSWORD=***
ADB_TABLE_PREFIX=AGENTFW
```
### Enterprise SSE
SSE now includes:
- per-session lock (`SessionLockManager`)
- configurable keepalive
- replay through `Last-Event-ID`
- event persistence in SQLite or Oracle
- connection, replay, keepalive, and disconnection telemetry
Endpoint:
```text
GET /gateway/events/{session_id}?last_event_id=123
```
### LangGraph Deep Telemetry
`LangGraphDeepTelemetry` was added with events:
- `langgraph.node.started`
- `langgraph.node.completed`
- `langgraph.node.failed`
- `langgraph.edge.selected`
These events are sent to Event Bus, Langfuse, and OpenTelemetry when enabled.
### Token and Cost Accounting
The following were added:
- `TokenUsageCollector`
- `CostTracker`
- calculation of `prompt_tokens`, `completion_tokens`, `cached_tokens`, `total_tokens`
- calculation of `cost_usd` and `cost_brl`
Optional configuration:
```env
USD_BRL_RATE=5.0
MODEL_PRICES_JSON={"openai.gpt-4.1":{"input_per_1m":"2.00","output_per_1m":"8.00"}}
```
### Enterprise Cache
Cache is now layered:
```text
L1: InMemory
L2: Redis, SQLite ou Oracle
```
Configuration:
```env
ENABLE_REDIS_CACHE=true
REDIS_URL=redis://localhost:6379/0
```
or:
```env
CACHE_BACKEND_PROVIDER=oracle
```
### Oracle 23ai RAG
`OracleVectorStore` was added, with support for a `VECTOR` column and `VECTOR_DISTANCE()` when an embedding provider is connected.
Without an embedding provider, it keeps a lexical fallback for local development.
`OracleGraphStore` was also added with an edge table, ready to evolve to PGQL/Property Graph.
### Langfuse
Each LLM call now generates a `generation` with:
- input
- output
- model
- provider
- token usage
- cost metadata
In addition, workflow, guardrail, judge, RAG, cache, checkpoint, SSE, and LangGraph spans are published through the same Event Bus.
### Enterprise Plus extensions
> Content consolidated from `Documentacao/README_FIRST_ENTERPRISE_PLUS.md`.
This version evolves the framework in the four requested areas:
1. **Complete Langfuse Enterprise**
- `Telemetry.span()` with trace/session/user/metadata/tags.
- `Telemetry.generation()` with `usage`, token/cost metadata, and Langfuse v2/v3 compatibility.
- `Telemetry.score()` for judges/evaluations.
- Arbitrary events are recorded as safe spans to avoid `Unknown observation type` in Langfuse.
2. **Complete Token/Cost Accounting**
- `TokenUsageCollector` supports `prompt_tokens`, `completion_tokens`, `cached_tokens`, `reasoning_tokens`, and `total_tokens`.
- Per-model pricing table through `MODEL_PRICES_JSON`.
- USD→BRL conversion through `USD_BRL_RATE`.
- Persistence in `UsageRepository` and `/debug/usage` endpoint.
3. **Distributed Redis**
- `DistributedCache`: L1 memory + L2 Redis/SQLite/Oracle.
- `RedisCache` with `redis.asyncio` when available and sync fallback.
- Namespace through `CACHE_KEY_PREFIX`.
- Cache hit/miss/set/delete telemetry.
4. **Real Oracle Vector + PGQL**
- `OracleVectorStore` uses `VECTOR_DISTANCE(..., COSINE)` and `TO_VECTOR()` in Oracle 23ai.
- Automatically attempts to create a vector index when supported.
- `OracleGraphStore` uses `GRAPH_NODE` and `GRAPH_EDGE` tables.
- Supports Property Graph creation and `GRAPH_TABLE`/PGQL queries, with SQL fallback.
The SSE duplication problem caused by replay + live queue was also fixed using `max_replayed_id` control in `SSEHub.subscribe()`.
### Tests
```bash
PYTHONPATH=agent_framework/src pytest -q tests/unit
```
Result validated in this generation:
```text
17 passed
```
### Security
The `.env` files were sanitized so they do not contain real keys. Configure your credentials locally before using OCI/Langfuse.
### Delta to FIRST standard
> Content consolidated from `Documentacao/README_FIRST_ENTERPRISE_DELTA.md`.
This version fixes the priorities identified in the comparison with FIRST:
1. Real Oracle Session Repository
2. Real Oracle Message History
3. Real Oracle LangGraph Checkpoint Repository
4. LangGraph Deep Telemetry
5. Token Accounting
6. Cost Accounting
7. SSE Session Lock
8. SSE Replay Buffer
9. SSE KeepAlive
10. Recovery through Last-Event-ID
11. Redis Provider and Distributed Cache
12. Oracle Vector Provider
13. Oracle Graph Provider
14. RAG Telemetry
15. Langfuse Generation Tracking
16. Compatible OpenTelemetry/Event Bus
17. Preserved OCI Streaming Exporter
Domain logic remains generic; the framework does not copy FIRST-specific billing rules.
### Maximum operations and accounting
> Content consolidated from `Documentacao/README_MAX_OPERACIONAL.md`.
This version adds the operational adjustments that were missing to bring the framework closer to the FIRST production standard.
### Adjustments included in this version
### 1. Langfuse Enterprise Adapter
New module:
```text
agent_framework/observability/langfuse_enterprise.py
```
Includes an adapter compatible with Langfuse SDKs v2/v3 for:
- trace updates;
- trace scoring/evaluation;
- prompt registry when supported by the SDK;
- isolation of Langfuse API differences.
### 2. Persistent Token and Cost Accounting
New package:
```text
agent_framework/billing/
```
Includes:
- `UsageRecord`
- `SQLiteUsageRepository`
- `OracleUsageRepository`
- `create_usage_repository(settings)`
The LLM provider now records automatically:
- `prompt_tokens`
- `completion_tokens`
- `cached_tokens`
- `total_tokens`
- `cost_usd`
- `cost_brl`
- `tenant_id`
- `agent_id`
- `session_id`
- `message_id`
New endpoint:
```http
GET /debug/usage
GET /debug/usage?tenant_id=default
GET /debug/usage?session_id=<id>
```
### 3. Operational RAG Service
New module:
```text
agent_framework/rag/rag_service.py
```
Includes:
- `RagService.add_documents()`
- `RagService.retrieve()`
- `RagResult.as_prompt_context()`
- telemetry for latency, document count, top scores, and graph.
### 4. New configuration
Variable added:
```env
USAGE_REPOSITORY_PROVIDER=sqlite
```
Values:
```text
sqlite
oracle
autonomous
```
### 5. Local operational compatibility
By default, usage accounting uses SQLite even when everything else is in memory. This makes it possible to test locally without Oracle.
### Quick test
```bash
cd agent_template_backend
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Test a message:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"text":"teste","user_id":"u1","session_id":"s1"}}'
```
Check usage/cost:
```bash
curl http://localhost:8000/debug/usage
```
### To run closer to a production pattern
```env
SESSION_REPOSITORY_PROVIDER=sqlite
MEMORY_REPOSITORY_PROVIDER=sqlite
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
USAGE_REPOSITORY_PROVIDER=sqlite
CACHE_BACKEND_PROVIDER=sqlite
VECTOR_STORE_PROVIDER=sqlite
ENABLE_LANGFUSE=true
LANGFUSE_HOST=http://localhost:3000
LANGFUSE_PUBLIC_KEY=...
LANGFUSE_SECRET_KEY=...
```
For Autonomous Database:
```env
SESSION_REPOSITORY_PROVIDER=oracle
MEMORY_REPOSITORY_PROVIDER=oracle
CHECKPOINT_REPOSITORY_PROVIDER=oracle
USAGE_REPOSITORY_PROVIDER=oracle
CACHE_BACKEND_PROVIDER=oracle
VECTOR_STORE_PROVIDER=oracle
GRAPH_STORE_PROVIDER=oracle
ADB_USER=...
ADB_PASSWORD=...
ADB_DSN=...
ADB_WALLET_LOCATION=...
ADB_TABLE_PREFIX=AGENTFW
```
### Complementary supervisor validation
> Content consolidated from `docs/docs_GLOBAL_SUPERVISOR_VALIDATION.txt`.
VALIDATION - GLOBAL SUPERVISOR
Implemented changes:
1. Framework
- agent_framework.global_supervisor.models
- agent_framework.global_supervisor.config
- agent_framework.global_supervisor.session_store
- agent_framework.global_supervisor.router
- agent_framework.global_supervisor.client
2. New service
- agent_gateway/app/main.py
- agent_gateway/app/settings.py
- agent_gateway/config/backends.yaml
- agent_gateway/README.md
- agent_gateway/Dockerfile
- agent_gateway/docs/ARQUITETURA_GLOBAL_SUPERVISOR.md
3. Docker Compose
- agent-gateway service added on port 8010.
Validations performed:
- python3 -m compileall -q agent_framework/src/agent_framework/global_supervisor agent_gateway/app
Result: OK
- Hybrid-routing smoke test:
Input 1: "My bill is too high" -> billing
Input 2: "and this amount?" on the same session_id -> billing via active_backend
Result: OK
- FastAPI app import smoke test:
from app.main import app, registry, router
Result: OK
Note:
- The gateway SSE proxy was left as a future step. The `/gateway/message/sse` endpoint already routes and forwards as a normal message; for end-to-end SSE, a proxy from `/gateway/events/{session_id}` to the active backend can be implemented.
### Source files
The files below were consolidated into this manual:
- `Documentacao/README_FIRST_READY.md`
- `Documentacao/README_FIRST_ENTERPRISE_PLUS.md`
- `Documentacao/README_FIRST_ENTERPRISE_DELTA.md`
- `Documentacao/README_MAX_OPERACIONAL.md`
- `docs/docs_GLOBAL_SUPERVISOR_VALIDATION.txt`
### Maintenance rule
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.

View File

@@ -0,0 +1,157 @@
# 12 — Input Guardrail Feedback and Blocked-Turn Semantics
## Goal
This document describes how `AgentWorkflow`, implemented in `app/workflows/agent_graph.py`, should handle a turn interrupted by an input guardrail without turning every interruption into a generic “security rule” message.
The core rule is to keep three concerns separate:
1. **the guardrail technical decision**, used by the runtime and observability;
2. **the user-facing message**, appropriate to the type of block or clarification need;
3. **the turn state**, which must not carry routing, tool, or judge data from a turn that was interrupted before those stages.
## Expected flow
```text
user message
input_guardrails
allowed?
├─ yes → routing → tools/agent → composition → output_guardrails
└─ no
select public handling
clear routing/tools/judges state for this turn
build a safe user-facing message
output_guardrails
persistence/response
```
A blocking input guardrail must be decided **before any side-effecting tool is executed**.
## Internal `reason` is not the user response
The `reason` field should remain available to logs, traces, events, and diagnostics. It should not be exposed verbatim when it may reveal internal mechanisms or when the technical wording is not appropriate for the end user.
Example:
```text
COER.reason = "utterance is incomprehensible or contains an ambiguous negation"
```
A public response may be:
```text
"I could not fully understand your last message because it seems incomplete or ambiguous. Could you rephrase or complete what you meant?"
```
## Handling by guardrail type
Exact behavior remains configurable, but the expected semantics are:
| Guardrail | Recommended public handling |
|---|---|
| `COER` | ask for clarification/rephrasing; do not frame ordinary ambiguity as a security incident |
| `PINJ` | block safely without describing the internal mechanism |
| `DLEX_IN` | block or request reformulation without exposing internal/sensitive data |
| `INPUT_SIZE` | ask the user to reduce the input |
| `TOX` | apply the configured policy for inappropriate content |
| `CMP` | respond according to the compliance policy |
| unknown | use a safe generic fallback |
## Clearing blocked-turn state
When input is blocked before routing, the final state for that turn must not reuse residual data from the previous turn.
At a minimum, the workflow should avoid presenting these as current:
```text
route_decision
mcp_tools
mcp_results
judge_results
```
Metadata should clearly indicate that the turn was interrupted at the input-guardrail stage.
This prevents misleading diagnostics such as:
```text
route = blocked
mcp_results = [tool executed]
```
when the tool result actually belongs to the previous turn.
## The public message also goes through output guardrails
A response created because of an input block is still agent output. Therefore it should follow the same output-validation pipeline before reaching the user.
This allows `DLEX_OUT`, `PINJ`, `TOXOUT`, Output Supervisor, and other policies to remove or sanitize information that should not be exposed.
## Relationship with `agent_graph.py`
This feature belongs to template orchestration because it defines precedence between graph nodes and blocked-turn state semantics.
When changing `app/workflows/agent_graph.py`, preserve these invariants:
- `input_guardrails` runs before routing/tools;
- an input block does not execute a transactional action after the block;
- the public response is not the raw guardrail `reason`;
- residual routing/tools/judges state does not survive as the blocked turn result;
- the public message passes through `output_guardrails` before persistence/response.
The same semantics must be preserved in the official templates and equivalent variants under `Tuning-Performance`.
## Troubleshooting
### The user receives “I could not continue because of a security rule” for a merely incomplete phrase
Check:
1. which guardrail returned `allowed=false`;
2. whether `COER` is handled as clarification rather than a generic security block;
3. whether the blocked branch builds a guardrail-specific public message;
4. whether the generic fallback is used only when no specific handling exists.
### Metadata shows a tool as executed while `route=blocked`
Check whether the blocked branch clears transient turn state before returning. Also confirm that the tool was not executed in the same turn before input-guardrail evaluation.
### The block response exposes internal details
Do not use `reason` directly as user-facing text. Generate the public message and keep `reason` for observability only.
### The block response skips output guardrails
Check the graph edge. The expected path is:
```text
input_guardrails blocked
→ build public response
→ output_guardrails
→ persist
```
not:
```text
input_guardrails blocked
→ persist
```
## Recommended regression tests
Cover at least:
- `COER=false` asks for clarification instead of returning a generic security message;
- blocked branch does not retain previous-turn `mcp_results`/routing;
- no transactional tool executes after an input block;
- the public message passes through output guardrails;
- an unknown guardrail still has a safe generic fallback.

View File

@@ -0,0 +1,143 @@
### Developer Index — Agent Framework OCI
### How to use this documentation
The documentation has three clear levels:
1. **Main tutorial:** [`README_en.md`](README_en.md) — creation, configuration, execution, and testing of an agent from start to finish.
2. **Architecture:** [01 — Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) — components, responsibilities, and where to implement each concern.
3. **Specialized references:** manuals `02` through `12` — in-depth implementation and troubleshooting by capability.
If you are starting a new agent, begin with `README_en.md`.
If something is not working, use **Search by problem** below.
### Search by problem
| Problem / question | What is usually involved | Where to look |
|---|---|---|
| The framework does not find the correct agent/intent | routing, intents, threshold, deterministic/LLM mode | [Routing and Stickiness](docs/developer/en/02_routing_stickiness_and_intent_shift.md) |
| The agent gets stuck on the same subject and does not change intent | route stickiness, intent shift, handoff | [Routing and Stickiness](docs/developer/en/02_routing_stickiness_and_intent_shift.md) |
| An answer that should fill a parameter is interpreted as a new intent | transactional precedence, parameter extraction | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) |
| The transaction keeps asking for the same parameter | transaction state, extractor, schema | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) and [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
| “yes/no” confirmation does not continue the flow | confirmation state, transaction state | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) |
| A completed transaction reappears | old checkpoint versus active transaction state | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) and [LTM/Checkpoint](docs/developer/en/08_long_term_memory_and_checkpoint.md) |
| The system says it executed something, but there is no evidence | MCP result, `COMPLETED` state, transactional judges | [Transactional Workflows](docs/developer/en/03_transaction_workflows_and_state.md) and [Guardrails/Judges](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md) |
| A tool does not appear or cannot be found | `tools.yaml`, MCP catalog, discovery | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
| MCP Server does not appear in the catalog | registration, manifest/discovery, MCP Gateway | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) and [Gateways](docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md) |
| Parameters sent to the tool are wrong | schema, mapping, BusinessContext, extractor | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
| A transactional operation executes without confirmation | tool policy, `require_confirmation` | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
| A name search requires an overly exact match | parameter extraction/mapping and agent logic | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
| I receive 401 between gateway/backend/MCP | Basic Auth, credentials per hop | [Gateways and Auth](docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md) |
| I need to decide whether something belongs to the framework or the agent | core/agent boundary | [Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) |
| An agent-specific guardrail is breaking another agent | extensibility, domain imports in the core | [Guardrails and Judges](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md) |
| An incomplete phrase receives a generic “security rule” message | input-guardrail feedback, `COER`, blocked-turn state | [Input Guardrail Feedback](./12_input_guardrail_feedback_and_blocked_turns.md) |
| `route=blocked` appears together with tools/results from another turn | blocked-turn state cleanup | [Input Guardrail Feedback](./12_input_guardrail_feedback_and_blocked_turns.md) |
| A judge does not run in a transaction | sampling, `always_run_for_transactional`, transaction signals | [Guardrails and Judges](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md) |
| Workflow completes and generates a protocol, but the final response becomes a safety message | `expected_protocols`, `CMP`, `DLEX_OUT`, `output_guardrails` ordering | [Guardrails and Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Groundedness is evaluating without the correct context | RAG context, MCP evidence, judge inputs | [RAG/Grounding](docs/developer/en/07_rag_business_context_and_grounding.md) |
| RAG does not find content | provider, ingestion, embeddings, configuration | [RAG/Grounding](docs/developer/en/07_rag_business_context_and_grounding.md) |
| I do not know whether to use RAG, memory, or a tool | separation of responsibilities | [Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) and [RAG/Grounding](docs/developer/en/07_rag_business_context_and_grounding.md) |
| Memory disappears when changing sessions | LTM versus conversation memory | [LTM and Checkpoint](docs/developer/en/08_long_term_memory_and_checkpoint.md) |
| Memory from one customer/agent appears in another | identity key, tenant/agent/customer isolation | [LTM and Checkpoint](docs/developer/en/08_long_term_memory_and_checkpoint.md) |
| I need to retrieve `reasoning_content` | `ainvoke_response()` | [LLM Rich Response](docs/developer/en/09_llm_rich_response_reasoning.md) |
| `reasoning_content` is `None` | provider/model does not expose the field | [LLM Rich Response](docs/developer/en/09_llm_rich_response_reasoning.md) |
| There are unnecessary LLM calls | deterministic routing, concurrency, cache | [Performance](docs/developer/en/10_performance_cache_and_async_runtime.md) |
| There is a deadlock or wait across event loops | cross-loop sequence/runtime | [Performance](docs/developer/en/10_performance_cache_and_async_runtime.md) |
| Logs/traces do not correlate the same agent | labels, IDs, and observability mapping | [Observability](docs/developer/en/11_observability_persistence_and_operational_readiness.md) |
| Sequence is interfering with processing | asynchronous sequence implementation | [Observability](docs/developer/en/11_observability_persistence_and_operational_readiness.md) and [Performance](docs/developer/en/10_performance_cache_and_async_runtime.md) |
| An old example does not compile | historical documentation versus current API | [README vs Code Validation](docs/developer/en/VALIDATION_README_ALIGNMENT.md) |
| I need to create a new agent from scratch | complete flow | [`README_en.md`](README_en.md) |
| I need to know where to place a new feature | architecture and boundaries | [Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) |
### Search by feature
### [01 — Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md)
**What it is:** overview of components, contracts, and responsibility boundaries.
**Use when:** you need to understand the platform, decide where to implement something, or avoid coupling between core and agent.
### [02 — Routing, Route Stickiness, and Intent Shift](docs/developer/en/02_routing_stickiness_and_intent_shift.md)
**What it is:** complete reference for agent/intent discovery, stickiness, handoff, and intent changes.
**Use when:** the message goes to the wrong agent, does not change intent, or loses continuity.
### [03 — Transactional Workflows and State](docs/developer/en/03_transaction_workflows_and_state.md)
**What it is:** multi-turn transaction lifecycle, states, confirmation, pause/resume, and operational evidence.
**Use when:** there are loops, incorrect confirmations, incorrect resumes, or critical operations.
### [04 — MCP, Tools, Policies, and Parameter Extraction](docs/developer/en/04_mcp_integration_tools_and_policies.md)
**What it is:** reference for tools, MCP Servers, mappings, policies, and parameter extraction.
**Use when:** tool integration/execution is incorrect or needs to be created.
### [05 — Agent Gateway, MCP Gateway, and Authentication](docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md)
**What it is:** gateway responsibilities, governance, and authentication between components.
**Use when:** there is an ingress, catalog, authorization, 401, or gateway deployment problem.
### [06 — Guardrails, Judges, and Transaction Evaluation](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md)
**What it is:** native/external validations, judges, grounding, and rules for transactional turns.
**Use when:** a validation blocks, does not run, or produces an incorrect evaluation.
### [07 — RAG, BusinessContext, and Grounding](docs/developer/en/07_rag_business_context_and_grounding.md)
**What it is:** RAG providers, retrieved context, BusinessContext, and grounding.
**Use when:** retrieved knowledge does not correctly reach the agent/judge.
### [08 — Long-Term Memory and Checkpoint](docs/developer/en/08_long_term_memory_and_checkpoint.md)
**What it is:** durable memory, conversation memory, identity, and state snapshots.
**Use when:** context disappears, leaks, or the workflow resumes from the wrong place.
### [09 — LLM Rich Response and reasoning_content](docs/developer/en/09_llm_rich_response_reasoning.md)
**What it is:** structured inference response beyond the `str` returned by `ainvoke()`.
**Use when:** consumers need metadata, usage, or reasoning exposed by the provider.
### [10 — Performance, Cache, and Async Runtime](docs/developer/en/10_performance_cache_and_async_runtime.md)
**What it is:** concurrency, cache, LLM, and event-loop optimizations.
**Use when:** there is avoidable latency, serial processing, or deadlock.
### [11 — Observability, Persistence, and Operational Readiness](docs/developer/en/11_observability_persistence_and_operational_readiness.md)
**What it is:** correlation, events, labels, sequence, persistence, and diagnostics.
**Use when:** it is necessary to prove the executed path or diagnose production.
### [12 — Input Guardrail Feedback and Blocked-Turn Semantics](./12_input_guardrail_feedback_and_blocked_turns.md)
**What it is:** public handling of input blocks, blocked-turn state cleanup, and output-guardrail validation of generated feedback.
**Use when:** block messages are generic, `COER` should ask for clarification, or blocked-turn metadata contains stale routing/tool results.
### Main tutorial
[`README_en.md`](README_en.md) remains the reference for the complete step-by-step flow:
`architecture → configuration → agent creation → registration → state → routing → tools → MCP → identity → execution → tests → gateways → memory → RAG`.
### Maintenance
Do not create another tutorial in parallel with `README_en.md`.
When evolving a feature:
- update the README only if the normal development flow changed;
- update the specialized manual with behavior, configuration, examples, and troubleshooting;
- update SPECs if the contract changed;
- keep release notes as history, not as the only current documentation.

View File

@@ -0,0 +1,83 @@
### Documentation Alignment Validation
### Goal
Record how the documentation for this version was reorganized and which sources developers should use.
### Structural decision
The root `README_en.md` is the **single end-to-end main tutorial**.
The former `01_architecture_and_agent_development.md` was removed because it repeated a large part of the README, but not all of it. This created ambiguity: two documents appeared to teach the same thing, but one was partial.
The new structure replaces that file with `01_architecture_and_concepts.md`, which contains only architecture, concepts, responsibilities, and extension criteria.
### Validation of `README_old2.md`
`Documentacao/README_old2.md` remains useful as history, but it is not the primary source for development.
Later evolutions were found in the current README and code, including:
- SPECs/SDDs;
- more complete `llm_profiles.yaml` configuration;
- Channel Gateway and canonical contracts;
- `memory` and `summary_memory` in the current agent lifecycle;
- `prepare_memory_context()` and `build_messages()`;
- `RuntimeContext`;
- `normalize_tools_by_intent()`;
- `build_tool_arguments()`;
- `execute_tools_for_intent()`;
- transaction-state helpers;
- direct MCP responses;
- evolution of gateways, RAG, memory, and policies.
### Correction applied to the main README
The following typo was corrected in the generated package:
```python
from app.agents.financeiro_agent import FinanceirotAgent
```
to:
```python
from app.agents.financeiro_agent import FinanceiroAgent
```
The correct class is confirmed by the code and the rest of the documentation.
### APIs confirmed in the current implementation
```python
AgentRuntimeMixin.get_runtime_context()
AgentRuntimeMixin.normalize_tools_by_intent()
AgentRuntimeMixin.build_tool_arguments()
AgentRuntimeMixin.execute_tools_for_intent()
AgentRuntimeMixin.prepare_memory_context()
AgentRuntimeMixin.build_messages()
AgentRuntimeMixin.transaction_state_patch()
AgentRuntimeMixin.transaction_clarification_message()
AgentRuntimeMixin.transaction_confirmation_message()
AgentRuntimeMixin.build_direct_mcp_answer()
```
### Trust order
1. code for the version;
2. main README for the same version;
3. SPECs/SDDs;
4. specialized manuals;
5. release notes;
6. `README_old*` documents.
### Future maintenance rule
A feature evolution should update:
1. the main README, **only if it changes the normal development path**;
2. the feature's specialized manual, with technical details, behavior, configuration, and troubleshooting;
3. the SPEC, when there is a contract change;
4. the release note, when it is necessary to record the historical change.
Do not create a new “main manual” for a feature. Do not keep functional fixes permanently only in release notes.

View File

@@ -0,0 +1,265 @@
### Arquitetura e Conceitos do Agent Framework OCI
### Propósito deste documento
Este documento **não substitui o `README.md` da raiz** e não repete o tutorial de criação de agente.
Use:
- [`README.md`](../../../README.md) para desenvolver, configurar, executar e testar um agente de ponta a ponta;
- este documento para compreender a arquitetura, os limites de responsabilidade, os componentes e onde cada tipo de implementação deve ficar;
- os demais manuais desta pasta para aprofundar uma capacidade específica ou solucionar um problema.
A separação é intencional: existe **um único tutorial principal** e vários **manuais de referência especializados**.
### Fonte de verdade
Quando existir divergência documental, use esta ordem:
1. código da versão em uso;
2. `README.md` / `README_en.md` da mesma versão;
3. SPECs/SDDs normativas;
4. manuais especializados desta pasta;
5. release notes e `README_old*` apenas como histórico.
### Modelo mental da plataforma
O Agent Framework OCI deve ser entendido como uma plataforma em camadas.
O **framework core** fornece mecanismos reutilizáveis e neutros de domínio: runtime, estado, memória, roteamento, integração de tools, guardrails, judges, persistência, observabilidade e contratos comuns.
O **agente** contém aquilo que é específico do caso de uso: intents, prompts, regras de domínio, policies específicas, workflow de negócio, mapeamentos, integrações e componentes externos pertencentes àquele agente.
Os **gateways** tratam responsabilidades transversais de entrada, governança e integração. Eles não devem absorver a lógica de negócio do agente.
Os **MCP Servers** encapsulam ferramentas e integrações com serviços de domínio ou legados. O **MCP Gateway** fornece catálogo e governança centralizada dessas tools.
### Componentes principais
| Componente | Responsabilidade principal | Não deve conter |
|---|---|---|
| `libs/agent_framework/` | Runtime genérico, contratos, estado, memória, routing, guardrails, judges, integrações comuns | Regra específica de uma empresa ou agente |
| `templates/agent_template_backend/` | Referência executável para criação de agentes | Fork permanente do core |
| `apps/agent_gateway/` | Entrada governada, policies transversais, rate limit, autenticação, metadados | Workflow de negócio |
| `apps/channel_gateway/` | Adaptação dos canais ao contrato canônico | Regra de negócio do agente |
| `apps/mcp_gateway/` | Catálogo, autorização e execução central de tools | Lógica conversacional |
| `mcp/servers/` | Integrações e tools por domínio | Orquestração global do agente |
| `evals/` | Certificação e regressão | Lógica produtiva |
| `deploy/` | Containers e Kubernetes | Regras funcionais |
### Fluxo conceitual de uma requisição
Uma requisição típica percorre as seguintes responsabilidades:
```text
Canal
|
v
Channel Gateway
|
v
Agent Gateway
| governança / autenticação / rate limit / metadata
v
Backend do agente
|
+--> Routing / stickiness / intent
|
+--> Estado / memória / checkpoint
|
+--> Guardrails / judges
|
+--> Workflow / políticas transacionais
|
+--> MCP Gateway
|
+--> MCP Server A --> sistema legado
+--> MCP Server B --> serviço externo
+--> MCP Server C --> API de domínio
```
Nem toda implantação precisa utilizar todos os componentes. A composição deve seguir a necessidade do agente e os contratos da plataforma.
### Runtime do agente
O runtime atual é baseado em `AgentRuntimeMixin` e `RuntimeContext`.
O template importa o runtime através de `app.agents.runtime`, que reexporta a implementação oficial do framework. O objetivo é impedir que cada agente mantenha sua própria cópia divergente do runtime.
Entre as APIs atuais confirmadas no código estão:
```python
AgentRuntimeMixin.get_runtime_context()
AgentRuntimeMixin.normalize_tools_by_intent()
AgentRuntimeMixin.build_tool_arguments()
AgentRuntimeMixin.execute_tools_for_intent()
AgentRuntimeMixin.prepare_memory_context()
AgentRuntimeMixin.build_messages()
AgentRuntimeMixin.transaction_state_patch()
AgentRuntimeMixin.transaction_clarification_message()
AgentRuntimeMixin.transaction_confirmation_message()
AgentRuntimeMixin.build_direct_mcp_answer()
```
Essas APIs representam capacidades do runtime. O desenvolvedor deve preferi-las a reconstruir manualmente a mesma lógica dentro de cada agente.
### Configuração versus código
Uma diretriz central do framework é que comportamento configurável permaneça em configuração.
Exemplos:
- agentes e metadados: `config/agents.yaml`;
- roteamento: `config/routing.yaml`;
- tools: `config/tools.yaml`;
- MCP Servers e mappings: configuração MCP correspondente;
- perfis de LLM: `llm_profiles.yaml`;
- policies e extensões: arquivos de configuração específicos da capacidade.
O código deve implementar mecanismos. YAML/config deve escolher comportamento sempre que isso puder ser feito sem comprometer segurança ou contratos.
### Separação entre framework e agente
Uma mudança pertence ao **framework** quando introduz um mecanismo reutilizável por diferentes agentes.
Exemplos:
- nova SPI de guardrail;
- novo contrato de resposta rica de LLM;
- nova capacidade genérica de checkpoint;
- novo mecanismo configurável de tool policy;
- nova estratégia genérica de routing.
Uma mudança pertence ao **agente** quando expressa uma regra de um domínio ou empresa.
Exemplos:
- quais cobranças podem ser contestadas;
- um prompt específico de telecom;
- regras de VAS;
- códigos internos de uma empresa;
- mapeamento de um serviço legado;
- fraseologia específica.
Se o core precisa importar um módulo concreto do agente para funcionar, essa separação provavelmente foi quebrada.
### Estado, memória e checkpoint são conceitos diferentes
**Estado de execução** representa o que está acontecendo no turno e no workflow.
**Memória de conversa** preserva contexto conversacional.
**Long-Term Memory** guarda fatos duráveis associados a uma identidade de negócio.
**Checkpoint** persiste snapshots do estado LangGraph para retomada.
Um checkpoint antigo não deve, sozinho, determinar qual transação está ativa. A decisão funcional deve usar o estado transacional canônico.
### Routing e execução são responsabilidades diferentes
O routing responde: **qual agente/intent deve tratar esta mensagem?**
A execução responde: **o que esse agente deve fazer agora?**
Route stickiness preserva continuidade, mas não deve impedir uma mudança explícita de intenção. Durante uma transação, parâmetros esperados e confirmação válida têm precedência para evitar falsos intent shifts.
Detalhes completos: [Roteamento, Stickiness e Intent Shift](./02_routing_stickiness_and_intent_shift.md).
### Tools e MCP
Uma tool representa uma capacidade invocável.
O MCP Server implementa ou expõe essa capacidade.
O MCP Gateway organiza catálogo, autorização, mapping e execução centralizada.
O agente decide **quando** uma tool deve ser usada dentro do seu fluxo; a tool/MCP decide **como** acessar o serviço correspondente.
Detalhes completos: [MCP, Tools, Policies e Extração de Parâmetros](./04_mcp_integration_tools_and_policies.md).
### Transações
Operações com efeitos colaterais exigem tratamento diferente de consultas.
O framework fornece mecanismos de estado, confirmação, políticas e workflow determinístico. Regras concretas permanecem no agente.
O LLM pode participar da interpretação e composição, mas não deve ser a única fonte de verdade para afirmar que uma operação crítica foi executada.
Detalhes completos: [Workflows Transacionais e Estado](./03_transaction_workflows_and_state.md).
### Guardrails e Judges
Guardrails controlam ou validam comportamento durante o processamento.
Judges avaliam qualidade, grounding e outros critérios.
O core fornece mecanismos nativos e pontos de extensão. Guardrails/judges específicos de um domínio devem ser carregados pelo agente por configuração, evitando imports específicos dentro do framework.
Detalhes completos: [Guardrails, Judges e Avaliação Transacional](./06_guardrails_judges_and_transaction_evaluation.md).
### RAG, memória e ferramentas não são equivalentes
- **RAG** recupera conhecimento.
- **Memory** preserva contexto/fatos.
- **Tool** executa ou consulta uma capacidade externa.
Escolher o mecanismo errado cria bugs difíceis de diagnosticar. Uma informação que precisa ser atualizada em sistema não deve ser resolvida apenas por RAG; um fato durável do cliente não deve depender apenas do histórico do prompt.
### Observabilidade como contrato transversal
Roteamento, agente, transação, tool, guardrail, judge e falha precisam ser correlacionáveis.
Observabilidade deve registrar o que aconteceu, mas não controlar estado de negócio. Sequence, trace IDs e labels são infraestrutura de diagnóstico e auditoria.
Detalhes completos: [Observabilidade, Persistência e Prontidão Operacional](./11_observability_persistence_and_operational_readiness.md).
### Onde colocar uma nova funcionalidade
Antes de implementar, faça estas perguntas:
1. A capacidade é reutilizável por diferentes agentes?
2. Existe regra específica de domínio?
3. Precisa de estado entre turnos?
4. Produz efeito colateral?
5. Depende de sistema externo?
6. Deve ser configurável?
7. Precisa aparecer em observabilidade?
8. Precisa ser avaliada por guardrail/judge?
Uma feature reutilizável normalmente começa no core e é habilitada/configurada pelo agente. Uma regra de negócio normalmente começa no agente e usa interfaces do core.
### Anti-padrões
Evite:
- importar pacote concreto de um agente dentro do core;
- duplicar `AgentRuntimeMixin` em cada agente;
- codificar nomes de agentes, intents, tools ou empresas no runtime;
- usar resposta do LLM como prova de execução de operação;
- confundir checkpoint antigo com transação ativa;
- executar operação transacional sem política/confirmacão quando ela é requerida;
- acoplar agente diretamente a dezenas de serviços quando o MCP Gateway é a camada prevista;
- criar um novo documento funcional para cada bug fix em vez de atualizar o manual da feature.
### Caminho recomendado para um novo desenvolvedor
1. Leia a visão arquitetural neste documento.
2. Siga o [`README.md`](../../../README.md) do início ao fim para criar e executar um agente.
3. Quando chegar a uma capacidade específica, use o manual especializado correspondente.
4. Para falhas, comece pelo [Índice de Desenvolvimento](./INDEX_DEVELOPER_GUIDE.md), na seção **Buscar pelo problema**.
5. Antes de copiar código antigo, confirme API/import no template e no core atuais.
### Documentos relacionados
- [Tutorial principal — README.md](../../../README.md)
- [Roteamento, Stickiness e Intent Shift](./02_routing_stickiness_and_intent_shift.md)
- [Workflows Transacionais e Estado](./03_transaction_workflows_and_state.md)
- [MCP, Tools, Policies e Parâmetros](./04_mcp_integration_tools_and_policies.md)
- [Gateways e Autenticação](./05_agent_gateway_mcp_gateway_and_auth.md)
- [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md)
- [RAG e BusinessContext](./07_rag_business_context_and_grounding.md)
- [Long-Term Memory e Checkpoint](./08_long_term_memory_and_checkpoint.md)
- [LLM Rich Response](./09_llm_rich_response_reasoning.md)
- [Performance, Cache e Runtime Assíncrono](./10_performance_cache_and_async_runtime.md)
- [Observabilidade e Prontidão Operacional](./11_observability_persistence_and_operational_readiness.md)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,693 @@
### Workflows Transacionais e Estado
### Como usar este manual
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **estado transacional, coleta de parâmetros, confirmação, pausa/retomada e evidência operacional**.
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
### Relação com o tutorial principal
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
### Escopo
Estado transacional, coleta de parâmetros, confirmação, pausa/retomada e evidência operacional.
### Conteúdo técnico consolidado
### Workflows Transacionais, Estado Multi-turno e Retomada
Guia de implementação para operações multi-etapas, fonte canônica do estado transacional, confirmação, merge de parâmetros, pausa/retomada, evidência operacional e interação com roteamento.
### Como usar este documento
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
### Guia de estado transacional multi-turno
> Conteúdo consolidado a partir de `docs/TRANSACTION_STATE_DEVELOPER_GUIDE.md`.
Este documento define o contrato operacional para transações multi-turno no Agent Framework OCI. Ele é normativo para hosts e templates que utilizam `AgentRuntime`, checkpoint LangGraph e tools transacionais.
### 1. Objetivo
Uma transação pode atravessar vários turnos. Exemplo:
```text
Usuário: quero cancelar o pedido
Framework: informe o número do pedido
Usuário: PED-1001
Framework: confirma o cancelamento?
Usuário: sim
Framework: executa a tool
```
O framework precisa preservar a transação entre todos esses turnos sem depender de reclassificação por LLM, keyword routing ou reextração de parâmetros já obtidos.
### 2. Fonte canônica do estado transacional
O estado canônico da transação em andamento é `active_transaction`.
```python
active_transaction: dict[str, Any]
last_transaction: dict[str, Any]
```
Todo `AgentState` usado por um host que habilita transações multi-turno **DEVE** declarar os dois campos. Como o LangGraph usa o schema do state para persistência/checkpoint, um campo criado apenas dinamicamente pelo runtime não é um contrato durável seguro.
Exemplo mínimo:
```python
from typing import Any, TypedDict
class AgentState(TypedDict, total=False):
# ...campos normais...
selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any]
active_transaction: dict[str, Any]
last_transaction: dict[str, Any]
transaction_status: str
missing_parameters: list[str]
confirmation_required: bool
confirmation_received: bool
```
### 3. Papel de cada campo
| Campo | Papel | Regra |
|---|---|---|
| `active_transaction` | Fonte canônica da transação ativa | Deve sobreviver a checkpoint/resume enquanto a transação estiver ativa. |
| `last_transaction` | Snapshot da última transação terminal | Usado para auditoria, evidência e continuidade controlada; não reativa automaticamente a transação. |
| `transaction_status` | Estado lógico atual | Ex.: `COLLECTING_PARAMETERS`, `AWAITING_CONFIRMATION`, `COMPLETED`, `CANCELLED`, `OUT_OF_SCOPE`. |
| `missing_parameters` | Parâmetros ainda necessários | Deve refletir o estado canônico da transação, não apenas a mensagem corrente. |
| `selected_tool_call` | Estado auxiliar/compatibilidade | Não deve substituir `active_transaction` como fonte canônica. |
| `pending_tool_call` | Estado auxiliar/compatibilidade | Pode ser usado por compatibilidade, mas não como latch principal. |
| `next_state` | Orientação de roteamento do workflow | Ajuda a manter o nó/agente correto durante coleta/confirmação. |
| `transaction_pre_validation` | Evidência de pré-validação | Mantém resultado de validação antes da confirmação/execução. |
| `transaction_evidence` | Evidências da execução | Mantém resultados e trilha de execução da transação. |
### 4. Ciclo de vida recomendado
```text
IDLE
↓ intenção transacional
COLLECTING_PARAMETERS
↓ parâmetros completos
PRE_VALIDATION (quando configurado)
↓ elegível
AWAITING_CONFIRMATION
↓ confirmação positiva
EXECUTING
COMPLETED
```
Saídas terminais alternativas:
```text
CANCELLED
OUT_OF_SCOPE
FAILED
```
O runtime pode representar algumas fases internamente sem um `transaction_status` público separado. O requisito é preservar o latch e não perder argumentos já coletados.
### 5. Merge incremental de parâmetros
Uma resposta posterior deve complementar a transação existente, nunca recriá-la apenas a partir do texto atual.
```python
existing = dict((state.get("active_transaction") or {}).get("arguments") or {})
new_values = {"valor": "71.99"}
arguments = {**existing, **new_values}
```
Exemplo esperado:
```text
Turno 1: subject = "TIM CTRL Redes Sociais 8.0"
Turno 2: valor = "71.99"
Resultado: subject + valor permanecem disponíveis
```
### 6. Precedência de roteamento durante transação
Quando existe `active_transaction` em `COLLECTING_PARAMETERS`, a mensagem deve primeiro ser avaliada como possível resposta aos parâmetros pendentes.
Precedência normativa:
1. parâmetro pendente claramente preenchido → continuar a transação;
2. cancelamento/abandono explícito → cancelar a transação;
3. nova intenção inequívoca → interromper a transação e rotear;
4. keyword genérica do mesmo domínio/agente → **não** interromper a transação;
5. mensagem ambígua → manter a transação e clarificar.
Exemplos:
| Estado atual | Mensagem | Resultado correto |
|---|---|---|
| `retail_order_cancel`, falta `order_id` | `PED-1001` | Continua cancelamento e preenche `order_id`. |
| `retail_order_cancel`, falta `order_id` | `o pedido é o PED-1001` | Continua cancelamento; `pedido` não deve virar tracking. |
| contestação, falta `valor` | `R$ 71,99` | Continua contestação e preenche `valor`. |
| cancelamento pendente | `esquece, quero ver minha fatura` | Interrupção explícita permitida. |
| cancelamento pendente | `quero rastrear pedido` | Mudança inequívoca para tracking permitida. |
### 7. Checkpoint e retomada
Antes de executar roteamento normal, o host deve restaurar o checkpoint usando a mesma identidade de conversa (`tenant_id`, `agent_id`, `session_id`/`conversation_key` conforme contrato do host).
Após a restauração:
```text
active_transaction existe
status ativo?
↓ sim
retomar a transação antes de keyword routing / continuity LLM
```
Um estado `COLLECTING_PARAMETERS` sem `active_transaction` deve ser tratado como inconsistência de estado e observado/diagnosticado; não deve silenciosamente reiniciar a tool a partir da mensagem corrente.
### 8. O que pertence ao framework e ao agente
Framework:
- persistência do latch;
- merge de argumentos;
- estados de coleta/confirmação;
- precedência de retomada;
- confirmação determinística;
- idempotência e evidência;
- checkpoint/resume.
Agente:
- definição das tools de domínio;
- parâmetros obrigatórios e mensagens de domínio;
- regras de elegibilidade específicas;
- pre-validation específica, quando houver;
- resposta final ao cliente.
O agente não deve implementar um segundo motor transacional paralelo ao `AgentRuntime`.
### 9. Checklist para novos hosts/templates
- [ ] `AgentState` declara `active_transaction`.
- [ ] `AgentState` declara `last_transaction`.
- [ ] `transaction_status` e `missing_parameters` fazem parte do state quando usados.
- [ ] O host usa checkpoint compatível com o schema do state.
- [ ] A mesma `conversation_key` é usada entre turnos da mesma conversa.
- [ ] Parâmetros já coletados são mesclados com novos valores.
- [ ] Respostas a parâmetros têm precedência sobre keyword routing genérico.
- [ ] Mudança explícita de intenção continua possível.
- [ ] O agente usa `transaction_state_patch(state)` ao retornar respostas transacionais quando o template o exige.
- [ ] Existem testes multi-turno para coleta, confirmação, interrupção e resume.
### 10. Testes regressivos mínimos
```text
A. cancelamento de pedido
1. "quero cancelar pedido"
2. "o pedido é o PED-1001"
Esperado: continua retail_order_cancel; não vira retail_order_tracking.
B. contestação
1. "não contratei TIM CTRL Redes Sociais 8.0"
2. "R$ 71,99"
Esperado: subject e valor chegam juntos à pre-validation.
C. interrupção explícita
1. iniciar transação e deixar parâmetro pendente
2. "esquece, quero ver minha fatura"
Esperado: transação é interrompida e nova intenção é roteada.
D. checkpoint/resume
1. iniciar transação
2. persistir/checkpoint
3. reconstruir execução usando a mesma conversation_key
4. fornecer o parâmetro faltante
Esperado: active_transaction é restaurado e concluído sem reiniciar a tool.
```
### 11. Anti-patterns
- reconstruir a transação somente a partir da última mensagem;
- usar `selected_tool_call` como única fonte do latch;
- remover `active_transaction` do `AgentState` por parecer redundante;
- permitir uma keyword genérica como `pedido` interromper coleta de `order_id`;
- armazenar parâmetros apenas em variáveis locais do nó;
- duplicar confirmação transacional no prompt do agente;
- limpar o latch antes do estado terminal.
### 12. Referências no projeto
- `specs/SPEC-002-Agent-Runtime.md`
- `specs/SPEC-010-Agent-Development.md`
- `templates/agent_template_backend/app/state.py`
- `libs/agent_framework/src/agent_framework/runtime/agent_runtime.py`
- `libs/agent_framework/src/agent_framework/routing/enterprise_router.py`
- `Tuning-Performance/Deterministic_Transactional_Workflow/`
- `Tuning-Performance/Transaction_Pre_Validation/`
- `Tuning-Performance/Transaction_Evidence/`
### Decisão arquitetural do motor de workflows
> Conteúdo consolidado a partir de `docs/ADR_TRANSACTIONAL_WORKFLOW_ENGINE.md`.
### Decisão
Adicionar ao framework uma capacidade opcional de execução determinística baseada em LangGraph. O motor é genérico; definições YAML e actions de domínio permanecem nos agentes.
### Razão
Operações multi-etapas com efeitos colaterais não devem depender do LLM para escolher a sequência crítica. A solução reduz tokens, latência e variação, além de melhorar auditoria, testes e versionamento.
### Compatibilidade
`execution.mode` assume `direct_tool`. Projetos existentes continuam usando MCP diretamente. A adoção de workflow é explícita por tool e pode ser controlada por `ENABLE_TRANSACTIONAL_WORKFLOWS`.
### Limites desta entrega
A base inclui validação, versionamento por arquivo, registry, execução sync/async, condições, retry por nó, cache de grafos e adapter de policy. Persistência corporativa de execution records, compensação/Saga, autorização por escopo e emissão de IC/NOC específica devem ser conectadas às abstrações existentes de cada deployment antes do uso em transações financeiras críticas.
### Implementação dos workflows determinísticos
> Conteúdo consolidado a partir de `Documentacao/IMPLEMENTACAO_WORKFLOWS_TRANSACIONAIS.md`.
### Entrega
Foi adicionada ao `agent_framework_oci` uma capacidade opcional para executar transações multi-etapas como workflows determinísticos compilados em LangGraph.
### Módulo novo
`libs/agent_framework/src/agent_framework/workflows/`
- `models.py`: contratos Pydantic e validação estrutural;
- `repository.py`: resolução de versão ativa e leitura de YAML imutável;
- `registry.py`: registro desacoplado de actions sync/async;
- `runtime.py`: compilação, cache e execução do StateGraph;
- `tool_executor.py`: integração com a política da tool;
- `__init__.py`: API pública.
### Política expandida
`ToolPolicy` agora aceita:
```yaml
execution:
mode: direct_tool | workflow | agent
workflow: nome_do_workflow
version: active | 1
```
O default permanece `direct_tool`, preservando compatibilidade.
### Configuração
Foram adicionados:
- `ENABLE_TRANSACTIONAL_WORKFLOWS=false`;
- `WORKFLOWS_PATH=./workflows`.
### Template
Inclui um exemplo completo de devolução de pedido com:
- confirmação e campos obrigatórios pela política;
- workflow YAML versionado;
- actions de domínio no backend;
- bifurcação determinística baseada no resultado da validação.
### Validação realizada
- `tests/unit/test_tool_policies.py`: 4 testes aprovados;
- compilação Python de framework, template e novos testes: aprovada;
- o teste funcional novo do LangGraph foi criado, mas não pôde ser executado neste container porque `langgraph` não está instalado no ambiente. A dependência já está declarada no `pyproject.toml` do framework.
### Escopo e segurança
Esta entrega cria o motor e a integração de política. Para operações críticas em produção ainda é necessário conectar:
- execution store persistente;
- idempotência de negócio nas actions/APIs;
- autorização por escopo;
- telemetria IC/NOC específica de workflow;
- compensação/Saga quando aplicável;
- estratégia corporativa de timeout e retry.
Esses itens foram explicitamente documentados para evitar a falsa impressão de que retry por si só garante segurança transacional.
### Precedência da coleta de parâmetros
> Conteúdo consolidado a partir de `FIX_TRANSACTION_PARAMETER_PRECEDENCE.md`.
Esta correção remove a extração textual hardcoded de parâmetros transacionais e faz a coleta de `policy.requires` por um extrator LLM genérico.
### Regra de precedência
Enquanto existir uma transação ativa, o framework trata o turno nesta ordem:
```text
ACTIVE_TRANSACTION
|
+-- COLLECTING_PARAMETERS
| |
| +-- LLM tenta extrair SOMENTE os parâmetros ainda pendentes
| |
| +-- extraiu >= 1 ?
| |
| +-- SIM -> continua a transação; NÃO avalia intent_shift
| |
| +-- NÃO -> libera EnterpriseRouter para avaliar intent_shift
|
+-- AWAITING_CONFIRMATION
|
+-- reconhece confirmação/rejeição explícita
|
+-- reconheceu ?
|
+-- SIM -> continua/cancela a transação; NÃO avalia intent_shift
|
+-- NÃO -> libera EnterpriseRouter para avaliar intent_shift
```
### TransactionParameterExtractor
Novo componente:
`libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py`
A extração textual dos parâmetros de negócio é feita exclusivamente por LLM. O componente recebe:
- nome da tool/transação ativa;
- parâmetros atualmente pendentes;
- argumentos já conhecidos;
- schema/tipos declarados em `tools.yaml` quando disponíveis;
- descrição da tool;
- mensagem atual do usuário.
Ele não conhece nomes de domínio como `order_id`, `reason`, `subject`, `valor`, TIM ou retail. Não há regex de entidades de negócio.
A LLM pode interpretar, por exemplo:
- `PED-1001` quando só há um parâmetro compatível pendente;
- `o pedido é PED-1001`;
- `PED-1001, desisti da compra` preenchendo dois parâmetros no mesmo turno;
- respostas com o nome do parâmetro seguido do valor;
- respostas apenas com o valor, quando semanticamente inequívocas.
Em caso de dúvida, o prompt manda retornar `null`. Uma nova solicitação não deve ser transformada em valor de parâmetro.
### Separação de responsabilidades
`tool_policies.yaml` continua sendo a fonte de verdade para `requires`.
`tools.yaml` pode fornecer tipos via `args_schema` e descrição da tool para melhorar a interpretação sem introduzir código específico de domínio.
`mcp_parameter_mapping.yaml` continua responsável pelos parâmetros auxiliares/contrato MCP. As strategies do mapper são explicitamente excluídas dos campos presentes em `policy.requires`, para não misturar extração MCP com coleta transacional.
O `EnterpriseRouter` usa o mesmo extrator LLM apenas como *probe* de precedência. Se pelo menos um parâmetro pendente for encontrado, o turno permanece no estado transacional. Os valores extraídos são colocados no metadata da decisão e reutilizados pelo runtime, evitando uma segunda chamada LLM no mesmo turno.
### Profile LLM
Foi adicionado aos templates:
```yaml
transaction_parameter_extraction:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 500
timeout_seconds: 8
```
Generation/component:
- `llm.transaction_parameter_extraction`
- `transaction_parameter_extraction`
### Limpeza de estado
Em `intent_shift`, `transaction_pre_validation` da transação abandonada é removido para não contaminar a nova transação. O resultado de pre-validation continua preservado enquanto pertence à própria transação para auditoria.
### Testes adicionados
`tests/test_transaction_parameter_llm_precedence.py`
Cobertura:
1. dois parâmetros extraídos no mesmo turno;
2. um parâmetro preenchido ganha precedência sobre keyword que indicaria outra intent;
3. nenhum parâmetro encontrado libera `intent_shift`;
4. ausência do antigo `_extract_action_arguments()` hardcoded;
5. confirmação `sim` ganha precedência sobre intent shift.
### Correção de loop entre transação e intent
> Conteúdo consolidado a partir de `FIX_TRANSACTION_INTENT_LOOP.md`.
Correção aplicada em 2026-08-20 para impedir que uma sessão fique presa em `COLLECTING_PARAMETERS` ou `AWAITING_CONFIRMATION` quando o usuário muda explicitamente de assunto.
### Comportamento corrigido
Antes:
1. uma transação entrava em `COLLECTING_PARAMETERS`;
2. `next_state` forçava o mesmo agente via `state_policies`;
3. toda mensagem seguinte era tratada como tentativa de preencher o parâmetro faltante;
4. uma nova intenção como `quais sao meus servicos` permanecia presa no fluxo anterior.
Agora:
- o `EnterpriseRouter` verifica mudança explícita de intenção antes de aplicar o lock de estado;
- keyword explícita tem prioridade;
- quando necessário, o LLM router pode detectar mudança com confiança >= `router.confidence_threshold`;
- a decisão recebe `metadata.transaction_interruption=intent_shift`;
- o runtime encerra a transação pendente como `CANCELLED`, limpa `next_state`, parâmetros e latches, e prossegue com a nova intent;
- cancelamentos explícitos como `cancele essa operação anterior` funcionam também durante `COLLECTING_PARAMETERS`.
### Testes adicionados
- mudança de intent durante `COLLECTING_PARAMETERS`;
- resposta curta/baixa confiança permanece na transação;
- cancelamento explícito durante coleta de parâmetros;
- limpeza do estado transacional antes de executar a nova intent.
Testes focados: 19 passed.
### Evidência operacional de execução
> Conteúdo consolidado a partir de `docs/TRANSACTION_OPERATIONAL_EVIDENCE_FIX.md`.
### Problem
A confirmed transactional tool result was available only in the execution turn. On a later read-only turn, conversational memory could still mention the prior transaction (for example, a cancellation protocol), while the groundedness judge received only the current MCP results. This could classify a factually correct follow-up as unsupported.
### Fix
The framework now records completed/failed transactional tool outcomes as bounded operational evidence in LangGraph state/checkpoint (`transaction_evidence`). This is operational state, not Long Term Memory.
For each new turn, the runtime correlates previous transaction evidence with the current resource using generic identifiers (`*_id`, `order_id`, `invoice_id`, `asset_id`, `resource_key`, etc.). Only relevant evidence is materialized as `relevant_transaction_evidence`.
The same relevant evidence is:
- injected into the answering LLM prompt;
- merged with current MCP results for groundedness judges;
- exposed in response metadata as `transaction_evidence` for diagnostics;
- emitted with the completion telemetry event.
The history is bounded to the 10 most recent transaction outcomes, and at most 5 correlated entries are injected for a turn.
### Expected retail example
1. `cancelar_pedido(PED-1001)` returns protocol `CANCEL-2026-001`.
2. The result is persisted as transaction evidence.
3. The next `consultar_pedido(PED-1001)` returns `EM_TRANSPORTE`.
4. The answering agent and groundedness judge receive both the current order result and the prior cancellation evidence.
5. A response that mentions `CANCEL-2026-001` is grounded rather than treated as an unsupported claim.
### Validação integrada Backend/MCP
> Conteúdo consolidado a partir de `Documentacao/VALIDACAO_TRANSACIONAL_BACKEND_MCP.md`.
### Correções implementadas
- `mcp_tools` é tratado como allowlist, não como lista de execução automática.
- Tools `read_only` continuam disponíveis para enriquecimento de contexto.
- Somente uma tool transacional compatível com a solicitação é selecionada.
- `require_confirmation: true` cria `pending_tool_call` e `AWAITING_CONFIRMATION`.
- O turno de confirmação executa a chamada pendente com `confirmed: true`.
- O estado expõe `selected_tool_call`, `tool_policy_result`, `confirmation_required`, `confirmation_received` e `transaction_status`.
- `reason` foi padronizado entre catálogo, mapping e FastMCP Retail.
- Pedido `123` e `PED-ENTREGUE` retornam status `ENTREGUE` para testes positivos.
- A keyword genérica `produto` foi removida da intenção Telecom para não capturar devoluções Retail.
- Templates `Normal` e `Route_Stickness` em `Tuning-Performance` foram atualizados.
### Teste recomendado
1. `Quero devolver o pedido 123 porque me arrependi da compra.`
2. Esperado: `transaction_status=AWAITING_CONFIRMATION`, sem execução de `solicitar_devolucao`.
3. `Sim, confirmo a devolução.`
4. Esperado: `transaction_status=COMPLETED` e execução única de `solicitar_devolucao`.
### Resultado automatizado
```text
7 passed
```
### Arquivos de origem
Os arquivos abaixo foram consolidados neste manual:
- `docs/TRANSACTION_STATE_DEVELOPER_GUIDE.md`
- `docs/ADR_TRANSACTIONAL_WORKFLOW_ENGINE.md`
- `Documentacao/IMPLEMENTACAO_WORKFLOWS_TRANSACIONAIS.md`
- `FIX_TRANSACTION_PARAMETER_PRECEDENCE.md`
- `FIX_TRANSACTION_INTENT_LOOP.md`
- `docs/TRANSACTION_OPERATIONAL_EVIDENCE_FIX.md`
- `Documentacao/VALIDACAO_TRANSACIONAL_BACKEND_MCP.md`
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.
## Resolução canônica e revalidação de domínio antes da execução
Quando uma pré-validação resolve uma referência do usuário para uma entidade canônica, o framework **não deve simplesmente sobrescrever o parâmetro e executar a tool originalmente escolhida**. O contrato separa três valores:
```text
requested_subject = "youtube"
resolved_subject = "Youtube Premium"
execution_subject = "Youtube Premium"
```
O validador de domínio pode devolver `transaction_decision` com:
```json
{
"resolved_arguments": {"subject": "Youtube Premium"},
"target_tool": "tratar_vas_estrategico",
"action_changed": true,
"requires_reconfirmation": true,
"confirmation_message": "Identifiquei o serviço Youtube Premium. Esse serviço possui tratamento específico. Você deseja prosseguir?"
}
```
Responsabilidades:
- **Framework:** preserva argumentos solicitados, aplica apenas os argumentos canônicos declarados pelo validador, atualiza a transação para a `target_tool`, respeita `requires_reconfirmation` e mantém a decisão na evidência de pré-validação.
- **Agente/domínio:** decide classe, política e tool efetiva. O framework não conhece regras como “Youtube Premium é estratégico”.
- **MCP/backend:** executa a operação final já decidida pelo domínio.
Se a canonicalização não alterar a ação (`Tamboro``Tamboro Mensal`, por exemplo), a tool pode permanecer a mesma. Se a resolução alterar classe/política/tool, a decisão de domínio precisa ocorrer **antes da confirmação e da execução**. Em caso de ambiguidade ou baixa confiança, o validador deve pedir nova coleta/clarificação em vez de promover silenciosamente um candidato.
### Troubleshooting: resolved_subject correto, mas tool recebe o texto original
Sintoma: a pré-validação registra `resolved_subject="Youtube Premium"`, porém a execução ainda recebe `subject="youtube"`. Verifique se o validador retorna `transaction_decision.resolved_arguments` e se o runtime aplicou a decisão antes de congelar `pending_tool_call`/`confirmation_snapshot`.
Sintoma: a entidade foi resolvida corretamente, mas a tool final continua inadequada. Verifique `transaction_decision.target_tool`; a reclassificação de domínio pertence ao agente/validador, não ao framework.
Para domínios que possuem uma classificação autoritativa no detalhe do backend, a revalidação deve usar essa evidência antes de categorias agregadas. No Contas, por exemplo, `invoice_detail.parsed_content` preserva `classe=avulso|estrategico|bundle`; `billing_analysis` pode agrupar o mesmo item em seções mais amplas como `streaming` ou serviços de parceiros. A entidade canônica pode ser descoberta por qualquer evidência autorizada, mas a **decisão de negócio** deve priorizar a fonte que preserva a classificação de domínio. Se houver conflito de classificação, não troque a ação silenciosamente: mantenha a operação original ou peça esclarecimento conforme a política do agente.
## Confirmação transacional semântica: SIM / NAO / CONTINUAR
Transações em `AWAITING_CONFIRMATION` usam duas camadas, nesta ordem:
1. **Parser determinístico** para confirmações/recusas explícitas (`sim`, `não`, `confirmo`, `pode fazer`, etc.). Esse caminho continua sendo o mais barato, rápido e seguro e **não chama LLM**.
2. **Fallback semântico por LLM** somente quando o parser determinístico retorna inconclusivo. O fallback reutiliza o mesmo mecanismo declarativo de `expected_input.semantic_classifier` dos workflows pausados e injeta a pergunta pendente, o histórico recente relacionado ao mesmo tema e a fala atual.
A configuração fica em `config/routing.yaml`, sob `router.transaction_confirmation.semantic_fallback`:
```yaml
router:
transaction_confirmation:
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Classes permitidas: {{ allowed_values }}
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual:
{{ user_input }}
```
### Significado das classes
- `SIM`: aceite inequívoco da ação pendente. Exemplos: `isso mesmo, pode confirmar`, `é isso`, `pode seguir`, quando o contexto torna o aceite claro.
- `NAO`: recusa inequívoca da ação pendente. Exemplos: `melhor não`, `não quero mais`, `cancela isso`.
- `CONTINUAR`: a fala não confirma nem rejeita de forma inequívoca. Exemplos: pergunta adicional, correção de parâmetro, informação nova, ambiguidade ou possível mudança de assunto. Nesse caso a tool não é executada por confirmação.
### Exemplo
Contexto:
```text
Cliente: quero cancelar o Tamboro Mensal
Agente: Você confirma o cancelamento do serviço Tamboro Mensal?
Cliente: isso mesmo, pode confirmar
```
O parser determinístico não precisa conhecer literalmente `isso mesmo, pode confirmar`. O fallback recebe:
```text
pending_prompt = "Você confirma o cancelamento do serviço Tamboro Mensal?"
relevant_conversation_context = histórico recente do mesmo fluxo
user_input = "isso mesmo, pode confirmar"
```
e deve retornar apenas:
```text
SIM
```
O router então publica em `route_decision.metadata`:
```json
{
"transaction_turn_consumed": true,
"transaction_confirmation_decision": "confirm",
"transaction_confirmation_source": "semantic"
}
```
O `AgentRuntime` reutiliza essa decisão e **não tenta reclassificar a mesma fala com o parser determinístico**. Isso evita a regressão em que o router entende semanticamente a confirmação, mas o runtime volta a tratá-la como inconclusiva.
### Precedência e compatibilidade
A funcionalidade é aditiva. Entradas determinísticas já suportadas continuam com o mesmo comportamento e sem custo adicional de LLM. O fallback semântico só roda quando a primeira camada não consegue decidir. Assim, `sim` e `não` continuam tendo precedência absoluta sobre `intent_shift`. Uma saída `CONTINUAR` não confirma nem rejeita automaticamente a transação; o fluxo normal pode então avaliar continuação contextual ou mudança de intenção conforme as políticas existentes.
### Observabilidade
Para confirmações semânticas, o framework registra a geração como `transaction.confirmation.semantic_classifier` e acrescenta ao metadata do roteamento a fonte `semantic`, a classificação retornada e o contexto conversacional relevante utilizado. Para confirmações literais, a fonte permanece `deterministic`.
### Compatibilidade de interrupts duráveis no pause/resume
O runtime não usa `snapshot.next` isoladamente para decidir se um workflow está pausado. Um `next` pode representar trabalho auxiliar do LangGraph, inclusive nós sintéticos criados pelo framework como `__pause` e `__continue`.
A pausa é reconhecida por um interrupt real. Dependendo da versão do LangGraph/checkpointer, esse interrupt pode aparecer em `task.interrupts` ou persistido em `snapshot.values["__interrupt__"]`. O runtime aceita ambas as formas e deduplica o payload quando as duas são expostas simultaneamente.
Isso evita dois falsos diagnósticos:
- considerar `snapshot.next` como `PAUSED` quando não existe interrupt real;
- considerar um `next=("<node>__pause",)` como erro de trabalho pendente quando o interrupt está persistido em `__interrupt__`.
Em workflows com `expected_input.semantic_classifier`, os tokens internos `SIM`, `NAO` e `CONTINUAR` continuam sendo valores de controle do resume e não devem ser confundidos com resposta final ao cliente.

View File

@@ -0,0 +1,826 @@
### MCP, Tools, Policies e Extração de Parâmetros
### Como usar este manual
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **tools, MCP Servers, mappings, policies read-only/transacionais e extração de parâmetros**.
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
### Relação com o tutorial principal
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
### Escopo
Tools, mcp servers, mappings, policies read-only/transacionais e extração de parâmetros.
### Conteúdo técnico consolidado
### Integração MCP, Tools, Políticas e Extração de Parâmetros
Manual de desenvolvimento para integrar MCP Servers, registrar tools, isolar tools por agente, configurar políticas read-only/transacionais, confirmação e extração contextual de parâmetros.
### Como usar este documento
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
### Manual completo de integração MCP Servers
> Conteúdo consolidado a partir de `Documentacao/Manual_Integracao_MCP_Servers_Agent_Framework.docx`.
Manual de Integração com Servidores MCP
Agent Framework Multi-Agent - Router, Supervisor, Tools e Servidores Externos
Este documento explica os conceitos de MCP, como o projeto atual integra servidores MCP, como subir os servidores de exemplo Telecom e Retail, como configurar tools por agente e como evoluir a implementação para um MCP mais aderente ao padrão oficial. O objetivo é servir como guia de desenvolvimento, operação local e implantação em container/OCI.
### Conceitos de MCP
MCP significa Model Context Protocol. Ele define uma forma padronizada para aplicações de IA acessarem contexto externo, ferramentas e capacidades de sistemas fora do modelo. Em vez de colocar integrações diretamente dentro do prompt ou dentro do agente, o MCP separa a responsabilidade: o agente decide o que precisa, e um servidor MCP oferece tools, resources e prompts de forma controlada.
No padrão oficial, o MCP usa mensagens JSON-RPC e define transportes como stdio e Streamable HTTP. O projeto atual usa uma implementação HTTP simplificada para facilitar entendimento e testes locais, com endpoints REST /mcp/tools/list e /mcp/tools/call. Isso é adequado para tutorial e prototipação, mas pode ser evoluído para um client MCP oficial posteriormente.
### Como o projeto atual organiza MCP
A estrutura relevante do projeto é:
```
projeto_multi_agent_isolado/
agent_framework/
src/agent_framework/mcp/
client.py
models.py
registry.py
tool_router.py
agent_template_backend/
config/
mcp_servers.yaml
mcp_servers.docker.yaml
tools.yaml
mcp_parameter_mapping.yaml
app/
main.py
workflows/agent_graph.py
mcp_servers/
telecom_mcp_server/
main.py
requirements.txt
Dockerfile
retail_mcp_server/
main.py
requirements.txt
Dockerfile
scripts/
run_mcp_servers.sh
docker-compose.yml
```
### Componentes principais
### Contrato HTTP simplificado usado no projeto
```
GET /mcp/tools/list
POST /mcp/tools/call
Payload de chamada:
{
"tool_name": "consultar_fatura",
"arguments": {
"msisdn": "11999999999",
"invoice_id": "INV-001"
}
}
Resposta esperada:
{
"ok": true,
"result": { ... },
"metadata": {
"server": "telecom",
"tool": "consultar_fatura"
}
}
```
### Como subir os servidores MCP de exemplo
O projeto possui dois servidores MCP de exemplo: Telecom e Retail. Eles são FastAPI apps independentes. O servidor Telecom roda na porta 8100 e expõe tools como consultar_fatura, consultar_pagamentos, consultar_plano e listar_servicos. O servidor Retail roda na porta 8200 e expõe tools como consultar_pedido, consultar_entrega, solicitar_troca e solicitar_devolucao.
### Subida local via script
```
cd projeto_multi_agent_isolado
bash ./scripts/run_mcp_servers.sh
```
O script cria uma venv no diretório raiz, instala as dependências dos servidores MCP e sobe os dois processos uvicorn em background:
```
Telecom MCP: http://localhost:8100
Retail MCP: http://localhost:8200
```
### Subida manual do Telecom MCP
```
cd projeto_multi_agent_isolado
python -m venv .venv
source .venv/bin/activate
pip install -r mcp_servers/telecom_mcp_server/requirements.txt
uvicorn --app-dir mcp_servers/telecom_mcp_server main:app --host 0.0.0.0 --port 8100
```
### Subida manual do Retail MCP
```
cd projeto_multi_agent_isolado
source .venv/bin/activate
pip install -r mcp_servers/retail_mcp_server/requirements.txt
uvicorn --app-dir mcp_servers/retail_mcp_server main:app --host 0.0.0.0 --port 8200
```
### Subida com Docker Compose
```
cd projeto_multi_agent_isolado
docker compose up --build
```
No Docker Compose, o backend usa mcp_servers.docker.yaml porque, dentro da rede do compose, localhost apontaria para o próprio container do backend. Por isso os endpoints usam nomes de serviço: telecom-mcp e retail-mcp.
```
services:
telecom-mcp:
ports:
- "8100:8100"
retail-mcp:
ports:
- "8200:8200"
backend:
environment:
MCP_SERVERS_CONFIG_PATH: /app/config/mcp_servers.docker.yaml
depends_on:
- telecom-mcp
- retail-mcp
```
### Como testar as tools MCP
### Health check direto nos servidores
```
curl http://localhost:8100/health
curl http://localhost:8200/health
```
### Listar tools diretamente no Telecom MCP
```
curl http://localhost:8100/mcp/tools/list
```
### Chamar tool diretamente no Telecom MCP
```
curl -X POST http://localhost:8100/mcp/tools/call -H 'Content-Type: application/json' -d '{
"tool_name": "consultar_fatura",
"arguments": {
"msisdn": "11999999999",
"invoice_id": "INV-001"
}
}'
```
### Chamar tool diretamente no Retail MCP
```
curl -X POST http://localhost:8200/mcp/tools/call -H 'Content-Type: application/json' -d '{
"tool_name": "consultar_pedido",
"arguments": {
"order_id": "PED-1001",
"customer_id": "C-001"
}
}'
```
### Testar via backend do agente
Após subir os servidores MCP e o backend, o backend disponibiliza endpoints de debug para listar e chamar tools através do MCPToolRouter.
```
cd agent_template_backend
python -m venv .venv
source .venv/bin/activate
pip install -e ../agent_framework
pip install -r requirements.txt
uvicorn app.main:app --reload --reload-dir app --reload-dir config --port 8000
curl http://localhost:8000/debug/mcp/tools
curl -X POST http://localhost:8000/debug/mcp/call/consultar_fatura -H 'Content-Type: application/json' -d '{"msisdn":"11999999999","invoice_id":"INV-001"}'
```
### Como o agente chama MCP no fluxo
O agente não precisa conhecer a URL do servidor. Ele chama uma tool lógica pelo MCPToolRouter. O fluxo esperado é:
```
Usuário
-> FastAPI /gateway/message
-> Guardrails de input
-> Router ou Supervisor escolhe o agente
-> LangGraph executa o agent graph
-> Agent decide usar uma tool
-> MCPToolRouter.call("consultar_fatura", {...})
-> MCPRegistry resolve servidor telecom
-> MCPHttpClient chama http://localhost:8100/mcp/tools/call
-> Resultado volta ao agent graph
-> Guardrails de output
-> Judges
-> Resposta final
```
### Exemplo conceitual em Python
```
result = await tool_router.call(
"consultar_fatura",
{
"msisdn": context.get("msisdn"),
"invoice_id": context.get("invoice_id"),
},
)
if result.ok:
dados_fatura = result.result
else:
# fallback controlado, telemetria e resposta segura
erro = result.error
```
### Exemplo via mensagem do gateway
```
curl -X POST http://localhost:8000/gateway/message -H 'Content-Type: application/json' -d '{
"channel": "web",
"payload": {
"session_id": "sess-tel-1",
"message": "Minha fatura veio alta",
"context": {
"msisdn": "11999999999",
"invoice_id": "INV-001"
}
}
}'
curl -X POST http://localhost:8000/gateway/message -H 'Content-Type: application/json' -d '{
"channel": "web",
"payload": {
"session_id": "sess-ret-1",
"message": "Meu pedido não chegou",
"context": {
"order_id": "PED-1001",
"customer_id": "C-001"
}
}
}'
```
### Como configurar novos servidores e tools
### Adicionar um novo MCP Server
Edite agent_template_backend/config/mcp_servers.yaml para execução local:
```
servers:
crm:
transport: http
endpoint: http://localhost:8300/mcp
enabled: true
description: MCP Server de CRM.
```
Edite agent_template_backend/config/mcp_servers.docker.yaml para execução em Docker:
```
servers:
crm:
transport: http
endpoint: http://crm-mcp:8300/mcp
enabled: true
description: MCP Server de CRM via docker-compose.
```
### Registrar uma nova tool
Edite agent_template_backend/config/tools.yaml:
```
tools:
consultar_cliente:
description: Consulta dados cadastrais resumidos do cliente.
mcp_server: crm
enabled: true
args_schema:
customer_id: string
document_id: string
```
### Implementar o endpoint no servidor MCP
```
TOOLS = {
"consultar_cliente": {
"description": "Consulta dados cadastrais resumidos do cliente.",
"input_schema": {
"customer_id": "string",
"document_id": "string"
},
},
}
@app.post("/mcp/tools/call")
async def call_tool(call: ToolCall):
if call.tool_name == "consultar_cliente":
return {
"ok": True,
"result": {
"customer_id": call.arguments.get("customer_id"),
"status": "ATIVO",
"segmento": "PREMIUM"
},
"metadata": {"server": "crm", "tool": "consultar_cliente"}
}
```
### Como isolar MCP por agente
Em uma arquitetura multi-agent, nem todo agente deve enxergar todas as tools. O agente de pedidos pode usar consultar_pedido e consultar_entrega. O agente de contas pode usar consultar_fatura e consultar_pagamentos. Esse isolamento reduz risco operacional, melhora governança e simplifica o prompt de cada agente.
### Opção simples: allowlist por agente
```
agents:
- agent_id: billing_agent
allowed_tools:
- consultar_fatura
- consultar_pagamentos
- consultar_plano
- listar_servicos
- agent_id: orders_agent
allowed_tools:
- consultar_pedido
- consultar_entrega
- solicitar_troca
- solicitar_devolucao
```
### Opção recomendada: tools por arquivo de configuração
Para projetos grandes, cada agente pode ter seu próprio arquivo tools.yaml, guardrails.yaml e judges.yaml. Isso mantém isolamento real por agente e facilita versionamento.
```
config/agents/telecom_contas/
prompt_policy.yaml
guardrails.yaml
judges.yaml
tools.yaml
config/agents/retail_orders/
prompt_policy.yaml
guardrails.yaml
judges.yaml
tools.yaml
```
### Como implantar com Docker e OCI
### Implantação local com Docker Compose
O docker-compose.yml atual já possui serviços separados para telecom-mcp, retail-mcp, backend e frontend. Essa separação é correta porque MCP Servers devem ser escaláveis e versionáveis de forma independente do backend do agente.
```
docker compose up --build
# URLs externas para teste local:
http://localhost:8100/health
http://localhost:8200/health
http://localhost:8000/debug/mcp/tools
http://localhost:5173
```
### Implantação em OCI/OKE
Em Kubernetes/OKE, cada MCP Server deve ser implantado como Deployment + Service. O backend do agente aponta para o DNS interno do Service. Exemplo conceitual:
```
apiVersion: v1
kind: Service
metadata:
name: telecom-mcp
spec:
selector:
app: telecom-mcp
ports:
- port: 8100
targetPort: 8100
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: telecom-mcp
spec:
replicas: 2
selector:
matchLabels:
app: telecom-mcp
template:
metadata:
labels:
app: telecom-mcp
spec:
containers:
- name: telecom-mcp
image: <registry>/telecom-mcp:1.0.0
ports:
- containerPort: 8100
```
### Configuração do backend em Kubernetes
```
servers:
telecom:
transport: http
endpoint: http://telecom-mcp.default.svc.cluster.local:8100/mcp
enabled: true
retail:
transport: http
endpoint: http://retail-mcp.default.svc.cluster.local:8200/mcp
enabled: true
```
### Segurança, guardrails e observabilidade
MCP aumenta muito a capacidade do agente, mas também aumenta a superfície de risco. Uma tool pode consultar dados sensíveis, abrir protocolos, cancelar serviços, gerar créditos ou executar ações de negócio. Por isso, a integração precisa ser protegida antes, durante e depois da chamada.
### Checklist de segurança mínimo
- Toda tool deve ter descrição clara e schema de argumentos.
- Toda tool de ação deve exigir confirmação explícita do usuário antes da execução.
- Cada agente deve ter allowlist de tools.
- Dados sensíveis retornados por MCP devem passar por masking/sanitização antes da resposta final.
- Toda chamada MCP deve gerar trace/span/event em Langfuse ou OpenTelemetry.
- Timeouts e limites de retries devem ser configurados por tool ou por servidor.
- Não expor MCP Servers diretamente à internet sem autenticação, TLS e controle de rede.
- Separar tools read-only de tools transacionais.
### Telemetria recomendada
```
span: mcp.tool_call
attributes:
tenant_id
agent_id
session_id
tool_name
mcp_server
latency_ms
ok
error
input_argument_keys
result_size
event: mcp.tool_call.completed
metadata:
tool_name
server
ok
error
```
### Evolução para MCP oficial
O projeto atual usa um contrato HTTP simplificado. Para produção corporativa, existem duas opções. A primeira é manter esse contrato interno por simplicidade, desde que ele seja bem documentado, seguro e versionado. A segunda é evoluir para um client/server MCP oficial com JSON-RPC, stdio ou Streamable HTTP.
### Passo a passo completo para o desenvolvedor
```
# 1. Baixar e abrir o projeto
cd projeto_multi_agent_isolado
# 2. Subir servidores MCP de exemplo
bash ./scripts/run_mcp_servers.sh
# 3. Em outro terminal, subir backend
cd agent_template_backend
python -m venv .venv
source .venv/bin/activate
pip install -e ../agent_framework
pip install -r requirements.txt
uvicorn app.main:app --reload --reload-dir app --reload-dir config --port 8000
# 4. Validar tools carregadas pelo backend
curl http://localhost:8000/debug/mcp/tools
# 5. Chamar tool Telecom
curl -X POST http://localhost:8000/debug/mcp/call/consultar_fatura -H 'Content-Type: application/json' -d '{"msisdn":"11999999999","invoice_id":"INV-001"}'
# 6. Chamar tool Retail
curl -X POST http://localhost:8000/debug/mcp/call/consultar_pedido -H 'Content-Type: application/json' -d '{"order_id":"PED-1001","customer_id":"C-001"}'
# 7. Testar pelo gateway conversacional
curl -X POST http://localhost:8000/gateway/message -H 'Content-Type: application/json' -d '{"channel":"web","payload":{"session_id":"sess-ret-1","message":"Meu pedido não chegou","context":{"order_id":"PED-1001","customer_id":"C-001"}}}'
```
### Troubleshooting
### Referências
- Model Context Protocol Specification: https://modelcontextprotocol.io/specification
- MCP Transports: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports
- MCP Resources: https://modelcontextprotocol.io/specification/2025-06-18/server/resources
- Reference MCP Servers: https://github.com/modelcontextprotocol/servers
- LangChain MCP Adapters: https://docs.langchain.com/oss/python/langchain/mcp
- Arquivos do projeto: agent_framework/src/agent_framework/mcp/*, agent_template_backend/config/mcp_servers.yaml, agent_template_backend/config/tools.yaml, mcp_servers/*
### Políticas read-only e transacionais
O framework aplica uma política conversacional mínima imediatamente antes da chamada MCP. A classificação read_only identifica consultas; transactional identifica operações que alteram estado. Autorização, idempotência, validação e atomicidade continuam sob responsabilidade do MCP Server.
### Configuração no backend
A configuração é opcional e fica em config/tool_policies.yaml no agent_template_backend. O caminho pode ser definido por TOOL_POLICIES_PATH. Não coloque políticas de domínio dentro da biblioteca compartilhada.
Exemplo:
defaults:
operation_type: read_only
require_confirmation: false
tool_policies:
alterar_plano:
operation_type: transactional
require_confirmation: true
requires: [new_plan_id]
### Execução e compatibilidade
- A confirmação deve chegar como confirmed: true ou confirmation: true; texto com valor true não é suficiente.
- Se tool_policies.yaml não existir, permanecem válidos tool_type, requires, confirmation_required e execution_policy de tools.yaml.
- Tools antigas sem política continuam funcionando sem alteração de comportamento.
- Uma chamada bloqueada não alcança o MCP e retorna metadados blocked_by_policy, operation_type e policy_source.
### Políticas read-only e transacionais
> Conteúdo consolidado a partir de `Documentacao/README_TOOL_POLICIES.md`.
### Objetivo
O framework diferencia operações de consulta (`read_only`) e operações que alteram estado (`transactional`) imediatamente antes da chamada MCP. Essa classificação não substitui autorização, idempotência ou regras de negócio do servidor MCP; ela acrescenta somente a proteção conversacional mínima, especialmente confirmação explícita.
### Onde configurar
A parametrização pertence ao backend da aplicação:
```text
templates/agent_template_backend/config/tool_policies.yaml
```
A biblioteca compartilhada contém apenas o loader e a validação. O caminho é opcional:
```dotenv
TOOL_POLICIES_PATH=./config/tool_policies.yaml
```
### Exemplo
```yaml
version: 1
defaults:
operation_type: read_only
require_confirmation: false
tool_policies:
consultar_plano:
operation_type: read_only
alterar_plano:
operation_type: transactional
require_confirmation: true
requires: [new_plan_id]
```
Para executar `alterar_plano`, os argumentos precisam conter `new_plan_id` e um booleano literal de confirmação:
```json
{"new_plan_id": "CONTROLE_100", "confirmed": true}
```
Também é aceito `"confirmation": true`. Strings como `"true"` não são aceitas como confirmação.
### Compatibilidade
- Se `tool_policies.yaml` não existir, o framework continua usando `tool_type`, `requires`, `confirmation_required` e `execution_policy` de `tools.yaml`.
- Tools antigas sem política continuam executando como antes.
- Uma política explícita no arquivo novo prevalece para `operation_type` e confirmação daquela tool.
- O catálogo `tools.yaml` continua sendo a fonte de endpoint, schema, habilitação e cache.
- O novo arquivo não deve ser colocado em `libs/agent_framework`, pois as decisões variam por aplicação e domínio.
### Fluxo de execução
```text
agente -> MCPToolRouter -> validação da política -> mapeamento de parâmetros -> MCP Gateway/Server
```
Uma chamada bloqueada retorna `ok=false`, `metadata.blocked_by_policy=true`, o tipo da operação e a origem da política. O servidor MCP permanece a autoridade final para autenticação, autorização, validação, idempotência e transação de negócio.
### Migração recomendada
1. Atualize a biblioteca sem criar o arquivo: o comportamento permanece legado.
2. Crie `config/tool_policies.yaml` no backend.
3. Cadastre primeiro apenas operações transacionais que exigem confirmação.
4. Teste chamadas sem confirmação, com confirmação booleana e com campos obrigatórios ausentes.
5. Remova gradualmente duplicações de confirmação de `tools.yaml` quando todos os templates consumidores já usarem a nova configuração.
### Runtime transacional mínimo (correção de amarração)
A lista `mcp_tools` do roteamento é uma **allowlist**, não uma ordem para executar todas as ferramentas. O runtime agora:
1. executa automaticamente somente ferramentas `read_only`;
2. seleciona no máximo uma ação transacional compatível com o pedido do usuário;
3. quando `require_confirmation: true`, persiste `pending_tool_call` e `transaction_status: AWAITING_CONFIRMATION`;
4. no turno de confirmação, reutiliza a mesma chamada e executa com `confirmed: true`;
5. publica no estado `available_mcp_tools`, `selected_tool_call`, `tool_policy_result`, `confirmation_required` e `confirmation_received`.
Para o cenário de exemplo, o pedido `123` (ou `PED-ENTREGUE`) retorna `ENTREGUE` no MCP Retail. Use:
```text
Quero devolver o pedido 123 porque me arrependi da compra.
Sim, confirmo a devolução.
```
O contrato MCP foi padronizado para usar `reason` tanto no catálogo quanto no servidor FastMCP. `tool_policies.yaml` prevalece sobre os campos legados de `tools.yaml`; estes permanecem alinhados nos templates para compatibilidade.
### Integração e compatibilidade das tool policies
> Conteúdo consolidado a partir de `Documentacao/RELEASE_NOTES_TOOL_POLICIES.md`.
### Alterações
- Novo `ToolPolicyRegistry` opcional na biblioteca compartilhada.
- Validação central no `MCPToolRouter`, inclusive para chamadas diretas.
- Tipos mínimos `read_only` e `transactional`.
- Confirmação estrita por `confirmed: true` ou `confirmation: true`.
- Suporte opcional a campos obrigatórios por política.
- Fallback automático para `tool_type`, `requires`, `confirmation_required` e `execution_policy` de `tools.yaml`.
- `config/tool_policies.yaml` e variável `TOOL_POLICIES_PATH` nos templates principais, Day Zero e variantes de `Tuning-Performance/Normal` e `Tuning-Performance/Route_Stickness`.
- Testes unitários de política e compatibilidade adicionados em `tests/unit/test_tool_policies.py`.
### Verificações executadas
- Compilação de `libs`, `templates`, `Tuning-Performance` e `tests`: aprovada.
- Validação estrutural dos seis arquivos YAML: aprovada.
- Casos isolados do loader (política transacional, confirmação, ausência de arquivo e ausência de cadastro): aprovados.
- Renderização dos dois manuais Word atualizados: aprovada, sem cortes ou sobreposição nas páginas adicionadas.
### Limitação do ambiente de validação
A suíte `pytest` foi preparada, mas não pôde ser executada integralmente neste ambiente porque `pytest` e as dependências de runtime do projeto não estavam instalados e o acesso ao índice de pacotes expirou. Para reproduzir em um ambiente do projeto:
```bash
PYTHONPATH=libs/agent_framework/src:templates/agent_template_backend python -m pytest -q
```
### Correção de integração backend/MCP
- `mcp_tools` passou a ser tratado como allowlist.
- Ações não são mais executadas automaticamente junto com consultas.
- Confirmação transacional é persistida e retomada no turno seguinte.
- Corrigida incompatibilidade `reason`/`motivo` no MCP Retail.
- Adicionado pedido entregue determinístico para testes (`123`).
- Removida keyword genérica `produto` da intenção Telecom para evitar colisão com devoluções Retail.
- Templates Normal e Route_Stickness em `Tuning-Performance` foram sincronizados.
### Extração contextual de parâmetros MCP
> Conteúdo consolidado a partir de `Documentacao/RELEASE_NOTES_MCP_PARAMETER_EXTRACTION_FIX.md`.
### Problema corrigido
O bloco `extract` do `mcp_parameter_mapping.yaml` existia na configuração e na
documentação, mas não era executado pelo runtime. Além disso, valores do
Business Context podiam sobrescrever argumentos explícitos, fazendo
`contract_key` substituir o `order_id` informado pelo usuário.
### Correções
- implementação da extração genérica `strategy: llm` após a escolha da tool;
- suporte preservado para `strategy: month_name_pt`;
- profile dedicado `mcp_parameter_extraction`;
- telemetria `llm.mcp_parameter_extraction`;
- `extract` deixou de ser interpretado como mapeamento simples;
- argumentos explícitos/extraídos têm precedência sobre Business Context;
- remoção de `contract_key: order_id` dos templates;
- `order_id` configurado como `string`;
- atualização das variantes em `Tuning-Performance`.
### Resultado esperado
Para a mensagem `consultar pedido 123`, a chamada MCP deve receber
`order_id=123`, mesmo quando o Business Context contém outro `contract_key`.
### Uso local de MCP tools
> Conteúdo consolidado a partir de `Documentacao/README_MCP.md`.
Esta versão adiciona uma camada MCP ao framework:
- `agent_framework.mcp.MCPToolRouter`
- `agent_template_backend/config/mcp_servers.yaml`
- `agent_template_backend/config/tools.yaml`
- `mcp_servers/telecom_mcp_server`
- `mcp_servers/retail_mcp_server`
### Subir localmente
Terminal 1:
```bash
bash ./scripts/run_mcp_servers.sh
```
Terminal 2:
```bash
cd agent_template_backend
python -m venv .venv
source .venv/bin/activate
pip install -e ../agent_framework
pip install -r requirements.txt
uvicorn app.main:app --reload --reload-dir app --reload-dir config --port 8000
```
Terminal 3:
```bash
cd agent_frontend
python -m http.server 5173
```
### Testes rápidos
Listar tools MCP carregadas pelo backend:
```bash
curl http://localhost:8000/debug/mcp/tools
```
Chamar tool diretamente via backend:
```bash
curl -X POST http://localhost:8000/debug/mcp/call/consultar_fatura \
-H 'Content-Type: application/json' \
-d '{"msisdn":"11999999999","invoice_id":"INV-001"}'
```
Roteamento Telecom + MCP:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"session_id":"sess-tel-1","message":"Minha fatura veio alta","context":{"msisdn":"11999999999","invoice_id":"INV-001"}}}'
```
Roteamento Retail + MCP:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"session_id":"sess-ret-1","message":"Meu pedido não chegou","context":{"order_id":"PED-1001","customer_id":"C-001"}}}'
```
### Docker Compose
```bash
docker compose up --build
```
No compose, o backend usa `config/mcp_servers.docker.yaml` para apontar para `telecom-mcp` e `retail-mcp`.
### Operações read-only e transacionais
Use `config/tool_policies.yaml` no backend para classificar somente as operações que precisam de tratamento adicional. A validação é aplicada no roteador central antes do MCP Gateway/Server. O arquivo é opcional e templates antigos continuam usando as políticas já presentes em `tools.yaml`. A configuração completa e o roteiro de migração estão em [README_TOOL_POLICIES.md](README_TOOL_POLICIES.md).
### Arquivos de origem
Os arquivos abaixo foram consolidados neste manual:
- `Documentacao/Manual_Integracao_MCP_Servers_Agent_Framework.docx`
- `Documentacao/README_TOOL_POLICIES.md`
- `Documentacao/RELEASE_NOTES_TOOL_POLICIES.md`
- `Documentacao/RELEASE_NOTES_MCP_PARAMETER_EXTRACTION_FIX.md`
- `Documentacao/README_MCP.md`
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,292 @@
### Guardrails, Judges e Avaliação Transacional
### Como usar este manual
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **guardrails nativos/externos, judges, sampling transacional e grounding**.
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
### Relação com o tutorial principal
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
### Escopo
Guardrails nativos/externos, judges, sampling transacional e grounding.
### Conteúdo técnico consolidado
### Guardrails, Judges e Avaliação Transacional
Manual para guardrails de entrada/saída, extensões específicas por agente, judges externos, execução obrigatória em transações e sinais/evidências usados na avaliação.
### Como usar este documento
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
### Guardrails implementados no framework
> Conteúdo consolidado a partir de `Documentacao/README_GUARDRAILS_IMPLEMENTADOS.md`.
Esta versão adiciona uma camada pragmática de guardrails ao `agent_framework`, inspirada na separação de rails por estágio: input, output, retrieval e execução/tool.
### Rails de input
- `MSIZE` — bloqueia mensagens excessivamente grandes.
- `MSK` — mascara CPF, CNPJ, telefone, e-mail, cartão, CEP, RG, tokens e chaves.
- `TOX` — detecta toxicidade e registra severidade sem bloquear por padrão.
- `PINJ` — detecta prompt injection e registra score.
- `JBRK` — detecta jailbreak/roleplay de burla e registra score.
- `VLOOP` — bloqueia loop conversacional repetitivo.
### Rails de output
- `PII_OUT` — mascara PII na resposta do agente.
- `CMP` — suaviza promessas absolutas e linguagem de garantia excessiva.
- `REVPREC` — bloqueia verbalização de ação operacional sem confirmação de tool.
- `GND` — sinaliza groundedness/risco quando há resposta específica sem evidência.
- `ALUC_RISK` — marca risco de alucinação para telemetria e judges.
### Rails opcionais
- `RET_REL` — valida relevância de chunks de retrieval por score mínimo.
- `TOOL_VAL` — valida ferramenta MCP/tool, argumentos obrigatórios, valores negativos e allowlist.
### Contrato para protocolos autorizados em guardrails de saída
Quando um workflow ou tool produz um **protocolo que deve ser exibido ao próprio cliente**, o código de integração do agente deve registrar esse valor no contexto de saída antes da execução dos guardrails:
```python
ctx["expected_protocols"] = [protocol_number]
```
Esse campo é um **contrato do framework**. Ele informa que aqueles valores específicos foram produzidos ou validados pelo fluxo atual e, portanto, podem ser usados pelos guardrails de saída como evidência de autorização.
Fluxo esperado:
```text
workflow/tool gera protocolo
agente registra em expected_protocols
CMP valida que o protocolo exibido pertence aos valores esperados
DLEX_OUT não bloqueia esse protocolo apenas por classificá-lo como identificador
resposta pode informar o protocolo ao cliente
```
Regras importantes:
- `expected_protocols` deve conter **somente protocolos realmente produzidos/esperados no turno ou transação atual**.
- Não use `expected_protocols` para liberar tokens, credenciais, IDs internos arbitrários ou dados de terceiros.
- A autorização vale somente para os valores listados; outro identificador continua sujeito às regras normais de `DLEX_OUT`.
- O valor deve ser propagado **antes de `output_guardrails`**. Se o protocolo só for adicionado depois, a autorização não terá efeito.
- Em respostas transacionais, mantenha a evidência do protocolo no resultado da tool/workflow para que `CMP`, `GND` e observabilidade consigam correlacionar o valor.
Exemplo:
```python
result = await executar_workflow(...)
protocol_number = result.get("protocol_number") or result.get("protocolo_id")
if protocol_number:
ctx["expected_protocols"] = [str(protocol_number)]
```
#### Troubleshooting: workflow concluiu, mas a resposta foi substituída por mensagem de segurança
Sintoma típico:
```text
workflow = COMPLETED
CMP = allowed
DLEX_OUT = blocked por "protocolo interno"
resposta final = "Não consegui validar essa resposta com segurança..."
```
Verifique, nesta ordem:
1. O protocolo gerado está presente no resultado/evidência da tool ou workflow?
2. O agente propagou o mesmo valor em `ctx["expected_protocols"]`?
3. `expected_protocols` foi preenchido antes de `output_guardrails`?
4. O protocolo presente na resposta é exatamente um dos valores esperados?
5. O `DLEX_OUT` está bloqueando por outro motivo real, como segredo, token ou dado de terceiro?
Se `expected_protocols` estiver ausente, o framework não deve presumir que qualquer identificador textual é seguro para divulgação.
### Arquivos alterados
- `agent_framework/src/agent_framework/guardrails/rails.py`
- `agent_framework/src/agent_framework/guardrails/pipeline.py`
- `agent_framework/src/agent_framework/guardrails/__init__.py`
### Uso rápido
```python
from agent_framework.guardrails.pipeline import GuardrailPipeline
pipeline = GuardrailPipeline()
sanitized_input, input_decisions = await pipeline.run_input(
user_text,
{"history_texts": history_texts},
)
final_answer, output_decisions = await pipeline.run_output(
answer,
context,
)
```
Para tools/MCP:
```python
_, decisions = await pipeline.run_tool(
"cancelar_produto",
{"produto": "VAS", "valor": 0},
{
"required_args": ["produto"],
"allowed_tools": ["cancelar_produto", "consultar_fatura"],
},
)
```
### SPI de guardrails e judges externos
> Conteúdo consolidado a partir de `docs/EXTERNAL_GUARDRAILS_JUDGES.md`.
`agent_framework_oci` supports agent-owned guardrails and judges without importing domain code into the core.
```yaml
output:
- code: ACME_POLICY
type: external
class: app.extensions.guardrails:AcmePolicyRail
```
```yaml
judges:
- name: acme_quality
type: external
class: app.extensions.judges:AcmeQualityJudge
threshold: 0.7
```
Native entries remain unchanged. External synchronous `evaluate()` methods execute in worker threads via `asyncio.to_thread`; asynchronous methods execute concurrently on the framework event loop. Judges run concurrently with `asyncio.gather`, preserving YAML result order. Agent plugins should reuse the LLM supplied by the framework rather than instantiate a separate provider.
The core must not reference a concrete agent package, company, product, telecom identifier or domain-specific policy. Domain-specific variants belong to the agent and should receive distinct public codes/names.
### Compatibility rule
Domain policies must not be replaced by cosmetically generic text inside the core while losing the original policy. The generic core implementation and the agent-specific implementation may coexist; the embedding agent explicitly selects its own code/name in YAML.
Legacy business validators should migrate to the agent domain. A temporary compatibility shim is acceptable for old imports, but new application code must import the agent-owned implementation.
### Execução obrigatória de judges em transações
> Conteúdo consolidado a partir de `docs/JUDGES_TRANSACTIONAL_SAMPLING_FIX.md`.
### Problema
Mesmo com `always_run_for_transactional: true`, os judges podiam ser ignorados
pela amostragem porque o nó `judge` enviava apenas `context`, `route`, `intent` e
`mcp_results`. Os campos transacionais produzidos pelo runtime não chegavam ao
`JudgePipeline`.
### Correção
O nó `judge` agora repassa:
- `transaction_status`
- `confirmation_required`
- `confirmation_received`
- `tool_policy_result`
- `selected_tool_call`
- `pending_tool_call`
- `mcp_results` como evidência
O `JudgePipeline` detecta transações por múltiplos sinais e avalia
`always_run_for_transactional` antes de aplicar `sample_rate`.
Com a configuração abaixo, consultas comuns continuam sendo amostradas em 25%,
mas turnos `AWAITING_CONFIRMATION`, `COMPLETED`, `FAILED` ou `CANCELLED` executam
os judges sempre.
```yaml
enabled: true
sample_rate: 0.25
always_run_for_transactional: true
```
### Validação do Global Supervisor
> Conteúdo consolidado a partir de `docs/docs_GLOBAL_SUPERVISOR_VALIDATION.txt`.
VALIDAÇÃO - GLOBAL SUPERVISOR
Alterações implementadas:
1. Framework
- agent_framework.global_supervisor.models
- agent_framework.global_supervisor.config
- agent_framework.global_supervisor.session_store
- agent_framework.global_supervisor.router
- agent_framework.global_supervisor.client
2. Novo serviço
- agent_gateway/app/main.py
- agent_gateway/app/settings.py
- agent_gateway/config/backends.yaml
- agent_gateway/README.md
- agent_gateway/Dockerfile
- agent_gateway/docs/ARQUITETURA_GLOBAL_SUPERVISOR.md
3. Docker Compose
- serviço agent-gateway adicionado na porta 8010.
Validações executadas:
- python3 -m compileall -q agent_framework/src/agent_framework/global_supervisor agent_gateway/app
Resultado: OK
- Smoke test do roteamento híbrido:
Entrada 1: "Minha fatura veio alta" -> contas
Entrada 2: "e esse valor?" na mesma session_id -> contas por active_backend
Resultado: OK
- Smoke test de import do app FastAPI:
from app.main import app, registry, router
Resultado: OK
Observação:
- O proxy SSE do gateway foi deixado como etapa futura. O endpoint /gateway/message/sse já roteia e encaminha como mensagem normal; para SSE fim-a-fim, pode-se implementar proxy de /gateway/events/{session_id} para o backend ativo.
### Validação de eventos de guardrail
> Conteúdo consolidado a partir de `docs/docs_VALIDATION_GUARDRAILS_IC.txt`.
VALIDATION REPORT - guardrails parallel fail-fast + observer IC
Date: 2026-06-03
compileall: OK
smoke-tests: OK
### Arquivos de origem
Os arquivos abaixo foram consolidados neste manual:
- `Documentacao/README_GUARDRAILS_IMPLEMENTADOS.md`
- `docs/EXTERNAL_GUARDRAILS_JUDGES.md`
- `docs/JUDGES_TRANSACTIONAL_SAMPLING_FIX.md`
- `docs/docs_GLOBAL_SUPERVISOR_VALIDATION.txt`
- `docs/docs_VALIDATION_GUARDRAILS_IC.txt`
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.

View File

@@ -0,0 +1,396 @@
### RAG, BusinessContext e Grounding
### Como usar este manual
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **RAG, providers, BusinessContext, contexto recuperado e grounding**.
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
### Relação com o tutorial principal
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
### Escopo
Rag, providers, businesscontext, contexto recuperado e grounding.
### Conteúdo técnico consolidado
### RAG, Providers Enterprise, BusinessContext e Grounding
Guia de integração de conhecimento recuperado, seleção entre providers de RAG, configuração KBDB, amostras, suficiência MCP e uso do BusinessContext como contrato de dados.
### Como usar este documento
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
### Provider RAG Standard versus KBDB Enterprise
> Conteúdo consolidado a partir de `docs/RAG_PROVIDER_KBDB.md`.
O framework passa a suportar dois backends de retrieval pelo mesmo contrato `RagService`, sem alterar os agentes nem `_retrieve_rag_context()`.
### Seleção
```env
RAG_PROVIDER=standard # default: comportamento anterior
# ou
RAG_PROVIDER=kbdb # KBDB enterprise
```
A seleção é exclusiva por processo. Os dois RAGs não executam juntos e não compartilham vector store, graph store ou ingestão.
### `standard`
Mantém integralmente o RAG já existente no `agent_framework_oci`: `VECTOR_STORE_PROVIDER`, `GRAPH_STORE_PROVIDER`, embedding, query rewrite, compression, retrieval guardrails e geração continuam válidos.
### `kbdb`
O framework integra somente a porta estável de serving do projeto KBDB:
`PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE`
O pipeline enterprise continua externo ao runtime do agente e preserva sua própria arquitetura RAW → SILVER → GOLD, HVI/hybrid search, property graph, publicação, lifecycle, auditoria e observabilidade.
O envelope KBDB é adaptado para `RagResult`/`VectorDocument`; portanto os agentes existentes continuam chamando `_retrieve_rag_context()` e os retrieval guardrails do framework continuam depois do retrieval.
### Configuração
```env
RAG_PROVIDER=kbdb
RAG_TOP_K=5
KBDB_DB_USER=KB_USER
KBDB_DB_PASSWORD=...
KBDB_DB_DSN=...
KBDB_DB_WALLET_LOCATION=...
KBDB_DB_WALLET_PASSWORD=...
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
```
Quando `RAG_PROVIDER=kbdb`, `KBDB_DB_USER`, `KBDB_DB_PASSWORD` e `KBDB_DB_DSN` são obrigatórios. O KBDB usa conexão isolada porque pode residir em outro Autonomous. `KBDB_DB_DSN` segue a mesma semântica de `ADB_DSN`: use o alias TNS existente no `tnsnames.ora` da wallet indicada por `KBDB_DB_WALLET_LOCATION`, e não uma URL `tcps://...`.
### Isolamento e compatibilidade
- `RAG_PROVIDER=standard` não importa nem conecta ao KBDB.
- `RAG_PROVIDER=kbdb` não instancia vector/graph stores do RAG padrão.
- Ingestão por `RagService.add_documents()` não é permitida no modo KBDB: deve passar pelo pipeline/publicação KBDB.
- Query rewrite e context compression continuam opcionais e são aplicados pela camada comum do framework.
- `AgentRuntimeMixin._retrieve_rag_context()` e os agentes permanecem inalterados.
- Falhas do KBDB seguem a semântica existente do framework: retrieval é evidência auxiliar e a exceção é convertida em metadata técnica sem derrubar a jornada.
### Resposta direta de tool e RAG
O framework não considera mais que um resultado MCP estruturado é, por si só, uma resposta suficiente ao usuário.
Uma política `response.renderer` define somente **como** apresentar o resultado. Ela não encerra o fluxo antes de RAG/LLM. Para uma tool deliberadamente produzir uma resposta final direta, a aplicação deve declarar explicitamente:
```yaml
response:
mode: renderer
renderer: meu.renderer
direct: true
```
Sem `direct: true`, o resultado da tool permanece como evidência MCP e o fluxo segue para `_retrieve_rag_context()` e composição LLM. Isso permite, por exemplo, que uma consulta operacional de plano seja combinada com conhecimento documental do KBDB quando a pergunta pedir regras, políticas ou explicações.
O core do framework não possui fallback por nome de tool (`consultar_plano`, `consultar_pedido`, etc.). Regras de apresentação pertencem à aplicação/domínio.
### Suficiência MCP e grounding
Um resultado MCP bem-sucedido **não** faz o framework pular RAG automaticamente.
O domínio só pode declarar suficiência documental explicitamente no payload com
`rag_sufficient=true` ou `knowledge_sufficient=true`. Essa decisão é genérica e
não depende do nome da tool nem de palavras-chave de telecom/retail.
No provider `kbdb`, `KBDB_GROUNDED_ONLY=true` é o padrão. Quando a busca KBDB
retorna vazia, bloqueada ou com erro, a composição LLM pode usar fatos comprovados
por MCP/business context, mas não pode completar a parte documental com conhecimento
paramétrico do modelo. Deve informar que não há evidência suficiente na base.
Eventos do ProductAgent registram `IC.PRODUCT_RAG_CONTEXT_EVALUATED` em toda
tentativa/decisão e `IC.PRODUCT_RAG_CONTEXT_RETRIEVED` somente quando há contexto
recuperado. Os metadados incluem `provider`, `status`, `document_count`, `reason`,
`error`, `query`, `namespace` e `latency_ms`.
### Amostras e testes de RAG
> Conteúdo consolidado a partir de `docs/README_rag_samples.md`.
These PDF files are synthetic, searchable sample documents created to validate the RAG embedding and retrieval flow of `agent_template_backend`.
### Files
- `01_billing_agent_invoice_policy.pdf` - sample knowledge for `billing_agent`
- `02_orders_agent_lifecycle_policy.pdf` - sample knowledge for `orders_agent`
- `03_product_agent_catalog_policy.pdf` - sample knowledge for `product_agent`
- `04_support_agent_sla_policy.pdf` - sample knowledge for `support_agent`
- `05_business_context_rag_flow.pdf` - sample knowledge about BusinessContext, identity.yaml and MCP parameter mapping
### How to use
Copy the PDF files to the backend documentation directory:
```bash
mkdir -p agent_template_backend/docs/rag_samples
cp *.pdf agent_template_backend/docs/rag_samples/
```
For a local smoke test, use:
```env
VECTOR_STORE_PROVIDER=sqlite
EMBEDDING_PROVIDER=mock
SQLITE_DB_PATH=./data/agent_framework.db
RAG_TOP_K=4
```
Then run:
```bash
python scripts/generate_rag_embeddings.py \
--docs-dir ./agent_template_backend/docs/rag_samples \
--namespace default
```
For production-like semantic embeddings with OCI Generative AI, use:
```env
VECTOR_STORE_PROVIDER=autonomous
EMBEDDING_PROVIDER=oci
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..xxxx
OCI_REGION=us-chicago-1
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
```
### Suggested retrieval test questions
- What is a prorated charge?
- When can the OrdersAgent open an exchange request?
- Which SKU represents the AI Agents book?
- What is the target response for a critical support ticket?
- How does BusinessContext map customer_key to MCP tool parameters?
### BusinessContext v2
> Conteúdo consolidado a partir de `Documentacao/README_TEMPLATE_BUSINESS_CONTEXT_V2.md`.
Este pacote atualiza o `agent_template_backend` e o `agent_frontend` para refletir o framework novo, onde as chaves vindas do canal/front-end são resolvidas uma vez como chaves canônicas e propagadas pelas camadas até o MCP Server.
### Fluxo implementado
1. O front-end envia `tenant_id`, `agent_id`, `session_id` e `business_context`.
2. O backend normaliza a mensagem via `ChannelGateway` preservando todo o payload no `context`.
3. O backend usa `IdentityResolver` com `config/identity.yaml` para gerar `BusinessContext`:
- `customer_key`
- `contract_key`
- `interaction_key`
- `account_key`
- `resource_key`
- `session_key`
4. O workflow recebe `context.business_context`.
5. Os agentes de exemplo não montam mais argumentos específicos como `msisdn`, `invoice_id` ou `order_id` diretamente.
6. O `MCPToolRouter` usa `config/mcp_parameter_mapping.yaml` para converter chaves canônicas em parâmetros reais de cada tool MCP.
### Arquivos principais ajustados
- `agent_template_backend/app/main.py`
- carrega `IdentityResolver`;
- resolve `BusinessContext` por mensagem;
- persiste as chaves na sessão/memória/metadata/SSE;
- adiciona `/debug/identity`.
- `agent_template_backend/app/agents/runtime.py`
- adiciona `_collect_mcp_context()` centralizado;
- repassa `business_context` e `original_context` para o MCP Router.
- `agent_template_backend/app/agents/*_agent.py`
- agentes passam a usar `_collect_mcp_context()` em vez de montar argumentos específicos.
- `agent_template_backend/config/identity.yaml`
- define como campos do canal/front-end alimentam as chaves canônicas.
- `agent_template_backend/config/mcp_parameter_mapping.yaml`
- define como chaves canônicas viram parâmetros reais por tool MCP.
- `agent_frontend/index.html` e `agent_frontend/app.js`
- adicionam campos de `tenant`, `agent` e chaves canônicas;
- enviam `business_context` no payload;
- mantêm aliases de domínio para compatibilidade (`msisdn`, `invoice_id`, `order_id`, etc.).
### Teste rápido
Suba backend, frontend e MCP servers. Depois teste:
```bash
curl -s http://localhost:8000/health | jq
curl -s -X POST http://localhost:8000/debug/identity \
-H 'Content-Type: application/json' \
-d '{
"channel":"web",
"tenant_id":"default",
"agent_id":"telecom_contas",
"payload":{
"message":"Minha fatura veio alta",
"session_id":"teste-001",
"msisdn":"11999999999",
"invoice_id":"3000131180",
"ura_call_id":"URA-123",
"business_context":{
"customer_key":"11999999999",
"contract_key":"3000131180",
"interaction_key":"URA-123",
"session_key":"teste-001"
}
}
}' | jq
curl -s -X POST http://localhost:8000/debug/mcp/call/consultar_fatura \
-H 'Content-Type: application/json' \
-d '{
"business_context": {
"customer_key":"11999999999",
"contract_key":"3000131180",
"interaction_key":"URA-123",
"session_key":"teste-001"
}
}' | jq
```
No log do backend, procure por `mcp.tool.mapped`. Ele deve indicar as chaves mapeadas e `has_msisdn=true`, `has_invoice_id=true` para o domínio telecom.
### Integração operacional de RAG e cache
> Conteúdo consolidado a partir de `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`.
Esta versão corrige os gaps identificados na comparação contra o FIRST.
### Correções aplicadas
### 1. Checkpoint LangGraph operacional
O workflow não compila mais com `MemorySaver()` diretamente. Foi criado o adaptador:
```text
agent_framework/checkpoints/langgraph_saver.py
```
Ele conecta o LangGraph ao repository configurado do framework:
- `memory`
- `sqlite`
- `oracle` / `autonomous`
No workflow:
```python
builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
```
### 2. Telemetria LangGraph envolvendo a execução real
Foi adicionado wrapper de nó no workflow:
```python
self._node("billing_agent", self.billing_agent)
```
Assim o span/evento `langgraph.node.*` envolve a execução real do nó, não apenas um bloco vazio.
Eventos emitidos:
- `langgraph.node.started`
- `langgraph.node.completed`
- `langgraph.node.failed`
- `langgraph.edge.selected`
### 3. RAG integrado aos agentes
Os agentes agora recebem `RagService` e usam o contexto recuperado no prompt:
- BillingAgent
- ProductAgent
- OrdersAgent
- SupportAgent
O RAG usa:
- `VECTOR_STORE_PROVIDER=memory|sqlite|oracle|autonomous`
- `GRAPH_STORE_PROVIDER=memory|oracle|autonomous`
- `RAG_TOP_K`
### 4. Cache integrado ao runtime dos agentes
Criado mixin:
```text
agent_template_backend/app/agents/runtime.py
```
Ele adiciona:
- busca RAG padronizada;
- chave de cache para chamada LLM;
- hit/miss com telemetria;
- cache distribuído via `create_cache(settings)`.
### 5. Testes unitários
Criada pasta:
```text
tests/unit
```
Cobertura inicial:
- cache;
- SSE;
- RAG;
- checkpoint saver;
- telemetria LangGraph;
- runtime dos agentes;
- verificação estática do workflow;
- imports principais.
Validação local executada:
```text
12 passed
```
### Como testar
```bash
cd projeto_agent_framework_first_ready
pip install -r agent_template_backend/requirements.txt
pytest -q tests/unit
```
### Arquivos de origem
Os arquivos abaixo foram consolidados neste manual:
- `docs/RAG_PROVIDER_KBDB.md`
- `docs/README_rag_samples.md`
- `Documentacao/README_TEMPLATE_BUSINESS_CONTEXT_V2.md`
- `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.

View File

@@ -0,0 +1,636 @@
### Long-Term Memory e Checkpoint
### Como usar este manual
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **LTM, memória de conversa, isolamento por identidade e persistência de estado**.
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
### Relação com o tutorial principal
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
### Escopo
Ltm, memória de conversa, isolamento por identidade e persistência de estado.
### Conteúdo técnico consolidado
### Long-Term Memory e Checkpoint Enterprise
Manual de implementação de memória durável, isolamento por identidade, stores, extração, integração LangGraph, testes de persistência e diferenças entre LTM, histórico, sumário e checkpoint.
### Como usar este documento
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
### Implementação completa de Long-Term Memory
> Conteúdo consolidado a partir de `Documentacao/Manual_Long_Term_Memory_PT.md`.
### Conceito
A Long-Term Memory (LTM) é a capacidade do `agent_framework` de armazenar e recuperar fatos duradouros além da duração de uma sessão de conversa.
Diferentemente do histórico de mensagens, que normalmente está associado a um `session_id`, a memória de longo prazo é associada à identidade de negócio do usuário ou cliente. Na implementação atual, essa identidade é composta por:
```text
tenant_id
agent_id
customer_key
```
Isso permite que um agente recupere preferências, informações de identidade, projetos e restrições mesmo quando uma nova sessão é criada.
### Para que serve
A Long-Term Memory serve para:
- manter continuidade entre sessões;
- personalizar respostas;
- evitar que o usuário repita informações já fornecidas;
- reduzir a necessidade de enviar todo o histórico ao modelo;
- armazenar preferências, projetos atuais, nomes preferidos e restrições;
- isolar a memória entre tenants, agentes e clientes.
Exemplo:
```text
Sessão A:
"Me chame de Cris. Minha linguagem preferida é Python."
Sessão B, com outro session_id e o mesmo customer_key:
"O que você lembra sobre mim?"
Resposta esperada:
"Seu nome preferido é Cris e sua linguagem preferida é Python."
```
### Diferença entre os tipos de memória
### Conversation Memory
Mantém as mensagens da conversa atual e normalmente está associada ao `session_id`.
### Summary Memory
Mantém um resumo da conversa para reduzir o tamanho do contexto enviado ao modelo.
### Long-Term Memory
Mantém fatos duradouros entre sessões e é associada à identidade de negócio, principalmente ao `customer_key`.
### Componentes da funcionalidade
### LongTermMemoryManager
Responsável por coordenar:
- carregamento das memórias;
- recuperação por identidade;
- renderização do contexto;
- extração de novos fatos;
- persistência dos fatos;
- deduplicação e atualização.
### LongTermMemoryStore
Interface de persistência utilizada pelo manager.
### SQLiteLongTermMemoryStore
Implementação de referência baseada em SQLite.
É apropriada para:
- desenvolvimento local;
- testes;
- demonstrações;
- ambientes de baixa escala.
### InMemoryLongTermMemoryStore
Implementação em memória utilizada para testes rápidos.
O conteúdo é perdido quando o processo do backend é encerrado.
### LongTermMemoryExtractor
Responsável por identificar fatos duradouros nas mensagens.
Exemplos de fatos:
```text
preferred_name = Cris
preferred_language = Python
current_project = Atlas
```
### LongTermMemoryItem
Modelo que representa um item persistido, incluindo identidade, chave, valor, categoria, confiança e metadados.
### AgentRuntime
Carrega a memória antes da execução do agente e injeta o contexto no prompt.
### Nó persist_long_term_memory
Nó do LangGraph responsável por persistir os fatos após a geração e validação da resposta final.
### Estrutura dos arquivos
```text
libs/
└── agent_framework/
└── src/
└── agent_framework/
└── memory/
├── __init__.py
├── long_term_extractor.py
├── long_term_memory.py
├── long_term_models.py
└── long_term_store.py
```
### Fluxo de execução
```text
Mensagem do usuário
AgentRuntime.prepare_memory_context()
├── Conversation Memory
├── Summary Memory
└── Long-Term Memory
long_term_memory_context
Prompt do agente
Agente
Guardrails / Judges / Supervisor
persist_long_term_memory
LongTermMemoryExtractor
LongTermMemoryStore
```
### Configuração do framework
### Novos módulos
Copie os arquivos:
```text
libs/agent_framework/src/agent_framework/memory/long_term_extractor.py
libs/agent_framework/src/agent_framework/memory/long_term_memory.py
libs/agent_framework/src/agent_framework/memory/long_term_models.py
libs/agent_framework/src/agent_framework/memory/long_term_store.py
```
### Atualização de memory/__init__.py
Exporte os componentes da Long-Term Memory:
```python
from agent_framework.memory.long_term_memory import (
LongTermMemoryManager,
create_long_term_memory_manager,
)
from agent_framework.memory.long_term_models import LongTermMemoryItem
from agent_framework.memory.long_term_store import (
InMemoryLongTermMemoryStore,
LongTermMemoryStore,
SQLiteLongTermMemoryStore,
create_long_term_memory_store,
)
```
### Atualização de settings.py
Adicione as configurações:
```python
ENABLE_LONG_TERM_MEMORY: bool = False
LONG_TERM_MEMORY_PROVIDER: str = "sqlite"
LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db"
LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory"
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20
LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70
LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True
LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True
```
### Integração com AgentRuntime
O runtime deve:
1. verificar se a funcionalidade está habilitada;
2. criar o manager quando necessário;
3. recuperar os fatos pela identidade;
4. preencher o estado;
5. injetar o contexto no prompt.
Campos adicionados ao estado:
```python
long_term_memories: list[dict]
long_term_memory_context: str
long_term_memory_write_result: dict
```
### Inicialização no AgentWorkflow
O manager deve ser criado no `AgentWorkflow`:
```python
self.long_term_memory_manager = create_long_term_memory_manager(
settings,
telemetry=telemetry,
)
```
### Inicialização correta dos agentes
O `long_term_memory_manager` não deve ser passado pelo `agent_kwargs` caso os construtores de `BillingAgent`, `ProductAgent`, `OrdersAgent` e `SupportAgent` não declarem esse parâmetro.
Esta inicialização causa erro:
```python
agent_kwargs = {
"telemetry": telemetry,
"settings": settings,
"memory": memory,
"summary_memory": summary_memory,
"long_term_memory_manager": self.long_term_memory_manager,
}
self.billing = BillingAgent(llm, **agent_kwargs)
```
Erro resultante:
```text
TypeError: BillingAgent.__init__() got an unexpected keyword argument
'long_term_memory_manager'
```
A forma recomendada é criar os agentes com a assinatura já existente e injetar o manager como atributo após a inicialização:
```python
agent_kwargs = {
"telemetry": telemetry,
"tool_router": getattr(self, "tool_router", None),
"rag_service": self.rag_service,
"cache": self.cache,
"settings": settings,
"observer": self.observer,
"memory": memory,
"summary_memory": summary_memory,
}
self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs)
self.orders = OrdersAgent(llm, **agent_kwargs)
self.support = SupportAgent(llm, **agent_kwargs)
for agent in (
self.billing,
self.product,
self.orders,
self.support,
):
agent.long_term_memory_manager = self.long_term_memory_manager
```
Essa abordagem evita alterar os construtores de todos os agentes e mantém a funcionalidade encapsulada no framework.
### Configuração do LangGraph
Registre o nó:
```python
builder.add_node(
"persist_long_term_memory",
self._node(
"persist_long_term_memory",
self.persist_long_term_memory,
),
)
```
Altere o fluxo:
```python
builder.add_edge(
"supervisor_review",
"persist_long_term_memory",
)
builder.add_edge(
"persist_long_term_memory",
"persist",
)
```
Implemente o método:
```python
async def persist_long_term_memory(
self,
state: AgentState,
) -> dict[str, object]:
result = await self.long_term_memory_manager.persist_turn(state)
return {
"long_term_memory_write_result": result,
}
```
Fluxo final:
```text
supervisor_review
persist_long_term_memory
persist
```
### Variáveis de ambiente
```env
ENABLE_LONG_TERM_MEMORY=true
LONG_TERM_MEMORY_PROVIDER=sqlite
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
LONG_TERM_MEMORY_AUTO_EXTRACT=true
LONG_TERM_MEMORY_INJECT_CONTEXT=true
```
### Caminho do banco SQLite
O caminho relativo é resolvido a partir do diretório em que o backend é iniciado.
Para evitar que bancos diferentes sejam criados acidentalmente, prefira um caminho absoluto em ambientes de desenvolvimento:
```env
LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db
```
Crie a pasta antes de iniciar:
```bash
mkdir -p data
```
### Como testar
### Teste 1 — Gravação
Envie:
```json
{
"session_id": "default:telecom_contas:memory-session-a",
"customer_key": "11999999999",
"message": "Me chame de Cris. Minha linguagem preferida é Python e meu projeto atual se chama Atlas."
}
```
### Teste 2 — Recuperação em outra sessão
Utilize outro `session_id`, mantendo o mesmo `customer_key`:
```json
{
"session_id": "default:telecom_contas:memory-session-b",
"customer_key": "11999999999",
"message": "O que você lembra sobre mim, minhas preferências e meu projeto?"
}
```
Resultado esperado:
```text
Seu nome preferido é Cris.
Sua linguagem preferida é Python.
Seu projeto atual se chama Atlas.
```
### Teste 3 — Isolamento
Utilize outro cliente:
```json
{
"session_id": "default:telecom_contas:memory-session-c",
"customer_key": "outro-cliente",
"message": "Qual é meu nome preferido e qual é meu projeto atual?"
}
```
Os dados de `11999999999` não devem aparecer.
### Teste 4 — Reinicialização do frontend
Reinicie ou resete o frontend e confirme que ele continua enviando o mesmo `customer_key`.
A memória deve sobreviver à troca do `session_id`. O reset do frontend não apaga o SQLite.
### Teste 5 — Reinicialização do backend
Reinicie o Uvicorn e repita a consulta.
Com:
```env
LONG_TERM_MEMORY_PROVIDER=sqlite
```
a memória deve continuar disponível.
Com:
```env
LONG_TERM_MEMORY_PROVIDER=memory
```
a memória será perdida quando o processo for encerrado.
### Verificação direta no SQLite
Localize o banco:
```bash
find . -name "agent_framework.db" -type f
```
Abra:
```bash
sqlite3 ./data/agent_framework.db
```
Consulte:
```sql
SELECT
tenant_id,
agent_id,
customer_key,
memory_type,
memory_key,
memory_value,
confidence,
created_at,
updated_at
FROM agentfw_long_term_memory
ORDER BY updated_at DESC;
```
### Critérios de sucesso
A implementação está funcionando quando:
- a memória é recuperada com outro `session_id`;
- o mesmo `customer_key` recupera os fatos anteriores;
- outro `customer_key` não acessa esses fatos;
- reiniciar o frontend não apaga a memória;
- reiniciar o backend não apaga a memória quando o provider é SQLite;
- o nó `persist_long_term_memory` é executado;
- o prompt recebe `long_term_memory_context`.
### Boas práticas
- Persistir somente fatos duradouros.
- Não armazenar a conversa completa como Long-Term Memory.
- Isolar dados por `tenant_id`, `agent_id` e `customer_key`.
- Não utilizar `session_id` como identidade permanente do usuário.
- Persistir somente depois das validações finais.
- Evitar armazenar resultados temporários de ferramentas.
- Registrar telemetria de leitura, escrita, atualização e falha.
- Definir políticas de retenção e exclusão.
- Usar caminho absoluto para SQLite em ambientes com múltiplos diretórios de execução.
- Migrar para um banco corporativo em ambientes de produção e alta disponibilidade.
### Limitações da implementação de referência
A implementação atual utiliza extração baseada em regras e SQLite como provider de referência.
Evoluções recomendadas:
- extração de fatos com LLM;
- memória semântica com vetores;
- memória episódica;
- expiração e versionamento;
- deduplicação semântica;
- política de consentimento;
- API de consulta e exclusão;
- provider Oracle Autonomous Database;
- criptografia e classificação de dados sensíveis.
### Checkpoint Enterprise no LangGraph
> Conteúdo consolidado a partir de `Documentacao/README_CHECKPOINT_ENTERPRISE.md`.
Esta versão adiciona quatro capacidades ao checkpointer do LangGraph usado pelo framework:
1. **Checkpoint Integrity**: cada checkpoint é salvo dentro de um envelope com `schema_version`, `checkpoint_id`, `payload_hash` SHA-256 e `created_at`. Na leitura, o hash é recalculado. Se o payload foi truncado, alterado ou corrompido, o checkpoint é ignorado no recovery.
2. **Checkpoint Compaction**: checkpoints antigos são removidos automaticamente conforme a configuração `CHECKPOINT_COMPACT_EVERY` e `CHECKPOINT_KEEP_LAST`. Isso evita crescimento infinito da tabela `workflow_checkpoints`.
3. **Resilient Checkpointer**: gravações e leituras usam retry com backoff e jitter. A camada resiliente funciona sobre memory, SQLite e Oracle/Autonomous Database.
4. **Checkpoint Recovery**: ao recuperar o estado, o framework varre os últimos checkpoints e retorna o mais recente válido, pulando checkpoints corrompidos.
### Configuração
No `.env`:
```env
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
ENABLE_RESILIENT_CHECKPOINTER=true
ENABLE_CHECKPOINT_INTEGRITY=true
ENABLE_CHECKPOINT_COMPACTION=true
CHECKPOINT_COMPACT_EVERY=50
CHECKPOINT_KEEP_LAST=20
CHECKPOINT_RECOVERY_SCAN_LIMIT=25
CHECKPOINT_RETRY_MAX_ATTEMPTS=3
CHECKPOINT_RETRY_BASE_DELAY_SECONDS=0.05
CHECKPOINT_RETRY_MAX_DELAY_SECONDS=1.0
CHECKPOINT_RETRY_JITTER_SECONDS=0.05
```
Para produção com múltiplos pods, prefira:
```env
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
ADB_USER=...
ADB_PASSWORD=...
ADB_DSN=...
ADB_WALLET_LOCATION=...
ADB_TABLE_PREFIX=AGENTFW
```
### Uso no LangGraph
```python
from agent_framework.checkpoints import create_langgraph_checkpointer
checkpointer = create_langgraph_checkpointer(settings)
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": session_id}}
result = graph.invoke(input_state, config=config)
```
O `thread_id` continua sendo a chave de recuperação da conversa. Em ambiente com Load Balancer, qualquer pod consegue retomar a execução se usar o mesmo repositório persistente.
### Arquivos alterados
- `agent_framework/src/agent_framework/checkpoints/checkpoint_repository.py`
- `agent_framework/src/agent_framework/checkpoints/langgraph_saver.py`
- `agent_framework/src/agent_framework/checkpoints/__init__.py`
- `agent_framework/src/agent_framework/config/settings.py`
- `tests/unit/test_resilient_checkpointer.py`
### Observação importante
O provider `memory` agora também usa o `RepositoryCheckpointSaver` quando `ENABLE_RESILIENT_CHECKPOINTER=true`. Para voltar ao `MemorySaver` puro do LangGraph em testes locais, configure:
```env
ENABLE_RESILIENT_CHECKPOINTER=false
CHECKPOINT_REPOSITORY_PROVIDER=memory
```
### Arquivos de origem
Os arquivos abaixo foram consolidados neste manual:
- `Documentacao/Manual_Long_Term_Memory_PT.md`
- `Documentacao/README_CHECKPOINT_ENTERPRISE.md`
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.

View File

@@ -0,0 +1,118 @@
### LLM Rich Response e reasoning_content
### Como usar este manual
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **`ainvoke_response()`, metadados de inferência e `reasoning_content` opcional**.
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
### Relação com o tutorial principal
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
### Escopo
`ainvoke_response()`, metadados de inferência e `reasoning_content` opcional.
### Conteúdo técnico consolidado
### LLM Rich Response e reasoning_content
Guia para usar a API opt-in de resposta estruturada do LLM sem quebrar o contrato legado de ainvoke(), incluindo reasoning_content, usage, model, provider, fallback e testes.
### Como usar este documento
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
### API rica de resposta LLM
> Conteúdo consolidado a partir de `docs/LLM_RICH_RESPONSE.md`.
### Objetivo
O framework mantém `ainvoke()` como API retrocompatível, retornando apenas `str`, e adiciona `ainvoke_response()` para consumidores que precisam de metadados adicionais da inferência, incluindo `reasoning_content` quando o modelo/provider/API o disponibilizar.
### APIs
### API legada — sem alteração
```python
answer = await llm.ainvoke(messages)
assert isinstance(answer, str)
```
Nenhum agente existente precisa ser alterado.
### Nova API rica — opt-in
```python
response = await llm.ainvoke_response(messages)
answer = response.content
reasoning = response.reasoning_content
usage = response.usage
model = response.model
provider = response.provider
```
`reasoning_content` é `str | None`. `None` é o comportamento esperado quando o modelo, provider ou API não expõe reasoning textual.
### Backoffice
Um consumidor que antes fazia:
```python
answer = await llm.ainvoke(messages)
template = extract_response(answer)
```
pode passar a fazer:
```python
response = await llm.ainvoke_response(messages)
template = extract_response(response.content)
reasoning_content = response.reasoning_content
```
A lógica que espera texto continua recebendo `response.content`; o reasoning fica separado e não contamina resposta, cache, memória, judges ou guardrails.
### Compatibilidade de providers customizados
`LLMProvider.ainvoke_response()` possui fallback. Um provider externo que implemente apenas `ainvoke()` continua funcionando e recebe automaticamente um `LLMResponse(content=<texto>)`, com `reasoning_content=None`.
Providers nativos (`mock`, OpenAI-compatible/OCI OpenAI e OCI SDK) implementam a resposta rica e tentam preservar reasoning quando presente.
### Garantias de compatibilidade
- `ainvoke()` continua retornando `str`.
- Nenhum router, judge, RAG, memória, cache ou runtime existente foi migrado para a nova API.
- `reasoning_content` nunca é fabricado pelo framework.
- Ausência de reasoning não gera erro.
- O output existente de telemetria continua sendo o conteúdo final, sem anexar reasoning automaticamente.
### Testes
Os testes específicos estão em `tests/unit/test_llm_rich_response.py` e verificam:
1. provider legado que só implementa `ainvoke()`;
2. manutenção do retorno `str` em `ainvoke()`;
3. retorno de `LLMResponse` em `ainvoke_response()`;
4. reasoning via atributo direto;
5. reasoning via `model_extra`;
6. ausência de reasoning e extração no formato OCI SDK.
### Arquivos de origem
Os arquivos abaixo foram consolidados neste manual:
- `docs/LLM_RICH_RESPONSE.md`
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.

View File

@@ -0,0 +1,356 @@
### Performance, Cache e Runtime Assíncrono
### Como usar este manual
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **concorrência, cache, redução de chamadas LLM e correções cross-loop**.
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
### Relação com o tutorial principal
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
### Escopo
Concorrência, cache, redução de chamadas llm e correções cross-loop.
### Conteúdo técnico consolidado
### Performance, Cache, Concorrência e Runtime Assíncrono
Manual das otimizações no caminho crítico de MCP, RAG e Judges, redução de chamadas LLM, preempção determinística e correção de deadlock cross-loop no sequenciamento.
### Como usar este documento
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
### Otimizações MCP, RAG e Judges
> Conteúdo consolidado a partir de `docs/PERFORMANCE_OPTIMIZATIONS_MCP_JUDGES_RAG.md`.
- `mcp_tools` permanece allowlist; somente a consulta selecionada por `selection_keywords` é executada.
- Extração `strategy: hybrid` tenta `pattern` regex antes do perfil LLM.
- RAG é ignorado quando MCP bem-sucedido é suficiente, salvo perguntas de política/regra.
- `mcp_results` é fornecido como evidência ao groundedness judge.
- `judges.yaml` aceita `sample_rate` e `always_run_for_transactional`.
- Consultas estruturadas simples podem retornar resposta determinística sem LLM do agente.
### Mudança de consulta para ação transacional
A route stickiness é preemptada quando uma keyword explícita configurada em `routing.yaml` identifica outra intent/agente. Assim, uma sessão em `retail_order_tracking` muda para `retail_support_exchange_return` ao receber pedidos como “devolver pedido”. Além disso, respostas diretas de tools read-only são bloqueadas quando a mensagem contém `selection_keywords` de qualquer tool transacional registrada.
As palavras de ação ficam em `config/tools.yaml`; o runtime não mantém aliases de domínio hardcoded.
### Preempção determinística de mudança explícita de intent
A stickiness não chama um segundo LLM quando a mensagem contém uma mudança explícita que pode ser reconhecida deterministicamente. Keywords multi-token configuradas em `routing.yaml` aceitam até três tokens intermediários, preservando a ordem. Assim, `cancelar pedido` reconhece `quero cancelar meu pedido`, `cancelar o meu pedido` e `pode cancelar esse pedido`. Nesse caso a nova intent preempta a stickiness e o metadado `keyword_match_strategy=ordered_tokens` permite auditar a decisão. Mensagens sem sinal explícito continuam usando a route stickiness normalmente.
### Correção de deadlock cross-loop
> Conteúdo consolidado a partir de `Documentacao/FIX_DEADLOCK_SEQUENCE_CROSS_LOOP.md`.
### Problema
A API síncrona `agent_framework.observer.event()` podia ser chamada em uma worker thread sem event loop ativo. Nesse caso, a implementação anterior executava `asyncio.run(aevent(...))`, criando um novo event loop temporário. Ao mesmo tempo, `analytics/tim_sequence.py` compartilhava instâncias globais de `asyncio.Lock` (`_mongo_index_lock` e `_memory_lock`) entre chamadas que podiam vir de event loops diferentes.
Na primeira operação Mongo, `_ensure_mongo_ttl_index_once()` mantinha `_mongo_index_lock` durante a criação do índice TTL. A contenção por outro loop podia deixar a segunda chamada aguardando indefinidamente.
### Alterações aplicadas
1. `observer.py`
- removido `asyncio.run()` do caminho síncrono de `event()`;
- adicionado um event loop dedicado e reutilizável para chamadas síncronas;
- submissão cross-thread feita com `asyncio.run_coroutine_threadsafe()`;
- encerramento best-effort do loop no shutdown do processo.
2. `analytics/tim_sequence.py`
- `_mongo_index_lock`: `asyncio.Lock` -> `threading.Lock`;
- `_memory_lock`: `asyncio.Lock` -> `threading.Lock`;
- inicialização do índice TTL movida para uma função síncrona protegida por lock de thread e chamada via `asyncio.to_thread()`;
- o contador de fallback em memória usa uma seção crítica curta e thread-safe.
3. Testes
- `tests/test_observer_cross_loop_deadlock_fix.py` valida:
- múltiplas worker threads usando `event()` compartilham o mesmo loop síncrono do observer;
- sequence em memória permanece monotônica entre event loops independentes;
- criação do índice TTL ocorre apenas uma vez sob contenção cross-loop.
### Validação executada
```bash
PYTHONPATH=libs/agent_framework/src pytest -q tests/test_observer_cross_loop_deadlock_fix.py
```
Resultado: `3 passed`.
A suíte completa do repositório possui falhas preexistentes/independentes desta alteração, incluindo conflitos de coleta de arquivos `test_long_term_memory.py`, caminhos estáticos de template e testes de checkpoint/workflow. Esses itens não foram alterados por esta correção.
### Recursos operacionais de performance
> Conteúdo consolidado a partir de `Documentacao/README_MAX_OPERACIONAL.md`.
Esta versão adiciona os ajustes operacionais que faltavam para aproximar o framework do padrão FIRST em produção.
### Ajustes incluídos nesta versão
### 1. Langfuse Enterprise Adapter
Novo módulo:
```text
agent_framework/observability/langfuse_enterprise.py
```
Inclui adaptador compatível com SDKs Langfuse v2/v3 para:
- atualização de trace;
- score/avaliação de trace;
- prompt registry quando suportado pelo SDK;
- isolamento das diferenças de API do Langfuse.
### 2. Token e Cost Accounting persistente
Novo pacote:
```text
agent_framework/billing/
```
Inclui:
- `UsageRecord`
- `SQLiteUsageRepository`
- `OracleUsageRepository`
- `create_usage_repository(settings)`
O provider LLM agora registra automaticamente:
- `prompt_tokens`
- `completion_tokens`
- `cached_tokens`
- `total_tokens`
- `cost_usd`
- `cost_brl`
- `tenant_id`
- `agent_id`
- `session_id`
- `message_id`
Novo endpoint:
```http
GET /debug/usage
GET /debug/usage?tenant_id=default
GET /debug/usage?session_id=<id>
```
### 3. RAG Service operacional
Novo módulo:
```text
agent_framework/rag/rag_service.py
```
Inclui:
- `RagService.add_documents()`
- `RagService.retrieve()`
- `RagResult.as_prompt_context()`
- telemetria de latência, quantidade de documentos, top scores e grafo.
### 4. Configuração nova
Variável adicionada:
```env
USAGE_REPOSITORY_PROVIDER=sqlite
```
Valores:
```text
sqlite
oracle
autonomous
```
### 5. Compatibilidade operacional local
Por padrão, a contabilização de uso usa SQLite mesmo que o restante esteja em memória. Assim é possível testar localmente sem Oracle.
### Teste rápido
```bash
cd agent_template_backend
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Teste uma mensagem:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"text":"teste","user_id":"u1","session_id":"s1"}}'
```
Verifique uso/custo:
```bash
curl http://localhost:8000/debug/usage
```
### Para rodar com padrão mais próximo de produção
```env
SESSION_REPOSITORY_PROVIDER=sqlite
MEMORY_REPOSITORY_PROVIDER=sqlite
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
USAGE_REPOSITORY_PROVIDER=sqlite
CACHE_BACKEND_PROVIDER=sqlite
VECTOR_STORE_PROVIDER=sqlite
ENABLE_LANGFUSE=true
LANGFUSE_HOST=http://localhost:3000
LANGFUSE_PUBLIC_KEY=...
LANGFUSE_SECRET_KEY=...
```
Para Autonomous Database:
```env
SESSION_REPOSITORY_PROVIDER=oracle
MEMORY_REPOSITORY_PROVIDER=oracle
CHECKPOINT_REPOSITORY_PROVIDER=oracle
USAGE_REPOSITORY_PROVIDER=oracle
CACHE_BACKEND_PROVIDER=oracle
VECTOR_STORE_PROVIDER=oracle
GRAPH_STORE_PROVIDER=oracle
ADB_USER=...
ADB_PASSWORD=...
ADB_DSN=...
ADB_WALLET_LOCATION=...
ADB_TABLE_PREFIX=AGENTFW
```
### Ajustes finais de cache, RAG e telemetria
> Conteúdo consolidado a partir de `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`.
Esta versão corrige os gaps identificados na comparação contra o FIRST.
### Correções aplicadas
### 1. Checkpoint LangGraph operacional
O workflow não compila mais com `MemorySaver()` diretamente. Foi criado o adaptador:
```text
agent_framework/checkpoints/langgraph_saver.py
```
Ele conecta o LangGraph ao repository configurado do framework:
- `memory`
- `sqlite`
- `oracle` / `autonomous`
No workflow:
```python
builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
```
### 2. Telemetria LangGraph envolvendo a execução real
Foi adicionado wrapper de nó no workflow:
```python
self._node("billing_agent", self.billing_agent)
```
Assim o span/evento `langgraph.node.*` envolve a execução real do nó, não apenas um bloco vazio.
Eventos emitidos:
- `langgraph.node.started`
- `langgraph.node.completed`
- `langgraph.node.failed`
- `langgraph.edge.selected`
### 3. RAG integrado aos agentes
Os agentes agora recebem `RagService` e usam o contexto recuperado no prompt:
- BillingAgent
- ProductAgent
- OrdersAgent
- SupportAgent
O RAG usa:
- `VECTOR_STORE_PROVIDER=memory|sqlite|oracle|autonomous`
- `GRAPH_STORE_PROVIDER=memory|oracle|autonomous`
- `RAG_TOP_K`
### 4. Cache integrado ao runtime dos agentes
Criado mixin:
```text
agent_template_backend/app/agents/runtime.py
```
Ele adiciona:
- busca RAG padronizada;
- chave de cache para chamada LLM;
- hit/miss com telemetria;
- cache distribuído via `create_cache(settings)`.
### 5. Testes unitários
Criada pasta:
```text
tests/unit
```
Cobertura inicial:
- cache;
- SSE;
- RAG;
- checkpoint saver;
- telemetria LangGraph;
- runtime dos agentes;
- verificação estática do workflow;
- imports principais.
Validação local executada:
```text
12 passed
```
### Como testar
```bash
cd projeto_agent_framework_first_ready
pip install -r agent_template_backend/requirements.txt
pytest -q tests/unit
```
### Arquivos de origem
Os arquivos abaixo foram consolidados neste manual:
- `docs/PERFORMANCE_OPTIMIZATIONS_MCP_JUDGES_RAG.md`
- `Documentacao/FIX_DEADLOCK_SEQUENCE_CROSS_LOOP.md`
- `Documentacao/README_MAX_OPERACIONAL.md`
- `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.

View File

@@ -0,0 +1,685 @@
### Observabilidade, Persistência e Prontidão Operacional
### Como usar este manual
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **telemetria, IC/NOC/GRL, correlação, sequência, persistência e diagnóstico operacional**.
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
### Relação com o tutorial principal
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
### Escopo
Telemetria, ic/noc/grl, correlação, sequência, persistência e diagnóstico operacional.
### Conteúdo técnico consolidado
### Observabilidade, Persistência e Prontidão Operacional
Guia consolidado das capacidades FIRST-ready: correlação ponta-a-ponta, Langfuse, OpenTelemetry, SSE observável, persistência Oracle, token/cost accounting, cache e telemetria LangGraph.
### Como usar este documento
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
### Base FIRST-ready e observabilidade
> Conteúdo consolidado a partir de `Documentacao/README_FIRST_READY.md`.
Esta versão mantém a arquitetura do `meu_projeto_agent_framework` e adiciona os padrões operacionais encontrados no projeto FIRST.
### Recursos adicionados
1. **SSE no padrão FIRST**
- `GET /gateway/events/{session_id}` para stream `text/event-stream`.
- `POST /gateway/message/sse` para processar mensagem emitindo eventos SSE.
- Eventos: `connected`, `flow.start`, `session.upserted`, `message.received`, `workflow.started`, `workflow.completed`, `message.responded`, `flow.end`.
- Keepalive configurável por `SSE_KEEPALIVE_SECONDS`.
- Lock por sessão para evitar concorrência dentro da mesma conversa.
- Replay de eventos via `Last-Event-ID` ou query param `last_event_id`.
2. **Persistência de sessão e mensagens**
- Implementado provider `sqlite`, executável localmente.
- `SESSION_REPOSITORY_PROVIDER=sqlite`.
- `MEMORY_REPOSITORY_PROVIDER=sqlite`.
- Tabelas locais: `agent_sessions`, `agent_messages`.
- Idempotência por `message_id`.
3. **Checkpoint persistente**
- Implementado provider `sqlite` para checkpoint final do workflow.
- `CHECKPOINT_REPOSITORY_PROVIDER=sqlite`.
- Endpoint de leitura: `GET /sessions/{session_id}/checkpoint`.
4. **Histórico de mensagens**
- Endpoint: `GET /sessions/{session_id}/messages`.
- Histórico usado como memória conversacional antes de chamar o LangGraph.
5. **Cache**
- Novo módulo `agent_framework.cache.cache`.
- Suporta cache local em memória e Redis se `ENABLE_REDIS_CACHE=true`.
6. **RAG / Vector Store**
- `agent_framework.rag.vector_store` agora possui `InMemoryVectorStore`, `SQLiteVectorStore` e contrato `AutonomousVectorStore`.
- A versão SQLite usa busca lexical local para desenvolvimento.
- O contrato permite trocar por Oracle Vector Search sem alterar a camada de aplicação.
7. **Observabilidade**
- Mantém Langfuse existente.
- Acrescenta eventos de gateway/SSE/workflow com `session_id`, `agent_id`, `tenant_id`, `message_id`, rota e intenção.
### Arquitetura resultante
```text
Browser
|-- POST /gateway/message/sse
|-- GET /gateway/events/{session_id}
|
FastAPI Template Backend
|
ChannelGateway
|
SessionRepository + MessageHistory + CheckpointRepository
|
LangGraph AgentWorkflow
|
Guardrails -> Router/Supervisor -> Agent -> Output Guardrails -> Judges
|
Telemetry / Langfuse / OCI Streaming
```
### Como rodar localmente
```bash
cd agent_template_backend
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e ../agent_framework
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Frontend:
```bash
cd agent_frontend
python -m http.server 3000
```
Abra:
```text
http://localhost:3000
```
### Variáveis principais
```env
SESSION_REPOSITORY_PROVIDER=sqlite
MEMORY_REPOSITORY_PROVIDER=sqlite
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
VECTOR_STORE_PROVIDER=sqlite
SQLITE_DB_PATH=./data/agent_framework.db
ENABLE_SSE=true
SSE_KEEPALIVE_SECONDS=15
ENABLE_MESSAGE_IDEMPOTENCY=true
```
### Teste via curl
Mensagem normal:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"text":"teste","message":"teste","session_id":"s1","user_id":"u1","message_id":"m1"}}'
```
Mensagem com SSE:
```bash
curl -N http://localhost:8000/gateway/events/s1
```
Em outro terminal:
```bash
curl -X POST http://localhost:8000/gateway/message/sse \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"text":"teste","message":"teste","session_id":"s1","user_id":"u1","message_id":"m2"}}'
```
Histórico:
```bash
curl http://localhost:8000/sessions/s1/messages
```
Checkpoint:
```bash
curl http://localhost:8000/sessions/s1/checkpoint
```
### Observação importante
A versão adicionada é executável localmente com SQLite. As classes `AutonomousSessionRepository`, `DatabaseMessageHistory`, `AutonomousCheckpointRepository` e `AutonomousVectorStore` mantêm o contrato para Oracle Autonomous Database, mas nesta entrega usam SQLite como backend local para permitir rodar e testar sem infraestrutura Oracle.
### Evolução de Observabilidade no padrão FIRST
Esta versão adiciona uma camada corporativa de observabilidade ao framework, mantendo os componentes reutilizáveis dentro de `agent_framework`.
### Componentes adicionados
```text
agent_framework/observability/
├── context.py # ContextVar: request_id, session_id, user_id, tenant_id, agent_id, channel, ura_call_id, workflow_id, message_id
├── telemetry.py # Facade central: span, event, generation, rag_event, cache_event, checkpoint_event
├── event_bus.py # Event bus interno para plugar logs, SSE, OCI Streaming, Elastic, Phoenix etc.
├── otel.py # OpenTelemetry opcional via OTLP
├── workflow_events.py # workflow.started, node.started, node.completed, edge.selected, workflow.failed
├── guardrail_events.py # guardrail.<CODE>.evaluated e guardrail.<CODE>.blocked
├── judge_events.py # judge.<NAME>.evaluated
├── streaming_events.py # sse.connected, sse.keepalive, sse.event.emitted
└── decorators.py # decorator @traced para classes do framework
```
### Correlação ponta-a-ponta
Cada chamada HTTP cria ou propaga `x-request-id` e o fluxo de mensagem vincula:
```text
request_id → tenant_id → agent_id → session_id → user_id → channel → message_id → workflow_id
```
O contexto usa `ContextVar`, portanto funciona em chamadas assíncronas, FastAPI, LangGraph e providers LLM.
### Langfuse
Ative no `.env`:
```env
ENABLE_LANGFUSE=true
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=http://localhost:3000
```
O framework registra:
```text
Trace de conversa
├── http.request
├── agent.gateway_message
├── workflow.langgraph.ainvoke
├── workflow.input_guardrails
│ └── guardrail.<CODE>.evaluated / blocked
├── workflow.routing_decision
├── workflow.agent.<agent>
│ └── generation.<model>
├── workflow.output_guardrails
├── workflow.judge
│ └── judge.<NAME>.evaluated
├── workflow.supervisor_review
├── workflow.persist
└── sse.event.emitted / sse.keepalive
```
### OpenTelemetry
Ative no `.env`:
```env
ENABLE_OTEL=true
OTEL_SERVICE_NAME=agent-framework-template
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
```
Com isso, os mesmos spans são exportados via OTLP para Elastic, Grafana Tempo, Jaeger, Collector ou outro backend compatível.
### SSE observável
O `SSEHub` agora registra eventos de:
- conexão aberta;
- replay de eventos;
- evento emitido;
- keepalive;
- lock por sessão no processamento de mensagem.
### Guardrails e Judges
Além dos eventos agregados (`guardrails.input.completed`, `judges.completed`), cada decisão individual gera telemetria própria:
```text
guardrail.MSK.evaluated
guardrail.OOS.blocked
judge.response_quality.evaluated
judge.groundedness.evaluated
```
### Extensão para outros backends
A classe `Telemetry.event_bus` permite plugar novos handlers sem alterar o workflow. Exemplo:
```python
async def enviar_para_elastic(event):
...
telemetry.event_bus.subscribe(enviar_para_elastic)
```
---
### Evolução FIRST Enterprise Completa
Esta versão recebeu os componentes que faltavam para aproximar o framework do padrão operacional do projeto FIRST:
### Persistência Oracle Autonomous Database
Foram adicionados providers reais Oracle:
- `OracleSessionRepository`
- `OracleMessageHistory`
- `OracleCheckpointRepository`
- `OracleCache`
- `OracleVectorStore`
- `OracleGraphStore`
- `OracleStore`
Tabelas criadas automaticamente com prefixo configurável `ADB_TABLE_PREFIX`:
- `<PREFIX>_AGENT_SESSION`
- `<PREFIX>_AGENT_MESSAGE`
- `<PREFIX>_WORKFLOW_CHECKPOINT`
- `<PREFIX>_WORKFLOW_CHECKPOINT_WRITE`
- `<PREFIX>_WORKFLOW_CHECKPOINT_BLOB`
- `<PREFIX>_SSE_EVENT`
- `<PREFIX>_CACHE_ENTRY`
- `<PREFIX>_RAG_DOCUMENT`
- `<PREFIX>_GRAPH_EDGE`
### Configuração Oracle
```env
SESSION_REPOSITORY_PROVIDER=oracle
MEMORY_REPOSITORY_PROVIDER=oracle
CHECKPOINT_REPOSITORY_PROVIDER=oracle
CACHE_BACKEND_PROVIDER=oracle
VECTOR_STORE_PROVIDER=oracle
GRAPH_STORE_PROVIDER=oracle
SSE_STORE_PROVIDER=oracle
ADB_USER=ADMIN
ADB_PASSWORD=***
ADB_DSN=meu_adb_high
ADB_WALLET_LOCATION=/path/wallet
ADB_WALLET_PASSWORD=***
ADB_TABLE_PREFIX=AGENTFW
```
### SSE Enterprise
O SSE agora possui:
- lock por sessão (`SessionLockManager`)
- keepalive configurável
- replay por `Last-Event-ID`
- persistência de eventos em SQLite ou Oracle
- telemetria de conexão, replay, keepalive e desconexão
Endpoint:
```text
GET /gateway/events/{session_id}?last_event_id=123
```
### LangGraph Deep Telemetry
Foi adicionado `LangGraphDeepTelemetry` com eventos:
- `langgraph.node.started`
- `langgraph.node.completed`
- `langgraph.node.failed`
- `langgraph.edge.selected`
Esses eventos são enviados para o Event Bus, Langfuse e OpenTelemetry quando habilitados.
### Token e Cost Accounting
Foi adicionado:
- `TokenUsageCollector`
- `CostTracker`
- cálculo de `prompt_tokens`, `completion_tokens`, `cached_tokens`, `total_tokens`
- cálculo de `cost_usd` e `cost_brl`
Configuração opcional:
```env
USD_BRL_RATE=5.0
MODEL_PRICES_JSON={"openai.gpt-4.1":{"input_per_1m":"2.00","output_per_1m":"8.00"}}
```
### Cache Enterprise
O cache agora é em cascata:
```text
L1: InMemory
L2: Redis, SQLite ou Oracle
```
Configuração:
```env
ENABLE_REDIS_CACHE=true
REDIS_URL=redis://localhost:6379/0
```
ou:
```env
CACHE_BACKEND_PROVIDER=oracle
```
### RAG Oracle 23ai
Foi adicionado `OracleVectorStore`, com suporte a coluna `VECTOR` e `VECTOR_DISTANCE()` quando um embedding provider for conectado.
Sem embedding provider, mantém fallback lexical para desenvolvimento local.
Também foi adicionado `OracleGraphStore` com tabela de arestas, pronto para evoluir para PGQL/Property Graph.
### Langfuse
Cada chamada LLM agora gera `generation` com:
- input
- output
- model
- provider
- token usage
- cost metadata
Além disso, spans de workflow, guardrails, judges, RAG, cache, checkpoint, SSE e LangGraph são publicados pelo mesmo Event Bus.
### Extensões Enterprise Plus
> Conteúdo consolidado a partir de `Documentacao/README_FIRST_ENTERPRISE_PLUS.md`.
Esta versão evolui o framework nos quatro blocos solicitados:
1. **Langfuse Enterprise completo**
- `Telemetry.span()` com trace/session/user/metadata/tags.
- `Telemetry.generation()` com `usage`, token/cost metadata e compatibilidade Langfuse v2/v3.
- `Telemetry.score()` para judges/avaliações.
- Eventos arbitrários são registrados como spans seguros para evitar `Unknown observation type` no Langfuse.
2. **Token/Cost Accounting completo**
- `TokenUsageCollector` suporta `prompt_tokens`, `completion_tokens`, `cached_tokens`, `reasoning_tokens` e `total_tokens`.
- Tabela de preços por modelo via `MODEL_PRICES_JSON`.
- Conversão USD→BRL via `USD_BRL_RATE`.
- Persistência em `UsageRepository` e endpoint `/debug/usage`.
3. **Redis distribuído**
- `DistributedCache`: L1 memória + L2 Redis/SQLite/Oracle.
- `RedisCache` com `redis.asyncio` quando disponível e fallback sync.
- Namespace por `CACHE_KEY_PREFIX`.
- Telemetria de cache hit/miss/set/delete.
4. **Oracle Vector + PGQL reais**
- `OracleVectorStore` usa `VECTOR_DISTANCE(..., COSINE)` e `TO_VECTOR()` no Oracle 23ai.
- Tentativa automática de criar vector index quando suportado.
- `OracleGraphStore` usa tabelas `GRAPH_NODE` e `GRAPH_EDGE`.
- Suporte a criação de Property Graph e consulta por `GRAPH_TABLE`/PGQL, com fallback SQL.
Também foi corrigido o problema de duplicação SSE por replay + fila live usando controle de `max_replayed_id` no `SSEHub.subscribe()`.
### Testes
```bash
PYTHONPATH=agent_framework/src pytest -q tests/unit
```
Resultado validado nesta geração:
```text
17 passed
```
### Segurança
Os arquivos `.env` foram higienizados para não conter chaves reais. Configure suas credenciais localmente antes de usar OCI/Langfuse.
### Delta para padrão FIRST
> Conteúdo consolidado a partir de `Documentacao/README_FIRST_ENTERPRISE_DELTA.md`.
Esta versão corrige as prioridades levantadas na comparação com o FIRST:
1. Oracle Session Repository real
2. Oracle Message History real
3. Oracle LangGraph Checkpoint Repository real
4. LangGraph Deep Telemetry
5. Token Accounting
6. Cost Accounting
7. Session Lock SSE
8. Replay Buffer SSE
9. KeepAlive SSE
10. Recovery por Last-Event-ID
11. Redis Provider e Distributed Cache
12. Oracle Vector Provider
13. Oracle Graph Provider
14. RAG Telemetry
15. Langfuse Generation Tracking
16. OpenTelemetry/Event Bus compatível
17. OCI Streaming Exporter preservado
A lógica de domínio continua genérica; o framework não copia regras específicas de cobrança do FIRST.
### Operação máxima e contabilização
> Conteúdo consolidado a partir de `Documentacao/README_MAX_OPERACIONAL.md`.
Esta versão adiciona os ajustes operacionais que faltavam para aproximar o framework do padrão FIRST em produção.
### Ajustes incluídos nesta versão
### 1. Langfuse Enterprise Adapter
Novo módulo:
```text
agent_framework/observability/langfuse_enterprise.py
```
Inclui adaptador compatível com SDKs Langfuse v2/v3 para:
- atualização de trace;
- score/avaliação de trace;
- prompt registry quando suportado pelo SDK;
- isolamento das diferenças de API do Langfuse.
### 2. Token e Cost Accounting persistente
Novo pacote:
```text
agent_framework/billing/
```
Inclui:
- `UsageRecord`
- `SQLiteUsageRepository`
- `OracleUsageRepository`
- `create_usage_repository(settings)`
O provider LLM agora registra automaticamente:
- `prompt_tokens`
- `completion_tokens`
- `cached_tokens`
- `total_tokens`
- `cost_usd`
- `cost_brl`
- `tenant_id`
- `agent_id`
- `session_id`
- `message_id`
Novo endpoint:
```http
GET /debug/usage
GET /debug/usage?tenant_id=default
GET /debug/usage?session_id=<id>
```
### 3. RAG Service operacional
Novo módulo:
```text
agent_framework/rag/rag_service.py
```
Inclui:
- `RagService.add_documents()`
- `RagService.retrieve()`
- `RagResult.as_prompt_context()`
- telemetria de latência, quantidade de documentos, top scores e grafo.
### 4. Configuração nova
Variável adicionada:
```env
USAGE_REPOSITORY_PROVIDER=sqlite
```
Valores:
```text
sqlite
oracle
autonomous
```
### 5. Compatibilidade operacional local
Por padrão, a contabilização de uso usa SQLite mesmo que o restante esteja em memória. Assim é possível testar localmente sem Oracle.
### Teste rápido
```bash
cd agent_template_backend
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Teste uma mensagem:
```bash
curl -X POST http://localhost:8000/gateway/message \
-H 'Content-Type: application/json' \
-d '{"channel":"web","payload":{"text":"teste","user_id":"u1","session_id":"s1"}}'
```
Verifique uso/custo:
```bash
curl http://localhost:8000/debug/usage
```
### Para rodar com padrão mais próximo de produção
```env
SESSION_REPOSITORY_PROVIDER=sqlite
MEMORY_REPOSITORY_PROVIDER=sqlite
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
USAGE_REPOSITORY_PROVIDER=sqlite
CACHE_BACKEND_PROVIDER=sqlite
VECTOR_STORE_PROVIDER=sqlite
ENABLE_LANGFUSE=true
LANGFUSE_HOST=http://localhost:3000
LANGFUSE_PUBLIC_KEY=...
LANGFUSE_SECRET_KEY=...
```
Para Autonomous Database:
```env
SESSION_REPOSITORY_PROVIDER=oracle
MEMORY_REPOSITORY_PROVIDER=oracle
CHECKPOINT_REPOSITORY_PROVIDER=oracle
USAGE_REPOSITORY_PROVIDER=oracle
CACHE_BACKEND_PROVIDER=oracle
VECTOR_STORE_PROVIDER=oracle
GRAPH_STORE_PROVIDER=oracle
ADB_USER=...
ADB_PASSWORD=...
ADB_DSN=...
ADB_WALLET_LOCATION=...
ADB_TABLE_PREFIX=AGENTFW
```
### Validação complementar do supervisor
> Conteúdo consolidado a partir de `docs/docs_GLOBAL_SUPERVISOR_VALIDATION.txt`.
VALIDAÇÃO - GLOBAL SUPERVISOR
Alterações implementadas:
1. Framework
- agent_framework.global_supervisor.models
- agent_framework.global_supervisor.config
- agent_framework.global_supervisor.session_store
- agent_framework.global_supervisor.router
- agent_framework.global_supervisor.client
2. Novo serviço
- agent_gateway/app/main.py
- agent_gateway/app/settings.py
- agent_gateway/config/backends.yaml
- agent_gateway/README.md
- agent_gateway/Dockerfile
- agent_gateway/docs/ARQUITETURA_GLOBAL_SUPERVISOR.md
3. Docker Compose
- serviço agent-gateway adicionado na porta 8010.
Validações executadas:
- python3 -m compileall -q agent_framework/src/agent_framework/global_supervisor agent_gateway/app
Resultado: OK
- Smoke test do roteamento híbrido:
Entrada 1: "Minha fatura veio alta" -> contas
Entrada 2: "e esse valor?" na mesma session_id -> contas por active_backend
Resultado: OK
- Smoke test de import do app FastAPI:
from app.main import app, registry, router
Resultado: OK
Observação:
- O proxy SSE do gateway foi deixado como etapa futura. O endpoint /gateway/message/sse já roteia e encaminha como mensagem normal; para SSE fim-a-fim, pode-se implementar proxy de /gateway/events/{session_id} para o backend ativo.
### Arquivos de origem
Os arquivos abaixo foram consolidados neste manual:
- `Documentacao/README_FIRST_READY.md`
- `Documentacao/README_FIRST_ENTERPRISE_PLUS.md`
- `Documentacao/README_FIRST_ENTERPRISE_DELTA.md`
- `Documentacao/README_MAX_OPERACIONAL.md`
- `docs/docs_GLOBAL_SUPERVISOR_VALIDATION.txt`
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.

View File

@@ -0,0 +1,157 @@
# 12 — Feedback de Guardrails de Entrada e Semântica de Turno Bloqueado
## Objetivo
Este documento descreve como o `AgentWorkflow`, implementado em `app/workflows/agent_graph.py`, deve tratar um turno interrompido por guardrail de entrada sem transformar toda interrupção em uma mensagem genérica de “regra de segurança”.
A regra central é separar três coisas:
1. **decisão técnica do guardrail**, usada pelo runtime e pela observabilidade;
2. **mensagem pública ao usuário**, adequada ao tipo de bloqueio ou necessidade de esclarecimento;
3. **estado do turno**, que não pode carregar routing, tools ou judges de um turno que foi interrompido antes dessas etapas.
## Fluxo esperado
```text
mensagem do usuário
input_guardrails
allowed?
├─ sim → routing → tools/agente → composição → output_guardrails
└─ não
classificar tratamento público
limpar estado de routing/tools/judges do turno
construir mensagem pública segura
output_guardrails
persistência/resposta
```
Um guardrail de entrada bloqueante deve ser decidido **antes de qualquer tool com efeito colateral**.
## `reason` interno não é a resposta ao usuário
O campo `reason` deve permanecer disponível para logs, traces, eventos e diagnóstico. Ele não deve ser exibido literalmente quando puder revelar mecanismo interno ou quando a frase técnica não for apropriada ao usuário final.
Exemplo:
```text
COER.reason = "fala incompreensível ou negação ambígua na transcrição"
```
A resposta pública pode ser:
```text
"Não consegui entender sua última mensagem porque ela parece incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
```
## Tratamento por tipo de guardrail
O comportamento exato continua configurável, mas a semântica esperada é:
| Guardrail | Tratamento público recomendado |
|---|---|
| `COER` | solicitar esclarecimento/reformulação; não tratar ambiguidade como incidente de segurança |
| `PINJ` | bloquear com mensagem segura sem explicar o mecanismo interno |
| `DLEX_IN` | bloquear ou orientar reformulação sem expor dado interno/sensível |
| `INPUT_SIZE` | solicitar redução da entrada |
| `TOX` | aplicar a política configurada para conteúdo inadequado |
| `CMP` | responder segundo a política de compliance |
| desconhecido | usar fallback seguro e genérico |
## Limpeza do estado do turno bloqueado
Quando o input é bloqueado antes do routing, o estado final daquele turno não deve reutilizar dados residuais do turno anterior.
No mínimo, o workflow deve evitar apresentar como atuais:
```text
route_decision
mcp_tools
mcp_results
judge_results
```
O metadata deve deixar explícito que o turno foi interrompido no estágio de input guardrails.
Isso evita um diagnóstico falso como:
```text
route = blocked
mcp_results = [tool executada]
```
quando a tool na realidade pertence ao turno anterior.
## Mensagem pública também passa pelos guardrails de saída
Uma resposta criada em função de um bloqueio de entrada ainda é uma saída do agente. Portanto ela deve seguir o mesmo pipeline de validação de saída antes de chegar ao usuário.
Isso permite que `DLEX_OUT`, `PINJ`, `TOXOUT`, Output Supervisor e outras políticas removam ou sanitizem informação que não deva ser apresentada.
## Relação com `agent_graph.py`
Esta feature é responsabilidade da orquestração do template, porque define a precedência entre nós do grafo e o estado do turno.
Ao alterar `app/workflows/agent_graph.py`, preserve estas invariantes:
- `input_guardrails` antecede routing/tools;
- um bloqueio de input não executa ação transacional depois do bloqueio;
- resposta pública não é o `reason` bruto do guardrail;
- estado residual de routing/tools/judges não sobrevive como resultado do turno bloqueado;
- a resposta pública passa por `output_guardrails` antes da persistência/resposta.
A mesma semântica deve ser mantida nos templates oficiais e nas variantes equivalentes em `Tuning-Performance`.
## Troubleshooting
### O usuário recebe “Não consegui seguir com essa mensagem por regra de segurança” para uma frase apenas incompleta
Verifique:
1. qual guardrail retornou `allowed=false`;
2. se `COER` está sendo tratado como esclarecimento e não como bloqueio genérico;
3. se o caminho de bloqueio usa uma mensagem pública específica;
4. se o fallback genérico está sendo usado somente quando não existe tratamento específico.
### O metadata mostra tool executada mesmo com `route=blocked`
Verifique se o ramo de bloqueio limpa o estado transitório do turno antes de retornar a resposta. Confirme também se a tool não foi executada no mesmo turno antes do guardrail de entrada.
### A mensagem de bloqueio expõe detalhes internos
Não use `reason` diretamente como texto público. Gere a mensagem pública e deixe o `reason` apenas em observabilidade.
### A resposta de bloqueio ignora guardrails de saída
Verifique a aresta do grafo. O fluxo esperado é:
```text
input_guardrails bloqueou
→ construir resposta pública
→ output_guardrails
→ persist
```
não:
```text
input_guardrails bloqueou
→ persist
```
## Testes de regressão recomendados
Cubra pelo menos:
- `COER=false` gera solicitação de esclarecimento, não mensagem genérica de segurança;
- ramo bloqueado não conserva `mcp_results`/routing de turno anterior;
- nenhuma tool transacional é executada depois de um bloqueio de input;
- mensagem pública passa pelos guardrails de saída;
- guardrail desconhecido ainda possui fallback seguro.

View File

@@ -0,0 +1,143 @@
### Índice de Desenvolvimento — Agent Framework OCI
### Como usar esta documentação
A documentação possui três níveis claros:
1. **Tutorial principal:** [`README.md`](../../../README.md) — criação, configuração, execução e teste de um agente do início ao fim.
2. **Arquitetura:** [01 — Arquitetura e Conceitos](./01_architecture_and_concepts.md) — componentes, responsabilidades e onde implementar cada coisa.
3. **Referências especializadas:** manuais `02` a `12` — implementação profunda e troubleshooting por capacidade.
Se você está começando um novo agente, comece pelo `README.md`.
Se algo não está funcionando, use **Buscar pelo problema** abaixo.
### Buscar pelo problema
| Problema / dúvida | O que normalmente está envolvido | Onde procurar |
|---|---|---|
| O framework não encontra o agente/intenção correta | routing, intents, threshold, modo determinístico/LLM | [Routing e Stickiness](./02_routing_stickiness_and_intent_shift.md) |
| O agente fica preso no mesmo assunto e não troca de intent | route stickiness, intent shift, handoff | [Routing e Stickiness](./02_routing_stickiness_and_intent_shift.md) |
| Uma resposta que deveria preencher parâmetro é interpretada como novo intent | precedência transacional, parameter extraction | [Workflows Transacionais](./03_transaction_workflows_and_state.md) |
| A transação fica pedindo o mesmo parâmetro | estado transacional, extractor, schema | [Workflows Transacionais](./03_transaction_workflows_and_state.md) e [MCP/Tools](./04_mcp_integration_tools_and_policies.md) |
| A confirmação “sim/não” não continua o fluxo | confirmation state, transaction state | [Workflows Transacionais](./03_transaction_workflows_and_state.md) |
| Uma transação encerrada reaparece | checkpoint antigo versus estado transacional ativo | [Workflows Transacionais](./03_transaction_workflows_and_state.md) e [LTM/Checkpoint](./08_long_term_memory_and_checkpoint.md) |
| O sistema diz que executou algo, mas não existe evidência | MCP result, estado `COMPLETED`, judges transacionais | [Workflows Transacionais](./03_transaction_workflows_and_state.md) e [Guardrails/Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Uma tool não aparece ou não é encontrada | `tools.yaml`, catálogo MCP, discovery | [MCP/Tools](./04_mcp_integration_tools_and_policies.md) |
| MCP Server não aparece no catálogo | registration, manifest/discovery, MCP Gateway | [MCP/Tools](./04_mcp_integration_tools_and_policies.md) e [Gateways](./05_agent_gateway_mcp_gateway_and_auth.md) |
| Parâmetros enviados à tool estão errados | schema, mapping, BusinessContext, extractor | [MCP/Tools](./04_mcp_integration_tools_and_policies.md) |
| Uma operação transacional executa sem confirmação | tool policy, `require_confirmation` | [MCP/Tools](./04_mcp_integration_tools_and_policies.md) |
| Uma busca por nome exige correspondência exata demais | extração/mapeamento de parâmetros e lógica do agente | [MCP/Tools](./04_mcp_integration_tools_and_policies.md) |
| Recebo 401 entre gateway/backend/MCP | Basic Auth, credenciais por hop | [Gateways e Auth](./05_agent_gateway_mcp_gateway_and_auth.md) |
| Preciso decidir se algo pertence ao framework ou ao agente | boundary core/agente | [Arquitetura e Conceitos](./01_architecture_and_concepts.md) |
| Guardrail específico de um agente está quebrando outro | extensibilidade, imports de domínio no core | [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Uma frase incompleta recebe mensagem genérica de “regra de segurança” | feedback de input guardrail, `COER`, blocked-turn state | [Feedback de Guardrails de Entrada](./12_input_guardrail_feedback_and_blocked_turns.md) |
| `route=blocked` aparece junto com tools/resultados de outro turno | limpeza de estado do turno bloqueado | [Feedback de Guardrails de Entrada](./12_input_guardrail_feedback_and_blocked_turns.md) |
| Workflow conclui e gera protocolo, mas a resposta final vira mensagem de segurança | `expected_protocols`, `CMP`, `DLEX_OUT`, ordem de `output_guardrails` | [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Judge não roda em uma transação | sampling, `always_run_for_transactional`, sinais transacionais | [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Groundedness está avaliando sem contexto correto | RAG context, MCP evidence, judge inputs | [RAG/Grounding](./07_rag_business_context_and_grounding.md) |
| RAG não encontra conteúdo | provider, ingestão, embeddings, configuração | [RAG/Grounding](./07_rag_business_context_and_grounding.md) |
| Não sei se usar RAG, memória ou tool | separação de responsabilidades | [Arquitetura e Conceitos](./01_architecture_and_concepts.md) e [RAG/Grounding](./07_rag_business_context_and_grounding.md) |
| Memória desaparece ao trocar de sessão | LTM versus conversation memory | [LTM e Checkpoint](./08_long_term_memory_and_checkpoint.md) |
| Memória de um cliente/agente aparece em outro | identity key, tenant/agent/customer isolation | [LTM e Checkpoint](./08_long_term_memory_and_checkpoint.md) |
| Preciso recuperar `reasoning_content` | `ainvoke_response()` | [LLM Rich Response](./09_llm_rich_response_reasoning.md) |
| `reasoning_content` vem `None` | provider/model não expõe o campo | [LLM Rich Response](./09_llm_rich_response_reasoning.md) |
| Há chamadas LLM desnecessárias | routing determinístico, concorrência, cache | [Performance](./10_performance_cache_and_async_runtime.md) |
| Há deadlock ou espera entre event loops | cross-loop sequence/runtime | [Performance](./10_performance_cache_and_async_runtime.md) |
| Logs/traces não correlacionam o mesmo agente | labels, IDs e mapeamento de observabilidade | [Observabilidade](./11_observability_persistence_and_operational_readiness.md) |
| Sequence está interferindo no processamento | implementação assíncrona de sequência | [Observabilidade](./11_observability_persistence_and_operational_readiness.md) e [Performance](./10_performance_cache_and_async_runtime.md) |
| Um exemplo antigo não compila | documentação histórica versus API atual | [Validação README x Código](./VALIDATION_README_ALIGNMENT.md) |
| Preciso criar um agente novo do zero | fluxo completo | [`README.md`](../../../README.md) |
| Preciso saber onde colocar uma nova feature | arquitetura e boundaries | [Arquitetura e Conceitos](./01_architecture_and_concepts.md) |
### Buscar pela funcionalidade
### [01 — Arquitetura e Conceitos](./01_architecture_and_concepts.md)
**O que é:** visão dos componentes, contratos e limites de responsabilidade.
**Use quando:** precisar entender a plataforma, decidir onde implementar algo ou evitar acoplamento entre core e agente.
### [02 — Routing, Route Stickiness e Intent Shift](./02_routing_stickiness_and_intent_shift.md)
**O que é:** referência completa de descoberta de agente/intent, stickiness, handoff e mudança de intenção.
**Use quando:** a mensagem cai no agente errado, não troca de intent ou perde continuidade.
### [03 — Workflows Transacionais e Estado](./03_transaction_workflows_and_state.md)
**O que é:** ciclo transacional multi-turno, estados, confirmação, pausa/retomada e evidência operacional.
**Use quando:** há loops, confirmações incorretas, retomadas erradas ou operações críticas.
### [04 — MCP, Tools, Policies e Extração de Parâmetros](./04_mcp_integration_tools_and_policies.md)
**O que é:** referência de tools, MCP Servers, mappings, policies e parameter extraction.
**Use quando:** integração/execução de tool está incorreta ou precisa ser criada.
### [05 — Agent Gateway, MCP Gateway e Autenticação](./05_agent_gateway_mcp_gateway_and_auth.md)
**O que é:** responsabilidades dos gateways, governança e autenticação entre componentes.
**Use quando:** houver problema de entrada, catálogo, autorização, 401 ou deployment dos gateways.
### [06 — Guardrails, Judges e Avaliação Transacional](./06_guardrails_judges_and_transaction_evaluation.md)
**O que é:** validações nativas/externas, judges, grounding e regras para turnos transacionais.
**Use quando:** uma validação bloqueia, não roda ou produz avaliação incorreta.
### [07 — RAG, BusinessContext e Grounding](./07_rag_business_context_and_grounding.md)
**O que é:** providers de RAG, contexto recuperado, BusinessContext e grounding.
**Use quando:** conhecimento recuperado não chega corretamente ao agente/judge.
### [08 — Long-Term Memory e Checkpoint](./08_long_term_memory_and_checkpoint.md)
**O que é:** memória durável, memória conversacional, identidade e snapshots de estado.
**Use quando:** contexto some, vaza ou workflow retoma do lugar errado.
### [09 — LLM Rich Response e reasoning_content](./09_llm_rich_response_reasoning.md)
**O que é:** resposta estruturada de inferência além do `str` retornado por `ainvoke()`.
**Use quando:** consumidores precisam de metadados, usage ou reasoning disponibilizado pelo provider.
### [10 — Performance, Cache e Runtime Assíncrono](./10_performance_cache_and_async_runtime.md)
**O que é:** otimizações de concorrência, cache, LLM e event loops.
**Use quando:** houver latência evitável, processamento serial ou deadlock.
### [11 — Observabilidade, Persistência e Prontidão Operacional](./11_observability_persistence_and_operational_readiness.md)
**O que é:** correlação, eventos, labels, sequence, persistência e diagnóstico.
**Use quando:** for necessário provar o caminho executado ou diagnosticar produção.
### [12 — Feedback de Guardrails de Entrada e Turnos Bloqueados](./12_input_guardrail_feedback_and_blocked_turns.md)
**O que é:** tratamento público de bloqueios de input, limpeza do estado do turno e validação da mensagem gerada pelos guardrails de saída.
**Use quando:** mensagens de bloqueio são genéricas, `COER` deveria pedir esclarecimento ou o metadata de um turno bloqueado contém routing/tools antigos.
### Tutorial principal
[`README.md`](../../../README.md) continua sendo a referência para o passo a passo completo:
`arquitetura → configuração → criação do agente → registro → estado → routing → tools → MCP → identidade → execução → testes → gateways → memória → RAG`.
### Manutenção
Não crie outro tutorial paralelo ao `README.md`.
Ao evoluir uma feature:
- atualize o README somente se o fluxo normal de desenvolvimento mudou;
- atualize o manual especializado com comportamento, configuração, exemplos e troubleshooting;
- atualize SPECs se o contrato mudou;
- mantenha release notes como histórico, não como única documentação atual.

View File

@@ -0,0 +1,84 @@
### Validação de Alinhamento da Documentação
### Objetivo
Registrar como a documentação desta versão foi reorganizada e quais fontes devem ser usadas pelo desenvolvedor.
### Decisão estrutural
O `README.md` da raiz é o **único tutorial principal ponta a ponta**.
O antigo `01_architecture_and_agent_development.md` foi removido porque repetia grande parte do README, mas não todo ele. Isso criava ambiguidade: dois documentos aparentavam ensinar a mesma coisa, porém um era parcial.
A nova estrutura substitui esse arquivo por `01_architecture_and_concepts.md`, que contém apenas arquitetura, conceitos, responsabilidades e critérios de extensão.
### Validação de `README_old2.md`
`Documentacao/README_old2.md` permanece útil como histórico, mas não é fonte principal para desenvolvimento.
Foram encontradas evoluções posteriores no README atual e no código, incluindo:
- SPECs/SDDs;
- configuração mais completa de `llm_profiles.yaml`;
- Channel Gateway e contratos canônicos;
- `memory` e `summary_memory` no ciclo atual do agente;
- `prepare_memory_context()` e `build_messages()`;
- `RuntimeContext`;
- `normalize_tools_by_intent()`;
- `build_tool_arguments()`;
- `execute_tools_for_intent()`;
- helpers de estado transacional;
- respostas MCP diretas;
- evolução de gateways, RAG, memória e políticas.
### Correção aplicada ao README principal
Foi corrigido no pacote gerado o typo:
```python
from app.agents.financeiro_agent import FinanceirotAgent
```
para:
```python
from app.agents.financeiro_agent import FinanceiroAgent
```
A classe correta é confirmada pelo código e pelo restante da documentação.
### APIs confirmadas na implementação atual
```python
AgentRuntimeMixin.get_runtime_context()
AgentRuntimeMixin.normalize_tools_by_intent()
AgentRuntimeMixin.build_tool_arguments()
AgentRuntimeMixin.execute_tools_for_intent()
AgentRuntimeMixin.prepare_memory_context()
AgentRuntimeMixin.build_messages()
AgentRuntimeMixin.transaction_state_patch()
AgentRuntimeMixin.transaction_clarification_message()
AgentRuntimeMixin.transaction_confirmation_message()
AgentRuntimeMixin.build_direct_mcp_answer()
```
### Ordem de confiança
1. código da versão;
2. README principal da mesma versão;
3. SPECs/SDDs;
4. manuais especializados;
5. release notes;
6. documentos `README_old*`.
### Regra de manutenção futura
Uma evolução de feature deve atualizar:
1. o README principal, **somente se alterar o caminho normal de desenvolvimento**;
2. o manual especializado da feature, com detalhes técnicos, comportamento, configuração e troubleshooting;
3. a SPEC, quando houver mudança de contrato;
4. release note, quando for necessário registrar a mudança histórica.
Não crie um novo “manual principal” para uma feature. Não mantenha correções funcionais permanentemente apenas em release notes.

View File

@@ -1,4 +0,0 @@
__all__ = ['settings']
from .config.settings import settings
from .idempotency import IdempotencyStore, InMemoryIdempotencyStore, create_idempotency_store

View File

@@ -1,12 +0,0 @@
from .publisher import AnalyticsPublisher, NoopAnalyticsPublisher
from .composite_publisher import CompositeAnalyticsPublisher
from .event_builder import build_analytics_event
from .factory import create_analytics_publisher
__all__ = [
"AnalyticsPublisher",
"NoopAnalyticsPublisher",
"CompositeAnalyticsPublisher",
"build_analytics_event",
"create_analytics_publisher",
]

View File

@@ -1,35 +0,0 @@
from __future__ import annotations
import asyncio
import logging
from typing import Any, Iterable
from .publisher import AnalyticsPublisher
logger = logging.getLogger("agent_framework.analytics.composite")
class CompositeAnalyticsPublisher(AnalyticsPublisher):
"""Publica o mesmo evento em múltiplos destinos.
Use para rodar OCI Streaming e Pub/Sub em paralelo durante transição,
homologação ou estratégia multi-cloud.
"""
def __init__(self, publishers: Iterable[AnalyticsPublisher], *, fail_silent: bool = True):
self.publishers = list(publishers)
self.fail_silent = fail_silent
async def publish(self, event_type: str, payload: dict[str, Any]) -> None:
if not self.publishers:
return
async def _safe_publish(publisher: AnalyticsPublisher) -> None:
try:
await publisher.publish(event_type, payload)
except Exception:
logger.exception("analytics.publisher_failed provider=%s event_type=%s", publisher.__class__.__name__, event_type)
if not self.fail_silent:
raise
await asyncio.gather(*[_safe_publish(p) for p in self.publishers])

View File

@@ -1,27 +0,0 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any
def build_analytics_event(
event_type: str,
payload: dict[str, Any] | None = None,
*,
source: str = "agent_framework",
metadata: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Monta envelope uniforme para IC/NOC/GRL.
O campo metadata.noc=true é preservado para que o Observer consiga rotear
eventos também para NOC/OTEL/Elastic quando aplicável.
"""
body = dict(payload or {})
meta = dict(metadata or {})
return {
"eventType": event_type,
"source": source,
"eventDate": datetime.now(timezone.utc).isoformat(),
"payload": body,
"metadata": meta,
}

View File

@@ -1,85 +0,0 @@
from __future__ import annotations
import logging
from typing import Any
from .composite_publisher import CompositeAnalyticsPublisher
from .publisher import AnalyticsPublisher, NoopAnalyticsPublisher
logger = logging.getLogger("agent_framework.analytics.factory")
def _split_csv(value: str | None) -> list[str]:
return [item.strip().lower() for item in (value or "").split(",") if item.strip()]
def create_analytics_publisher(settings: Any | None = None) -> AnalyticsPublisher:
"""Cria publisher conforme env/config.
Variáveis novas compatíveis:
- ENABLE_ANALYTICS=true|false
- ANALYTICS_PROVIDERS=oci_streaming,pubsub
- GCP_PUBSUB_TOPIC_PATH=projects/.../topics/...
- AGENT_PUBSUB_TOPIC=projects/.../topics/... # compatibilidade FIRST/TIM
- GCP_PROJECT_ID=... + GCP_PUBSUB_TOPIC=...
"""
if settings is None:
from agent_framework.config.settings import settings as default_settings
settings = default_settings
analytics_enabled = bool(getattr(settings, "ENABLE_ANALYTICS", False))
langfuse_enabled = bool(getattr(settings, "ENABLE_LANGFUSE", False))
# Historicamente o observer era usado para enviar IC/NOC/GRL ao Langfuse
# mesmo quando o pipeline de analytics/streaming não estava habilitado.
# Portanto, ENABLE_LANGFUSE=true também ativa o publisher Langfuse do observer.
if not analytics_enabled and not langfuse_enabled:
return NoopAnalyticsPublisher()
providers = _split_csv(getattr(settings, "ANALYTICS_PROVIDERS", "")) or ["oci_streaming"]
if langfuse_enabled and "langfuse" not in providers:
providers.insert(0, "langfuse")
# Se analytics geral estiver desligado, publica somente no Langfuse para
# evitar inicializar OCI Streaming/PubSub por engano em ambientes locais.
if not analytics_enabled:
providers = [p for p in providers if p in {"langfuse", "noop", "none"}] or ["langfuse"]
publishers: list[AnalyticsPublisher] = []
for provider in providers:
try:
if provider == "langfuse":
from .providers.langfuse import LangfuseAnalyticsPublisher
publishers.append(LangfuseAnalyticsPublisher(settings=settings))
elif provider == "oci_streaming":
from .providers.oci_streaming import OCIStreamingAnalyticsPublisher
publishers.append(OCIStreamingAnalyticsPublisher(settings=settings))
elif provider in {"pubsub", "gcp_pubsub", "gcp"}:
from .providers.pubsub import PubSubAnalyticsPublisher
topic = (
getattr(settings, "GCP_PUBSUB_TOPIC_PATH", None)
or getattr(settings, "AGENT_PUBSUB_TOPIC", None)
)
publishers.append(PubSubAnalyticsPublisher(topic_path=topic))
elif provider in {"noop", "none"}:
publishers.append(NoopAnalyticsPublisher())
else:
logger.warning("analytics.provider_ignored provider=%s", provider)
except Exception:
logger.exception("analytics.provider_init_failed provider=%s", provider)
if not publishers:
# Sem este log, "analytics ligado mas todos os providers falharam" fica
# indistinguivel de "analytics desligado": o publisher no-op descarta
# IC/NOC/GRL em silencio ate o processo ser reiniciado.
logger.error(
"analytics.no_publisher_available providers=%s enable_analytics=%s "
"enable_langfuse=%s; telemetria sera descartada ate o proximo restart",
",".join(providers),
analytics_enabled,
langfuse_enabled,
)
return NoopAnalyticsPublisher()
if len(publishers) == 1:
return publishers[0]
return CompositeAnalyticsPublisher(publishers)

View File

@@ -1,11 +0,0 @@
from .oci_streaming import OCIStreamingAnalyticsPublisher
from .pubsub import PubSubAnalyticsPublisher
from .kafka import KafkaAnalyticsPublisher
from .langfuse import LangfuseAnalyticsPublisher
__all__ = [
"OCIStreamingAnalyticsPublisher",
"PubSubAnalyticsPublisher",
"KafkaAnalyticsPublisher",
"LangfuseAnalyticsPublisher",
]

Some files were not shown because too many files have changed in this diff Show More