Ajustes conforme relatorio de testes 2026-08-27
This commit is contained in:
@@ -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.**
|
>**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
|
## 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.
|
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`.
|
|
||||||
|
|||||||
@@ -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.**
|
>**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
|
## 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.
|
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.
|
* 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.
|
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) |
|
||||||
|
|||||||
Binary file not shown.
@@ -160,7 +160,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_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_edge("load_long_term_memory", "routing_decision")
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
@@ -197,6 +197,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -281,12 +306,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
Binary file not shown.
@@ -160,7 +160,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_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_edge("load_long_term_memory", "routing_decision")
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
@@ -197,6 +197,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -281,12 +306,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
Binary file not shown.
@@ -159,7 +159,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_input_guardrails,
|
self._after_input_guardrails,
|
||||||
{"blocked": "persist", "continue": "routing_decision"},
|
{"blocked": "output_guardrails", "continue": "routing_decision"},
|
||||||
)
|
)
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"routing_decision",
|
"routing_decision",
|
||||||
@@ -195,6 +195,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -279,12 +304,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
Binary file not shown.
@@ -160,7 +160,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_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_edge("load_long_term_memory", "routing_decision")
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
@@ -197,6 +197,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -281,12 +306,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
Binary file not shown.
@@ -159,7 +159,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_input_guardrails,
|
self._after_input_guardrails,
|
||||||
{"blocked": "persist", "continue": "routing_decision"},
|
{"blocked": "output_guardrails", "continue": "routing_decision"},
|
||||||
)
|
)
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"routing_decision",
|
"routing_decision",
|
||||||
@@ -195,6 +195,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -279,12 +304,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
Binary file not shown.
@@ -159,7 +159,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_input_guardrails,
|
self._after_input_guardrails,
|
||||||
{"blocked": "persist", "continue": "routing_decision"},
|
{"blocked": "output_guardrails", "continue": "routing_decision"},
|
||||||
)
|
)
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"routing_decision",
|
"routing_decision",
|
||||||
@@ -195,6 +195,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -279,12 +304,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
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.
|
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.
|
||||||
|
|||||||
@@ -32,3 +32,49 @@ O exemplo usa `MemorySaver` apenas para ser autocontido. Em aplicações reais u
|
|||||||
## Regra arquitetural
|
## 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`.
|
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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
Binary file not shown.
Binary file not shown.
@@ -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())
|
||||||
@@ -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.
|
||||||
Binary file not shown.
@@ -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"]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
version: 1
|
||||||
@@ -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
|
||||||
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||||
Binary file not shown.
@@ -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"]
|
after = [item for item in resumed.trace if item.get("action") == "preparar_operacao"]
|
||||||
assert len(after) == 1
|
assert len(after) == 1
|
||||||
assert resumed.state["vars"]["decidir"]["confirmado"] is True
|
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"]
|
||||||
|
|||||||
@@ -17,6 +17,19 @@ nodes:
|
|||||||
key: resposta_usuario
|
key: resposta_usuario
|
||||||
allowed_values: [SIM, NAO]
|
allowed_values: [SIM, NAO]
|
||||||
normalize: upper_strip
|
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
|
resume_from: decidir
|
||||||
- id: decidir
|
- id: decidir
|
||||||
action: registrar_decisao
|
action: registrar_decisao
|
||||||
|
|||||||
Binary file not shown.
@@ -159,7 +159,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_input_guardrails,
|
self._after_input_guardrails,
|
||||||
{"blocked": "persist", "continue": "routing_decision"},
|
{"blocked": "output_guardrails", "continue": "routing_decision"},
|
||||||
)
|
)
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"routing_decision",
|
"routing_decision",
|
||||||
@@ -195,6 +195,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -279,12 +304,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
Binary file not shown.
@@ -159,7 +159,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_input_guardrails,
|
self._after_input_guardrails,
|
||||||
{"blocked": "persist", "continue": "routing_decision"},
|
{"blocked": "output_guardrails", "continue": "routing_decision"},
|
||||||
)
|
)
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"routing_decision",
|
"routing_decision",
|
||||||
@@ -195,6 +195,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -279,12 +304,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -14,45 +14,38 @@ CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
|||||||
# LLM - OCI Generative AI como provider principal
|
# LLM - OCI Generative AI como provider principal
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
||||||
LLM_PROVIDER=oci_sdk
|
LLM_PROVIDER=oci_openai
|
||||||
LLM_TEMPERATURE=0.2
|
LLM_TEMPERATURE=0.2
|
||||||
LLM_MAX_TOKENS=2048
|
LLM_MAX_TOKENS=2048
|
||||||
LLM_TIMEOUT_SECONDS=120
|
LLM_TIMEOUT_SECONDS=120
|
||||||
|
|
||||||
# OCI OpenAI-compatible endpoint
|
# 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_MODEL=openai.gpt-4.1
|
||||||
OCI_GENAI_API_KEY=sk-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS
|
OCI_GENAI_API_KEY=sk-ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6
|
||||||
OCI_GENAI_PROJECT_OCID=
|
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 SDK / signer / profiles
|
||||||
OCI_CONFIG_FILE=~/.oci/config
|
OCI_CONFIG_FILE=~/.oci/config
|
||||||
OCI_PROFILE=LATINOAMERICA-Chicago
|
OCI_PROFILE=DEFAULT
|
||||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q
|
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
OCI_REGION=us-chicago-1
|
OCI_REGION=us-chicago-1
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Persistência
|
# Persistência
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Opções: memory, autonomous, mongodb
|
# Opções: memory, autonomous, mongodb
|
||||||
SESSION_REPOSITORY_PROVIDER=autonomous
|
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||||
MEMORY_REPOSITORY_PROVIDER=autonomous
|
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||||
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
|
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||||
|
SQLITE_DB_PATH=./data/agent_framework.db
|
||||||
|
|
||||||
# Autonomous Database
|
# Autonomous Database
|
||||||
ADB_USER=admin
|
ADB_USER=admin
|
||||||
ADB_PASSWORD=Moniquinha19721972
|
ADB_PASSWORD=fjhsdf04954hf
|
||||||
ADB_DSN=oradb23ai_high
|
ADB_DSN=oradb23aidev_high
|
||||||
ADB_WALLET_LOCATION=/mnt/d/Dropbox/ORACLE/LatinoAmerica/Wallet_ORADB23ai
|
ADB_WALLET_LOCATION=/ORACLE/DEFAULT/Wallet_ORADB23aiDev
|
||||||
ADB_WALLET_PASSWORD=Moniquinha1972
|
ADB_WALLET_PASSWORD=fjhsdf04954hf
|
||||||
ADB_TABLE_PREFIX=AGENTFW
|
ADB_TABLE_PREFIX=AGENTFW
|
||||||
|
|
||||||
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
|
# 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
|
# RAG / Vector / Graph
|
||||||
###############################################################################
|
###############################################################################
|
||||||
VECTOR_STORE_PROVIDER=autonomous
|
VECTOR_STORE_PROVIDER=sqlite
|
||||||
GRAPH_STORE_PROVIDER=autonomous
|
GRAPH_STORE_PROVIDER=sqlite
|
||||||
RAG_TOP_K=5
|
RAG_TOP_K=5
|
||||||
EMBEDDING_PROVIDER=oci
|
EMBEDDING_PROVIDER=mock
|
||||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||||
|
|
||||||
@@ -77,21 +70,14 @@ RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
|||||||
# Observabilidade
|
# Observabilidade
|
||||||
###############################################################################
|
###############################################################################
|
||||||
ENABLE_LANGFUSE=true
|
ENABLE_LANGFUSE=true
|
||||||
# Opcional: verbose, compact
|
LANGFUSE_TRACE_MODE=compact # Opcional: verbose, compact
|
||||||
LANGFUSE_TRACE_MODE=compact
|
LANGFUSE_PUBLIC_KEY=pk-lf-2f9da109-5b0f-4c78-b61d-9598ed787eba
|
||||||
# Nome customizado do trace pai, ex.: backoffice.checklist.workflow ou backoffice.emulador.workflow
|
LANGFUSE_SECRET_KEY=sk-lf-a4cb0cdd-f2ea-4468-9911-cebeb91ba944
|
||||||
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_HOST=http://localhost:3005
|
LANGFUSE_HOST=http://localhost:3005
|
||||||
ENABLE_OTEL=false
|
ENABLE_OTEL=false
|
||||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||||
OTEL_SERVICE_NAME=ai-agent-template
|
OTEL_SERVICE_NAME=ai-agent-template
|
||||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||||
ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Analytics / Observer corporativo
|
# Analytics / Observer corporativo
|
||||||
@@ -99,7 +85,7 @@ ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
|
|||||||
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
||||||
ENABLE_ANALYTICS=false
|
ENABLE_ANALYTICS=false
|
||||||
# Providers aceitos: oci_streaming,pubsub,noop
|
# Providers aceitos: oci_streaming,pubsub,noop
|
||||||
ANALYTICS_PROVIDERS=oci_streaming
|
ANALYTICS_PROVIDERS=pubsub
|
||||||
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
||||||
AGENT_PUBSUB_TOPIC=
|
AGENT_PUBSUB_TOPIC=
|
||||||
GCP_PUBSUB_TOPIC_PATH=
|
GCP_PUBSUB_TOPIC_PATH=
|
||||||
@@ -162,6 +148,7 @@ ROUTE_STICKINESS_HISTORY_TURNS=2
|
|||||||
ROUTE_STICKINESS_MAX_TOKENS=80
|
ROUTE_STICKINESS_MAX_TOKENS=80
|
||||||
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
|
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
|
||||||
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
|
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
|
# MCP / Tools
|
||||||
@@ -169,13 +156,14 @@ END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
|
|||||||
ENABLE_MCP_TOOLS=true
|
ENABLE_MCP_TOOLS=true
|
||||||
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
||||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
TOOLS_CONFIG_PATH=./config/tools.yaml
|
||||||
|
TOOL_POLICIES_PATH=./config/tool_policies.yaml
|
||||||
MCP_TOOL_TIMEOUT_SECONDS=30
|
MCP_TOOL_TIMEOUT_SECONDS=30
|
||||||
|
|
||||||
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
|
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
|
||||||
ROUTING_MODE=router
|
ROUTING_MODE=router
|
||||||
|
|
||||||
# Usage/cost accounting
|
# Usage/cost accounting
|
||||||
USAGE_REPOSITORY_PROVIDER=autonomous
|
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
||||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.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_RECENT_MESSAGES=true
|
||||||
MEMORY_INJECT_SUMMARY=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
|
# LONG-TERM MEMORY
|
||||||
###############################################################################
|
###############################################################################
|
||||||
Binary file not shown.
@@ -160,7 +160,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_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_edge("load_long_term_memory", "routing_decision")
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
@@ -197,6 +197,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -281,12 +306,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
Binary file not shown.
@@ -160,7 +160,7 @@ class AgentWorkflow:
|
|||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"input_guardrails",
|
"input_guardrails",
|
||||||
self._after_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_edge("load_long_term_memory", "routing_decision")
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
@@ -197,6 +197,31 @@ class AgentWorkflow:
|
|||||||
def _after_input_guardrails(self, state):
|
def _after_input_guardrails(self, state):
|
||||||
return "blocked" if state.get("blocked") else "continue"
|
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):
|
async def input_guardrails(self, state):
|
||||||
if state.get("session_ended") is True:
|
if state.get("session_ended") is True:
|
||||||
answer = str(getattr(
|
answer = str(getattr(
|
||||||
@@ -281,12 +306,33 @@ class AgentWorkflow:
|
|||||||
component="workflow.input_guardrails.final",
|
component="workflow.input_guardrails.final",
|
||||||
)
|
)
|
||||||
if any(not d.allowed for d in decisions):
|
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 {
|
return {
|
||||||
"sanitized_input": sanitized,
|
"sanitized_input": sanitized,
|
||||||
"answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"answer": user_message,
|
||||||
"final_answer": "Não consegui seguir com essa mensagem por regra de segurança.",
|
"final_answer": None,
|
||||||
"guardrail_decisions": [d.model_dump() for d in decisions],
|
"guardrail_decisions": [d.model_dump() for d in decisions],
|
||||||
"route": "blocked",
|
"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,
|
"blocked": True,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,35 @@ router:
|
|||||||
confidence_threshold: 0.65
|
confidence_threshold: 0.65
|
||||||
allow_handoff: true
|
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_policies:
|
||||||
- state: WAITING_BILLING_CONFIRMATION
|
- state: WAITING_BILLING_CONFIRMATION
|
||||||
agent: billing_agent
|
agent: billing_agent
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
143
agent_framework_oci/docs/developer/en/INDEX_DEVELOPER_GUIDE.md
Normal file
143
agent_framework_oci/docs/developer/en/INDEX_DEVELOPER_GUIDE.md
Normal 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.
|
||||||
|
|
||||||
@@ -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.
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
143
agent_framework_oci/docs/developer/pt/INDEX_DEVELOPER_GUIDE.md
Normal file
143
agent_framework_oci/docs/developer/pt/INDEX_DEVELOPER_GUIDE.md
Normal 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.
|
||||||
@@ -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.
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
__all__ = ['settings']
|
|
||||||
from .config.settings import settings
|
|
||||||
|
|
||||||
from .idempotency import IdempotencyStore, InMemoryIdempotencyStore, create_idempotency_store
|
|
||||||
@@ -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",
|
|
||||||
]
|
|
||||||
@@ -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])
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
Reference in New Issue
Block a user