First commit
This commit is contained in:
292
docs/developer/en/01_architecture_and_concepts.md
Normal file
292
docs/developer/en/01_architecture_and_concepts.md
Normal file
@@ -0,0 +1,292 @@
|
||||
### 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.
|
||||
|
||||
### Agent extension folders in the template
|
||||
|
||||
`agent_template_backend` materializes selected framework extension points as local folders. These folders **do not duplicate the core**: they hold declarative configuration or domain-specific implementation that is registered with or consumed by generic `agent_framework` mechanisms.
|
||||
|
||||
```text
|
||||
templates/agent_template_backend/
|
||||
├── workflows/
|
||||
│ ├── <workflow>.vN.yaml # versioned deterministic workflow definition
|
||||
│ └── <workflow>.active.yaml # active version selected by the agent
|
||||
└── app/
|
||||
├── workflow_actions/ # business actions invoked by workflow YAML files
|
||||
├── presentation/ # domain-specific deterministic renderers
|
||||
└── observability/ # local telemetry observers/adapters and integration
|
||||
```
|
||||
|
||||
The architectural boundary is:
|
||||
|
||||
| Template folder | Should contain | Remains in the core |
|
||||
| --- | --- | --- |
|
||||
| `workflows/` | declarative, versioned domain YAML files | parser, validation, registry, executor, pause/resume, and workflow runtime |
|
||||
| `app/workflow_actions/` | business functions registered with `@workflow_action` | decorator, registry, execution, retry policy, and idempotency infrastructure |
|
||||
| `app/presentation/` | renderers that turn trusted results into domain responses | renderer registry, response-mode selection, and runtime fallback |
|
||||
| `app/observability/` | agent-specific observer/adapters, mappings, and enrichment | IC/NOC/GRL contracts, correlation, OTEL/Langfuse, exporters, and observability runtime |
|
||||
|
||||
Practical rule: if an implementation could be reused without knowing a specific telecom, retail, billing, order, or other business domain, it likely belongs in `agent_framework`, not in one of these agent folders. The core must also never import concrete modules from these folders; the agent registers its extensions during startup.
|
||||
|
||||
See: [Transactional Workflows and State](./03_transaction_workflows_and_state.md), [Advanced transactional workflows](./13_advanced_transactional_workflows.md), [Observability, Persistence, and Operational Readiness](./11_observability_persistence_and_operational_readiness.md), and [Presentation, recovery, and observability](./15_presentation_recovery_and_observability.md).
|
||||
|
||||
### 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)
|
||||
1489
docs/developer/en/02_routing_stickiness_and_intent_shift.md
Normal file
1489
docs/developer/en/02_routing_stickiness_and_intent_shift.md
Normal file
File diff suppressed because it is too large
Load Diff
727
docs/developer/en/03_transaction_workflows_and_state.md
Normal file
727
docs/developer/en/03_transaction_workflows_and_state.md
Normal file
@@ -0,0 +1,727 @@
|
||||
### 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/`
|
||||
|
||||
### Physical organization of `workflows/` in the template
|
||||
|
||||
In `agent_template_backend`, declarative definitions live in the agent's root `workflows/` folder:
|
||||
|
||||
```text
|
||||
templates/agent_template_backend/
|
||||
└── workflows/
|
||||
├── devolucao_pedido.v1.yaml
|
||||
└── devolucao_pedido.active.yaml
|
||||
```
|
||||
|
||||
This folder belongs to the **agent** because it expresses domain-specific business sequencing, versions, and decisions. The mechanism that interprets these files belongs to the **framework**. Therefore:
|
||||
|
||||
- do not implement parsers, executors, or registries inside `workflows/`;
|
||||
- do not place Python code there; Python actions belong in `app/workflow_actions/`;
|
||||
- keep immutable versions (`*.vN.yaml`) and a valid active definition (`*.active.yaml`);
|
||||
- use `WORKFLOWS_PATH` when a host must point to another directory;
|
||||
- generic pause/resume, retry, validation, or execution changes belong in `agent_framework.workflows`, not in copied template code.
|
||||
|
||||
The complete path is: **YAML in `workflows/` → framework engine → `node.action` → action registry → function in `app/workflow_actions/`**.
|
||||
|
||||
### 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.
|
||||
|
||||
### `expected_input.semantic_classifier.unmatched_value` and `reprompt`
|
||||
|
||||
Paused workflows may accept literal replies and also classify free-text replies semantically. When `semantic_classifier` is enabled, an utterance that **cannot safely be mapped to any valid class** must not be forced into `SIM`, `NAO`, or `CONTINUAR`. For that case, the workflow may declare a classifier sentinel through `unmatched_value`.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
expected_input:
|
||||
key: resposta_usuario
|
||||
allowed_values:
|
||||
- SIM
|
||||
- NAO
|
||||
- CONTINUAR
|
||||
normalize: upper_strip
|
||||
reprompt: >
|
||||
I did not understand. Did this explanation resolve your question?
|
||||
Please answer yes or no.
|
||||
|
||||
semantic_classifier:
|
||||
enabled: true
|
||||
include_relevant_context: true
|
||||
unmatched_value: OUTRO
|
||||
prompt: |
|
||||
Classify the customer's utterance into a valid option only when there is
|
||||
enough meaning to do so safely.
|
||||
|
||||
SIM: unambiguous understanding/acceptance.
|
||||
NAO: unambiguous negative answer.
|
||||
CONTINUAR: a meaningful utterance that complements or continues the request.
|
||||
OUTRO: incomprehensible, disconnected, or insufficient input for any valid class.
|
||||
|
||||
Return only SIM, NAO, CONTINUAR, or OUTRO.
|
||||
|
||||
option_actions:
|
||||
CONTINUAR:
|
||||
action: contextual_reentry
|
||||
```
|
||||
|
||||
The contract is:
|
||||
|
||||
```text
|
||||
SIM -> resume with SIM
|
||||
NAO -> resume with NAO
|
||||
CONTINUAR -> execute the configured option_action (for example contextual_reentry)
|
||||
OUTRO -> never resume with this value; return reprompt and keep the workflow paused
|
||||
```
|
||||
|
||||
Important rules:
|
||||
|
||||
- `unmatched_value` is a **classifier sentinel**, not a business response;
|
||||
- do not add the sentinel to `allowed_values`; `allowed_values` contains only values the workflow may consume;
|
||||
- `CONTINUAR` is valid only for a meaningful utterance that actually continues the topic; contextually meaningless input should return `unmatched_value` and trigger `reprompt`;
|
||||
- `reprompt` does not complete or restart the execution: the same workflow remains `PAUSED` and waits for another reply;
|
||||
- internal values such as `SIM`, `NAO`, `CONTINUAR`, and the sentinel must never leak as customer-facing output.
|
||||
|
||||
Behavioral example:
|
||||
|
||||
```text
|
||||
Pending prompt: "Did this explanation resolve your question?"
|
||||
|
||||
"sim" -> SIM
|
||||
"não resolveu" -> NAO
|
||||
"mas e a cobrança de R$ 14,99?" -> CONTINUAR -> contextual_reentry
|
||||
"ano" -> OUTRO -> reprompt
|
||||
```
|
||||
|
||||
This option is **opt-in per workflow**. Workflows that do not declare `semantic_classifier.unmatched_value` keep the previous contract, preventing a change in one flow from silently changing other workflows or agents.
|
||||
|
||||
### 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.
|
||||
846
docs/developer/en/04_mcp_integration_tools_and_policies.md
Normal file
846
docs/developer/en/04_mcp_integration_tools_and_policies.md
Normal file
@@ -0,0 +1,846 @@
|
||||
### 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
|
||||
|
||||
|
||||
|
||||
|
||||
### MCP multi-item: product, order, and resource lists
|
||||
|
||||
When one intent must operate on several items, **do not create an MCP loop inside the agent**. The framework supports scalar or `array/list` contracts, pre-validation with `resolved_arguments`, set-level transaction confirmation, and automatic normalization of per-item `results[]` into `SUCCESS`, `PARTIAL_SUCCESS`, or `FAILED`. Multi-item support does not depend on the input parameter type.
|
||||
|
||||
The complete contract, `tools.yaml` / `tool_policies.yaml` examples, MCP payloads, per-item output, workflow behavior, partial validation, and anti-patterns are documented in:
|
||||
|
||||
- [MCP Multi-item — Developer Guide](../../MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md)
|
||||
|
||||
Minimum structural result from the primary tool:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"results": [
|
||||
{"subject": "A", "success": true},
|
||||
{"subject": "B", "success": false, "reason": "not_found"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Every item must expose boolean `success` or `ok`. For workflow-backed tools, prefer `workflow.output[tool_name].results`. A later auxiliary failure must not erase terminal item outcomes.
|
||||
|
||||
### 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.
|
||||
2466
docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md
Normal file
2466
docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,267 @@
|
||||
### 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
|
||||
|
||||
Use extensions when a policy or quality criterion belongs to the agent domain. A guardrail implements `evaluate(text, context) -> RailDecision` and can run at `input`, `retrieval`, `tool`, or `output`. A judge implements `evaluate(question, answer, context) -> JudgeResult` and scores rather than rewrites a response.
|
||||
|
||||
The [external guardrails and judges guide](../../EXTERNAL_GUARDRAILS_JUDGES_en.md) covers directory layout, imports, copyable implementations, YAML, constructor injection, failure policy, observability, and executable verification.
|
||||
|
||||
Key constraints: `type: external` plus `class: package.module:Class` enables dynamic import; `kwargs` populate the constructor; judges may receive injected `llm` and `settings`; guardrails receive a shared LLM through context only when the pipeline was built with `llm=...`; synchronous implementations run in worker threads; and external judge exceptions should be handled by the implementation.
|
||||
|
||||
> **Current limitation:** `agent_template_backend` creates a global pipeline pair at startup. Registry fields `guardrails_config_path` and `judges_config_path` do not automatically select a configuration per request. LLM-backed extensions also require the composition to pass `llm` and `settings` as documented in the full guide.
|
||||
|
||||
Verify the published examples with `pytest -q tests/test_external_guardrails_judges_documentation.py`.
|
||||
|
||||
### 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.
|
||||
387
docs/developer/en/07_rag_business_context_and_grounding.md
Normal file
387
docs/developer/en/07_rag_business_context_and_grounding.md
Normal file
@@ -0,0 +1,387 @@
|
||||
### RAG, BusinessContext, and Grounding
|
||||
|
||||
### How to use this manual
|
||||
|
||||
This is a **specialized reference manual**. It does not replace the main tutorial.
|
||||
|
||||
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
|
||||
- Use this document when you need to implement, deepen, or diagnose **RAG, providers, BusinessContext, retrieved context, and grounding**.
|
||||
- Historical examples consolidated here should be read in light of the framework's current API.
|
||||
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
|
||||
|
||||
### Relationship with the main tutorial
|
||||
|
||||
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
|
||||
|
||||
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
|
||||
|
||||
### Scope
|
||||
|
||||
RAG, providers, BusinessContext, retrieved context, and grounding.
|
||||
|
||||
### Consolidated technical content
|
||||
|
||||
### RAG, Enterprise Providers, BusinessContext, and Grounding
|
||||
|
||||
Guide for integrating retrieved knowledge, selecting between RAG providers, configuring KBDB, using samples, MCP sufficiency, and using BusinessContext as a data contract.
|
||||
|
||||
### How to use this document
|
||||
|
||||
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
|
||||
|
||||
### Standard RAG Provider versus KBDB Enterprise
|
||||
|
||||
> Content consolidated from `docs/RAG_PROVIDER_KBDB.md`.
|
||||
|
||||
The framework now supports two retrieval backends through the same `RagService` contract, without changing agents or `_retrieve_rag_context()`.
|
||||
|
||||
### Selection
|
||||
|
||||
```env
|
||||
RAG_PROVIDER=standard # default: comportamento anterior
|
||||
# ou
|
||||
RAG_PROVIDER=kbdb # KBDB enterprise
|
||||
```
|
||||
|
||||
Selection is exclusive per process. The two RAG implementations do not run together and do not share vector store, graph store, or ingestion.
|
||||
|
||||
### `standard`
|
||||
|
||||
Fully preserves the existing RAG in `agent_framework_oci`: `VECTOR_STORE_PROVIDER`, `GRAPH_STORE_PROVIDER`, embedding, query rewrite, compression, retrieval guardrails, and generation remain valid.
|
||||
|
||||
### `kbdb`
|
||||
|
||||
The framework integrates only the stable serving port of the KBDB project:
|
||||
|
||||
`PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE`
|
||||
|
||||
The enterprise pipeline remains external to the agent runtime and preserves its own RAW → SILVER → GOLD architecture, HVI/hybrid search, property graph, publishing, lifecycle, audit, and observability.
|
||||
|
||||
The KBDB envelope is adapted to `RagResult`/`VectorDocument`; therefore existing agents continue calling `_retrieve_rag_context()` and the framework's retrieval guardrails continue after retrieval.
|
||||
|
||||
### Configuration
|
||||
|
||||
```env
|
||||
RAG_PROVIDER=kbdb
|
||||
RAG_TOP_K=5
|
||||
KBDB_DB_USER=KB_USER
|
||||
KBDB_DB_PASSWORD=...
|
||||
KBDB_DB_DSN=...
|
||||
KBDB_DB_WALLET_LOCATION=...
|
||||
KBDB_DB_WALLET_PASSWORD=...
|
||||
KBDB_SEARCH_TYPE=hybrid
|
||||
KBDB_NODE_EXPANSION=true
|
||||
KBDB_NODE_MAX_RELATED=8
|
||||
KBDB_GRAPH_CROSS_REF=false
|
||||
KBDB_MAX_CROSS_REF_HOPS=1
|
||||
KBDB_DOCUMENT_TYPE=customer_safe
|
||||
KBDB_METADATA_JSON=
|
||||
KBDB_MIN_SCORE=
|
||||
```
|
||||
|
||||
When `RAG_PROVIDER=kbdb`, `KBDB_DB_USER`, `KBDB_DB_PASSWORD`, and `KBDB_DB_DSN` are required. KBDB uses an isolated connection because it may reside in another Autonomous database. `KBDB_DB_DSN` follows the same semantics as `ADB_DSN`: use the existing TNS alias in the `tnsnames.ora` from the wallet indicated by `KBDB_DB_WALLET_LOCATION`, not a `tcps://...` URL.
|
||||
|
||||
### Isolation and compatibility
|
||||
|
||||
- `RAG_PROVIDER=standard` does not import or connect to KBDB.
|
||||
- `RAG_PROVIDER=kbdb` does not instantiate the standard RAG vector/graph stores.
|
||||
- Ingestion through `RagService.add_documents()` is not allowed in KBDB mode: it must go through the KBDB pipeline/publishing process.
|
||||
- Query rewrite and context compression remain optional and are applied by the framework's common layer.
|
||||
- `AgentRuntimeMixin._retrieve_rag_context()` and agents remain unchanged.
|
||||
- KBDB failures follow the framework's existing semantics: retrieval is auxiliary evidence and the exception is converted into technical metadata without breaking the user journey.
|
||||
|
||||
|
||||
### Direct tool response and RAG
|
||||
|
||||
The framework no longer considers a structured MCP result, by itself, to be a sufficient user response.
|
||||
|
||||
A `response.renderer` policy defines only **how** to present the result. It does not terminate the flow before RAG/LLM. For a tool to deliberately produce a direct final response, the application must explicitly declare:
|
||||
|
||||
```yaml
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: meu.renderer
|
||||
direct: true
|
||||
```
|
||||
|
||||
Without `direct: true`, the tool result remains MCP evidence and the flow continues to `_retrieve_rag_context()` and LLM composition. This allows, for example, an operational plan query to be combined with KBDB documentary knowledge when the question asks for rules, policies, or explanations.
|
||||
|
||||
The framework core has no fallback by tool name (`consultar_plano`, `consultar_pedido`, etc.). Presentation rules belong to the application/domain.
|
||||
|
||||
|
||||
### MCP sufficiency and grounding
|
||||
|
||||
A successful MCP result **does not** make the framework skip RAG automatically.
|
||||
The domain may declare documentary sufficiency only explicitly in the payload with `rag_sufficient=true` or `knowledge_sufficient=true`. This decision is generic and does not depend on the tool name or telecom/retail keywords.
|
||||
|
||||
For the `kbdb` provider, `KBDB_GROUNDED_ONLY=true` is the default. When KBDB search returns empty, blocked, or error, LLM composition may use facts proven by MCP/business context, but it must not fill the documentary portion using parametric model knowledge. It must state that there is insufficient evidence in the knowledge base.
|
||||
|
||||
ProductAgent events record `IC.PRODUCT_RAG_CONTEXT_EVALUATED` for every attempt/decision and `IC.PRODUCT_RAG_CONTEXT_RETRIEVED` only when context was retrieved. Metadata includes `provider`, `status`, `document_count`, `reason`, `error`, `query`, `namespace`, and `latency_ms`.
|
||||
|
||||
### RAG samples and tests
|
||||
|
||||
> Content consolidated from `docs/README_rag_samples.md`.
|
||||
|
||||
These PDF files are synthetic, searchable sample documents created to validate the RAG embedding and retrieval flow of `agent_template_backend`.
|
||||
|
||||
### Files
|
||||
|
||||
- `01_billing_agent_invoice_policy.pdf` - sample knowledge for `billing_agent`
|
||||
- `02_orders_agent_lifecycle_policy.pdf` - sample knowledge for `orders_agent`
|
||||
- `03_product_agent_catalog_policy.pdf` - sample knowledge for `product_agent`
|
||||
- `04_support_agent_sla_policy.pdf` - sample knowledge for `support_agent`
|
||||
- `05_business_context_rag_flow.pdf` - sample knowledge about BusinessContext, identity.yaml and MCP parameter mapping
|
||||
|
||||
### How to use
|
||||
|
||||
Copy the PDF files to the backend documentation directory:
|
||||
|
||||
```bash
|
||||
mkdir -p agent_template_backend/docs/rag_samples
|
||||
cp *.pdf agent_template_backend/docs/rag_samples/
|
||||
```
|
||||
|
||||
For a local smoke test, use:
|
||||
|
||||
```env
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
EMBEDDING_PROVIDER=mock
|
||||
SQLITE_DB_PATH=./data/agent_framework.db
|
||||
RAG_TOP_K=4
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
python scripts/generate_rag_embeddings.py \
|
||||
--docs-dir ./agent_template_backend/docs/rag_samples \
|
||||
--namespace default
|
||||
```
|
||||
|
||||
For production-like semantic embeddings with OCI Generative AI, use:
|
||||
|
||||
```env
|
||||
VECTOR_STORE_PROVIDER=autonomous
|
||||
EMBEDDING_PROVIDER=oci
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..xxxx
|
||||
OCI_REGION=us-chicago-1
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
```
|
||||
|
||||
### Suggested retrieval test questions
|
||||
|
||||
- What is a prorated charge?
|
||||
- When can the OrdersAgent open an exchange request?
|
||||
- Which SKU represents the AI Agents book?
|
||||
- What is the target response for a critical support ticket?
|
||||
- How does BusinessContext map customer_key to MCP tool parameters?
|
||||
|
||||
### BusinessContext v2
|
||||
|
||||
> Content consolidated from `Documentacao/README_TEMPLATE_BUSINESS_CONTEXT_V2.md`.
|
||||
|
||||
This package updates `agent_template_backend` and `agent_frontend` to reflect the new framework, where keys coming from the channel/front end are resolved once into canonical keys and propagated through the layers to the MCP Server.
|
||||
|
||||
### Implemented flow
|
||||
|
||||
1. The front end sends `tenant_id`, `agent_id`, `session_id`, and `business_context`.
|
||||
2. The backend normalizes the message through `ChannelGateway`, preserving the full payload in `context`.
|
||||
3. The backend uses `IdentityResolver` with `config/identity.yaml` to generate `BusinessContext`:
|
||||
- `customer_key`
|
||||
- `contract_key`
|
||||
- `interaction_key`
|
||||
- `account_key`
|
||||
- `resource_key`
|
||||
- `session_key`
|
||||
4. The workflow receives `context.business_context`.
|
||||
5. Example agents no longer build specific arguments such as `msisdn`, `invoice_id`, or `order_id` directly.
|
||||
6. `MCPToolRouter` uses `config/mcp_parameter_mapping.yaml` to convert canonical keys into the actual parameters of each MCP tool.
|
||||
|
||||
### Main files adjusted
|
||||
|
||||
- `agent_template_backend/app/main.py`
|
||||
- loads `IdentityResolver`;
|
||||
- resolves `BusinessContext` per message;
|
||||
- persists keys in session/memory/metadata/SSE;
|
||||
- adds `/debug/identity`.
|
||||
|
||||
- `agent_template_backend/app/agents/runtime.py`
|
||||
- adds centralized `_collect_mcp_context()`;
|
||||
- forwards `business_context` and `original_context` to the MCP Router.
|
||||
|
||||
- `agent_template_backend/app/agents/*_agent.py`
|
||||
- agents now use `_collect_mcp_context()` instead of building specific arguments.
|
||||
|
||||
- `agent_template_backend/config/identity.yaml`
|
||||
- defines how channel/front-end fields feed canonical keys.
|
||||
|
||||
- `agent_template_backend/config/mcp_parameter_mapping.yaml`
|
||||
- defines how canonical keys become real parameters per MCP tool.
|
||||
|
||||
- `agent_frontend/index.html` and `agent_frontend/app.js`
|
||||
- add `tenant`, `agent`, and canonical-key fields;
|
||||
- send `business_context` in the payload;
|
||||
- retain domain aliases for compatibility (`msisdn`, `invoice_id`, `order_id`, etc.).
|
||||
|
||||
### Quick test
|
||||
|
||||
Start backend, frontend, and MCP servers. Then test:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8000/health | jq
|
||||
|
||||
curl -s -X POST http://localhost:8000/debug/identity \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel":"web",
|
||||
"tenant_id":"default",
|
||||
"agent_id":"telecom_contas",
|
||||
"payload":{
|
||||
"message":"Minha fatura veio alta",
|
||||
"session_id":"teste-001",
|
||||
"msisdn":"11999999999",
|
||||
"invoice_id":"3000131180",
|
||||
"ura_call_id":"URA-123",
|
||||
"business_context":{
|
||||
"customer_key":"11999999999",
|
||||
"contract_key":"3000131180",
|
||||
"interaction_key":"URA-123",
|
||||
"session_key":"teste-001"
|
||||
}
|
||||
}
|
||||
}' | jq
|
||||
|
||||
curl -s -X POST http://localhost:8000/debug/mcp/call/consultar_fatura \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"business_context": {
|
||||
"customer_key":"11999999999",
|
||||
"contract_key":"3000131180",
|
||||
"interaction_key":"URA-123",
|
||||
"session_key":"teste-001"
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
|
||||
In the backend log, look for `mcp.tool.mapped`. It should indicate the mapped keys and `has_msisdn=true`, `has_invoice_id=true` for the telecom domain.
|
||||
|
||||
### Operational RAG and cache integration
|
||||
|
||||
> Content consolidated from `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`.
|
||||
|
||||
This version fixes the gaps identified in the comparison against FIRST.
|
||||
|
||||
### Applied fixes
|
||||
|
||||
### 1. Operational LangGraph checkpoint
|
||||
|
||||
The workflow no longer compiles directly with `MemorySaver()`. The following adapter was created:
|
||||
|
||||
```text
|
||||
agent_framework/checkpoints/langgraph_saver.py
|
||||
```
|
||||
|
||||
It connects LangGraph to the framework's configured repository:
|
||||
|
||||
- `memory`
|
||||
- `sqlite`
|
||||
- `oracle` / `autonomous`
|
||||
|
||||
In the workflow:
|
||||
|
||||
```python
|
||||
builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
|
||||
```
|
||||
|
||||
### 2. LangGraph telemetry wrapping actual execution
|
||||
|
||||
A node wrapper was added to the workflow:
|
||||
|
||||
```python
|
||||
self._node("billing_agent", self.billing_agent)
|
||||
```
|
||||
|
||||
This way the `langgraph.node.*` span/event wraps actual node execution, not just an empty block.
|
||||
|
||||
Events emitted:
|
||||
|
||||
- `langgraph.node.started`
|
||||
- `langgraph.node.completed`
|
||||
- `langgraph.node.failed`
|
||||
- `langgraph.edge.selected`
|
||||
|
||||
### 3. RAG integrated into agents
|
||||
|
||||
Agents now receive `RagService` and use retrieved context in the prompt:
|
||||
|
||||
- BillingAgent
|
||||
- ProductAgent
|
||||
- OrdersAgent
|
||||
- SupportAgent
|
||||
|
||||
RAG uses:
|
||||
|
||||
- `VECTOR_STORE_PROVIDER=memory|sqlite|oracle|autonomous`
|
||||
- `GRAPH_STORE_PROVIDER=memory|oracle|autonomous`
|
||||
- `RAG_TOP_K`
|
||||
|
||||
### 4. Cache integrated into agent runtime
|
||||
|
||||
The following mixin was created:
|
||||
|
||||
```text
|
||||
agent_template_backend/app/agents/runtime.py
|
||||
```
|
||||
|
||||
It adds:
|
||||
|
||||
- standardized RAG retrieval;
|
||||
- cache key for LLM calls;
|
||||
- hit/miss with telemetry;
|
||||
- distributed cache through `create_cache(settings)`.
|
||||
|
||||
### 5. Unit tests
|
||||
|
||||
The following directory was created:
|
||||
|
||||
```text
|
||||
tests/unit
|
||||
```
|
||||
|
||||
Initial coverage:
|
||||
|
||||
- cache;
|
||||
- SSE;
|
||||
- RAG;
|
||||
- checkpoint saver;
|
||||
- LangGraph telemetry;
|
||||
- agent runtime;
|
||||
- static workflow verification;
|
||||
- main imports.
|
||||
|
||||
Local validation performed:
|
||||
|
||||
```text
|
||||
12 passed
|
||||
```
|
||||
|
||||
### How to test
|
||||
|
||||
```bash
|
||||
cd projeto_agent_framework_first_ready
|
||||
pip install -r agent_template_backend/requirements.txt
|
||||
pytest -q tests/unit
|
||||
```
|
||||
|
||||
### Source files
|
||||
|
||||
The files below were consolidated into this manual:
|
||||
|
||||
- `docs/RAG_PROVIDER_KBDB.md`
|
||||
- `docs/README_rag_samples.md`
|
||||
- `Documentacao/README_TEMPLATE_BUSINESS_CONTEXT_V2.md`
|
||||
- `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`
|
||||
|
||||
### Maintenance rule
|
||||
|
||||
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.
|
||||
635
docs/developer/en/08_long_term_memory_and_checkpoint.md
Normal file
635
docs/developer/en/08_long_term_memory_and_checkpoint.md
Normal file
@@ -0,0 +1,635 @@
|
||||
### Long-Term Memory and Checkpoint
|
||||
|
||||
### How to use this manual
|
||||
|
||||
This is a **specialized reference manual**. It does not replace the main tutorial.
|
||||
|
||||
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
|
||||
- Use this document when you need to implement, deepen, or diagnose **LTM, conversation memory, identity-based isolation, and state persistence**.
|
||||
- Historical examples consolidated here should be read in light of the framework's current API.
|
||||
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
|
||||
|
||||
### Relationship with the main tutorial
|
||||
|
||||
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
|
||||
|
||||
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
|
||||
|
||||
### Scope
|
||||
|
||||
LTM, conversation memory, identity-based isolation, and state persistence.
|
||||
|
||||
### Consolidated technical content
|
||||
|
||||
### Long-Term Memory and Enterprise Checkpointing
|
||||
|
||||
Implementation manual for durable memory, identity isolation, stores, extraction, LangGraph integration, persistence testing, and the differences among LTM, history, summary, and checkpoint.
|
||||
|
||||
### How to use this document
|
||||
|
||||
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
|
||||
|
||||
### Complete Long-Term Memory implementation
|
||||
|
||||
> Content consolidated from `Documentacao/Manual_Long_Term_Memory_PT.md`.
|
||||
|
||||
### Concept
|
||||
|
||||
Long-Term Memory (LTM) is the `agent_framework` capability to store and retrieve durable facts beyond the lifetime of a conversation session.
|
||||
|
||||
Unlike message history, which is normally associated with a `session_id`, long-term memory is associated with the business identity of the user or customer. In the current implementation, this identity is composed of:
|
||||
|
||||
```text
|
||||
tenant_id
|
||||
agent_id
|
||||
customer_key
|
||||
```
|
||||
|
||||
This allows an agent to retrieve preferences, identity information, projects, and constraints even when a new session is created.
|
||||
|
||||
### What it is for
|
||||
|
||||
Long-Term Memory is used to:
|
||||
|
||||
- maintain continuity across sessions;
|
||||
- personalize responses;
|
||||
- avoid making the user repeat information already provided;
|
||||
- reduce the need to send the entire history to the model;
|
||||
- store preferences, current projects, preferred names, and constraints;
|
||||
- isolate memory across tenants, agents, and customers.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
Sessão A:
|
||||
"Me chame de Cris. Minha linguagem preferida é Python."
|
||||
|
||||
Sessão B, com outro session_id e o mesmo customer_key:
|
||||
"O que você lembra sobre mim?"
|
||||
|
||||
Resposta esperada:
|
||||
"Seu nome preferido é Cris e sua linguagem preferida é Python."
|
||||
```
|
||||
|
||||
### Difference among memory types
|
||||
|
||||
### Conversation Memory
|
||||
|
||||
Maintains messages from the current conversation and is normally associated with `session_id`.
|
||||
|
||||
### Summary Memory
|
||||
|
||||
Maintains a conversation summary to reduce the amount of context sent to the model.
|
||||
|
||||
### Long-Term Memory
|
||||
|
||||
Maintains durable facts across sessions and is associated with business identity, especially `customer_key`.
|
||||
|
||||
### Feature components
|
||||
|
||||
### LongTermMemoryManager
|
||||
|
||||
Responsible for coordinating:
|
||||
|
||||
- memory loading;
|
||||
- retrieval by identity;
|
||||
- context rendering;
|
||||
- extraction of new facts;
|
||||
- persistence of facts;
|
||||
- deduplication and updates.
|
||||
|
||||
### LongTermMemoryStore
|
||||
|
||||
Persistence interface used by the manager.
|
||||
|
||||
### SQLiteLongTermMemoryStore
|
||||
|
||||
Reference implementation based on SQLite.
|
||||
|
||||
It is appropriate for:
|
||||
|
||||
- local development;
|
||||
- tests;
|
||||
- demonstrations;
|
||||
- low-scale environments.
|
||||
|
||||
### InMemoryLongTermMemoryStore
|
||||
|
||||
In-memory implementation used for quick tests.
|
||||
|
||||
Its content is lost when the backend process terminates.
|
||||
|
||||
### LongTermMemoryExtractor
|
||||
|
||||
Responsible for identifying durable facts in messages.
|
||||
|
||||
Examples of facts:
|
||||
|
||||
```text
|
||||
preferred_name = Cris
|
||||
preferred_language = Python
|
||||
current_project = Atlas
|
||||
```
|
||||
|
||||
### LongTermMemoryItem
|
||||
|
||||
Model representing a persisted item, including identity, key, value, category, confidence, and metadata.
|
||||
|
||||
### AgentRuntime
|
||||
|
||||
Loads memory before agent execution and injects the context into the prompt.
|
||||
|
||||
### `persist_long_term_memory` node
|
||||
|
||||
LangGraph node responsible for persisting facts after final-response generation and validation.
|
||||
|
||||
### File structure
|
||||
|
||||
```text
|
||||
libs/
|
||||
└── agent_framework/
|
||||
└── src/
|
||||
└── agent_framework/
|
||||
└── memory/
|
||||
├── __init__.py
|
||||
├── long_term_extractor.py
|
||||
├── long_term_memory.py
|
||||
├── long_term_models.py
|
||||
└── long_term_store.py
|
||||
```
|
||||
|
||||
### Execution flow
|
||||
|
||||
```text
|
||||
Mensagem do usuário
|
||||
│
|
||||
▼
|
||||
AgentRuntime.prepare_memory_context()
|
||||
│
|
||||
├── Conversation Memory
|
||||
├── Summary Memory
|
||||
└── Long-Term Memory
|
||||
│
|
||||
▼
|
||||
long_term_memory_context
|
||||
│
|
||||
▼
|
||||
Prompt do agente
|
||||
│
|
||||
▼
|
||||
Agente
|
||||
│
|
||||
▼
|
||||
Guardrails / Judges / Supervisor
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
LongTermMemoryExtractor
|
||||
│
|
||||
▼
|
||||
LongTermMemoryStore
|
||||
```
|
||||
|
||||
### Framework configuration
|
||||
|
||||
### New modules
|
||||
|
||||
Copy the files:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_extractor.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_memory.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_models.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_store.py
|
||||
```
|
||||
|
||||
### Updating `memory/__init__.py`
|
||||
|
||||
Export the Long-Term Memory components:
|
||||
|
||||
```python
|
||||
from agent_framework.memory.long_term_memory import (
|
||||
LongTermMemoryManager,
|
||||
create_long_term_memory_manager,
|
||||
)
|
||||
from agent_framework.memory.long_term_models import LongTermMemoryItem
|
||||
from agent_framework.memory.long_term_store import (
|
||||
InMemoryLongTermMemoryStore,
|
||||
LongTermMemoryStore,
|
||||
SQLiteLongTermMemoryStore,
|
||||
create_long_term_memory_store,
|
||||
)
|
||||
```
|
||||
|
||||
### Updating `settings.py`
|
||||
|
||||
Add the configurations:
|
||||
|
||||
```python
|
||||
ENABLE_LONG_TERM_MEMORY: bool = False
|
||||
LONG_TERM_MEMORY_PROVIDER: str = "sqlite"
|
||||
LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db"
|
||||
LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory"
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True
|
||||
```
|
||||
|
||||
### Integration with AgentRuntime
|
||||
|
||||
The runtime must:
|
||||
|
||||
1. check whether the feature is enabled;
|
||||
2. create the manager when necessary;
|
||||
3. retrieve facts by identity;
|
||||
4. populate state;
|
||||
5. inject context into the prompt.
|
||||
|
||||
Fields added to state:
|
||||
|
||||
```python
|
||||
long_term_memories: list[dict]
|
||||
long_term_memory_context: str
|
||||
long_term_memory_write_result: dict
|
||||
```
|
||||
|
||||
### Initialization in AgentWorkflow
|
||||
|
||||
The manager must be created in `AgentWorkflow`:
|
||||
|
||||
```python
|
||||
self.long_term_memory_manager = create_long_term_memory_manager(
|
||||
settings,
|
||||
telemetry=telemetry,
|
||||
)
|
||||
```
|
||||
|
||||
### Correct agent initialization
|
||||
|
||||
`long_term_memory_manager` must not be passed through `agent_kwargs` if the constructors of `BillingAgent`, `ProductAgent`, `OrdersAgent`, and `SupportAgent` do not declare that parameter.
|
||||
|
||||
This initialization causes an error:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"settings": settings,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
"long_term_memory_manager": self.long_term_memory_manager,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
```
|
||||
|
||||
Resulting error:
|
||||
|
||||
```text
|
||||
TypeError: BillingAgent.__init__() got an unexpected keyword argument
|
||||
'long_term_memory_manager'
|
||||
```
|
||||
|
||||
The recommended form is to create agents using the existing signature and inject the manager as an attribute after initialization:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"tool_router": getattr(self, "tool_router", None),
|
||||
"rag_service": self.rag_service,
|
||||
"cache": self.cache,
|
||||
"settings": settings,
|
||||
"observer": self.observer,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
self.product = ProductAgent(llm, **agent_kwargs)
|
||||
self.orders = OrdersAgent(llm, **agent_kwargs)
|
||||
self.support = SupportAgent(llm, **agent_kwargs)
|
||||
|
||||
for agent in (
|
||||
self.billing,
|
||||
self.product,
|
||||
self.orders,
|
||||
self.support,
|
||||
):
|
||||
agent.long_term_memory_manager = self.long_term_memory_manager
|
||||
```
|
||||
|
||||
This approach avoids changing every agent constructor and keeps the capability encapsulated in the framework.
|
||||
|
||||
### LangGraph configuration
|
||||
|
||||
Register the node:
|
||||
|
||||
```python
|
||||
builder.add_node(
|
||||
"persist_long_term_memory",
|
||||
self._node(
|
||||
"persist_long_term_memory",
|
||||
self.persist_long_term_memory,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Change the flow:
|
||||
|
||||
```python
|
||||
builder.add_edge(
|
||||
"supervisor_review",
|
||||
"persist_long_term_memory",
|
||||
)
|
||||
builder.add_edge(
|
||||
"persist_long_term_memory",
|
||||
"persist",
|
||||
)
|
||||
```
|
||||
|
||||
Implement the method:
|
||||
|
||||
```python
|
||||
async def persist_long_term_memory(
|
||||
self,
|
||||
state: AgentState,
|
||||
) -> dict[str, object]:
|
||||
result = await self.long_term_memory_manager.persist_turn(state)
|
||||
|
||||
return {
|
||||
"long_term_memory_write_result": result,
|
||||
}
|
||||
```
|
||||
|
||||
Final flow:
|
||||
|
||||
```text
|
||||
supervisor_review
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
persist
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
```env
|
||||
ENABLE_LONG_TERM_MEMORY=true
|
||||
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
|
||||
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
|
||||
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT=true
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT=true
|
||||
```
|
||||
|
||||
### SQLite database path
|
||||
|
||||
The relative path is resolved from the directory where the backend is started.
|
||||
|
||||
To avoid accidentally creating different databases, prefer an absolute path in development environments:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db
|
||||
```
|
||||
|
||||
Create the directory before starting:
|
||||
|
||||
```bash
|
||||
mkdir -p data
|
||||
```
|
||||
|
||||
### How to test
|
||||
|
||||
### Test 1 — Write
|
||||
|
||||
Send:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-a",
|
||||
"customer_key": "11999999999",
|
||||
"message": "Me chame de Cris. Minha linguagem preferida é Python e meu projeto atual se chama Atlas."
|
||||
}
|
||||
```
|
||||
|
||||
### Test 2 — Retrieval in another session
|
||||
|
||||
Use another `session_id`, keeping the same `customer_key`:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-b",
|
||||
"customer_key": "11999999999",
|
||||
"message": "O que você lembra sobre mim, minhas preferências e meu projeto?"
|
||||
}
|
||||
```
|
||||
|
||||
Expected result:
|
||||
|
||||
```text
|
||||
Seu nome preferido é Cris.
|
||||
Sua linguagem preferida é Python.
|
||||
Seu projeto atual se chama Atlas.
|
||||
```
|
||||
|
||||
### Test 3 — Isolation
|
||||
|
||||
Use another customer:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-c",
|
||||
"customer_key": "outro-cliente",
|
||||
"message": "Qual é meu nome preferido e qual é meu projeto atual?"
|
||||
}
|
||||
```
|
||||
|
||||
The data for `11999999999` must not appear.
|
||||
|
||||
### Test 4 — Frontend restart
|
||||
|
||||
Restart or reset the frontend and confirm that it continues sending the same `customer_key`.
|
||||
|
||||
Memory must survive the `session_id` change. Resetting the frontend does not erase SQLite.
|
||||
|
||||
### Test 5 — Backend restart
|
||||
|
||||
Restart Uvicorn and repeat the query.
|
||||
|
||||
With:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
```
|
||||
|
||||
memory must remain available.
|
||||
|
||||
With:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
memory will be lost when the process terminates.
|
||||
|
||||
### Direct verification in SQLite
|
||||
|
||||
Locate the database:
|
||||
|
||||
```bash
|
||||
find . -name "agent_framework.db" -type f
|
||||
```
|
||||
|
||||
Open it:
|
||||
|
||||
```bash
|
||||
sqlite3 ./data/agent_framework.db
|
||||
```
|
||||
|
||||
Query it:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
tenant_id,
|
||||
agent_id,
|
||||
customer_key,
|
||||
memory_type,
|
||||
memory_key,
|
||||
memory_value,
|
||||
confidence,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM agentfw_long_term_memory
|
||||
ORDER BY updated_at DESC;
|
||||
```
|
||||
|
||||
### Success criteria
|
||||
|
||||
The implementation is working when:
|
||||
|
||||
- memory is retrieved with another `session_id`;
|
||||
- the same `customer_key` retrieves previous facts;
|
||||
- another `customer_key` cannot access those facts;
|
||||
- restarting the frontend does not erase memory;
|
||||
- restarting the backend does not erase memory when the provider is SQLite;
|
||||
- the `persist_long_term_memory` node executes;
|
||||
- the prompt receives `long_term_memory_context`.
|
||||
|
||||
### Best practices
|
||||
|
||||
- Persist only durable facts.
|
||||
- Do not store the full conversation as Long-Term Memory.
|
||||
- Isolate data by `tenant_id`, `agent_id`, and `customer_key`.
|
||||
- Do not use `session_id` as the user's permanent identity.
|
||||
- Persist only after final validations.
|
||||
- Avoid storing temporary tool results.
|
||||
- Record read, write, update, and failure telemetry.
|
||||
- Define retention and deletion policies.
|
||||
- Use an absolute SQLite path in environments with multiple execution directories.
|
||||
- Migrate to an enterprise database for production and high-availability environments.
|
||||
|
||||
### Reference-implementation limitations
|
||||
|
||||
The current implementation uses rule-based extraction and SQLite as the reference provider.
|
||||
|
||||
Recommended evolutions:
|
||||
|
||||
- fact extraction with LLM;
|
||||
- semantic memory with vectors;
|
||||
- episodic memory;
|
||||
- expiration and versioning;
|
||||
- semantic deduplication;
|
||||
- consent policy;
|
||||
- query and deletion API;
|
||||
- Oracle Autonomous Database provider;
|
||||
- encryption and sensitive-data classification.
|
||||
|
||||
### Enterprise Checkpointing in LangGraph
|
||||
|
||||
> Content consolidated from `Documentacao/README_CHECKPOINT_ENTERPRISE.md`.
|
||||
|
||||
This version adds four capabilities to the LangGraph checkpointer used by the framework:
|
||||
|
||||
1. **Checkpoint Integrity**: each checkpoint is stored inside an envelope containing `schema_version`, `checkpoint_id`, SHA-256 `payload_hash`, and `created_at`. On read, the hash is recalculated. If the payload was truncated, changed, or corrupted, the checkpoint is ignored during recovery.
|
||||
2. **Checkpoint Compaction**: old checkpoints are automatically removed according to `CHECKPOINT_COMPACT_EVERY` and `CHECKPOINT_KEEP_LAST`. This prevents unbounded growth of the `workflow_checkpoints` table.
|
||||
3. **Resilient Checkpointer**: writes and reads use retry with backoff and jitter. The resilient layer works over memory, SQLite, and Oracle/Autonomous Database.
|
||||
4. **Checkpoint Recovery**: when restoring state, the framework scans recent checkpoints and returns the newest valid one, skipping corrupted checkpoints.
|
||||
|
||||
### Configuration
|
||||
|
||||
In `.env`:
|
||||
|
||||
```env
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
ENABLE_RESILIENT_CHECKPOINTER=true
|
||||
ENABLE_CHECKPOINT_INTEGRITY=true
|
||||
ENABLE_CHECKPOINT_COMPACTION=true
|
||||
CHECKPOINT_COMPACT_EVERY=50
|
||||
CHECKPOINT_KEEP_LAST=20
|
||||
CHECKPOINT_RECOVERY_SCAN_LIMIT=25
|
||||
CHECKPOINT_RETRY_MAX_ATTEMPTS=3
|
||||
CHECKPOINT_RETRY_BASE_DELAY_SECONDS=0.05
|
||||
CHECKPOINT_RETRY_MAX_DELAY_SECONDS=1.0
|
||||
CHECKPOINT_RETRY_JITTER_SECONDS=0.05
|
||||
```
|
||||
|
||||
For production with multiple pods, prefer:
|
||||
|
||||
```env
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
|
||||
ADB_USER=...
|
||||
ADB_PASSWORD=...
|
||||
ADB_DSN=...
|
||||
ADB_WALLET_LOCATION=...
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
```
|
||||
|
||||
### Use in LangGraph
|
||||
|
||||
```python
|
||||
from agent_framework.checkpoints import create_langgraph_checkpointer
|
||||
|
||||
checkpointer = create_langgraph_checkpointer(settings)
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
config = {"configurable": {"thread_id": session_id}}
|
||||
result = graph.invoke(input_state, config=config)
|
||||
```
|
||||
|
||||
`thread_id` remains the conversation-recovery key. In an environment with a Load Balancer, any pod can resume execution if it uses the same persistent repository.
|
||||
|
||||
### Files changed
|
||||
|
||||
- `agent_framework/src/agent_framework/checkpoints/checkpoint_repository.py`
|
||||
- `agent_framework/src/agent_framework/checkpoints/langgraph_saver.py`
|
||||
- `agent_framework/src/agent_framework/checkpoints/__init__.py`
|
||||
- `agent_framework/src/agent_framework/config/settings.py`
|
||||
- `tests/unit/test_resilient_checkpointer.py`
|
||||
|
||||
### Important note
|
||||
|
||||
The `memory` provider now also uses `RepositoryCheckpointSaver` when `ENABLE_RESILIENT_CHECKPOINTER=true`. To return to LangGraph's pure `MemorySaver` for local tests, configure:
|
||||
|
||||
```env
|
||||
ENABLE_RESILIENT_CHECKPOINTER=false
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
### Source files
|
||||
|
||||
The files below were consolidated into this manual:
|
||||
|
||||
- `Documentacao/Manual_Long_Term_Memory_PT.md`
|
||||
- `Documentacao/README_CHECKPOINT_ENTERPRISE.md`
|
||||
|
||||
### Maintenance rule
|
||||
|
||||
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.
|
||||
117
docs/developer/en/09_llm_rich_response_reasoning.md
Normal file
117
docs/developer/en/09_llm_rich_response_reasoning.md
Normal file
@@ -0,0 +1,117 @@
|
||||
### LLM Rich Response and reasoning_content
|
||||
|
||||
### How to use this manual
|
||||
|
||||
This is a **specialized reference manual**. It does not replace the main tutorial.
|
||||
|
||||
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
|
||||
- Use this document when you need to implement, deepen, or diagnose **`ainvoke_response()`, inference metadata, and optional `reasoning_content`**.
|
||||
- Historical examples consolidated here should be read in light of the framework's current API.
|
||||
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
|
||||
|
||||
### Relationship with the main tutorial
|
||||
|
||||
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
|
||||
|
||||
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
|
||||
|
||||
### Scope
|
||||
|
||||
`ainvoke_response()`, inference metadata, and optional `reasoning_content`.
|
||||
|
||||
### Consolidated technical content
|
||||
|
||||
### LLM Rich Response and reasoning_content
|
||||
|
||||
Guide for using the opt-in structured LLM response API without breaking the legacy `ainvoke()` contract, including `reasoning_content`, usage, model, provider, fallback, and tests.
|
||||
|
||||
### How to use this document
|
||||
|
||||
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
|
||||
|
||||
### Rich LLM response API
|
||||
|
||||
> Content consolidated from `docs/LLM_RICH_RESPONSE.md`.
|
||||
|
||||
### Goal
|
||||
|
||||
The framework keeps `ainvoke()` as the backward-compatible API, returning only `str`, and adds `ainvoke_response()` for consumers that need additional inference metadata, including `reasoning_content` when the model/provider/API makes it available.
|
||||
|
||||
### APIs
|
||||
|
||||
### Legacy API — unchanged
|
||||
|
||||
```python
|
||||
answer = await llm.ainvoke(messages)
|
||||
assert isinstance(answer, str)
|
||||
```
|
||||
|
||||
No existing agent needs to be changed.
|
||||
|
||||
### New rich API — opt-in
|
||||
|
||||
```python
|
||||
response = await llm.ainvoke_response(messages)
|
||||
|
||||
answer = response.content
|
||||
reasoning = response.reasoning_content
|
||||
usage = response.usage
|
||||
model = response.model
|
||||
provider = response.provider
|
||||
```
|
||||
|
||||
`reasoning_content` is `str | None`. `None` is the expected behavior when the model, provider, or API does not expose textual reasoning.
|
||||
|
||||
### Backoffice
|
||||
|
||||
A consumer that previously did:
|
||||
|
||||
```python
|
||||
answer = await llm.ainvoke(messages)
|
||||
template = extract_response(answer)
|
||||
```
|
||||
|
||||
can instead do:
|
||||
|
||||
```python
|
||||
response = await llm.ainvoke_response(messages)
|
||||
template = extract_response(response.content)
|
||||
reasoning_content = response.reasoning_content
|
||||
```
|
||||
|
||||
Logic that expects text continues to receive `response.content`; reasoning remains separate and does not contaminate response, cache, memory, judges, or guardrails.
|
||||
|
||||
### Custom-provider compatibility
|
||||
|
||||
`LLMProvider.ainvoke_response()` has a fallback. An external provider that implements only `ainvoke()` continues to work and automatically receives `LLMResponse(content=<texto>)`, with `reasoning_content=None`.
|
||||
|
||||
Native providers (`mock`, OpenAI-compatible/OCI OpenAI, and OCI SDK) implement the rich response and attempt to preserve reasoning when present.
|
||||
|
||||
### Compatibility guarantees
|
||||
|
||||
- `ainvoke()` continues to return `str`.
|
||||
- No existing router, judge, RAG, memory, cache, or runtime has been migrated to the new API.
|
||||
- `reasoning_content` is never fabricated by the framework.
|
||||
- Missing reasoning does not generate an error.
|
||||
- Existing telemetry output continues to be the final content, without automatically appending reasoning.
|
||||
|
||||
### Tests
|
||||
|
||||
Specific tests are in `tests/unit/test_llm_rich_response.py` and verify:
|
||||
|
||||
1. a legacy provider that implements only `ainvoke()`;
|
||||
2. preservation of the `str` return from `ainvoke()`;
|
||||
3. `LLMResponse` return from `ainvoke_response()`;
|
||||
4. reasoning through a direct attribute;
|
||||
5. reasoning through `model_extra`;
|
||||
6. missing reasoning and extraction in OCI SDK format.
|
||||
|
||||
### Source files
|
||||
|
||||
The files below were consolidated into this manual:
|
||||
|
||||
- `docs/LLM_RICH_RESPONSE.md`
|
||||
|
||||
### Maintenance rule
|
||||
|
||||
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.
|
||||
360
docs/developer/en/10_performance_cache_and_async_runtime.md
Normal file
360
docs/developer/en/10_performance_cache_and_async_runtime.md
Normal file
@@ -0,0 +1,360 @@
|
||||
### Performance, Cache, and Async Runtime
|
||||
|
||||
### How to use this manual
|
||||
|
||||
This is a **specialized reference manual**. It does not replace the main tutorial.
|
||||
|
||||
- To create an agent from start to finish, use [`README_en.md`](../../../README_en.md).
|
||||
- Use this document when you need to implement, deepen, or diagnose **concurrency, cache, reduction of LLM calls, and cross-loop fixes**.
|
||||
- Historical examples consolidated here should be read in light of the framework's current API.
|
||||
- In case of divergence, the code for the version and the current `README_en.md` take precedence.
|
||||
|
||||
### Relationship with the main tutorial
|
||||
|
||||
The `README_en.md` presents this capability in the normal development flow. This manual brings together details that were distributed across `docs/`, `Documentacao/`, release notes, validations, and specialized guides.
|
||||
|
||||
The goal here is to answer **“how does this feature work in depth and how do I solve problems with it?”**, without turning this file into a second copy of the main tutorial.
|
||||
|
||||
### Scope
|
||||
|
||||
Concurrency, cache, reduction of LLM calls, and cross-loop fixes.
|
||||
|
||||
### Consolidated technical content
|
||||
|
||||
### Performance, Cache, Concurrency, and Async Runtime
|
||||
|
||||
Manual for optimizations on the critical MCP, RAG, and Judge path, reduction of LLM calls, deterministic preemption, and cross-loop deadlock correction in sequencing.
|
||||
|
||||
### How to use this document
|
||||
|
||||
This is the consolidated development document for this subject. It brings together architecture, configuration, examples, runtime behavior, compatibility, tests, and troubleshooting that were previously distributed across several files. Source sections were preserved when they provided distinct technical details; release notes were incorporated as current behavior or correction history.
|
||||
|
||||
### MCP, RAG, and Judge optimizations
|
||||
|
||||
> Content consolidated from `docs/PERFORMANCE_OPTIMIZATIONS_MCP_JUDGES_RAG.md`.
|
||||
|
||||
- `mcp_tools` remains an allowlist; only the query selected through `selection_keywords` is executed.
|
||||
- `strategy: hybrid` extraction tries a regex `pattern` before the LLM profile.
|
||||
- RAG is skipped when successful MCP evidence is sufficient, except for policy/rule questions.
|
||||
- `mcp_results` is provided as evidence to the groundedness judge.
|
||||
- `judges.yaml` accepts `sample_rate` and `always_run_for_transactional`.
|
||||
- Simple structured queries can return a deterministic response without invoking the agent LLM.
|
||||
|
||||
### Shift from query to transactional action
|
||||
|
||||
Route stickiness is preempted when an explicit keyword configured in `routing.yaml` identifies another intent/agent. Thus, a session in `retail_order_tracking` moves to `retail_support_exchange_return` when it receives requests such as “return order”. In addition, direct responses from read-only tools are blocked when the message contains `selection_keywords` from any registered transactional tool.
|
||||
|
||||
Action words remain in `config/tools.yaml`; the runtime does not maintain hardcoded domain aliases.
|
||||
|
||||
|
||||
### Deterministic preemption for an explicit intent change
|
||||
|
||||
Stickiness does not call a second LLM when the message contains an explicit change that can be recognized deterministically. Multi-token keywords configured in `routing.yaml` accept up to three intermediate tokens while preserving order. Therefore, `cancelar pedido` recognizes `quero cancelar meu pedido`, `cancelar o meu pedido`, and `pode cancelar esse pedido`. In this case the new intent preempts stickiness and the `keyword_match_strategy=ordered_tokens` metadata makes the decision auditable. Messages with no explicit signal continue using route stickiness normally.
|
||||
|
||||
### Cross-loop deadlock fix
|
||||
|
||||
> Content consolidated from `Documentacao/FIX_DEADLOCK_SEQUENCE_CROSS_LOOP.md`.
|
||||
|
||||
### Problem
|
||||
|
||||
The synchronous `agent_framework.observer.event()` API could be called from a worker thread with no active event loop. In that case, the previous implementation ran `asyncio.run(aevent(...))`, creating a temporary new event loop. At the same time, `analytics/tim_sequence.py` shared global `asyncio.Lock` instances (`_mongo_index_lock` and `_memory_lock`) across calls that could come from different event loops.
|
||||
|
||||
On the first Mongo operation, `_ensure_mongo_ttl_index_once()` held `_mongo_index_lock` while creating the TTL index. Contention from another loop could leave the second call waiting indefinitely.
|
||||
|
||||
### Applied changes
|
||||
|
||||
1. `observer.py`
|
||||
- removed `asyncio.run()` from the synchronous `event()` path;
|
||||
- added a dedicated reusable event loop for synchronous calls;
|
||||
- cross-thread submission uses `asyncio.run_coroutine_threadsafe()`;
|
||||
- best-effort loop shutdown when the process terminates.
|
||||
|
||||
2. `analytics/tim_sequence.py`
|
||||
- `_mongo_index_lock`: `asyncio.Lock` -> `threading.Lock`;
|
||||
- `_memory_lock`: `asyncio.Lock` -> `threading.Lock`;
|
||||
- TTL-index initialization moved to a synchronous function protected by a thread lock and called through `asyncio.to_thread()`;
|
||||
- the in-memory fallback counter uses a short thread-safe critical section.
|
||||
|
||||
3. Tests
|
||||
- `tests/test_observer_cross_loop_deadlock_fix.py` validates:
|
||||
- multiple worker threads using `event()` share the same synchronous observer loop;
|
||||
- in-memory sequence remains monotonic across independent event loops;
|
||||
- TTL-index creation happens only once under cross-loop contention.
|
||||
|
||||
### Validation performed
|
||||
|
||||
```bash
|
||||
PYTHONPATH=libs/agent_framework/src pytest -q tests/test_observer_cross_loop_deadlock_fix.py
|
||||
```
|
||||
|
||||
Result: `3 passed`.
|
||||
|
||||
The full repository suite has pre-existing/independent failures unrelated to this change, including collection conflicts for `test_long_term_memory.py`, static template paths, and checkpoint/workflow tests. Those items were not changed by this fix.
|
||||
|
||||
### Operational performance features
|
||||
|
||||
> Content consolidated from `Documentacao/README_MAX_OPERACIONAL.md`.
|
||||
|
||||
This version adds the operational adjustments that were missing to bring the framework closer to the FIRST production standard.
|
||||
|
||||
### Adjustments included in this version
|
||||
|
||||
### 1. Langfuse Enterprise Adapter
|
||||
|
||||
New module:
|
||||
|
||||
```text
|
||||
agent_framework/observability/langfuse_enterprise.py
|
||||
```
|
||||
|
||||
Includes an adapter compatible with Langfuse SDKs v2/v3 for:
|
||||
|
||||
- trace updates;
|
||||
- trace scoring/evaluation;
|
||||
- prompt registry when supported by the SDK;
|
||||
- isolation of Langfuse API differences.
|
||||
|
||||
### 2. Persistent Token and Cost Accounting
|
||||
|
||||
New package:
|
||||
|
||||
```text
|
||||
agent_framework/billing/
|
||||
```
|
||||
|
||||
Includes:
|
||||
|
||||
- `UsageRecord`
|
||||
- `SQLiteUsageRepository`
|
||||
- `OracleUsageRepository`
|
||||
- `create_usage_repository(settings)`
|
||||
|
||||
The LLM provider now records automatically:
|
||||
|
||||
- `prompt_tokens`
|
||||
- `completion_tokens`
|
||||
- `cached_tokens`
|
||||
- `total_tokens`
|
||||
- `cost_usd`
|
||||
- `cost_brl`
|
||||
- `tenant_id`
|
||||
- `agent_id`
|
||||
- `session_id`
|
||||
- `message_id`
|
||||
|
||||
New endpoint:
|
||||
|
||||
```http
|
||||
GET /debug/usage
|
||||
GET /debug/usage?tenant_id=default
|
||||
GET /debug/usage?session_id=<id>
|
||||
```
|
||||
|
||||
### 3. Operational RAG Service
|
||||
|
||||
New module:
|
||||
|
||||
```text
|
||||
agent_framework/rag/rag_service.py
|
||||
```
|
||||
|
||||
Includes:
|
||||
|
||||
- `RagService.add_documents()`
|
||||
- `RagService.retrieve()`
|
||||
- `RagResult.as_prompt_context()`
|
||||
- telemetry for latency, document count, top scores, and graph.
|
||||
|
||||
### 4. New configuration
|
||||
|
||||
Variable added:
|
||||
|
||||
```env
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
```
|
||||
|
||||
Values:
|
||||
|
||||
```text
|
||||
sqlite
|
||||
oracle
|
||||
autonomous
|
||||
```
|
||||
|
||||
### 5. Local operational compatibility
|
||||
|
||||
By default, usage accounting uses SQLite even when everything else is in memory. This makes local testing possible without Oracle.
|
||||
|
||||
### Quick test
|
||||
|
||||
```bash
|
||||
cd agent_template_backend
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
Test a message:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"channel":"web","payload":{"text":"teste","user_id":"u1","session_id":"s1"}}'
|
||||
```
|
||||
|
||||
Check usage/cost:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/debug/usage
|
||||
```
|
||||
|
||||
### To run closer to a production pattern
|
||||
|
||||
```env
|
||||
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
CACHE_BACKEND_PROVIDER=sqlite
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_HOST=http://localhost:3000
|
||||
LANGFUSE_PUBLIC_KEY=...
|
||||
LANGFUSE_SECRET_KEY=...
|
||||
```
|
||||
|
||||
For Autonomous Database:
|
||||
|
||||
```env
|
||||
SESSION_REPOSITORY_PROVIDER=oracle
|
||||
MEMORY_REPOSITORY_PROVIDER=oracle
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=oracle
|
||||
USAGE_REPOSITORY_PROVIDER=oracle
|
||||
CACHE_BACKEND_PROVIDER=oracle
|
||||
VECTOR_STORE_PROVIDER=oracle
|
||||
GRAPH_STORE_PROVIDER=oracle
|
||||
ADB_USER=...
|
||||
ADB_PASSWORD=...
|
||||
ADB_DSN=...
|
||||
ADB_WALLET_LOCATION=...
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
```
|
||||
|
||||
### Final cache, RAG, and telemetry adjustments
|
||||
|
||||
> Content consolidated from `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`.
|
||||
|
||||
This version fixes the gaps identified in the comparison against FIRST.
|
||||
|
||||
### Applied fixes
|
||||
|
||||
### 1. Operational LangGraph checkpoint
|
||||
|
||||
The workflow no longer compiles directly with `MemorySaver()`. The following adapter was created:
|
||||
|
||||
```text
|
||||
agent_framework/checkpoints/langgraph_saver.py
|
||||
```
|
||||
|
||||
It connects LangGraph to the framework's configured repository:
|
||||
|
||||
- `memory`
|
||||
- `sqlite`
|
||||
- `oracle` / `autonomous`
|
||||
|
||||
In the workflow:
|
||||
|
||||
```python
|
||||
builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
|
||||
```
|
||||
|
||||
### 2. LangGraph telemetry wrapping actual execution
|
||||
|
||||
A node wrapper was added to the workflow:
|
||||
|
||||
```python
|
||||
self._node("billing_agent", self.billing_agent)
|
||||
```
|
||||
|
||||
This way the `langgraph.node.*` span/event wraps actual node execution, not just an empty block.
|
||||
|
||||
Events emitted:
|
||||
|
||||
- `langgraph.node.started`
|
||||
- `langgraph.node.completed`
|
||||
- `langgraph.node.failed`
|
||||
- `langgraph.edge.selected`
|
||||
|
||||
### 3. RAG integrated into agents
|
||||
|
||||
Agents now receive `RagService` and use retrieved context in the prompt:
|
||||
|
||||
- BillingAgent
|
||||
- ProductAgent
|
||||
- OrdersAgent
|
||||
- SupportAgent
|
||||
|
||||
RAG uses:
|
||||
|
||||
- `VECTOR_STORE_PROVIDER=memory|sqlite|oracle|autonomous`
|
||||
- `GRAPH_STORE_PROVIDER=memory|oracle|autonomous`
|
||||
- `RAG_TOP_K`
|
||||
|
||||
### 4. Cache integrated into agent runtime
|
||||
|
||||
The following mixin was created:
|
||||
|
||||
```text
|
||||
agent_template_backend/app/agents/runtime.py
|
||||
```
|
||||
|
||||
It adds:
|
||||
|
||||
- standardized RAG retrieval;
|
||||
- cache key for LLM calls;
|
||||
- hit/miss with telemetry;
|
||||
- distributed cache through `create_cache(settings)`.
|
||||
|
||||
### 5. Unit tests
|
||||
|
||||
The following directory was created:
|
||||
|
||||
```text
|
||||
tests/unit
|
||||
```
|
||||
|
||||
Initial coverage:
|
||||
|
||||
- cache;
|
||||
- SSE;
|
||||
- RAG;
|
||||
- checkpoint saver;
|
||||
- LangGraph telemetry;
|
||||
- agent runtime;
|
||||
- static workflow verification;
|
||||
- main imports.
|
||||
|
||||
Local validation performed:
|
||||
|
||||
```text
|
||||
12 passed
|
||||
```
|
||||
|
||||
### How to test
|
||||
|
||||
```bash
|
||||
cd projeto_agent_framework_first_ready
|
||||
pip install -r agent_template_backend/requirements.txt
|
||||
pytest -q tests/unit
|
||||
```
|
||||
|
||||
### Source files
|
||||
|
||||
The files below were consolidated into this manual:
|
||||
|
||||
- `docs/PERFORMANCE_OPTIMIZATIONS_MCP_JUDGES_RAG.md`
|
||||
- `Documentacao/FIX_DEADLOCK_SEQUENCE_CROSS_LOOP.md`
|
||||
- `Documentacao/README_MAX_OPERACIONAL.md`
|
||||
- `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`
|
||||
|
||||
### Maintenance rule
|
||||
|
||||
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.
|
||||
@@ -0,0 +1,740 @@
|
||||
### 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 OpenAI auto-instrumentation policy
|
||||
|
||||
The official framework template configuration is:
|
||||
|
||||
```env
|
||||
ENABLE_LANGFUSE=true
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=false
|
||||
```
|
||||
|
||||
The `false` value is intentional. The framework already records model calls through `Telemetry.generation(...)` and keeps each generation inside the request trace. Enabling `langfuse.openai` at the same time adds a second instrumentation layer and may produce standalone `OpenAI-generation` root traces, duplicate observations, and duplicate token/cost accounting.
|
||||
|
||||
Use `true` only to capture direct OpenAI/OpenAI-compatible SDK calls that occur outside the framework telemetry layer. This is a compatibility/diagnostic mode, not the operational default. Every `.env.example` in the repository must explicitly keep the value set to `false`.
|
||||
|
||||
### 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.
|
||||
|
||||
### Role of `app/observability/` in an agent
|
||||
|
||||
The framework contains generic observability infrastructure; the template contains only the agent-specific integration point. In `agent_template_backend`, the reference structure is:
|
||||
|
||||
```text
|
||||
app/observability/
|
||||
├── __init__.py
|
||||
└── telemetry_observer.py
|
||||
```
|
||||
|
||||
`app/observability/` is appropriate for:
|
||||
|
||||
- constructing/configuring the observer used by the agent;
|
||||
- enriching events with host-specific metadata;
|
||||
- adapting the agent contract to external integrations;
|
||||
- registering template callbacks/adapters;
|
||||
- applying agent-specific mappings/overlays when configuration supports them.
|
||||
|
||||
It must **not** contain a second implementation of OpenTelemetry, Langfuse, IC/NOC/GRL, sequence, exporters, correlation, or persistence. Those are cross-cutting mechanisms owned by `agent_framework`.
|
||||
|
||||
Expected dependency direction:
|
||||
|
||||
```text
|
||||
app/observability/telemetry_observer.py
|
||||
│ configures/composes
|
||||
▼
|
||||
agent_framework.observability + shared contracts
|
||||
│
|
||||
├── Langfuse
|
||||
├── OpenTelemetry
|
||||
├── IC / NOC / GRL
|
||||
└── exporters / mappings / pub-sub
|
||||
```
|
||||
|
||||
When creating a new agent, prefer reusing the reference adapter and changing only configuration/domain metadata. If multiple agents need the same change, implement it in the core and keep `app/observability/` thin.
|
||||
|
||||
**Anti-patterns:** importing telemetry SDKs directly in every agent to emit equivalent events; hardcoding historical labels in the agent runtime; using observability events as the source of truth for transaction state; sending full prompts, secrets, PII, or raw financial payloads.
|
||||
|
||||
### 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,188 @@
|
||||
# 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.
|
||||
|
||||
## COER, `expected_input`, and `reprompt` in paused workflows
|
||||
|
||||
When an active `expected_input.semantic_classifier` exists, the framework may delegate semantic interpretation to the workflow contract instead of immediately treating an ambiguous input as a generic `COER` block. In this mode, the guardrail may be recorded as allowed/delegated, for example with mechanism `expected_input_semantic_classifier`.
|
||||
|
||||
The workflow remains responsible for distinguishing:
|
||||
|
||||
```text
|
||||
valid, meaningful reply -> valid class / option_action
|
||||
insufficient or meaningless -> unmatched_value -> reprompt
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
expected_input:
|
||||
allowed_values: [SIM, NAO, CONTINUAR]
|
||||
reprompt: "I did not understand. Did this explanation resolve your question? Please answer yes or no."
|
||||
semantic_classifier:
|
||||
enabled: true
|
||||
unmatched_value: OUTRO
|
||||
option_actions:
|
||||
CONTINUAR:
|
||||
action: contextual_reentry
|
||||
```
|
||||
|
||||
Under this contract, `CONTINUAR` does not mean "anything other than SIM or NAO". It represents a **coherent, contextual** utterance that continues the topic. Input such as `ano`, which has insufficient meaning for the pending prompt, should return sentinel `OUTRO` and produce the `reprompt`, while keeping the same workflow paused.
|
||||
|
||||
`OUTRO` must not be added to `allowed_values` or sent to the runtime as a resume value. See [03 — Transactional Workflows and State](./03_transaction_workflows_and_state.md#expected_inputsemantic_classifierunmatched_value-and-reprompt) for the full contract.
|
||||
|
||||
## 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;
|
||||
- with `expected_input.semantic_classifier.unmatched_value`, incoherent/insufficient input triggers `reprompt` and keeps the workflow `PAUSED`;
|
||||
- meaningful input outside literal SIM/NAO can still follow `CONTINUAR -> contextual_reentry` without being confused with incoherent input;
|
||||
- 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.
|
||||
121
docs/developer/en/13_advanced_transactional_workflows.md
Normal file
121
docs/developer/en/13_advanced_transactional_workflows.md
Normal file
@@ -0,0 +1,121 @@
|
||||
### Advanced transactional workflows
|
||||
|
||||
### Goal
|
||||
|
||||
This chapter turns a sensitive tool into a deterministic transaction with parameter collection, pre-validation, confirmation, versioned workflow, pause/resume, idempotency, and recovery. Business rules and actions belong to the agent; the execution engine belongs to the framework.
|
||||
|
||||
### Actual flow
|
||||
|
||||
1. `routing.yaml` selects the agent and limits candidate tools.
|
||||
2. `tools.yaml` describes arguments, prompts, and response behavior.
|
||||
3. `mcp_parameter_mapping.yaml` resolves identity, defaults, and extraction.
|
||||
4. `tool_policies.yaml` classifies the operation and requires confirmation.
|
||||
5. Optional pre-validation checks whether execution may continue without producing an effect.
|
||||
6. After unambiguous confirmation, execution is direct or delegated to `WorkflowToolExecutor`.
|
||||
7. `WorkflowRuntime` loads the active version, runs actions, persists pauses, and returns `COMPLETED`, `PAUSED`, or `FAILED`.
|
||||
8. An idempotency key prevents duplicate external effects.
|
||||
|
||||
### Catalog and policy are different contracts
|
||||
|
||||
```yaml
|
||||
# config/tools.yaml
|
||||
tools:
|
||||
request_return:
|
||||
description: Opens an order return.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
confirmation_required: true
|
||||
requires: [order_id, reason]
|
||||
args_schema:
|
||||
order_id:
|
||||
type: string
|
||||
label: the order number
|
||||
user_prompt: Provide the order number.
|
||||
reason:
|
||||
type: string
|
||||
label: the return reason
|
||||
user_prompt: What is the return reason?
|
||||
```
|
||||
|
||||
```yaml
|
||||
# config/tool_policies.yaml
|
||||
version: 1
|
||||
defaults:
|
||||
operation_type: read_only
|
||||
require_confirmation: false
|
||||
tool_policies:
|
||||
request_return:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [order_id, reason]
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validate_return
|
||||
fail_open: false
|
||||
execution:
|
||||
mode: workflow
|
||||
workflow: order_return
|
||||
version: active
|
||||
```
|
||||
|
||||
`tools.yaml` is the public catalog; `tool_policies.yaml` governs effects. Do not rely only on catalog `confirmation_required`: effective policy must declare `operation_type` and `require_confirmation`. A pre-validation tool must be read-only and never perform the transaction early. Prefer `fail_open: false` for sensitive operations.
|
||||
|
||||
### Versioned workflow and actions
|
||||
|
||||
Store definitions under `workflows/<name>.vN.yaml` and maintain a valid active definition. Node IDs must be unique; `start`, destinations, and `resume_from` must exist; `retry` accepts 0 through 10. Paths under `$.input.*` read workflow input and `$.nodes.<id>.*` read earlier results.
|
||||
|
||||
Agent actions use `@workflow_action("name")`, matching `node.action`, and implement `(params: dict, state: dict) -> dict`. Import the action module during startup so decorators register. Return serializable data only—never an HTTP client, connection, exception, or coroutine. Complete executable YAML and Python are available in the matching [Portuguese chapter](../pt/13_workflows_transacionais_avancados.md).
|
||||
|
||||
### Organization of `app/workflow_actions/`
|
||||
|
||||
In the template, domain-specific actions live in `app/workflow_actions/`. The distributed example uses:
|
||||
|
||||
```text
|
||||
app/workflow_actions/
|
||||
├── __init__.py
|
||||
└── devolucao.py
|
||||
```
|
||||
|
||||
This directory should contain **only business implementation invoked by workflows** and small helpers directly related to those actions. The workflow engine, decorator, registry, persistence, pause/resume mechanism, and generic idempotency infrastructure remain in `agent_framework`.
|
||||
|
||||
A larger agent can split actions by domain or aggregate:
|
||||
|
||||
```text
|
||||
app/workflow_actions/
|
||||
├── billing.py
|
||||
├── orders.py
|
||||
└── vas.py
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- YAML `node.action` must match the name registered with `@workflow_action`;
|
||||
- import required modules during startup so the registry is populated before first execution;
|
||||
- never make the core import `app.workflow_actions.*`; dependency direction is agent → framework;
|
||||
- do not turn an action into a second router or agent runtime; it should perform one bounded business step;
|
||||
- for external side effects, use framework policy/idempotency infrastructure and persist only confirmed outcomes.
|
||||
|
||||
**Anti-pattern:** copying `agent_framework.workflows` classes into `app/workflow_actions/` to customize a local case. If a generic mechanism is missing, extend the framework through a reusable interface and keep only domain rules in the agent.
|
||||
|
||||
### Idempotent external effects
|
||||
|
||||
Create one `create_idempotency_store(...)` instance at startup and inject it into actions. Build the key from stable business identity, operation, resource, and contract version—not merely a timestamp. `IDEMPOTENCY_PROVIDER` takes precedence over checkpoint, session, and cache providers. For production transactions, enable `IDEMPOTENCY_REQUIRE_DURABLE=true` and select an appropriate `oracle`, `redis`, or `sqlite` provider. Memory does not protect across pods or restarts. Persist the outcome only after the external system confirms the effect.
|
||||
|
||||
### Pause, expected input, and resume
|
||||
|
||||
A node may declare `pause.expected_input` with `key`, `allowed_values`, normalization, `reprompt`, and a semantic classifier. Explicit confirmation remains deterministic; the classifier handles only inconclusive replies and must return an allowed option. `contextual_reentry` releases the workflow and returns the utterance to normal routing without confirming user facts.
|
||||
|
||||
Resume with the same `execution_id`; a new execution creates a separate transaction. The checkpoint must contain serializable data sufficient to rebuild the pause. Duplicate replies must check idempotency before repeating an effect.
|
||||
|
||||
### Intent shift, recovery, and replay
|
||||
|
||||
When a user abandons a transaction, clear pending call, collected arguments, confirmation, and related transaction state. Do not reuse parameters from a closed transaction. A contextual question may be answered and return to the pause; a genuine intent change explicitly closes or suspends the flow according to agent policy.
|
||||
|
||||
- `PAUSED`: persist execution ID, prompt, and expected-input contract.
|
||||
- `FAILED`: keep technical details in telemetry and return a safe message.
|
||||
- timeout after sending: query idempotency or external status before resending.
|
||||
- replay after `COMPLETED`: return the persisted result without execution.
|
||||
- new version: in-flight runs retain their version; `active` applies to new runs.
|
||||
|
||||
Test invalid graphs, valid/invalid paths, retry, pause/resume, unmatched answers, intent shift, duplicates, idempotency-provider failure, and post-finalization replay. Run `pytest -q tests/test_advanced_developer_documentation.py`.
|
||||
@@ -0,0 +1,31 @@
|
||||
### Advanced routing, parameters, and state
|
||||
|
||||
### Decision precedence
|
||||
|
||||
Routing is not a keyword list. Safe precedence is: explicit block/finalization; pending collection or transaction through `state_policies`; deterministic confirmation; deterministic intent; optional LLM fallback; and finally fallback/handoff. Pending state must win so short replies and isolated parameters do not jump to another agent.
|
||||
|
||||
The complete executable YAML is in the matching [Portuguese chapter](../pt/14_routing_parametros_e_estado_avancados.md). In `routing.yaml`, lower `priority` values win ties in the current template. `mcp_tools` limits candidates but does not authorize sensitive effects. `confidence_threshold` gates semantic results, `allow_handoff` permits an explicit agent change, and `domain` supplies scope metadata rather than replacing `agent`.
|
||||
|
||||
### Semantic confirmation
|
||||
|
||||
Constrain semantic confirmation with `allowed_values`, plus explicit confirm, reject, and continue sets. The prompt must include the allowed values and prohibit execution or invented facts. `include_relevant_context` contributes bounded context related to the pause. If model output does not match an allowed option, keep the transaction pending. Explicit yes/no remains deterministic; the LLM handles inconclusive language only.
|
||||
|
||||
### Identity, mapping, and extraction
|
||||
|
||||
`identity.yaml` resolves canonical customer, contract, interaction, account, resource, and session keys from the payload, prior `business_context`, or previous context. Validate required identity before MCP execution. A session identifier is not customer identity.
|
||||
|
||||
Recommended argument precedence is: already confirmed transaction value, explicit current value, canonical identity mapping, deterministic extraction, LLM/hybrid extraction, and default. A new explicit value may correct an old one; an inferred value must never overwrite a confirmed value.
|
||||
|
||||
For `strategy: hybrid`, test both regex and LLM paths. `pattern` and `group` require positive and negative tests. `description` is an operational extraction instruction, not decorative documentation. Type conversion must fail safely when ambiguous.
|
||||
|
||||
### Temporal reconciliation
|
||||
|
||||
Temporal reconciliation is a fallback only after ordinary collection leaves missing fields. Search relevant messages newest-first and bind values only to fields described by the tool schema/mapping. It must not cross tenant/agent/session, revive a closed transaction, recover a user-invalidated value, replace safe exact matching with dangerous fuzzy matching, or overwrite a confirmed field. Record value origin and correlation without retaining unnecessary sensitive text.
|
||||
|
||||
### Dynamic states and multi-agent behavior
|
||||
|
||||
Declare new collection and confirmation state fields only when they carry extra data; otherwise reuse generic transaction keys. Every new state needs a matching `state_policies` entry, cleanup on success/cancellation/intent shift, and routing tests. A new state without policy commonly loses route stickiness.
|
||||
|
||||
Router mode selects one agent. Supervisor mode coordinates multiple agents and changes cost, latency, state, and telemetry. Do not enable it merely to improve classification. Give subagents minimum context and authorized tools, scoped by `tenant_id:agent_id:session_id`; never share raw memory across agents.
|
||||
|
||||
Test keyword overlap, priority ties, below-threshold output, fallback, pending states, short confirmations, ambiguity, parameter correction, regex/LLM extraction, temporal history, and isolation. Run `pytest -q tests/test_advanced_developer_documentation.py`.
|
||||
@@ -0,0 +1,46 @@
|
||||
### Presentation, recovery, and observability
|
||||
|
||||
### Deterministic renderers
|
||||
|
||||
Use `response.mode: renderer` for trusted structured tool results whose values must not be changed by an LLM. Register each renderer once at startup. Its keyword-only contract accepts `tool_name`, `result`, `state`, and `agent_label`; return `None` when required fields are absent so the configured fallback can run. Select, format, and mask allowed fields—never interpolate the complete payload. The executable YAML and implementation are in the matching [Portuguese chapter](../pt/15_apresentacao_recuperacao_e_observabilidade.md).
|
||||
|
||||
### Organization of `app/presentation/`
|
||||
|
||||
In `agent_template_backend`, domain-specific renderers live in:
|
||||
|
||||
```text
|
||||
app/presentation/
|
||||
├── __init__.py
|
||||
└── tool_renderers.py
|
||||
```
|
||||
|
||||
The folder separates **how a trusted domain result should be presented** from the generic infrastructure that selects and executes renderers. Therefore:
|
||||
|
||||
- `app/presentation/` implements functions such as `financial.balance`, `billing.invoice`, or another agent-specific renderer;
|
||||
- `agent_framework.presentation` owns the registry, call contract, and generic runtime integration;
|
||||
- `config/tools.yaml` selects `response.mode: renderer` and the renderer name;
|
||||
- agent startup imports/registers renderers once.
|
||||
|
||||
Renderers must not fetch data, decide authorization, execute tools, mutate transactional state, or reinterpret a result with an LLM. Their input should already be an authorized/trusted result; the renderer only selects, masks, and formats fields.
|
||||
|
||||
**Anti-pattern:** building a copy of the framework presentation registry or pipeline inside `app/presentation/`. If several agents need the same formatting/selection mechanism, evolve it in the core; only domain-specific presentation remains in the agent.
|
||||
|
||||
### Output supervision and recovery
|
||||
|
||||
The Output Supervisor may allow, sanitize, retry, block, hand over, or observe candidate output. Keep retries bounded, provide objective retry guidance, and revalidate rewritten output. Permanent authorization or dependency failure is not a content retry.
|
||||
|
||||
Distinguish validation, transient dependency, permanent dependency, persistence, and programming failures. Before an external send, bounded retry may be safe. After an ambiguous send, query status/idempotency before resending. After external confirmation, persist the outcome before replying. A new user message must not erase evidence of uncertain execution. Checkpoint retry, integrity checks, compaction, and recovery do not make an external action idempotent.
|
||||
|
||||
### Voice interruption and replay
|
||||
|
||||
On barge-in, preserve confirmed transcript, mark interrupted output, and discard stale audio. An interrupted utterance is not transaction confirmation. Replay uses interaction/message identity and the same scoped session; resume only after revalidating pending state, confirmation, and idempotency.
|
||||
|
||||
### Events, mappings, and Pub/Sub
|
||||
|
||||
Emit functional behavior as IC, operational dependency/latency as NOC, and guardrail/supervisor decisions as GRL through the observer abstraction. Include tenant, agent, session, transaction/execution, component, and duration where available. Never record credentials, complete prompts, PII, or raw financial payloads.
|
||||
|
||||
Map legacy consumer labels through a default registry plus agent overlay. Keep internal events semantic; do not hardcode historical external labels in runtime code.
|
||||
|
||||
Pub/Sub publication occurs after the functional decision and must not change its outcome. Ordering requires explicit `sequence`, correlation, and a stable partition key; multiple pods do not provide global ordering. Define retention/TTL, duplicate handling, filters, and failure policy. A Pub/Sub filter does not remove the event from other telemetry destinations.
|
||||
|
||||
Validate payload masking, supervisor revalidation, bounded retry, uncertain effects, correlation, mapping, multi-consumer ordering, and voice behavior. Run `pytest -q tests/test_advanced_developer_documentation.py`.
|
||||
@@ -0,0 +1,31 @@
|
||||
### Multi-agent composition and lifecycle
|
||||
|
||||
### Effective isolation
|
||||
|
||||
An `agent_id` is not merely a label. Real isolation includes prompt, routing, tools, MCP servers, guardrails, judges, memory, checkpoints, cache, telemetry, and identity, normally scoped as `tenant_id:agent_id:session_id`. The complete registry and runtime-bundle code are in the matching [Portuguese chapter](../pt/16_multiagente_composicao_e_ciclo_de_vida.md).
|
||||
|
||||
Every configured path must exist and be validated at startup. A system prefix guides the model but is not a security boundary; code must enforce tool authorization, persistence namespaces, and pipeline selection.
|
||||
|
||||
### Current template limitation
|
||||
|
||||
Although `agents.yaml` accepts per-agent paths, the template currently builds one router, one tool/RAG composition, and one global guardrail/judge pair. YAML alone does not provide per-request isolation. Use one global profile per deployment or implement an explicit cached runtime bundle per `agent_id`. Cache immutable/expensive objects rather than constructing LLM clients, routers, or pipelines for every message. Never silently fall back to another agent's bundle.
|
||||
|
||||
### Subagents and LLM composition
|
||||
|
||||
A subagent is a bounded capability with declared purpose, input/output, authorized tools, timeout/budget, and return conditions. A supervisor consolidates structured results and passes minimum relevant context with correlation and maximum delegation depth. Use routing for one-specialty turns; use supervision only for genuine decomposition. Handoff transfers conversation ownership; subagent delegation leaves final-response ownership with the supervisor.
|
||||
|
||||
Reuse framework LLM profiles and clients rather than creating provider SDKs inside domain components. Explicit, observable profile fallback preserves authentication, rate limits, retries, cache, and telemetry; configuration failure must not become an apparently valid answer.
|
||||
|
||||
### RAG, memory, and security
|
||||
|
||||
`RAG_PROVIDER=kbdb` works only when the execution's `RagService` uses the intended settings. A simple intent should still retrieve knowledge when agent policy requires it. Scope RAG collections, long-term memory, and checkpoints by tenant and agent. Long-term memory stores permitted summarized facts; checkpoints store execution state; neither may mix another agent's identity.
|
||||
|
||||
Authentication establishes a principal; authorization controls tenant, agent, route, and tool. Production secrets require hashing/rotation and TLS. Trusted-proxy headers are safe only behind an authenticated edge that removes client-supplied values.
|
||||
|
||||
### Startup and delivery lifecycle
|
||||
|
||||
At startup validate registry/paths, create providers, register actions/renderers, load workflows, construct bundles, and check mandatory dependencies. `/health` reports process liveness; `/ready` must reject traffic when a required dependency is unusable. Invalid configuration should fail before the first conversation.
|
||||
|
||||
Development order: copy the canonical template; create domain agent/configuration; register node and route; implement tools, mappings, policies, actions, and renderers; configure controls/RAG/memory/telemetry; run unit, loader, graph, conversational, and failure tests; run offline evaluation, load, and readiness checks; publish with version, compatibility, and rollback.
|
||||
|
||||
The definition of done includes isolation, confirmation, idempotency, recovery, grounding, controls, correlated telemetry, no data leakage, dependency-failure behavior, and PT/EN documentation parity. Run `pytest -q tests/test_advanced_developer_documentation.py`.
|
||||
168
docs/developer/en/INDEX_DEVELOPER_GUIDE.md
Normal file
168
docs/developer/en/INDEX_DEVELOPER_GUIDE.md
Normal file
@@ -0,0 +1,168 @@
|
||||
### 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) |
|
||||
| Invalid input during `expected_input` becomes `CONTINUAR` instead of asking for clarification | `semantic_classifier.unmatched_value`, `reprompt`, `contextual_reentry`, delegated COER | [Transactional Workflows](./03_transaction_workflows_and_state.md) and [Input Guardrail Feedback](./12_input_guardrail_feedback_and_blocked_turns.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) |
|
||||
| I need to run one operation for multiple products/orders/items | scalar or collection contract, pre-validation, single confirmation, per-item outcome | [MCP Multi-item](../../MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md) and [MCP/Tools](./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/resumes, unintended `CONTINUAR`/`contextual_reentry`, missing `reprompt`, 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, parameter extraction, and multi-item calls.
|
||||
|
||||
**Use when:** tool integration/execution is incorrect, needs to be created, or must process item lists without parallel logic in the agent.
|
||||
|
||||
### [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.
|
||||
|
||||
### [13 — Advanced Transactional Workflows](./13_advanced_transactional_workflows.md)
|
||||
|
||||
**What it is:** pre-validation, versioned workflow actions, pause/resume, idempotency, replay, and recovery.
|
||||
|
||||
**Use when:** a sensitive tool requires a deterministic and recoverable process.
|
||||
|
||||
### [14 — Advanced Routing, Parameters, and State](./14_advanced_routing_parameters_and_state.md)
|
||||
|
||||
**What it is:** routing precedence, semantic confirmation, hybrid extraction, temporal reconciliation, dynamic states, and subagents.
|
||||
|
||||
**Use when:** routing or collection works on the happy path but fails in real conversations.
|
||||
|
||||
### [15 — Presentation, Recovery, and Observability](./15_presentation_recovery_and_observability.md)
|
||||
|
||||
**What it is:** renderers, Output Supervisor, recovery, voice replay, events, mappings, and ordered Pub/Sub.
|
||||
|
||||
**Use when:** output changes evidence, an effect may repeat, or telemetry cannot reconstruct execution.
|
||||
|
||||
### [16 — Multi-Agent Composition and Lifecycle](./16_multi_agent_composition_and_lifecycle.md)
|
||||
|
||||
**What it is:** actual isolation, per-agent bundles, supervisor/subagents, LLM/RAG/memory, and readiness.
|
||||
|
||||
**Use when:** multiple agents share one deployment or a new capability must reach production.
|
||||
|
||||
### 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 → workflows → idempotency → execution → tests → gateways → memory → RAG → operations`.
|
||||
|
||||
### 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.
|
||||
83
docs/developer/en/VALIDATION_README_ALIGNMENT.md
Normal file
83
docs/developer/en/VALIDATION_README_ALIGNMENT.md
Normal file
@@ -0,0 +1,83 @@
|
||||
### Documentation Alignment Validation
|
||||
|
||||
### Goal
|
||||
|
||||
Record how the documentation for this version was reorganized and which sources developers should use.
|
||||
|
||||
### Structural decision
|
||||
|
||||
The root `README_en.md` is the **single end-to-end main tutorial**.
|
||||
|
||||
The former `01_architecture_and_agent_development.md` was removed because it repeated a large part of the README, but not all of it. This created ambiguity: two documents appeared to teach the same thing, but one was partial.
|
||||
|
||||
The new structure replaces that file with `01_architecture_and_concepts.md`, which contains only architecture, concepts, responsibilities, and extension criteria.
|
||||
|
||||
### Validation of `README_old2.md`
|
||||
|
||||
`Documentacao/README_old2.md` remains useful as history, but it is not the primary source for development.
|
||||
|
||||
Later evolutions were found in the current README and code, including:
|
||||
|
||||
- SPECs/SDDs;
|
||||
- more complete `llm_profiles.yaml` configuration;
|
||||
- Channel Gateway and canonical contracts;
|
||||
- `memory` and `summary_memory` in the current agent lifecycle;
|
||||
- `prepare_memory_context()` and `build_messages()`;
|
||||
- `RuntimeContext`;
|
||||
- `normalize_tools_by_intent()`;
|
||||
- `build_tool_arguments()`;
|
||||
- `execute_tools_for_intent()`;
|
||||
- transaction-state helpers;
|
||||
- direct MCP responses;
|
||||
- evolution of gateways, RAG, memory, and policies.
|
||||
|
||||
### Correction applied to the main README
|
||||
|
||||
The following typo was corrected in the generated package:
|
||||
|
||||
```python
|
||||
from app.agents.financeiro_agent import FinanceirotAgent
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```python
|
||||
from app.agents.financeiro_agent import FinanceiroAgent
|
||||
```
|
||||
|
||||
The correct class is confirmed by the code and the rest of the documentation.
|
||||
|
||||
### APIs confirmed in the current implementation
|
||||
|
||||
```python
|
||||
AgentRuntimeMixin.get_runtime_context()
|
||||
AgentRuntimeMixin.normalize_tools_by_intent()
|
||||
AgentRuntimeMixin.build_tool_arguments()
|
||||
AgentRuntimeMixin.execute_tools_for_intent()
|
||||
AgentRuntimeMixin.prepare_memory_context()
|
||||
AgentRuntimeMixin.build_messages()
|
||||
AgentRuntimeMixin.transaction_state_patch()
|
||||
AgentRuntimeMixin.transaction_clarification_message()
|
||||
AgentRuntimeMixin.transaction_confirmation_message()
|
||||
AgentRuntimeMixin.build_direct_mcp_answer()
|
||||
```
|
||||
|
||||
### Trust order
|
||||
|
||||
1. code for the version;
|
||||
2. main README for the same version;
|
||||
3. SPECs/SDDs;
|
||||
4. specialized manuals;
|
||||
5. release notes;
|
||||
6. `README_old*` documents.
|
||||
|
||||
### Future maintenance rule
|
||||
|
||||
A feature evolution should update:
|
||||
|
||||
1. the main README, **only if it changes the normal development path**;
|
||||
2. the feature's specialized manual, with technical details, behavior, configuration, and troubleshooting;
|
||||
3. the SPEC, when there is a contract change;
|
||||
4. the release note, when it is necessary to record the historical change.
|
||||
|
||||
Do not create a new “main manual” for a feature. Do not keep functional fixes permanently only in release notes.
|
||||
293
docs/developer/pt/01_architecture_and_concepts.md
Normal file
293
docs/developer/pt/01_architecture_and_concepts.md
Normal file
@@ -0,0 +1,293 @@
|
||||
|
||||
### 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.
|
||||
|
||||
### Pastas de extensão do agente no template
|
||||
|
||||
O `agent_template_backend` materializa alguns pontos de extensão do framework em pastas locais. Essas pastas **não duplicam o core**: elas contêm configuração declarativa ou implementação específica do domínio que é registrada/consumida pelos mecanismos genéricos de `agent_framework`.
|
||||
|
||||
```text
|
||||
templates/agent_template_backend/
|
||||
├── workflows/
|
||||
│ ├── <workflow>.vN.yaml # definição versionada do fluxo determinístico
|
||||
│ └── <workflow>.active.yaml # versão ativa selecionada pelo agente
|
||||
└── app/
|
||||
├── workflow_actions/ # actions de negócio chamadas pelos workflows YAML
|
||||
├── presentation/ # renderers determinísticos específicos do domínio
|
||||
└── observability/ # adapters/observers e integração local de telemetria
|
||||
```
|
||||
|
||||
A fronteira arquitetural é:
|
||||
|
||||
| Pasta do template | Deve conter | Permanece no core |
|
||||
| --- | --- | --- |
|
||||
| `workflows/` | YAMLs declarativos e versionados do domínio | parser, validação, registry, executor, pause/resume e runtime de workflow |
|
||||
| `app/workflow_actions/` | funções de negócio registradas com `@workflow_action` | decorator, registry, execução, política de retry e infraestrutura de idempotência |
|
||||
| `app/presentation/` | renderers que transformam resultados confiáveis em resposta de domínio | registry de renderers, seleção do modo de resposta e fallback do runtime |
|
||||
| `app/observability/` | observer/adapters, mappings e enriquecimento específicos do agente | contratos IC/NOC/GRL, correlação, OTEL/Langfuse, exporters e runtime de observabilidade |
|
||||
|
||||
Regra prática: se uma implementação poderia ser usada sem conhecer telecom, varejo, cobrança, pedido ou outro domínio específico, ela provavelmente pertence ao `agent_framework`, e não a uma dessas pastas do agente. O core também não deve importar módulos concretos dessas pastas; o agente registra suas extensões no startup.
|
||||
|
||||
Detalhes: [Workflows Transacionais e Estado](./03_transaction_workflows_and_state.md), [Workflows transacionais avançados](./13_workflows_transacionais_avancados.md), [Observabilidade, Persistência e Prontidão Operacional](./11_observability_persistence_and_operational_readiness.md) e [Apresentação, recuperação e observabilidade](./15_apresentacao_recuperacao_e_observabilidade.md).
|
||||
|
||||
### 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)
|
||||
1491
docs/developer/pt/02_routing_stickiness_and_intent_shift.md
Normal file
1491
docs/developer/pt/02_routing_stickiness_and_intent_shift.md
Normal file
File diff suppressed because it is too large
Load Diff
782
docs/developer/pt/03_transaction_workflows_and_state.md
Normal file
782
docs/developer/pt/03_transaction_workflows_and_state.md
Normal file
@@ -0,0 +1,782 @@
|
||||
|
||||
### 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/`
|
||||
|
||||
### Organização física de `workflows/` no template
|
||||
|
||||
No `agent_template_backend`, as definições declarativas ficam na raiz `workflows/` do próprio agente:
|
||||
|
||||
```text
|
||||
templates/agent_template_backend/
|
||||
└── workflows/
|
||||
├── devolucao_pedido.v1.yaml
|
||||
└── devolucao_pedido.active.yaml
|
||||
```
|
||||
|
||||
Essa pasta pertence ao **agente**, porque expressa a sequência de negócio, versões e decisões daquele domínio. O mecanismo que interpreta esses arquivos pertence ao **framework**. Portanto:
|
||||
|
||||
- não implemente parser, executor ou registry dentro de `workflows/`;
|
||||
- não coloque código Python nessa pasta; actions Python ficam em `app/workflow_actions/`;
|
||||
- mantenha versões imutáveis (`*.vN.yaml`) e uma definição ativa válida (`*.active.yaml`);
|
||||
- use `WORKFLOWS_PATH` quando o host precisar apontar para outro diretório;
|
||||
- mudanças genéricas em pause/resume, retry, validação ou execução devem ser feitas no módulo `agent_framework.workflows`, não copiadas para o template.
|
||||
|
||||
O fluxo completo é: **YAML em `workflows/` → engine do framework → `node.action` → registry de actions → função em `app/workflow_actions/`**.
|
||||
|
||||
### 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`.
|
||||
|
||||
### `expected_input.semantic_classifier.unmatched_value` e `reprompt`
|
||||
|
||||
Workflows pausados podem aceitar respostas literais e também interpretar respostas livres por semântica. Quando o `semantic_classifier` é habilitado, uma fala que **não representa com segurança nenhuma das classes válidas** não deve ser forçada para `SIM`, `NAO` ou `CONTINUAR`. Para esse caso, o workflow pode declarar um valor sentinela com `unmatched_value`.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```yaml
|
||||
expected_input:
|
||||
key: resposta_usuario
|
||||
allowed_values:
|
||||
- SIM
|
||||
- NAO
|
||||
- CONTINUAR
|
||||
normalize: upper_strip
|
||||
reprompt: >
|
||||
Não entendi. Essa explicação resolveu sua dúvida?
|
||||
Responda sim ou não.
|
||||
|
||||
semantic_classifier:
|
||||
enabled: true
|
||||
include_relevant_context: true
|
||||
unmatched_value: OUTRO
|
||||
prompt: |
|
||||
Classifique a fala do cliente em exatamente uma das opções válidas quando houver
|
||||
significado suficiente.
|
||||
|
||||
SIM: entendimento/aceite inequívoco.
|
||||
NAO: negativa inequívoca.
|
||||
CONTINUAR: fala compreensível que complementa ou continua a solicitação.
|
||||
OUTRO: fala incompreensível, desconexa ou insuficiente para qualquer classe válida.
|
||||
|
||||
Retorne somente SIM, NAO, CONTINUAR ou OUTRO.
|
||||
|
||||
option_actions:
|
||||
CONTINUAR:
|
||||
action: contextual_reentry
|
||||
```
|
||||
|
||||
O contrato é:
|
||||
|
||||
```text
|
||||
SIM -> resume com SIM
|
||||
NAO -> resume com NAO
|
||||
CONTINUAR -> executa a option_action configurada (por exemplo contextual_reentry)
|
||||
OUTRO -> não é usado como valor de resume; devolve o reprompt e mantém o workflow pausado
|
||||
```
|
||||
|
||||
Regras importantes:
|
||||
|
||||
- `unmatched_value` é **sentinela do classificador**, não uma resposta de negócio;
|
||||
- não adicione o sentinela a `allowed_values`; `allowed_values` continua descrevendo apenas valores que podem ser consumidos pelo workflow;
|
||||
- `CONTINUAR` deve ser usado somente quando a fala é compreensível e realmente continua o assunto; uma fala sem significado contextual suficiente deve produzir `unmatched_value` e cair em `reprompt`;
|
||||
- o `reprompt` não encerra nem reinicia a execução: o mesmo workflow permanece `PAUSED`, aguardando nova resposta;
|
||||
- valores internos como `SIM`, `NAO`, `CONTINUAR` e o sentinela não devem vazar como resposta final do agente.
|
||||
|
||||
Exemplo comportamental:
|
||||
|
||||
```text
|
||||
Pergunta pendente: "Com essa explicação, sanei sua dúvida?"
|
||||
|
||||
"sim" -> SIM
|
||||
"não resolveu" -> NAO
|
||||
"mas e a cobrança de R$ 14,99?" -> CONTINUAR -> contextual_reentry
|
||||
"ano" -> OUTRO -> reprompt
|
||||
```
|
||||
|
||||
A opção é **opt-in por workflow**. Workflows que não declaram `semantic_classifier.unmatched_value` preservam o contrato anterior. Isso evita que uma evolução específica de um fluxo altere silenciosamente os demais workflows/agentes.
|
||||
|
||||
### 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.
|
||||
851
docs/developer/pt/04_mcp_integration_tools_and_policies.md
Normal file
851
docs/developer/pt/04_mcp_integration_tools_and_policies.md
Normal file
@@ -0,0 +1,851 @@
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
|
||||
|
||||
### MCP multi-item: listas de produtos, pedidos e outros recursos
|
||||
|
||||
Quando uma única intenção precisa operar sobre vários itens, **não crie um loop MCP dentro do agente**. O framework suporta contratos escalares ou `array/list`, pre-validation com `resolved_arguments`, confirmação transacional do conjunto e normalização de `results[]` por item em `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`. Multi-item não depende do tipo do parâmetro de entrada.
|
||||
|
||||
O contrato completo, exemplos de `tools.yaml`, `tool_policies.yaml`, payload MCP, retorno por item, workflows, validação parcial e anti-patterns estão em:
|
||||
|
||||
- [MCP Multi-item — Guia do Desenvolvedor](../../MCP_MULTI_ITEM_DEVELOPER_GUIDE.md)
|
||||
|
||||
Regra estrutural mínima do retorno da tool primária:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"results": [
|
||||
{"subject": "A", "success": true},
|
||||
{"subject": "B", "success": false, "reason": "not_found"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Cada item deve expor `success` ou `ok` booleano. Para tool baseada em workflow, prefira `workflow.output[tool_name].results`. Uma falha auxiliar posterior não deve apagar resultados terminais já executados.
|
||||
|
||||
### 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.
|
||||
2466
docs/developer/pt/05_agent_gateway_mcp_gateway_and_auth.md
Normal file
2466
docs/developer/pt/05_agent_gateway_mcp_gateway_and_auth.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,282 @@
|
||||
|
||||
### 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
|
||||
|
||||
Use extensões quando a política ou o critério de qualidade pertencer ao domínio do agente. Um guardrail implementa `evaluate(text, context) -> RailDecision` e pode atuar em `input`, `retrieval`, `tool` ou `output`. Um judge implementa `evaluate(question, answer, context) -> JudgeResult` e avalia a resposta sem reescrevê-la.
|
||||
|
||||
O guia [Guardrails e judges externos](../../EXTERNAL_GUARDRAILS_JUDGES.md) contém a estrutura de diretórios, imports, classes completas para copiar, YAML, explicação de cada declaração, fail-open/fail-closed, observabilidade e teste executável.
|
||||
|
||||
Pontos indispensáveis:
|
||||
|
||||
- `type: external` e `class: pacote.modulo:Classe` ativam o import dinâmico;
|
||||
- `kwargs` são argumentos do construtor; judges também podem receber `llm` e `settings` injetados;
|
||||
- guardrails externos não recebem `llm` no construtor: o pipeline o disponibiliza no contexto somente quando criado com `llm=...`;
|
||||
- métodos síncronos rodam em worker thread; métodos assíncronos rodam no event loop;
|
||||
- judges são concorrentes e preservam a ordem declarada no YAML;
|
||||
- exceções esperadas de judges externos devem ser tratadas pela implementação;
|
||||
- códigos, regras e vocabulário de negócio ficam no pacote do agente, nunca importados pelo core.
|
||||
|
||||
> **Limitação atual:** o `agent_template_backend` instancia um par global de pipelines no startup. Os campos `guardrails_config_path` e `judges_config_path` do registro não selecionam automaticamente configurações por requisição. Extensões dependentes de LLM também exigem que a composição passe `llm` e `settings` aos pipelines, conforme o guia completo.
|
||||
|
||||
Valide o exemplo publicado com `pytest -q tests/test_external_guardrails_judges_documentation.py`.
|
||||
|
||||
### 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.
|
||||
396
docs/developer/pt/07_rag_business_context_and_grounding.md
Normal file
396
docs/developer/pt/07_rag_business_context_and_grounding.md
Normal file
@@ -0,0 +1,396 @@
|
||||
|
||||
### RAG, BusinessContext e Grounding
|
||||
|
||||
### Como usar este manual
|
||||
|
||||
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
|
||||
|
||||
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
|
||||
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **RAG, providers, BusinessContext, contexto recuperado e grounding**.
|
||||
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
|
||||
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
|
||||
|
||||
### Relação com o tutorial principal
|
||||
|
||||
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
|
||||
|
||||
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
|
||||
|
||||
### Escopo
|
||||
|
||||
Rag, providers, businesscontext, contexto recuperado e grounding.
|
||||
|
||||
### Conteúdo técnico consolidado
|
||||
|
||||
### RAG, Providers Enterprise, BusinessContext e Grounding
|
||||
|
||||
Guia de integração de conhecimento recuperado, seleção entre providers de RAG, configuração KBDB, amostras, suficiência MCP e uso do BusinessContext como contrato de dados.
|
||||
|
||||
### Como usar este documento
|
||||
|
||||
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
|
||||
|
||||
### Provider RAG Standard versus KBDB Enterprise
|
||||
|
||||
> Conteúdo consolidado a partir de `docs/RAG_PROVIDER_KBDB.md`.
|
||||
|
||||
O framework passa a suportar dois backends de retrieval pelo mesmo contrato `RagService`, sem alterar os agentes nem `_retrieve_rag_context()`.
|
||||
|
||||
### Seleção
|
||||
|
||||
```env
|
||||
RAG_PROVIDER=standard # default: comportamento anterior
|
||||
# ou
|
||||
RAG_PROVIDER=kbdb # KBDB enterprise
|
||||
```
|
||||
|
||||
A seleção é exclusiva por processo. Os dois RAGs não executam juntos e não compartilham vector store, graph store ou ingestão.
|
||||
|
||||
### `standard`
|
||||
|
||||
Mantém integralmente o RAG já existente no `agent_framework_oci`: `VECTOR_STORE_PROVIDER`, `GRAPH_STORE_PROVIDER`, embedding, query rewrite, compression, retrieval guardrails e geração continuam válidos.
|
||||
|
||||
### `kbdb`
|
||||
|
||||
O framework integra somente a porta estável de serving do projeto KBDB:
|
||||
|
||||
`PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE`
|
||||
|
||||
O pipeline enterprise continua externo ao runtime do agente e preserva sua própria arquitetura RAW → SILVER → GOLD, HVI/hybrid search, property graph, publicação, lifecycle, auditoria e observabilidade.
|
||||
|
||||
O envelope KBDB é adaptado para `RagResult`/`VectorDocument`; portanto os agentes existentes continuam chamando `_retrieve_rag_context()` e os retrieval guardrails do framework continuam depois do retrieval.
|
||||
|
||||
### Configuração
|
||||
|
||||
```env
|
||||
RAG_PROVIDER=kbdb
|
||||
RAG_TOP_K=5
|
||||
KBDB_DB_USER=KB_USER
|
||||
KBDB_DB_PASSWORD=...
|
||||
KBDB_DB_DSN=...
|
||||
KBDB_DB_WALLET_LOCATION=...
|
||||
KBDB_DB_WALLET_PASSWORD=...
|
||||
KBDB_SEARCH_TYPE=hybrid
|
||||
KBDB_NODE_EXPANSION=true
|
||||
KBDB_NODE_MAX_RELATED=8
|
||||
KBDB_GRAPH_CROSS_REF=false
|
||||
KBDB_MAX_CROSS_REF_HOPS=1
|
||||
KBDB_DOCUMENT_TYPE=customer_safe
|
||||
KBDB_METADATA_JSON=
|
||||
KBDB_MIN_SCORE=
|
||||
```
|
||||
|
||||
Quando `RAG_PROVIDER=kbdb`, `KBDB_DB_USER`, `KBDB_DB_PASSWORD` e `KBDB_DB_DSN` são obrigatórios. O KBDB usa conexão isolada porque pode residir em outro Autonomous. `KBDB_DB_DSN` segue a mesma semântica de `ADB_DSN`: use o alias TNS existente no `tnsnames.ora` da wallet indicada por `KBDB_DB_WALLET_LOCATION`, e não uma URL `tcps://...`.
|
||||
|
||||
### Isolamento e compatibilidade
|
||||
|
||||
- `RAG_PROVIDER=standard` não importa nem conecta ao KBDB.
|
||||
- `RAG_PROVIDER=kbdb` não instancia vector/graph stores do RAG padrão.
|
||||
- Ingestão por `RagService.add_documents()` não é permitida no modo KBDB: deve passar pelo pipeline/publicação KBDB.
|
||||
- Query rewrite e context compression continuam opcionais e são aplicados pela camada comum do framework.
|
||||
- `AgentRuntimeMixin._retrieve_rag_context()` e os agentes permanecem inalterados.
|
||||
- Falhas do KBDB seguem a semântica existente do framework: retrieval é evidência auxiliar e a exceção é convertida em metadata técnica sem derrubar a jornada.
|
||||
|
||||
|
||||
### Resposta direta de tool e RAG
|
||||
|
||||
O framework não considera mais que um resultado MCP estruturado é, por si só, uma resposta suficiente ao usuário.
|
||||
|
||||
Uma política `response.renderer` define somente **como** apresentar o resultado. Ela não encerra o fluxo antes de RAG/LLM. Para uma tool deliberadamente produzir uma resposta final direta, a aplicação deve declarar explicitamente:
|
||||
|
||||
```yaml
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: meu.renderer
|
||||
direct: true
|
||||
```
|
||||
|
||||
Sem `direct: true`, o resultado da tool permanece como evidência MCP e o fluxo segue para `_retrieve_rag_context()` e composição LLM. Isso permite, por exemplo, que uma consulta operacional de plano seja combinada com conhecimento documental do KBDB quando a pergunta pedir regras, políticas ou explicações.
|
||||
|
||||
O core do framework não possui fallback por nome de tool (`consultar_plano`, `consultar_pedido`, etc.). Regras de apresentação pertencem à aplicação/domínio.
|
||||
|
||||
|
||||
### Suficiência MCP e grounding
|
||||
|
||||
Um resultado MCP bem-sucedido **não** faz o framework pular RAG automaticamente.
|
||||
O domínio só pode declarar suficiência documental explicitamente no payload com
|
||||
`rag_sufficient=true` ou `knowledge_sufficient=true`. Essa decisão é genérica e
|
||||
não depende do nome da tool nem de palavras-chave de telecom/retail.
|
||||
|
||||
No provider `kbdb`, `KBDB_GROUNDED_ONLY=true` é o padrão. Quando a busca KBDB
|
||||
retorna vazia, bloqueada ou com erro, a composição LLM pode usar fatos comprovados
|
||||
por MCP/business context, mas não pode completar a parte documental com conhecimento
|
||||
paramétrico do modelo. Deve informar que não há evidência suficiente na base.
|
||||
|
||||
Eventos do ProductAgent registram `IC.PRODUCT_RAG_CONTEXT_EVALUATED` em toda
|
||||
tentativa/decisão e `IC.PRODUCT_RAG_CONTEXT_RETRIEVED` somente quando há contexto
|
||||
recuperado. Os metadados incluem `provider`, `status`, `document_count`, `reason`,
|
||||
`error`, `query`, `namespace` e `latency_ms`.
|
||||
|
||||
### Amostras e testes de RAG
|
||||
|
||||
> Conteúdo consolidado a partir de `docs/README_rag_samples.md`.
|
||||
|
||||
These PDF files are synthetic, searchable sample documents created to validate the RAG embedding and retrieval flow of `agent_template_backend`.
|
||||
|
||||
### Files
|
||||
|
||||
- `01_billing_agent_invoice_policy.pdf` - sample knowledge for `billing_agent`
|
||||
- `02_orders_agent_lifecycle_policy.pdf` - sample knowledge for `orders_agent`
|
||||
- `03_product_agent_catalog_policy.pdf` - sample knowledge for `product_agent`
|
||||
- `04_support_agent_sla_policy.pdf` - sample knowledge for `support_agent`
|
||||
- `05_business_context_rag_flow.pdf` - sample knowledge about BusinessContext, identity.yaml and MCP parameter mapping
|
||||
|
||||
### How to use
|
||||
|
||||
Copy the PDF files to the backend documentation directory:
|
||||
|
||||
```bash
|
||||
mkdir -p agent_template_backend/docs/rag_samples
|
||||
cp *.pdf agent_template_backend/docs/rag_samples/
|
||||
```
|
||||
|
||||
For a local smoke test, use:
|
||||
|
||||
```env
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
EMBEDDING_PROVIDER=mock
|
||||
SQLITE_DB_PATH=./data/agent_framework.db
|
||||
RAG_TOP_K=4
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
python scripts/generate_rag_embeddings.py \
|
||||
--docs-dir ./agent_template_backend/docs/rag_samples \
|
||||
--namespace default
|
||||
```
|
||||
|
||||
For production-like semantic embeddings with OCI Generative AI, use:
|
||||
|
||||
```env
|
||||
VECTOR_STORE_PROVIDER=autonomous
|
||||
EMBEDDING_PROVIDER=oci
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..xxxx
|
||||
OCI_REGION=us-chicago-1
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
```
|
||||
|
||||
### Suggested retrieval test questions
|
||||
|
||||
- What is a prorated charge?
|
||||
- When can the OrdersAgent open an exchange request?
|
||||
- Which SKU represents the AI Agents book?
|
||||
- What is the target response for a critical support ticket?
|
||||
- How does BusinessContext map customer_key to MCP tool parameters?
|
||||
|
||||
### BusinessContext v2
|
||||
|
||||
> Conteúdo consolidado a partir de `Documentacao/README_TEMPLATE_BUSINESS_CONTEXT_V2.md`.
|
||||
|
||||
Este pacote atualiza o `agent_template_backend` e o `agent_frontend` para refletir o framework novo, onde as chaves vindas do canal/front-end são resolvidas uma vez como chaves canônicas e propagadas pelas camadas até o MCP Server.
|
||||
|
||||
### Fluxo implementado
|
||||
|
||||
1. O front-end envia `tenant_id`, `agent_id`, `session_id` e `business_context`.
|
||||
2. O backend normaliza a mensagem via `ChannelGateway` preservando todo o payload no `context`.
|
||||
3. O backend usa `IdentityResolver` com `config/identity.yaml` para gerar `BusinessContext`:
|
||||
- `customer_key`
|
||||
- `contract_key`
|
||||
- `interaction_key`
|
||||
- `account_key`
|
||||
- `resource_key`
|
||||
- `session_key`
|
||||
4. O workflow recebe `context.business_context`.
|
||||
5. Os agentes de exemplo não montam mais argumentos específicos como `msisdn`, `invoice_id` ou `order_id` diretamente.
|
||||
6. O `MCPToolRouter` usa `config/mcp_parameter_mapping.yaml` para converter chaves canônicas em parâmetros reais de cada tool MCP.
|
||||
|
||||
### Arquivos principais ajustados
|
||||
|
||||
- `agent_template_backend/app/main.py`
|
||||
- carrega `IdentityResolver`;
|
||||
- resolve `BusinessContext` por mensagem;
|
||||
- persiste as chaves na sessão/memória/metadata/SSE;
|
||||
- adiciona `/debug/identity`.
|
||||
|
||||
- `agent_template_backend/app/agents/runtime.py`
|
||||
- adiciona `_collect_mcp_context()` centralizado;
|
||||
- repassa `business_context` e `original_context` para o MCP Router.
|
||||
|
||||
- `agent_template_backend/app/agents/*_agent.py`
|
||||
- agentes passam a usar `_collect_mcp_context()` em vez de montar argumentos específicos.
|
||||
|
||||
- `agent_template_backend/config/identity.yaml`
|
||||
- define como campos do canal/front-end alimentam as chaves canônicas.
|
||||
|
||||
- `agent_template_backend/config/mcp_parameter_mapping.yaml`
|
||||
- define como chaves canônicas viram parâmetros reais por tool MCP.
|
||||
|
||||
- `agent_frontend/index.html` e `agent_frontend/app.js`
|
||||
- adicionam campos de `tenant`, `agent` e chaves canônicas;
|
||||
- enviam `business_context` no payload;
|
||||
- mantêm aliases de domínio para compatibilidade (`msisdn`, `invoice_id`, `order_id`, etc.).
|
||||
|
||||
### Teste rápido
|
||||
|
||||
Suba backend, frontend e MCP servers. Depois teste:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8000/health | jq
|
||||
|
||||
curl -s -X POST http://localhost:8000/debug/identity \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel":"web",
|
||||
"tenant_id":"default",
|
||||
"agent_id":"telecom_contas",
|
||||
"payload":{
|
||||
"message":"Minha fatura veio alta",
|
||||
"session_id":"teste-001",
|
||||
"msisdn":"11999999999",
|
||||
"invoice_id":"3000131180",
|
||||
"ura_call_id":"URA-123",
|
||||
"business_context":{
|
||||
"customer_key":"11999999999",
|
||||
"contract_key":"3000131180",
|
||||
"interaction_key":"URA-123",
|
||||
"session_key":"teste-001"
|
||||
}
|
||||
}
|
||||
}' | jq
|
||||
|
||||
curl -s -X POST http://localhost:8000/debug/mcp/call/consultar_fatura \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"business_context": {
|
||||
"customer_key":"11999999999",
|
||||
"contract_key":"3000131180",
|
||||
"interaction_key":"URA-123",
|
||||
"session_key":"teste-001"
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
|
||||
No log do backend, procure por `mcp.tool.mapped`. Ele deve indicar as chaves mapeadas e `has_msisdn=true`, `has_invoice_id=true` para o domínio telecom.
|
||||
|
||||
### Integração operacional de RAG e cache
|
||||
|
||||
> Conteúdo consolidado a partir de `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`.
|
||||
|
||||
Esta versão corrige os gaps identificados na comparação contra o FIRST.
|
||||
|
||||
### Correções aplicadas
|
||||
|
||||
### 1. Checkpoint LangGraph operacional
|
||||
|
||||
O workflow não compila mais com `MemorySaver()` diretamente. Foi criado o adaptador:
|
||||
|
||||
```text
|
||||
agent_framework/checkpoints/langgraph_saver.py
|
||||
```
|
||||
|
||||
Ele conecta o LangGraph ao repository configurado do framework:
|
||||
|
||||
- `memory`
|
||||
- `sqlite`
|
||||
- `oracle` / `autonomous`
|
||||
|
||||
No workflow:
|
||||
|
||||
```python
|
||||
builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
|
||||
```
|
||||
|
||||
### 2. Telemetria LangGraph envolvendo a execução real
|
||||
|
||||
Foi adicionado wrapper de nó no workflow:
|
||||
|
||||
```python
|
||||
self._node("billing_agent", self.billing_agent)
|
||||
```
|
||||
|
||||
Assim o span/evento `langgraph.node.*` envolve a execução real do nó, não apenas um bloco vazio.
|
||||
|
||||
Eventos emitidos:
|
||||
|
||||
- `langgraph.node.started`
|
||||
- `langgraph.node.completed`
|
||||
- `langgraph.node.failed`
|
||||
- `langgraph.edge.selected`
|
||||
|
||||
### 3. RAG integrado aos agentes
|
||||
|
||||
Os agentes agora recebem `RagService` e usam o contexto recuperado no prompt:
|
||||
|
||||
- BillingAgent
|
||||
- ProductAgent
|
||||
- OrdersAgent
|
||||
- SupportAgent
|
||||
|
||||
O RAG usa:
|
||||
|
||||
- `VECTOR_STORE_PROVIDER=memory|sqlite|oracle|autonomous`
|
||||
- `GRAPH_STORE_PROVIDER=memory|oracle|autonomous`
|
||||
- `RAG_TOP_K`
|
||||
|
||||
### 4. Cache integrado ao runtime dos agentes
|
||||
|
||||
Criado mixin:
|
||||
|
||||
```text
|
||||
agent_template_backend/app/agents/runtime.py
|
||||
```
|
||||
|
||||
Ele adiciona:
|
||||
|
||||
- busca RAG padronizada;
|
||||
- chave de cache para chamada LLM;
|
||||
- hit/miss com telemetria;
|
||||
- cache distribuído via `create_cache(settings)`.
|
||||
|
||||
### 5. Testes unitários
|
||||
|
||||
Criada pasta:
|
||||
|
||||
```text
|
||||
tests/unit
|
||||
```
|
||||
|
||||
Cobertura inicial:
|
||||
|
||||
- cache;
|
||||
- SSE;
|
||||
- RAG;
|
||||
- checkpoint saver;
|
||||
- telemetria LangGraph;
|
||||
- runtime dos agentes;
|
||||
- verificação estática do workflow;
|
||||
- imports principais.
|
||||
|
||||
Validação local executada:
|
||||
|
||||
```text
|
||||
12 passed
|
||||
```
|
||||
|
||||
### Como testar
|
||||
|
||||
```bash
|
||||
cd projeto_agent_framework_first_ready
|
||||
pip install -r agent_template_backend/requirements.txt
|
||||
pytest -q tests/unit
|
||||
```
|
||||
|
||||
### Arquivos de origem
|
||||
|
||||
Os arquivos abaixo foram consolidados neste manual:
|
||||
|
||||
- `docs/RAG_PROVIDER_KBDB.md`
|
||||
- `docs/README_rag_samples.md`
|
||||
- `Documentacao/README_TEMPLATE_BUSINESS_CONTEXT_V2.md`
|
||||
- `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`
|
||||
|
||||
### Regra de manutenção
|
||||
|
||||
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.
|
||||
636
docs/developer/pt/08_long_term_memory_and_checkpoint.md
Normal file
636
docs/developer/pt/08_long_term_memory_and_checkpoint.md
Normal file
@@ -0,0 +1,636 @@
|
||||
|
||||
### Long-Term Memory e Checkpoint
|
||||
|
||||
### Como usar este manual
|
||||
|
||||
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
|
||||
|
||||
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
|
||||
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **LTM, memória de conversa, isolamento por identidade e persistência de estado**.
|
||||
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
|
||||
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
|
||||
|
||||
### Relação com o tutorial principal
|
||||
|
||||
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
|
||||
|
||||
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
|
||||
|
||||
### Escopo
|
||||
|
||||
Ltm, memória de conversa, isolamento por identidade e persistência de estado.
|
||||
|
||||
### Conteúdo técnico consolidado
|
||||
|
||||
### Long-Term Memory e Checkpoint Enterprise
|
||||
|
||||
Manual de implementação de memória durável, isolamento por identidade, stores, extração, integração LangGraph, testes de persistência e diferenças entre LTM, histórico, sumário e checkpoint.
|
||||
|
||||
### Como usar este documento
|
||||
|
||||
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
|
||||
|
||||
### Implementação completa de Long-Term Memory
|
||||
|
||||
> Conteúdo consolidado a partir de `Documentacao/Manual_Long_Term_Memory_PT.md`.
|
||||
|
||||
### Conceito
|
||||
|
||||
A Long-Term Memory (LTM) é a capacidade do `agent_framework` de armazenar e recuperar fatos duradouros além da duração de uma sessão de conversa.
|
||||
|
||||
Diferentemente do histórico de mensagens, que normalmente está associado a um `session_id`, a memória de longo prazo é associada à identidade de negócio do usuário ou cliente. Na implementação atual, essa identidade é composta por:
|
||||
|
||||
```text
|
||||
tenant_id
|
||||
agent_id
|
||||
customer_key
|
||||
```
|
||||
|
||||
Isso permite que um agente recupere preferências, informações de identidade, projetos e restrições mesmo quando uma nova sessão é criada.
|
||||
|
||||
### Para que serve
|
||||
|
||||
A Long-Term Memory serve para:
|
||||
|
||||
- manter continuidade entre sessões;
|
||||
- personalizar respostas;
|
||||
- evitar que o usuário repita informações já fornecidas;
|
||||
- reduzir a necessidade de enviar todo o histórico ao modelo;
|
||||
- armazenar preferências, projetos atuais, nomes preferidos e restrições;
|
||||
- isolar a memória entre tenants, agentes e clientes.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```text
|
||||
Sessão A:
|
||||
"Me chame de Cris. Minha linguagem preferida é Python."
|
||||
|
||||
Sessão B, com outro session_id e o mesmo customer_key:
|
||||
"O que você lembra sobre mim?"
|
||||
|
||||
Resposta esperada:
|
||||
"Seu nome preferido é Cris e sua linguagem preferida é Python."
|
||||
```
|
||||
|
||||
### Diferença entre os tipos de memória
|
||||
|
||||
### Conversation Memory
|
||||
|
||||
Mantém as mensagens da conversa atual e normalmente está associada ao `session_id`.
|
||||
|
||||
### Summary Memory
|
||||
|
||||
Mantém um resumo da conversa para reduzir o tamanho do contexto enviado ao modelo.
|
||||
|
||||
### Long-Term Memory
|
||||
|
||||
Mantém fatos duradouros entre sessões e é associada à identidade de negócio, principalmente ao `customer_key`.
|
||||
|
||||
### Componentes da funcionalidade
|
||||
|
||||
### LongTermMemoryManager
|
||||
|
||||
Responsável por coordenar:
|
||||
|
||||
- carregamento das memórias;
|
||||
- recuperação por identidade;
|
||||
- renderização do contexto;
|
||||
- extração de novos fatos;
|
||||
- persistência dos fatos;
|
||||
- deduplicação e atualização.
|
||||
|
||||
### LongTermMemoryStore
|
||||
|
||||
Interface de persistência utilizada pelo manager.
|
||||
|
||||
### SQLiteLongTermMemoryStore
|
||||
|
||||
Implementação de referência baseada em SQLite.
|
||||
|
||||
É apropriada para:
|
||||
|
||||
- desenvolvimento local;
|
||||
- testes;
|
||||
- demonstrações;
|
||||
- ambientes de baixa escala.
|
||||
|
||||
### InMemoryLongTermMemoryStore
|
||||
|
||||
Implementação em memória utilizada para testes rápidos.
|
||||
|
||||
O conteúdo é perdido quando o processo do backend é encerrado.
|
||||
|
||||
### LongTermMemoryExtractor
|
||||
|
||||
Responsável por identificar fatos duradouros nas mensagens.
|
||||
|
||||
Exemplos de fatos:
|
||||
|
||||
```text
|
||||
preferred_name = Cris
|
||||
preferred_language = Python
|
||||
current_project = Atlas
|
||||
```
|
||||
|
||||
### LongTermMemoryItem
|
||||
|
||||
Modelo que representa um item persistido, incluindo identidade, chave, valor, categoria, confiança e metadados.
|
||||
|
||||
### AgentRuntime
|
||||
|
||||
Carrega a memória antes da execução do agente e injeta o contexto no prompt.
|
||||
|
||||
### Nó persist_long_term_memory
|
||||
|
||||
Nó do LangGraph responsável por persistir os fatos após a geração e validação da resposta final.
|
||||
|
||||
### Estrutura dos arquivos
|
||||
|
||||
```text
|
||||
libs/
|
||||
└── agent_framework/
|
||||
└── src/
|
||||
└── agent_framework/
|
||||
└── memory/
|
||||
├── __init__.py
|
||||
├── long_term_extractor.py
|
||||
├── long_term_memory.py
|
||||
├── long_term_models.py
|
||||
└── long_term_store.py
|
||||
```
|
||||
|
||||
### Fluxo de execução
|
||||
|
||||
```text
|
||||
Mensagem do usuário
|
||||
│
|
||||
▼
|
||||
AgentRuntime.prepare_memory_context()
|
||||
│
|
||||
├── Conversation Memory
|
||||
├── Summary Memory
|
||||
└── Long-Term Memory
|
||||
│
|
||||
▼
|
||||
long_term_memory_context
|
||||
│
|
||||
▼
|
||||
Prompt do agente
|
||||
│
|
||||
▼
|
||||
Agente
|
||||
│
|
||||
▼
|
||||
Guardrails / Judges / Supervisor
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
LongTermMemoryExtractor
|
||||
│
|
||||
▼
|
||||
LongTermMemoryStore
|
||||
```
|
||||
|
||||
### Configuração do framework
|
||||
|
||||
### Novos módulos
|
||||
|
||||
Copie os arquivos:
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_extractor.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_memory.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_models.py
|
||||
libs/agent_framework/src/agent_framework/memory/long_term_store.py
|
||||
```
|
||||
|
||||
### Atualização de memory/__init__.py
|
||||
|
||||
Exporte os componentes da Long-Term Memory:
|
||||
|
||||
```python
|
||||
from agent_framework.memory.long_term_memory import (
|
||||
LongTermMemoryManager,
|
||||
create_long_term_memory_manager,
|
||||
)
|
||||
from agent_framework.memory.long_term_models import LongTermMemoryItem
|
||||
from agent_framework.memory.long_term_store import (
|
||||
InMemoryLongTermMemoryStore,
|
||||
LongTermMemoryStore,
|
||||
SQLiteLongTermMemoryStore,
|
||||
create_long_term_memory_store,
|
||||
)
|
||||
```
|
||||
|
||||
### Atualização de settings.py
|
||||
|
||||
Adicione as configurações:
|
||||
|
||||
```python
|
||||
ENABLE_LONG_TERM_MEMORY: bool = False
|
||||
LONG_TERM_MEMORY_PROVIDER: str = "sqlite"
|
||||
LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db"
|
||||
LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory"
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True
|
||||
```
|
||||
|
||||
### Integração com AgentRuntime
|
||||
|
||||
O runtime deve:
|
||||
|
||||
1. verificar se a funcionalidade está habilitada;
|
||||
2. criar o manager quando necessário;
|
||||
3. recuperar os fatos pela identidade;
|
||||
4. preencher o estado;
|
||||
5. injetar o contexto no prompt.
|
||||
|
||||
Campos adicionados ao estado:
|
||||
|
||||
```python
|
||||
long_term_memories: list[dict]
|
||||
long_term_memory_context: str
|
||||
long_term_memory_write_result: dict
|
||||
```
|
||||
|
||||
### Inicialização no AgentWorkflow
|
||||
|
||||
O manager deve ser criado no `AgentWorkflow`:
|
||||
|
||||
```python
|
||||
self.long_term_memory_manager = create_long_term_memory_manager(
|
||||
settings,
|
||||
telemetry=telemetry,
|
||||
)
|
||||
```
|
||||
|
||||
### Inicialização correta dos agentes
|
||||
|
||||
O `long_term_memory_manager` não deve ser passado pelo `agent_kwargs` caso os construtores de `BillingAgent`, `ProductAgent`, `OrdersAgent` e `SupportAgent` não declarem esse parâmetro.
|
||||
|
||||
Esta inicialização causa erro:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"settings": settings,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
"long_term_memory_manager": self.long_term_memory_manager,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
```
|
||||
|
||||
Erro resultante:
|
||||
|
||||
```text
|
||||
TypeError: BillingAgent.__init__() got an unexpected keyword argument
|
||||
'long_term_memory_manager'
|
||||
```
|
||||
|
||||
A forma recomendada é criar os agentes com a assinatura já existente e injetar o manager como atributo após a inicialização:
|
||||
|
||||
```python
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"tool_router": getattr(self, "tool_router", None),
|
||||
"rag_service": self.rag_service,
|
||||
"cache": self.cache,
|
||||
"settings": settings,
|
||||
"observer": self.observer,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
}
|
||||
|
||||
self.billing = BillingAgent(llm, **agent_kwargs)
|
||||
self.product = ProductAgent(llm, **agent_kwargs)
|
||||
self.orders = OrdersAgent(llm, **agent_kwargs)
|
||||
self.support = SupportAgent(llm, **agent_kwargs)
|
||||
|
||||
for agent in (
|
||||
self.billing,
|
||||
self.product,
|
||||
self.orders,
|
||||
self.support,
|
||||
):
|
||||
agent.long_term_memory_manager = self.long_term_memory_manager
|
||||
```
|
||||
|
||||
Essa abordagem evita alterar os construtores de todos os agentes e mantém a funcionalidade encapsulada no framework.
|
||||
|
||||
### Configuração do LangGraph
|
||||
|
||||
Registre o nó:
|
||||
|
||||
```python
|
||||
builder.add_node(
|
||||
"persist_long_term_memory",
|
||||
self._node(
|
||||
"persist_long_term_memory",
|
||||
self.persist_long_term_memory,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Altere o fluxo:
|
||||
|
||||
```python
|
||||
builder.add_edge(
|
||||
"supervisor_review",
|
||||
"persist_long_term_memory",
|
||||
)
|
||||
builder.add_edge(
|
||||
"persist_long_term_memory",
|
||||
"persist",
|
||||
)
|
||||
```
|
||||
|
||||
Implemente o método:
|
||||
|
||||
```python
|
||||
async def persist_long_term_memory(
|
||||
self,
|
||||
state: AgentState,
|
||||
) -> dict[str, object]:
|
||||
result = await self.long_term_memory_manager.persist_turn(state)
|
||||
|
||||
return {
|
||||
"long_term_memory_write_result": result,
|
||||
}
|
||||
```
|
||||
|
||||
Fluxo final:
|
||||
|
||||
```text
|
||||
supervisor_review
|
||||
│
|
||||
▼
|
||||
persist_long_term_memory
|
||||
│
|
||||
▼
|
||||
persist
|
||||
```
|
||||
|
||||
### Variáveis de ambiente
|
||||
|
||||
```env
|
||||
ENABLE_LONG_TERM_MEMORY=true
|
||||
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
|
||||
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
|
||||
|
||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
|
||||
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
|
||||
LONG_TERM_MEMORY_AUTO_EXTRACT=true
|
||||
LONG_TERM_MEMORY_INJECT_CONTEXT=true
|
||||
```
|
||||
|
||||
### Caminho do banco SQLite
|
||||
|
||||
O caminho relativo é resolvido a partir do diretório em que o backend é iniciado.
|
||||
|
||||
Para evitar que bancos diferentes sejam criados acidentalmente, prefira um caminho absoluto em ambientes de desenvolvimento:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db
|
||||
```
|
||||
|
||||
Crie a pasta antes de iniciar:
|
||||
|
||||
```bash
|
||||
mkdir -p data
|
||||
```
|
||||
|
||||
### Como testar
|
||||
|
||||
### Teste 1 — Gravação
|
||||
|
||||
Envie:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-a",
|
||||
"customer_key": "11999999999",
|
||||
"message": "Me chame de Cris. Minha linguagem preferida é Python e meu projeto atual se chama Atlas."
|
||||
}
|
||||
```
|
||||
|
||||
### Teste 2 — Recuperação em outra sessão
|
||||
|
||||
Utilize outro `session_id`, mantendo o mesmo `customer_key`:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-b",
|
||||
"customer_key": "11999999999",
|
||||
"message": "O que você lembra sobre mim, minhas preferências e meu projeto?"
|
||||
}
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
```text
|
||||
Seu nome preferido é Cris.
|
||||
Sua linguagem preferida é Python.
|
||||
Seu projeto atual se chama Atlas.
|
||||
```
|
||||
|
||||
### Teste 3 — Isolamento
|
||||
|
||||
Utilize outro cliente:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "default:telecom_contas:memory-session-c",
|
||||
"customer_key": "outro-cliente",
|
||||
"message": "Qual é meu nome preferido e qual é meu projeto atual?"
|
||||
}
|
||||
```
|
||||
|
||||
Os dados de `11999999999` não devem aparecer.
|
||||
|
||||
### Teste 4 — Reinicialização do frontend
|
||||
|
||||
Reinicie ou resete o frontend e confirme que ele continua enviando o mesmo `customer_key`.
|
||||
|
||||
A memória deve sobreviver à troca do `session_id`. O reset do frontend não apaga o SQLite.
|
||||
|
||||
### Teste 5 — Reinicialização do backend
|
||||
|
||||
Reinicie o Uvicorn e repita a consulta.
|
||||
|
||||
Com:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
||||
```
|
||||
|
||||
a memória deve continuar disponível.
|
||||
|
||||
Com:
|
||||
|
||||
```env
|
||||
LONG_TERM_MEMORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
a memória será perdida quando o processo for encerrado.
|
||||
|
||||
### Verificação direta no SQLite
|
||||
|
||||
Localize o banco:
|
||||
|
||||
```bash
|
||||
find . -name "agent_framework.db" -type f
|
||||
```
|
||||
|
||||
Abra:
|
||||
|
||||
```bash
|
||||
sqlite3 ./data/agent_framework.db
|
||||
```
|
||||
|
||||
Consulte:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
tenant_id,
|
||||
agent_id,
|
||||
customer_key,
|
||||
memory_type,
|
||||
memory_key,
|
||||
memory_value,
|
||||
confidence,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM agentfw_long_term_memory
|
||||
ORDER BY updated_at DESC;
|
||||
```
|
||||
|
||||
### Critérios de sucesso
|
||||
|
||||
A implementação está funcionando quando:
|
||||
|
||||
- a memória é recuperada com outro `session_id`;
|
||||
- o mesmo `customer_key` recupera os fatos anteriores;
|
||||
- outro `customer_key` não acessa esses fatos;
|
||||
- reiniciar o frontend não apaga a memória;
|
||||
- reiniciar o backend não apaga a memória quando o provider é SQLite;
|
||||
- o nó `persist_long_term_memory` é executado;
|
||||
- o prompt recebe `long_term_memory_context`.
|
||||
|
||||
### Boas práticas
|
||||
|
||||
- Persistir somente fatos duradouros.
|
||||
- Não armazenar a conversa completa como Long-Term Memory.
|
||||
- Isolar dados por `tenant_id`, `agent_id` e `customer_key`.
|
||||
- Não utilizar `session_id` como identidade permanente do usuário.
|
||||
- Persistir somente depois das validações finais.
|
||||
- Evitar armazenar resultados temporários de ferramentas.
|
||||
- Registrar telemetria de leitura, escrita, atualização e falha.
|
||||
- Definir políticas de retenção e exclusão.
|
||||
- Usar caminho absoluto para SQLite em ambientes com múltiplos diretórios de execução.
|
||||
- Migrar para um banco corporativo em ambientes de produção e alta disponibilidade.
|
||||
|
||||
### Limitações da implementação de referência
|
||||
|
||||
A implementação atual utiliza extração baseada em regras e SQLite como provider de referência.
|
||||
|
||||
Evoluções recomendadas:
|
||||
|
||||
- extração de fatos com LLM;
|
||||
- memória semântica com vetores;
|
||||
- memória episódica;
|
||||
- expiração e versionamento;
|
||||
- deduplicação semântica;
|
||||
- política de consentimento;
|
||||
- API de consulta e exclusão;
|
||||
- provider Oracle Autonomous Database;
|
||||
- criptografia e classificação de dados sensíveis.
|
||||
|
||||
### Checkpoint Enterprise no LangGraph
|
||||
|
||||
> Conteúdo consolidado a partir de `Documentacao/README_CHECKPOINT_ENTERPRISE.md`.
|
||||
|
||||
Esta versão adiciona quatro capacidades ao checkpointer do LangGraph usado pelo framework:
|
||||
|
||||
1. **Checkpoint Integrity**: cada checkpoint é salvo dentro de um envelope com `schema_version`, `checkpoint_id`, `payload_hash` SHA-256 e `created_at`. Na leitura, o hash é recalculado. Se o payload foi truncado, alterado ou corrompido, o checkpoint é ignorado no recovery.
|
||||
2. **Checkpoint Compaction**: checkpoints antigos são removidos automaticamente conforme a configuração `CHECKPOINT_COMPACT_EVERY` e `CHECKPOINT_KEEP_LAST`. Isso evita crescimento infinito da tabela `workflow_checkpoints`.
|
||||
3. **Resilient Checkpointer**: gravações e leituras usam retry com backoff e jitter. A camada resiliente funciona sobre memory, SQLite e Oracle/Autonomous Database.
|
||||
4. **Checkpoint Recovery**: ao recuperar o estado, o framework varre os últimos checkpoints e retorna o mais recente válido, pulando checkpoints corrompidos.
|
||||
|
||||
### Configuração
|
||||
|
||||
No `.env`:
|
||||
|
||||
```env
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
ENABLE_RESILIENT_CHECKPOINTER=true
|
||||
ENABLE_CHECKPOINT_INTEGRITY=true
|
||||
ENABLE_CHECKPOINT_COMPACTION=true
|
||||
CHECKPOINT_COMPACT_EVERY=50
|
||||
CHECKPOINT_KEEP_LAST=20
|
||||
CHECKPOINT_RECOVERY_SCAN_LIMIT=25
|
||||
CHECKPOINT_RETRY_MAX_ATTEMPTS=3
|
||||
CHECKPOINT_RETRY_BASE_DELAY_SECONDS=0.05
|
||||
CHECKPOINT_RETRY_MAX_DELAY_SECONDS=1.0
|
||||
CHECKPOINT_RETRY_JITTER_SECONDS=0.05
|
||||
```
|
||||
|
||||
Para produção com múltiplos pods, prefira:
|
||||
|
||||
```env
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
|
||||
ADB_USER=...
|
||||
ADB_PASSWORD=...
|
||||
ADB_DSN=...
|
||||
ADB_WALLET_LOCATION=...
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
```
|
||||
|
||||
### Uso no LangGraph
|
||||
|
||||
```python
|
||||
from agent_framework.checkpoints import create_langgraph_checkpointer
|
||||
|
||||
checkpointer = create_langgraph_checkpointer(settings)
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
config = {"configurable": {"thread_id": session_id}}
|
||||
result = graph.invoke(input_state, config=config)
|
||||
```
|
||||
|
||||
O `thread_id` continua sendo a chave de recuperação da conversa. Em ambiente com Load Balancer, qualquer pod consegue retomar a execução se usar o mesmo repositório persistente.
|
||||
|
||||
### Arquivos alterados
|
||||
|
||||
- `agent_framework/src/agent_framework/checkpoints/checkpoint_repository.py`
|
||||
- `agent_framework/src/agent_framework/checkpoints/langgraph_saver.py`
|
||||
- `agent_framework/src/agent_framework/checkpoints/__init__.py`
|
||||
- `agent_framework/src/agent_framework/config/settings.py`
|
||||
- `tests/unit/test_resilient_checkpointer.py`
|
||||
|
||||
### Observação importante
|
||||
|
||||
O provider `memory` agora também usa o `RepositoryCheckpointSaver` quando `ENABLE_RESILIENT_CHECKPOINTER=true`. Para voltar ao `MemorySaver` puro do LangGraph em testes locais, configure:
|
||||
|
||||
```env
|
||||
ENABLE_RESILIENT_CHECKPOINTER=false
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=memory
|
||||
```
|
||||
|
||||
### Arquivos de origem
|
||||
|
||||
Os arquivos abaixo foram consolidados neste manual:
|
||||
|
||||
- `Documentacao/Manual_Long_Term_Memory_PT.md`
|
||||
- `Documentacao/README_CHECKPOINT_ENTERPRISE.md`
|
||||
|
||||
### Regra de manutenção
|
||||
|
||||
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.
|
||||
118
docs/developer/pt/09_llm_rich_response_reasoning.md
Normal file
118
docs/developer/pt/09_llm_rich_response_reasoning.md
Normal file
@@ -0,0 +1,118 @@
|
||||
|
||||
### LLM Rich Response e reasoning_content
|
||||
|
||||
### Como usar este manual
|
||||
|
||||
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
|
||||
|
||||
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
|
||||
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **`ainvoke_response()`, metadados de inferência e `reasoning_content` opcional**.
|
||||
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
|
||||
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
|
||||
|
||||
### Relação com o tutorial principal
|
||||
|
||||
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
|
||||
|
||||
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
|
||||
|
||||
### Escopo
|
||||
|
||||
`ainvoke_response()`, metadados de inferência e `reasoning_content` opcional.
|
||||
|
||||
### Conteúdo técnico consolidado
|
||||
|
||||
### LLM Rich Response e reasoning_content
|
||||
|
||||
Guia para usar a API opt-in de resposta estruturada do LLM sem quebrar o contrato legado de ainvoke(), incluindo reasoning_content, usage, model, provider, fallback e testes.
|
||||
|
||||
### Como usar este documento
|
||||
|
||||
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
|
||||
|
||||
### API rica de resposta LLM
|
||||
|
||||
> Conteúdo consolidado a partir de `docs/LLM_RICH_RESPONSE.md`.
|
||||
|
||||
### Objetivo
|
||||
|
||||
O framework mantém `ainvoke()` como API retrocompatível, retornando apenas `str`, e adiciona `ainvoke_response()` para consumidores que precisam de metadados adicionais da inferência, incluindo `reasoning_content` quando o modelo/provider/API o disponibilizar.
|
||||
|
||||
### APIs
|
||||
|
||||
### API legada — sem alteração
|
||||
|
||||
```python
|
||||
answer = await llm.ainvoke(messages)
|
||||
assert isinstance(answer, str)
|
||||
```
|
||||
|
||||
Nenhum agente existente precisa ser alterado.
|
||||
|
||||
### Nova API rica — opt-in
|
||||
|
||||
```python
|
||||
response = await llm.ainvoke_response(messages)
|
||||
|
||||
answer = response.content
|
||||
reasoning = response.reasoning_content
|
||||
usage = response.usage
|
||||
model = response.model
|
||||
provider = response.provider
|
||||
```
|
||||
|
||||
`reasoning_content` é `str | None`. `None` é o comportamento esperado quando o modelo, provider ou API não expõe reasoning textual.
|
||||
|
||||
### Backoffice
|
||||
|
||||
Um consumidor que antes fazia:
|
||||
|
||||
```python
|
||||
answer = await llm.ainvoke(messages)
|
||||
template = extract_response(answer)
|
||||
```
|
||||
|
||||
pode passar a fazer:
|
||||
|
||||
```python
|
||||
response = await llm.ainvoke_response(messages)
|
||||
template = extract_response(response.content)
|
||||
reasoning_content = response.reasoning_content
|
||||
```
|
||||
|
||||
A lógica que espera texto continua recebendo `response.content`; o reasoning fica separado e não contamina resposta, cache, memória, judges ou guardrails.
|
||||
|
||||
### Compatibilidade de providers customizados
|
||||
|
||||
`LLMProvider.ainvoke_response()` possui fallback. Um provider externo que implemente apenas `ainvoke()` continua funcionando e recebe automaticamente um `LLMResponse(content=<texto>)`, com `reasoning_content=None`.
|
||||
|
||||
Providers nativos (`mock`, OpenAI-compatible/OCI OpenAI e OCI SDK) implementam a resposta rica e tentam preservar reasoning quando presente.
|
||||
|
||||
### Garantias de compatibilidade
|
||||
|
||||
- `ainvoke()` continua retornando `str`.
|
||||
- Nenhum router, judge, RAG, memória, cache ou runtime existente foi migrado para a nova API.
|
||||
- `reasoning_content` nunca é fabricado pelo framework.
|
||||
- Ausência de reasoning não gera erro.
|
||||
- O output existente de telemetria continua sendo o conteúdo final, sem anexar reasoning automaticamente.
|
||||
|
||||
### Testes
|
||||
|
||||
Os testes específicos estão em `tests/unit/test_llm_rich_response.py` e verificam:
|
||||
|
||||
1. provider legado que só implementa `ainvoke()`;
|
||||
2. manutenção do retorno `str` em `ainvoke()`;
|
||||
3. retorno de `LLMResponse` em `ainvoke_response()`;
|
||||
4. reasoning via atributo direto;
|
||||
5. reasoning via `model_extra`;
|
||||
6. ausência de reasoning e extração no formato OCI SDK.
|
||||
|
||||
### Arquivos de origem
|
||||
|
||||
Os arquivos abaixo foram consolidados neste manual:
|
||||
|
||||
- `docs/LLM_RICH_RESPONSE.md`
|
||||
|
||||
### Regra de manutenção
|
||||
|
||||
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.
|
||||
356
docs/developer/pt/10_performance_cache_and_async_runtime.md
Normal file
356
docs/developer/pt/10_performance_cache_and_async_runtime.md
Normal file
@@ -0,0 +1,356 @@
|
||||
|
||||
### Performance, Cache e Runtime Assíncrono
|
||||
|
||||
### Como usar este manual
|
||||
|
||||
Este é um **manual de referência especializado**. Ele não substitui o tutorial principal.
|
||||
|
||||
- Para criar um agente do início ao fim, use [`README.md`](../../../README.md).
|
||||
- Use este documento quando precisar implementar, aprofundar ou diagnosticar **concorrência, cache, redução de chamadas LLM e correções cross-loop**.
|
||||
- Os exemplos históricos consolidados aqui devem ser lidos à luz da API atual do framework.
|
||||
- Em caso de divergência, o código da versão e o `README.md` atual prevalecem.
|
||||
|
||||
### Relação com o tutorial principal
|
||||
|
||||
O `README.md` apresenta essa capacidade no fluxo normal de desenvolvimento. Este manual reúne detalhes que estavam distribuídos em `docs/`, `Documentacao/`, release notes, validações e guias especializados.
|
||||
|
||||
O objetivo aqui é responder **“como essa feature funciona em profundidade e como eu resolvo problemas nela?”**, sem transformar este arquivo em uma segunda cópia do tutorial principal.
|
||||
|
||||
### Escopo
|
||||
|
||||
Concorrência, cache, redução de chamadas llm e correções cross-loop.
|
||||
|
||||
### Conteúdo técnico consolidado
|
||||
|
||||
### Performance, Cache, Concorrência e Runtime Assíncrono
|
||||
|
||||
Manual das otimizações no caminho crítico de MCP, RAG e Judges, redução de chamadas LLM, preempção determinística e correção de deadlock cross-loop no sequenciamento.
|
||||
|
||||
### Como usar este documento
|
||||
|
||||
Este é o documento consolidado de desenvolvimento para este assunto. Ele reúne arquitetura, configuração, exemplos, comportamento de runtime, compatibilidade, testes e troubleshooting que antes estavam distribuídos em vários arquivos. As seções de origem foram preservadas quando traziam detalhes técnicos distintos; notas de release foram incorporadas como comportamento atual ou histórico de correção.
|
||||
|
||||
### Otimizações MCP, RAG e Judges
|
||||
|
||||
> Conteúdo consolidado a partir de `docs/PERFORMANCE_OPTIMIZATIONS_MCP_JUDGES_RAG.md`.
|
||||
|
||||
- `mcp_tools` permanece allowlist; somente a consulta selecionada por `selection_keywords` é executada.
|
||||
- Extração `strategy: hybrid` tenta `pattern` regex antes do perfil LLM.
|
||||
- RAG é ignorado quando MCP bem-sucedido é suficiente, salvo perguntas de política/regra.
|
||||
- `mcp_results` é fornecido como evidência ao groundedness judge.
|
||||
- `judges.yaml` aceita `sample_rate` e `always_run_for_transactional`.
|
||||
- Consultas estruturadas simples podem retornar resposta determinística sem LLM do agente.
|
||||
|
||||
### Mudança de consulta para ação transacional
|
||||
|
||||
A route stickiness é preemptada quando uma keyword explícita configurada em `routing.yaml` identifica outra intent/agente. Assim, uma sessão em `retail_order_tracking` muda para `retail_support_exchange_return` ao receber pedidos como “devolver pedido”. Além disso, respostas diretas de tools read-only são bloqueadas quando a mensagem contém `selection_keywords` de qualquer tool transacional registrada.
|
||||
|
||||
As palavras de ação ficam em `config/tools.yaml`; o runtime não mantém aliases de domínio hardcoded.
|
||||
|
||||
|
||||
### Preempção determinística de mudança explícita de intent
|
||||
|
||||
A stickiness não chama um segundo LLM quando a mensagem contém uma mudança explícita que pode ser reconhecida deterministicamente. Keywords multi-token configuradas em `routing.yaml` aceitam até três tokens intermediários, preservando a ordem. Assim, `cancelar pedido` reconhece `quero cancelar meu pedido`, `cancelar o meu pedido` e `pode cancelar esse pedido`. Nesse caso a nova intent preempta a stickiness e o metadado `keyword_match_strategy=ordered_tokens` permite auditar a decisão. Mensagens sem sinal explícito continuam usando a route stickiness normalmente.
|
||||
|
||||
### Correção de deadlock cross-loop
|
||||
|
||||
> Conteúdo consolidado a partir de `Documentacao/FIX_DEADLOCK_SEQUENCE_CROSS_LOOP.md`.
|
||||
|
||||
### Problema
|
||||
|
||||
A API síncrona `agent_framework.observer.event()` podia ser chamada em uma worker thread sem event loop ativo. Nesse caso, a implementação anterior executava `asyncio.run(aevent(...))`, criando um novo event loop temporário. Ao mesmo tempo, `analytics/tim_sequence.py` compartilhava instâncias globais de `asyncio.Lock` (`_mongo_index_lock` e `_memory_lock`) entre chamadas que podiam vir de event loops diferentes.
|
||||
|
||||
Na primeira operação Mongo, `_ensure_mongo_ttl_index_once()` mantinha `_mongo_index_lock` durante a criação do índice TTL. A contenção por outro loop podia deixar a segunda chamada aguardando indefinidamente.
|
||||
|
||||
### Alterações aplicadas
|
||||
|
||||
1. `observer.py`
|
||||
- removido `asyncio.run()` do caminho síncrono de `event()`;
|
||||
- adicionado um event loop dedicado e reutilizável para chamadas síncronas;
|
||||
- submissão cross-thread feita com `asyncio.run_coroutine_threadsafe()`;
|
||||
- encerramento best-effort do loop no shutdown do processo.
|
||||
|
||||
2. `analytics/tim_sequence.py`
|
||||
- `_mongo_index_lock`: `asyncio.Lock` -> `threading.Lock`;
|
||||
- `_memory_lock`: `asyncio.Lock` -> `threading.Lock`;
|
||||
- inicialização do índice TTL movida para uma função síncrona protegida por lock de thread e chamada via `asyncio.to_thread()`;
|
||||
- o contador de fallback em memória usa uma seção crítica curta e thread-safe.
|
||||
|
||||
3. Testes
|
||||
- `tests/test_observer_cross_loop_deadlock_fix.py` valida:
|
||||
- múltiplas worker threads usando `event()` compartilham o mesmo loop síncrono do observer;
|
||||
- sequence em memória permanece monotônica entre event loops independentes;
|
||||
- criação do índice TTL ocorre apenas uma vez sob contenção cross-loop.
|
||||
|
||||
### Validação executada
|
||||
|
||||
```bash
|
||||
PYTHONPATH=libs/agent_framework/src pytest -q tests/test_observer_cross_loop_deadlock_fix.py
|
||||
```
|
||||
|
||||
Resultado: `3 passed`.
|
||||
|
||||
A suíte completa do repositório possui falhas preexistentes/independentes desta alteração, incluindo conflitos de coleta de arquivos `test_long_term_memory.py`, caminhos estáticos de template e testes de checkpoint/workflow. Esses itens não foram alterados por esta correção.
|
||||
|
||||
### Recursos operacionais de performance
|
||||
|
||||
> Conteúdo consolidado a partir de `Documentacao/README_MAX_OPERACIONAL.md`.
|
||||
|
||||
Esta versão adiciona os ajustes operacionais que faltavam para aproximar o framework do padrão FIRST em produção.
|
||||
|
||||
### Ajustes incluídos nesta versão
|
||||
|
||||
### 1. Langfuse Enterprise Adapter
|
||||
Novo módulo:
|
||||
|
||||
```text
|
||||
agent_framework/observability/langfuse_enterprise.py
|
||||
```
|
||||
|
||||
Inclui adaptador compatível com SDKs Langfuse v2/v3 para:
|
||||
|
||||
- atualização de trace;
|
||||
- score/avaliação de trace;
|
||||
- prompt registry quando suportado pelo SDK;
|
||||
- isolamento das diferenças de API do Langfuse.
|
||||
|
||||
### 2. Token e Cost Accounting persistente
|
||||
Novo pacote:
|
||||
|
||||
```text
|
||||
agent_framework/billing/
|
||||
```
|
||||
|
||||
Inclui:
|
||||
|
||||
- `UsageRecord`
|
||||
- `SQLiteUsageRepository`
|
||||
- `OracleUsageRepository`
|
||||
- `create_usage_repository(settings)`
|
||||
|
||||
O provider LLM agora registra automaticamente:
|
||||
|
||||
- `prompt_tokens`
|
||||
- `completion_tokens`
|
||||
- `cached_tokens`
|
||||
- `total_tokens`
|
||||
- `cost_usd`
|
||||
- `cost_brl`
|
||||
- `tenant_id`
|
||||
- `agent_id`
|
||||
- `session_id`
|
||||
- `message_id`
|
||||
|
||||
Novo endpoint:
|
||||
|
||||
```http
|
||||
GET /debug/usage
|
||||
GET /debug/usage?tenant_id=default
|
||||
GET /debug/usage?session_id=<id>
|
||||
```
|
||||
|
||||
### 3. RAG Service operacional
|
||||
Novo módulo:
|
||||
|
||||
```text
|
||||
agent_framework/rag/rag_service.py
|
||||
```
|
||||
|
||||
Inclui:
|
||||
|
||||
- `RagService.add_documents()`
|
||||
- `RagService.retrieve()`
|
||||
- `RagResult.as_prompt_context()`
|
||||
- telemetria de latência, quantidade de documentos, top scores e grafo.
|
||||
|
||||
### 4. Configuração nova
|
||||
Variável adicionada:
|
||||
|
||||
```env
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
```
|
||||
|
||||
Valores:
|
||||
|
||||
```text
|
||||
sqlite
|
||||
oracle
|
||||
autonomous
|
||||
```
|
||||
|
||||
### 5. Compatibilidade operacional local
|
||||
Por padrão, a contabilização de uso usa SQLite mesmo que o restante esteja em memória. Assim é possível testar localmente sem Oracle.
|
||||
|
||||
### Teste rápido
|
||||
|
||||
```bash
|
||||
cd agent_template_backend
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
Teste uma mensagem:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"channel":"web","payload":{"text":"teste","user_id":"u1","session_id":"s1"}}'
|
||||
```
|
||||
|
||||
Verifique uso/custo:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/debug/usage
|
||||
```
|
||||
|
||||
### Para rodar com padrão mais próximo de produção
|
||||
|
||||
```env
|
||||
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
CACHE_BACKEND_PROVIDER=sqlite
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_HOST=http://localhost:3000
|
||||
LANGFUSE_PUBLIC_KEY=...
|
||||
LANGFUSE_SECRET_KEY=...
|
||||
```
|
||||
|
||||
Para Autonomous Database:
|
||||
|
||||
```env
|
||||
SESSION_REPOSITORY_PROVIDER=oracle
|
||||
MEMORY_REPOSITORY_PROVIDER=oracle
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=oracle
|
||||
USAGE_REPOSITORY_PROVIDER=oracle
|
||||
CACHE_BACKEND_PROVIDER=oracle
|
||||
VECTOR_STORE_PROVIDER=oracle
|
||||
GRAPH_STORE_PROVIDER=oracle
|
||||
ADB_USER=...
|
||||
ADB_PASSWORD=...
|
||||
ADB_DSN=...
|
||||
ADB_WALLET_LOCATION=...
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
```
|
||||
|
||||
### Ajustes finais de cache, RAG e telemetria
|
||||
|
||||
> Conteúdo consolidado a partir de `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`.
|
||||
|
||||
Esta versão corrige os gaps identificados na comparação contra o FIRST.
|
||||
|
||||
### Correções aplicadas
|
||||
|
||||
### 1. Checkpoint LangGraph operacional
|
||||
|
||||
O workflow não compila mais com `MemorySaver()` diretamente. Foi criado o adaptador:
|
||||
|
||||
```text
|
||||
agent_framework/checkpoints/langgraph_saver.py
|
||||
```
|
||||
|
||||
Ele conecta o LangGraph ao repository configurado do framework:
|
||||
|
||||
- `memory`
|
||||
- `sqlite`
|
||||
- `oracle` / `autonomous`
|
||||
|
||||
No workflow:
|
||||
|
||||
```python
|
||||
builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
|
||||
```
|
||||
|
||||
### 2. Telemetria LangGraph envolvendo a execução real
|
||||
|
||||
Foi adicionado wrapper de nó no workflow:
|
||||
|
||||
```python
|
||||
self._node("billing_agent", self.billing_agent)
|
||||
```
|
||||
|
||||
Assim o span/evento `langgraph.node.*` envolve a execução real do nó, não apenas um bloco vazio.
|
||||
|
||||
Eventos emitidos:
|
||||
|
||||
- `langgraph.node.started`
|
||||
- `langgraph.node.completed`
|
||||
- `langgraph.node.failed`
|
||||
- `langgraph.edge.selected`
|
||||
|
||||
### 3. RAG integrado aos agentes
|
||||
|
||||
Os agentes agora recebem `RagService` e usam o contexto recuperado no prompt:
|
||||
|
||||
- BillingAgent
|
||||
- ProductAgent
|
||||
- OrdersAgent
|
||||
- SupportAgent
|
||||
|
||||
O RAG usa:
|
||||
|
||||
- `VECTOR_STORE_PROVIDER=memory|sqlite|oracle|autonomous`
|
||||
- `GRAPH_STORE_PROVIDER=memory|oracle|autonomous`
|
||||
- `RAG_TOP_K`
|
||||
|
||||
### 4. Cache integrado ao runtime dos agentes
|
||||
|
||||
Criado mixin:
|
||||
|
||||
```text
|
||||
agent_template_backend/app/agents/runtime.py
|
||||
```
|
||||
|
||||
Ele adiciona:
|
||||
|
||||
- busca RAG padronizada;
|
||||
- chave de cache para chamada LLM;
|
||||
- hit/miss com telemetria;
|
||||
- cache distribuído via `create_cache(settings)`.
|
||||
|
||||
### 5. Testes unitários
|
||||
|
||||
Criada pasta:
|
||||
|
||||
```text
|
||||
tests/unit
|
||||
```
|
||||
|
||||
Cobertura inicial:
|
||||
|
||||
- cache;
|
||||
- SSE;
|
||||
- RAG;
|
||||
- checkpoint saver;
|
||||
- telemetria LangGraph;
|
||||
- runtime dos agentes;
|
||||
- verificação estática do workflow;
|
||||
- imports principais.
|
||||
|
||||
Validação local executada:
|
||||
|
||||
```text
|
||||
12 passed
|
||||
```
|
||||
|
||||
### Como testar
|
||||
|
||||
```bash
|
||||
cd projeto_agent_framework_first_ready
|
||||
pip install -r agent_template_backend/requirements.txt
|
||||
pytest -q tests/unit
|
||||
```
|
||||
|
||||
### Arquivos de origem
|
||||
|
||||
Os arquivos abaixo foram consolidados neste manual:
|
||||
|
||||
- `docs/PERFORMANCE_OPTIMIZATIONS_MCP_JUDGES_RAG.md`
|
||||
- `Documentacao/FIX_DEADLOCK_SEQUENCE_CROSS_LOOP.md`
|
||||
- `Documentacao/README_MAX_OPERACIONAL.md`
|
||||
- `Documentacao/README_FIRST_MAX_OPERATIONAL_FIXES.md`
|
||||
|
||||
### Regra de manutenção
|
||||
|
||||
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.
|
||||
@@ -0,0 +1,736 @@
|
||||
|
||||
### 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.
|
||||
|
||||
### Política de auto-instrumentação OpenAI no Langfuse
|
||||
|
||||
A configuração oficial dos templates do framework é:
|
||||
|
||||
```env
|
||||
ENABLE_LANGFUSE=true
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=false
|
||||
```
|
||||
|
||||
O `false` é intencional. O framework já registra as chamadas ao modelo usando `Telemetry.generation(...)` e mantém a generation dentro do trace da requisição. Habilitar simultaneamente `langfuse.openai` cria uma segunda camada de instrumentação e pode resultar em `OpenAI-generation` como trace raiz, observations duplicadas e dupla contabilização de tokens/custos.
|
||||
|
||||
Use `true` somente para capturar chamadas diretas ao SDK OpenAI/OpenAI-compatible que ocorram fora da telemetria do framework. Esse é um modo de compatibilidade/diagnóstico, não o padrão operacional. Todos os `.env.example` do repositório devem permanecer explicitamente com o valor `false`.
|
||||
|
||||
### 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.
|
||||
|
||||
### Papel de `app/observability/` no agente
|
||||
|
||||
O framework contém a infraestrutura genérica de observabilidade; o template contém apenas o ponto de integração específico do agente. No `agent_template_backend`, a estrutura de referência é:
|
||||
|
||||
```text
|
||||
app/observability/
|
||||
├── __init__.py
|
||||
└── telemetry_observer.py
|
||||
```
|
||||
|
||||
`app/observability/` é apropriado para:
|
||||
|
||||
- construir/configurar o observer usado pelo agente;
|
||||
- enriquecer eventos com metadados específicos disponíveis no host;
|
||||
- adaptar o contrato do agente a integrações externas;
|
||||
- registrar callbacks/adapters do template;
|
||||
- aplicar mappings/overlays específicos do agente quando previstos por configuração.
|
||||
|
||||
Ele **não** deve conter uma segunda implementação de OpenTelemetry, Langfuse, IC/NOC/GRL, sequence, exporters, correlação ou persistência. Esses mecanismos são transversais e pertencem ao `agent_framework`.
|
||||
|
||||
A dependência esperada é:
|
||||
|
||||
```text
|
||||
app/observability/telemetry_observer.py
|
||||
│ configura/compõe
|
||||
▼
|
||||
agent_framework.observability + contratos compartilhados
|
||||
│
|
||||
├── Langfuse
|
||||
├── OpenTelemetry
|
||||
├── IC / NOC / GRL
|
||||
└── exporters / mappings / pub-sub
|
||||
```
|
||||
|
||||
Ao criar um novo agente, prefira reutilizar o adapter de referência e alterar somente configuração/metadados de domínio. Se uma mudança for necessária para vários agentes, implemente-a no core e mantenha `app/observability/` fino.
|
||||
|
||||
**Anti-padrões:** importar diretamente SDKs de telemetria em cada agente para emitir eventos equivalentes; codificar labels históricos no runtime do agente; usar eventos de observabilidade como fonte de verdade do estado transacional; enviar prompt integral, segredo, PII ou payload financeiro bruto.
|
||||
|
||||
### 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,188 @@
|
||||
# 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.
|
||||
|
||||
## COER, `expected_input` e `reprompt` em workflows pausados
|
||||
|
||||
Quando existe um `expected_input.semantic_classifier` ativo, o framework pode delegar a decisão semântica ao contrato do workflow em vez de tratar imediatamente uma entrada ambígua como bloqueio genérico de `COER`. Nesse modo, o guardrail pode aparecer como permitido/delegado, por exemplo com mecanismo `expected_input_semantic_classifier`.
|
||||
|
||||
O workflow continua responsável por diferenciar:
|
||||
|
||||
```text
|
||||
resposta válida e compreensível -> classe válida / option_action
|
||||
resposta sem significado suficiente -> unmatched_value -> reprompt
|
||||
```
|
||||
|
||||
Exemplo de configuração:
|
||||
|
||||
```yaml
|
||||
expected_input:
|
||||
allowed_values: [SIM, NAO, CONTINUAR]
|
||||
reprompt: "Não entendi. Essa explicação resolveu sua dúvida? Responda sim ou não."
|
||||
semantic_classifier:
|
||||
enabled: true
|
||||
unmatched_value: OUTRO
|
||||
option_actions:
|
||||
CONTINUAR:
|
||||
action: contextual_reentry
|
||||
```
|
||||
|
||||
Nesse contrato, `CONTINUAR` não significa "qualquer coisa que não seja SIM ou NAO". Ele representa uma fala **coerente e contextual** que continua o assunto. Uma entrada como `ano`, sem significado suficiente para a pergunta pendente, deve retornar o sentinela `OUTRO` e produzir o `reprompt`, mantendo o mesmo workflow pausado.
|
||||
|
||||
`OUTRO` não deve ser adicionado a `allowed_values` nem enviado ao runtime como valor de resume. A descrição completa do contrato está em [03 — Workflows Transacionais e Estado](./03_transaction_workflows_and_state.md#expected_inputsemantic_classifierunmatched_value-e-reprompt).
|
||||
|
||||
## 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;
|
||||
- com `expected_input.semantic_classifier.unmatched_value`, entrada incoerente/sem significado suficiente aciona `reprompt` e mantém o workflow `PAUSED`;
|
||||
- uma entrada compreensível fora de SIM/NAO pode seguir `CONTINUAR -> contextual_reentry` sem ser confundida com uma entrada incoerente;
|
||||
- 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.
|
||||
183
docs/developer/pt/13_workflows_transacionais_avancados.md
Normal file
183
docs/developer/pt/13_workflows_transacionais_avancados.md
Normal file
@@ -0,0 +1,183 @@
|
||||
### Workflows transacionais avançados
|
||||
|
||||
### Objetivo
|
||||
|
||||
Este capítulo ensina a transformar uma tool sensível em uma transação determinística, com coleta de parâmetros, pré-validação, confirmação, workflow versionado, pausa/retomada, idempotência e recuperação. A regra de negócio e as actions ficam no agente; o motor de execução fica no framework.
|
||||
|
||||
### Fluxo real
|
||||
|
||||
1. `routing.yaml` escolhe o agente e restringe as tools candidatas.
|
||||
2. `tools.yaml` descreve argumentos, prompts e resposta.
|
||||
3. `mcp_parameter_mapping.yaml` resolve identidade, defaults e extrações.
|
||||
4. `tool_policies.yaml` classifica a operação e exige confirmação.
|
||||
5. A pré-validação opcional verifica se a operação pode prosseguir sem causar efeito.
|
||||
6. Após confirmação inequívoca, a execução é direta ou encaminhada ao `WorkflowToolExecutor`.
|
||||
7. O `WorkflowRuntime` carrega a versão ativa, executa actions, persiste pausa e devolve `COMPLETED`, `PAUSED` ou `FAILED`.
|
||||
8. A chave idempotente impede a repetição do efeito externo.
|
||||
|
||||
### Catálogo e política são contratos diferentes
|
||||
|
||||
```yaml
|
||||
# config/tools.yaml
|
||||
tools:
|
||||
solicitar_devolucao:
|
||||
description: Abre uma devolução de pedido.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
confirmation_required: true
|
||||
requires: [order_id, reason]
|
||||
args_schema:
|
||||
order_id:
|
||||
type: string
|
||||
label: o número do pedido
|
||||
user_prompt: Informe o número do pedido.
|
||||
reason:
|
||||
type: string
|
||||
label: o motivo da devolução
|
||||
user_prompt: Qual é o motivo da devolução?
|
||||
```
|
||||
|
||||
```yaml
|
||||
# config/tool_policies.yaml
|
||||
version: 1
|
||||
defaults:
|
||||
operation_type: read_only
|
||||
require_confirmation: false
|
||||
tool_policies:
|
||||
solicitar_devolucao:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [order_id, reason]
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_devolucao
|
||||
fail_open: false
|
||||
execution:
|
||||
mode: workflow
|
||||
workflow: devolucao_pedido
|
||||
version: active
|
||||
```
|
||||
|
||||
`tools.yaml` é o catálogo público. `tool_policies.yaml` governa efeitos. Não confie somente em `confirmation_required` do catálogo: a política efetiva deve declarar `operation_type` e `require_confirmation`. `pre_validation.tool` deve ser read-only e nunca executar a transação antecipadamente. Para segurança, use `fail_open: false`.
|
||||
|
||||
### Definição versionada do workflow
|
||||
|
||||
```yaml workflow-definition-example
|
||||
name: devolucao_pedido
|
||||
version: 1
|
||||
start: validar_pedido
|
||||
nodes:
|
||||
- id: validar_pedido
|
||||
action: validar_pedido
|
||||
input:
|
||||
order_id: $.input.order_id
|
||||
- id: registrar_devolucao
|
||||
action: registrar_devolucao
|
||||
retry: 1
|
||||
input:
|
||||
order_id: $.input.order_id
|
||||
reason: $.input.reason
|
||||
edges:
|
||||
- from: validar_pedido
|
||||
to: registrar_devolucao
|
||||
when:
|
||||
path: $.nodes.validar_pedido.valid
|
||||
equals: true
|
||||
- from: validar_pedido
|
||||
to: END
|
||||
when:
|
||||
path: $.nodes.validar_pedido.valid
|
||||
equals: false
|
||||
- from: registrar_devolucao
|
||||
to: END
|
||||
```
|
||||
|
||||
Salve como `workflows/devolucao_pedido.v1.yaml`. O arquivo `devolucao_pedido.active.yaml` deve apontar para uma definição válida da versão ativa. IDs de nós não podem repetir; `start`, destinos e `resume_from` precisam existir. `retry` aceita de 0 a 10. Paths `$.input.*` leem a entrada e `$.nodes.<id>.*` leem resultados anteriores.
|
||||
|
||||
### Actions pertencentes ao agente
|
||||
|
||||
```python workflow-actions-example
|
||||
from agent_framework.workflows import workflow_action
|
||||
|
||||
|
||||
@workflow_action("validar_pedido")
|
||||
async def validar_pedido(params: dict, state: dict) -> dict:
|
||||
return {"valid": bool(params.get("order_id"))}
|
||||
|
||||
|
||||
@workflow_action("registrar_devolucao")
|
||||
async def registrar_devolucao(params: dict, state: dict) -> dict:
|
||||
order_id = str(params["order_id"])
|
||||
return {"protocol": f"DEV-{order_id}", "status": "REQUESTED"}
|
||||
```
|
||||
|
||||
O nome do decorator deve ser igual a `node.action`. A assinatura obrigatória é `(params: dict, state: dict) -> dict`. O módulo precisa ser importado no startup para registrar decorators. Actions devem devolver dados serializáveis; não devolva cliente HTTP, conexão, exception ou coroutine.
|
||||
|
||||
### Organização de `app/workflow_actions/`
|
||||
|
||||
No template, actions específicas do domínio ficam em `app/workflow_actions/`. O exemplo distribuído usa:
|
||||
|
||||
```text
|
||||
app/workflow_actions/
|
||||
├── __init__.py
|
||||
└── devolucao.py
|
||||
```
|
||||
|
||||
Esse diretório deve conter **somente implementação de negócio acionável pelo workflow** e pequenas funções auxiliares diretamente relacionadas. O motor de workflows, o decorator, o registry, a persistência, o mecanismo de pause/resume e a infraestrutura genérica de idempotência continuam no `agent_framework`.
|
||||
|
||||
Uma organização maior pode separar actions por domínio ou agregado:
|
||||
|
||||
```text
|
||||
app/workflow_actions/
|
||||
├── billing.py
|
||||
├── orders.py
|
||||
└── vas.py
|
||||
```
|
||||
|
||||
Regras:
|
||||
|
||||
- o `node.action` do YAML deve corresponder ao nome registrado em `@workflow_action`;
|
||||
- importe os módulos necessários no startup para que o registry seja populado antes da primeira execução;
|
||||
- não faça o core importar `app.workflow_actions.*`; o sentido da dependência é do agente para o framework;
|
||||
- não use a action como um segundo router ou como um novo runtime de agentes; ela deve executar uma etapa de negócio bem delimitada;
|
||||
- para efeitos externos, use a política e a infraestrutura de idempotência do framework e persista apenas resultados confirmados.
|
||||
|
||||
**Anti-padrão:** copiar classes de `agent_framework.workflows` para `app/workflow_actions/` para customizar um caso local. Se o mecanismo genérico não atende, estenda o framework por uma interface reutilizável; mantenha apenas a regra específica no agente.
|
||||
|
||||
### Idempotência do efeito externo
|
||||
|
||||
```python idempotency-example
|
||||
async def execute_once(store, customer_key: str, order_id: str, invoke):
|
||||
key = store.canonical_key(customer_key, order_id, "solicitar_devolucao", "v1")
|
||||
previous = await store.get(key)
|
||||
if previous is not None:
|
||||
return previous
|
||||
result = await invoke()
|
||||
await store.set(key, result)
|
||||
return result
|
||||
```
|
||||
|
||||
Crie uma única instância no startup com `create_idempotency_store(settings, namespace="return-request", require_durable=True)` e injete-a nas actions. Use componentes de negócio estáveis na chave; nunca use apenas timestamp. `IDEMPOTENCY_PROVIDER` tem precedência sobre checkpoint, session e cache. Em produção transacional, configure `IDEMPOTENCY_REQUIRE_DURABLE=true` e um provider `oracle`, `redis` ou `sqlite` apropriado. `memory` não protege entre pods ou reinícios. Grave o resultado somente depois de o efeito ter sido confirmado pelo sistema externo.
|
||||
|
||||
### Pausa, entrada esperada e retomada
|
||||
|
||||
Um nó pode declarar `pause.expected_input` com `key`, `allowed_values`, normalização, `reprompt` e classificador semântico. A confirmação explícita continua determinística; o classificador só trata respostas inconclusivas e deve retornar uma das opções permitidas. `contextual_reentry` libera o workflow e devolve a fala ao roteamento normal, sem confirmar fatos automaticamente.
|
||||
|
||||
Ao retomar, use o mesmo `execution_id`. Uma nova execução cria outra transação. O checkpoint deve conter somente dados serializáveis e suficientes para reconstruir a pausa. Respostas duplicadas precisam consultar idempotência antes de repetir qualquer efeito.
|
||||
|
||||
### Intent shift e limpeza
|
||||
|
||||
Quando o usuário abandona a transação, limpe `pending_tool_call`, argumentos coletados, confirmação e estado transacional relacionado. Não reutilize parâmetros de uma transação encerrada. Uma pergunta contextual durante a pausa pode ser respondida e retornar ao workflow; uma mudança real de intenção encerra ou suspende explicitamente o fluxo conforme a política do agente.
|
||||
|
||||
### Recuperação e post-finalization replay
|
||||
|
||||
- `PAUSED`: persistir `execution_id`, prompt e contrato da entrada esperada.
|
||||
- `FAILED`: manter detalhes técnicos na telemetria e uma mensagem segura ao usuário.
|
||||
- timeout após envio: consultar idempotência ou status externo antes de reenviar.
|
||||
- replay depois de `COMPLETED`: devolver o resultado persistido; nunca executar novamente.
|
||||
- versão nova: execuções em andamento continuam com sua versão; `active` vale para novas execuções.
|
||||
|
||||
### Como testar
|
||||
|
||||
Teste grafo inválido, caminho válido/inválido, retry, pause/resume, resposta não reconhecida, intent shift, duplicidade, falha do provider de idempotência e replay após finalização. O contrato copiável deste capítulo é validado por `tests/test_advanced_developer_documentation.py`.
|
||||
122
docs/developer/pt/14_routing_parametros_e_estado_avancados.md
Normal file
122
docs/developer/pt/14_routing_parametros_e_estado_avancados.md
Normal file
@@ -0,0 +1,122 @@
|
||||
### Routing, parâmetros e estado avançados
|
||||
|
||||
### Precedência de decisão
|
||||
|
||||
O desenvolvedor não deve tratar `routing.yaml` como uma lista de palavras. A decisão combina estado pendente, regras determinísticas e fallback semântico. A ordem segura é:
|
||||
|
||||
1. bloqueio ou finalização explícita;
|
||||
2. transação/coleção pendente por `state_policies`;
|
||||
3. confirmação determinística;
|
||||
4. intenção determinística por keywords/examples/priority;
|
||||
5. fallback LLM, quando habilitado;
|
||||
6. `fallback_agent` ou handoff.
|
||||
|
||||
O estado pendente tem precedência para que “sim”, “não” ou um parâmetro isolado não sejam roteados para outro agente.
|
||||
|
||||
### Contrato comentado do `routing.yaml`
|
||||
|
||||
```yaml routing-advanced-example
|
||||
router:
|
||||
mode: router
|
||||
fallback_agent: billing_agent
|
||||
confidence_threshold: 0.65
|
||||
allow_handoff: true
|
||||
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: |
|
||||
Classifique somente como {{ allowed_values }}.
|
||||
Pergunta pendente: {{ pending_prompt }}
|
||||
Histórico relevante: {{ relevant_conversation_context }}
|
||||
Resposta atual: {{ user_input }}
|
||||
state_policies:
|
||||
- state: COLLECTING_FINANCIAL_PARAMETERS
|
||||
agent: financeiro_agent
|
||||
description: Mantém respostas curtas na coleta financeira.
|
||||
- state: WAITING_FINANCIAL_CONFIRMATION
|
||||
agent: financeiro_agent
|
||||
description: Mantém a confirmação no fluxo financeiro.
|
||||
intents:
|
||||
- name: financial_analysis
|
||||
domain: financial
|
||||
agent: financeiro_agent
|
||||
description: Consulta saldo, posição e movimentações.
|
||||
priority: 10
|
||||
mcp_tools: [consultar_saldo, consultar_movimentacoes]
|
||||
keywords: [saldo, extrato, movimentação]
|
||||
examples:
|
||||
- Qual é meu saldo?
|
||||
- Mostre minhas movimentações.
|
||||
```
|
||||
|
||||
`priority` desempata intenções; menor valor tem precedência no template atual. `mcp_tools` restringe capacidades candidatas, mas não autoriza efeito sensível. `confidence_threshold` define quando aceitar o resultado semântico. `allow_handoff` permite troca explícita de agente. `domain` é metadado de escopo e não substitui `agent`.
|
||||
|
||||
O prompt semântico deve conter `allowed_values` e instruir o modelo a não executar ações nem inventar fatos. `include_relevant_context` inclui somente contexto limitado relacionado à pausa. Se a saída não corresponder a uma opção permitida, mantenha a transação pendente.
|
||||
|
||||
### Identidade antes da extração
|
||||
|
||||
`identity.yaml` resolve chaves canônicas (`customer_key`, `contract_key`, `interaction_key`, `account_key`, `resource_key`, `session_key`) a partir do payload, do `business_context` ou do contexto anterior. A validação de `required` deve ocorrer antes de chamar MCP. Nunca use `session_id` como identidade de cliente.
|
||||
|
||||
### Mapeamento e extração
|
||||
|
||||
```yaml parameter-mapping-example
|
||||
mcp_parameter_mapping:
|
||||
defaults:
|
||||
use_mock: false
|
||||
tools:
|
||||
consultar_movimentacoes:
|
||||
map:
|
||||
customer_key: customer_id
|
||||
account_key: account_id
|
||||
session_key: session_id
|
||||
defaults:
|
||||
limit: 20
|
||||
extract:
|
||||
month:
|
||||
from: message
|
||||
type: int
|
||||
strategy: month_name_pt
|
||||
description: Converta o mês mencionado em número de 1 a 12.
|
||||
transaction_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
pattern: '(?i)\\b(?:transação|transaction)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b'
|
||||
group: 1
|
||||
description: Extraia somente um identificador explicitamente informado.
|
||||
```
|
||||
|
||||
Precedência recomendada para argumentos: valor já confirmado na transação, argumento explícito atual, identidade canônica mapeada, extração determinística, extração LLM/híbrida e default. Um valor novo explícito pode corrigir o anterior; um valor inferido nunca deve sobrescrever um valor confirmado.
|
||||
|
||||
`strategy: hybrid` tenta a parte determinística e usa LLM quando necessário. `pattern` e `group` devem ser testados com casos positivos e negativos. `description` é instrução operacional do extrator, não documentação decorativa. `type` deve falhar de forma controlada se a conversão não for segura.
|
||||
|
||||
### Reconciliação temporal
|
||||
|
||||
A reconciliação temporal é fallback da coleta tradicional. Quando ainda faltarem campos, percorra mensagens relevantes da mais recente para a mais antiga e associe valores somente aos campos descritos no `args_schema`/mapeamento. Ela não deve:
|
||||
|
||||
- reabrir transação encerrada;
|
||||
- misturar agentes, tenants ou sessões;
|
||||
- recuperar valor invalidado pelo usuário;
|
||||
- trocar correspondência exata por aproximação perigosa;
|
||||
- substituir parâmetro já confirmado.
|
||||
|
||||
Registre a origem (`current_message`, `business_context`, `history`, `default`), a mensagem correlacionada e a confiança, sem persistir conteúdo sensível desnecessário.
|
||||
|
||||
### Estados dinâmicos
|
||||
|
||||
Para um agente novo, declare os estados de coleta e confirmação em `AgentState` somente quando eles carregarem campos adicionais; caso contrário, use as chaves transacionais genéricas. Registre ambos em `state_policies`, implemente a limpeza em sucesso/cancelamento/intent shift e inclua os estados nos testes de roteamento. Estado novo sem política correspondente costuma causar perda de stickiness.
|
||||
|
||||
### Router versus supervisor
|
||||
|
||||
No modo `router`, uma decisão escolhe um agente. No modo `supervisor`, o supervisor pode coordenar vários agentes e consolidar resultados. Não habilite supervisor apenas para melhorar classificação: ele muda custo, latência, estado e observabilidade. Um subagente deve receber contexto mínimo, ferramentas autorizadas e namespace `tenant_id:agent_id:session_id`; nunca compartilhe memória bruta entre agentes.
|
||||
|
||||
### Testes obrigatórios
|
||||
|
||||
Teste sobreposição de keywords, empate de prioridade, abaixo do threshold, agente fallback, estado pendente, confirmação curta, resposta ambígua, correção de parâmetro, extração regex/LLM, histórico temporal e isolamento entre agentes. Os YAMLs deste capítulo são analisados por `tests/test_advanced_developer_documentation.py`.
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
### Apresentação, recuperação e observabilidade
|
||||
|
||||
### Renderers de resposta
|
||||
|
||||
Quando uma tool retorna dados estruturados confiáveis, `response.mode: renderer` produz uma resposta determinística e evita que o LLM altere valores, protocolos ou status.
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
consultar_saldo:
|
||||
description: Consulta saldo da conta.
|
||||
mcp_server: financial
|
||||
enabled: true
|
||||
response:
|
||||
mode: renderer
|
||||
renderer: financial.balance
|
||||
```
|
||||
|
||||
```python renderer-example
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.presentation import register_tool_response_renderer
|
||||
|
||||
|
||||
def render_balance(*, tool_name: str, result: dict[str, Any],
|
||||
state: dict[str, Any], agent_label: str) -> str | None:
|
||||
account = result.get("account_masked")
|
||||
balance = result.get("balance")
|
||||
currency = result.get("currency", "BRL")
|
||||
if account is None or balance is None:
|
||||
return None
|
||||
return f"[{agent_label}] Conta {account}: saldo {currency} {float(balance):.2f}."
|
||||
|
||||
|
||||
def register_financial_renderers() -> None:
|
||||
register_tool_response_renderer("financial.balance", render_balance)
|
||||
```
|
||||
|
||||
Importe e execute `register_financial_renderers()` uma vez no startup. A assinatura é keyword-only e deve aceitar `tool_name`, `result`, `state` e `agent_label`. Retorne `None` quando faltarem campos obrigatórios para permitir o fallback previsto pelo runtime. Nunca interpolar o payload inteiro: selecione campos permitidos, mascare identidade e formate valores explicitamente.
|
||||
|
||||
### Organização de `app/presentation/`
|
||||
|
||||
No `agent_template_backend`, renderers específicos do domínio ficam em:
|
||||
|
||||
```text
|
||||
app/presentation/
|
||||
├── __init__.py
|
||||
└── tool_renderers.py
|
||||
```
|
||||
|
||||
A pasta existe para separar **como um resultado confiável do domínio deve ser apresentado** da infraestrutura genérica que seleciona e executa renderers. Assim:
|
||||
|
||||
- `app/presentation/` implementa funções como `financial.balance`, `billing.invoice` ou outro renderer específico do agente;
|
||||
- `agent_framework.presentation` mantém registry, contrato de chamada e mecanismo genérico de integração com o runtime;
|
||||
- `config/tools.yaml` seleciona `response.mode: renderer` e o nome do renderer;
|
||||
- o startup do agente importa/registra os renderers uma única vez.
|
||||
|
||||
Renderers não devem buscar dados, decidir autorização, executar tools, alterar estado transacional ou reinterpretar um resultado usando LLM. A entrada já deve representar um resultado autorizado/confiável; o renderer apenas seleciona, mascara e formata campos.
|
||||
|
||||
**Anti-padrão:** criar em `app/presentation/` uma cópia do registry ou do pipeline de apresentação do framework. Se vários agentes precisarem do mesmo mecanismo de formatação/seleção, ele deve evoluir no core; somente a apresentação específica do domínio fica no agente.
|
||||
|
||||
### Output Supervisor
|
||||
|
||||
O Output Supervisor valida a resposta candidata antes da saída final. Ele pode permitir, sanitizar, solicitar retry, bloquear, entregar a humano ou observar. `OUTPUT_SUPERVISOR_MAX_RETRIES` deve ter limite pequeno; cada retry precisa de orientação objetiva e a resposta reescrita deve passar novamente pelos controles. Não use retry para erro permanente de autorização ou indisponibilidade.
|
||||
|
||||
### Recuperação de workflow
|
||||
|
||||
Classifique falhas em: validação, dependência temporária, dependência permanente, persistência e erro de programação. O usuário recebe mensagem segura; a telemetria recebe classe, fase, tentativa, workflow/version/execution ID e correlação. Para efeitos externos:
|
||||
|
||||
- antes do envio, um retry controlado é aceitável;
|
||||
- depois de envio com resultado incerto, consulte status/idempotência;
|
||||
- após confirmação externa, persista o resultado antes de responder;
|
||||
- uma nova mensagem do usuário não deve apagar evidência de uma execução incerta.
|
||||
|
||||
Checkpoints resilientes podem repetir operações de armazenamento, validar integridade, compactar histórico e recuperar o último checkpoint válido. Isso não torna automaticamente uma action externa idempotente.
|
||||
|
||||
### Interrupção de voz e replay
|
||||
|
||||
No barge-in, preserve o texto confirmado, marque a resposta interrompida e não reapresente áudio obsoleto. O replay deve usar identificadores de interação/mensagem e a mesma sessão escopada. Se uma transação estava pendente, a fala interrompida não conta como confirmação. Ao retomar, revalide estado, confirmação e idempotência.
|
||||
|
||||
### Eventos IC, NOC e GRL
|
||||
|
||||
- IC: comportamento funcional, decisão, tool, cache e workflow;
|
||||
- NOC: disponibilidade, timeout, dependência e latência operacional;
|
||||
- GRL: decisão e ação de guardrail/supervisor.
|
||||
|
||||
Use o `AgentObserver`/adapter do template em vez de chamar diretamente um backend de telemetria. Inclua `tenant_id`, `agent_id`, `session_id`, `transaction_id`/`execution_id`, componente e duração quando disponíveis. Nunca inclua credenciais, prompt integral, PII ou payload financeiro bruto.
|
||||
|
||||
### Mapeamento e compatibilidade
|
||||
|
||||
Quando consumidores históricos exigirem códigos diferentes, use um registry/overlay como `observability_mapping.yaml`: evento semântico interno → label externo. O default é compartilhado e o overlay pertence ao agente. Não hardcode labels históricas no executor nem renomeie o evento interno de forma que perca semântica.
|
||||
|
||||
### Pub/Sub e sequência
|
||||
|
||||
Publicação deve ocorrer após a decisão funcional e não pode alterar seu resultado. Configure provider, tópicos/streams, filtros e política de falha. Quando o consumidor exigir ordem, publique `sequence`, chave de partição estável e correlação; múltiplos pods não garantem ordem global. Defina TTL/retenção e comportamento para duplicatas. Eventos filtrados no Pub/Sub continuam disponíveis nos outros destinos de observabilidade.
|
||||
|
||||
### Checklist operacional
|
||||
|
||||
- renderer não expõe payload bruto;
|
||||
- resposta sanitizada é revalidada;
|
||||
- retries têm limite e distinguem erro transitório;
|
||||
- efeitos incertos consultam idempotência/status;
|
||||
- evento contém correlação e não contém segredo;
|
||||
- labels externas são mapeadas por configuração;
|
||||
- sequência e chave de partição são testadas com múltiplos consumidores;
|
||||
- interrupção de voz não confirma nem repete transação.
|
||||
|
||||
O exemplo de renderer é compilado e executado por `tests/test_advanced_developer_documentation.py`.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
### Multiagente, composição e ciclo de vida
|
||||
|
||||
### O que constitui um agente isolado
|
||||
|
||||
Um `agent_id` não é apenas um label. O isolamento real precisa abranger prompt, routing, tools, MCP servers, guardrails, judges, memória, checkpoints, cache, telemetria e identidade. A chave operacional recomendada é `tenant_id:agent_id:session_id`.
|
||||
|
||||
```yaml agents-registry-example
|
||||
default_agent_id: financial
|
||||
agents:
|
||||
- agent_id: financial
|
||||
name: Agente Financeiro
|
||||
description: Consultas e operações financeiras.
|
||||
prompt_policy_path: ./config/agents/financial/prompt_policy.yaml
|
||||
routing_config_path: ./config/agents/financial/routing.yaml
|
||||
guardrails_config_path: ./config/agents/financial/guardrails.yaml
|
||||
judges_config_path: ./config/agents/financial/judges.yaml
|
||||
mcp_servers_config_path: ./config/agents/financial/mcp_servers.yaml
|
||||
tools_config_path: ./config/agents/financial/tools.yaml
|
||||
metadata:
|
||||
domain: financial
|
||||
system_prefix: |
|
||||
Execute somente políticas e recursos do agent_id financial.
|
||||
```
|
||||
|
||||
Todos os caminhos devem existir e ser validados no startup. O prefixo ajuda o LLM, mas não é barreira de segurança. Autorização de tools, namespaces de persistência e seleção de pipelines devem ser impostas pelo código.
|
||||
|
||||
### Limitação real do template atual
|
||||
|
||||
O registry aceita esses caminhos, mas o `AgentWorkflow` corrente ainda constrói um único router, um único conjunto de tools/RAG e um par global de guardrails/judges. Portanto, copiar o YAML acima não cria isolamento completo por requisição. Até existir uma factory/cache por perfil, use uma configuração global por deployment ou implemente a seleção explícita antes de invocar o grafo.
|
||||
|
||||
Uma composição correta mantém, por `agent_id`, os objetos imutáveis/caros em cache e injeta dependências na execução:
|
||||
|
||||
```python agent-runtime-bundle-example
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AgentRuntimeBundle:
|
||||
router: Any
|
||||
tool_router: Any
|
||||
rag_service: Any
|
||||
guardrails: Any
|
||||
judges: Any
|
||||
|
||||
|
||||
def select_bundle(registry: dict[str, AgentRuntimeBundle], agent_id: str) -> AgentRuntimeBundle:
|
||||
if agent_id not in registry:
|
||||
raise KeyError(f"agent_id não registrado: {agent_id}")
|
||||
return registry[agent_id]
|
||||
```
|
||||
|
||||
Não construa LLM, conexão, router ou pipeline a cada mensagem. Não use fallback silencioso para o bundle de outro agente; falhe de forma explícita ou use somente o `default_agent_id` autorizado na borda.
|
||||
|
||||
### Subagentes e supervisor
|
||||
|
||||
Subagente é uma capacidade delimitada, não apenas uma função Python. Declare objetivo, entradas, saídas, tools autorizadas, orçamento/timeout e condições de retorno. O supervisor recebe resultados estruturados e consolida; ele não deve compartilhar todo o histórico por padrão. Uma delegação deve carregar correlação e profundidade máxima para evitar ciclos.
|
||||
|
||||
Use router quando uma única especialidade resolve o turno. Use supervisor quando a tarefa realmente exigir decomposição ou combinação de duas ou mais capacidades. Para handoff, transfira propriedade da conversa; para subagente, mantenha o supervisor como proprietário da resposta final.
|
||||
|
||||
### Composição de LLM por domínio
|
||||
|
||||
Use `llm_profiles.yaml` e o cliente criado pelo framework. Componentes recebem `profile_name` ou o LLM compartilhado; não criam SDK/provedor com credenciais próprias. Isso preserva autenticação, rate limits, retries, cache e telemetria. Fallback entre profiles precisa ser explícito e observável; nunca converta falha de configuração em resposta aparentemente válida.
|
||||
|
||||
### RAG e memória por agente
|
||||
|
||||
`RAG_PROVIDER=kbdb` seleciona KBDB apenas quando o `RagService` daquela execução usa as settings corretas. A intenção simples também deve acionar RAG quando a política do agente exigir conhecimento. Separe coleção/namespace por tenant e agente. Memória de longo prazo guarda fatos permitidos e resumidos; checkpoint guarda execução. Nenhum dos dois deve misturar identidade de outro agente.
|
||||
|
||||
### Autenticação e autorização
|
||||
|
||||
Autenticação prova o principal; autorização decide agente, tenant, rota e tool. Basic, API key, bearer, JWT, introspection e trusted proxy são providers possíveis, mas produção deve usar segredo com hash/rotação e TLS. Headers de trusted proxy só são confiáveis quando a borda autenticada remove valores enviados pelo cliente.
|
||||
|
||||
### Inicialização e readiness
|
||||
|
||||
No startup: validar registry e caminhos; criar providers; registrar actions/renderers; carregar workflows; construir bundles; verificar conexões essenciais. `/ready` deve falhar quando uma dependência obrigatória não estiver utilizável; `/health` indica processo vivo. Configuração inválida deve falhar cedo, antes da primeira conversa.
|
||||
|
||||
### Ciclo de desenvolvimento e certificação
|
||||
|
||||
1. Copiar o template canônico sem copiar `.venv` ou caches.
|
||||
2. Criar agente, estado somente se necessário, configurações e recursos de domínio.
|
||||
3. Registrar nó/rota e decidir router ou supervisor.
|
||||
4. Implementar tools, mappings, políticas, actions e renderers.
|
||||
5. Configurar guardrails, judges, RAG, memória e observabilidade.
|
||||
6. Testar unidade, loader/config, grafo, regressão conversacional e falhas.
|
||||
7. Executar avaliação offline, carga e readiness.
|
||||
8. Publicar com versão, compatibilidade e rollback documentados.
|
||||
|
||||
### Definition of done
|
||||
|
||||
Um agente não está pronto apenas porque responde ao happy path. Ele precisa provar isolamento, confirmação antes de efeito, idempotência, recuperação, grounding, bloqueio/sanitização, telemetria correlacionada, ausência de vazamento, comportamento sob dependência indisponível e compatibilidade PT/EN dos manuais.
|
||||
|
||||
Os contratos deste capítulo são validados por `tests/test_advanced_developer_documentation.py`.
|
||||
|
||||
169
docs/developer/pt/INDEX_DEVELOPER_GUIDE.md
Normal file
169
docs/developer/pt/INDEX_DEVELOPER_GUIDE.md
Normal file
@@ -0,0 +1,169 @@
|
||||
|
||||
### Í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 fala inválida durante um `expected_input` vira `CONTINUAR` em vez de pedir esclarecimento | `semantic_classifier.unmatched_value`, `reprompt`, `contextual_reentry`, COER delegado | [Workflows Transacionais](./03_transaction_workflows_and_state.md) e [Feedback de Guardrails de Entrada](./12_input_guardrail_feedback_and_blocked_turns.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) |
|
||||
| Preciso chamar a mesma operação para vários produtos/pedidos/itens | contrato escalar ou coleção, pre-validation, confirmação única, resultado por item | [MCP Multi-item](../../MCP_MULTI_ITEM_DEVELOPER_GUIDE.md) e [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, `expected_input`, `semantic_classifier`, `unmatched_value`/`reprompt` e evidência operacional.
|
||||
|
||||
**Use quando:** há loops, confirmações incorretas, retomadas erradas, `CONTINUAR`/`contextual_reentry` indevido, `reprompt` ausente 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, parameter extraction e chamadas multi-item.
|
||||
|
||||
**Use quando:** integração/execução de tool está incorreta, precisa ser criada ou deve processar listas de itens sem lógica paralela no agente.
|
||||
|
||||
### [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.
|
||||
|
||||
### [13 — Workflows Transacionais Avançados](./13_workflows_transacionais_avancados.md)
|
||||
|
||||
**O que é:** implementação de pré-validação, workflow versionado, actions, pausa/retomada, idempotência, replay e recuperação.
|
||||
|
||||
**Use quando:** uma tool sensível precisa executar um processo determinístico e recuperável.
|
||||
|
||||
### [14 — Routing, Parâmetros e Estado Avançados](./14_routing_parametros_e_estado_avancados.md)
|
||||
|
||||
**O que é:** precedência do router, confirmação semântica, extração híbrida, reconciliação temporal, estados dinâmicos e subagentes.
|
||||
|
||||
**Use quando:** routing, coleta ou retomada funcionam no happy path, mas falham em conversas reais.
|
||||
|
||||
### [15 — Apresentação, Recuperação e Observabilidade](./15_apresentacao_recuperacao_e_observabilidade.md)
|
||||
|
||||
**O que é:** renderers, Output Supervisor, recuperação, voice replay, eventos, mappings e Pub/Sub ordenado.
|
||||
|
||||
**Use quando:** a resposta altera evidências, um efeito pode repetir ou a telemetria não permite reconstruir o fluxo.
|
||||
|
||||
### [16 — Multiagente, Composição e Ciclo de Vida](./16_multiagente_composicao_e_ciclo_de_vida.md)
|
||||
|
||||
**O que é:** isolamento efetivo, bundles por agente, supervisor/subagentes, LLM/RAG/memória e readiness.
|
||||
|
||||
**Use quando:** vários agentes compartilham o deployment ou uma nova capacidade precisa chegar à produção.
|
||||
|
||||
### 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 → workflows → idempotência → execução → testes → gateways → memória → RAG → operação`.
|
||||
|
||||
### 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.
|
||||
84
docs/developer/pt/VALIDATION_README_ALIGNMENT.md
Normal file
84
docs/developer/pt/VALIDATION_README_ALIGNMENT.md
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
### Validação de Alinhamento da Documentação
|
||||
|
||||
### Objetivo
|
||||
|
||||
Registrar como a documentação desta versão foi reorganizada e quais fontes devem ser usadas pelo desenvolvedor.
|
||||
|
||||
### Decisão estrutural
|
||||
|
||||
O `README.md` da raiz é o **único tutorial principal ponta a ponta**.
|
||||
|
||||
O antigo `01_architecture_and_agent_development.md` foi removido porque repetia grande parte do README, mas não todo ele. Isso criava ambiguidade: dois documentos aparentavam ensinar a mesma coisa, porém um era parcial.
|
||||
|
||||
A nova estrutura substitui esse arquivo por `01_architecture_and_concepts.md`, que contém apenas arquitetura, conceitos, responsabilidades e critérios de extensão.
|
||||
|
||||
### Validação de `README_old2.md`
|
||||
|
||||
`Documentacao/README_old2.md` permanece útil como histórico, mas não é fonte principal para desenvolvimento.
|
||||
|
||||
Foram encontradas evoluções posteriores no README atual e no código, incluindo:
|
||||
|
||||
- SPECs/SDDs;
|
||||
- configuração mais completa de `llm_profiles.yaml`;
|
||||
- Channel Gateway e contratos canônicos;
|
||||
- `memory` e `summary_memory` no ciclo atual do agente;
|
||||
- `prepare_memory_context()` e `build_messages()`;
|
||||
- `RuntimeContext`;
|
||||
- `normalize_tools_by_intent()`;
|
||||
- `build_tool_arguments()`;
|
||||
- `execute_tools_for_intent()`;
|
||||
- helpers de estado transacional;
|
||||
- respostas MCP diretas;
|
||||
- evolução de gateways, RAG, memória e políticas.
|
||||
|
||||
### Correção aplicada ao README principal
|
||||
|
||||
Foi corrigido no pacote gerado o typo:
|
||||
|
||||
```python
|
||||
from app.agents.financeiro_agent import FinanceirotAgent
|
||||
```
|
||||
|
||||
para:
|
||||
|
||||
```python
|
||||
from app.agents.financeiro_agent import FinanceiroAgent
|
||||
```
|
||||
|
||||
A classe correta é confirmada pelo código e pelo restante da documentação.
|
||||
|
||||
### APIs confirmadas na implementação atual
|
||||
|
||||
```python
|
||||
AgentRuntimeMixin.get_runtime_context()
|
||||
AgentRuntimeMixin.normalize_tools_by_intent()
|
||||
AgentRuntimeMixin.build_tool_arguments()
|
||||
AgentRuntimeMixin.execute_tools_for_intent()
|
||||
AgentRuntimeMixin.prepare_memory_context()
|
||||
AgentRuntimeMixin.build_messages()
|
||||
AgentRuntimeMixin.transaction_state_patch()
|
||||
AgentRuntimeMixin.transaction_clarification_message()
|
||||
AgentRuntimeMixin.transaction_confirmation_message()
|
||||
AgentRuntimeMixin.build_direct_mcp_answer()
|
||||
```
|
||||
|
||||
### Ordem de confiança
|
||||
|
||||
1. código da versão;
|
||||
2. README principal da mesma versão;
|
||||
3. SPECs/SDDs;
|
||||
4. manuais especializados;
|
||||
5. release notes;
|
||||
6. documentos `README_old*`.
|
||||
|
||||
### Regra de manutenção futura
|
||||
|
||||
Uma evolução de feature deve atualizar:
|
||||
|
||||
1. o README principal, **somente se alterar o caminho normal de desenvolvimento**;
|
||||
2. o manual especializado da feature, com detalhes técnicos, comportamento, configuração e troubleshooting;
|
||||
3. a SPEC, quando houver mudança de contrato;
|
||||
4. release note, quando for necessário registrar a mudança histórica.
|
||||
|
||||
Não crie um novo “manual principal” para uma feature. Não mantenha correções funcionais permanentemente apenas em release notes.
|
||||
Reference in New Issue
Block a user