bugfix: Multi-item

This commit is contained in:
2026-09-18 23:11:58 -03:00
parent 36f97811fa
commit c582150e1c
217 changed files with 2583 additions and 4 deletions

View File

@@ -0,0 +1,250 @@
###############################################################################
# AI AGENT PLATFORM - CONFIGURAÇÃO ÚNICA
# Este arquivo é lido por Pydantic Settings no framework e no backend template.
###############################################################################
APP_NAME=ai-agent-template
APP_ENV=local
LOG_LEVEL=INFO
API_HOST=0.0.0.0
API_PORT=8000
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
###############################################################################
# LLM - OCI Generative AI como provider principal
###############################################################################
# Opções: mock, oci_openai, oci_sdk, openai_compatible
LLM_PROVIDER=oci_sdk
LLM_TEMPERATURE=0.2
LLM_MAX_TOKENS=2048
LLM_TIMEOUT_SECONDS=120
# OCI OpenAI-compatible endpoint
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
OCI_GENAI_MODEL=openai.gpt-4.1
OCI_GENAI_API_KEY=sk-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS
OCI_GENAI_PROJECT_OCID=
#OCI_GENAI_BASE_URL=https://inference.generativeai.eu-frankfurt-1.oci.oraclecloud.com/openai/v1
#OCI_GENAI_MODEL=ocid1.generativeaiendpoint.oc1.eu-frankfurt-1.amaaaaaad6nji3aa5fmicyft7qci6snu5idhwoomviehnsv4lrostzbmenkq
#OCI_GENAI_API_KEY=sk-TB0OzfrhPLXbQTqZ4aKlrLpcgjHAcn11O1o8C4I4aPvWF0Vx
#OCI_GENAI_PROJECT_OCID=
# OCI_AUTH_MODE=config_file|instance_principal|resource_principal
OCI_AUTH_MODE=config_file
# OCI SDK / signer / profiles
OCI_CONFIG_FILE=~/.oci/config
OCI_PROFILE=LATINOAMERICA-Chicago
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q
OCI_REGION=us-chicago-1
###############################################################################
# Persistência
###############################################################################
# Opções: memory, autonomous, mongodb
SESSION_REPOSITORY_PROVIDER=autonomous
MEMORY_REPOSITORY_PROVIDER=autonomous
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
# Autonomous Database
ADB_USER=admin
ADB_PASSWORD=Moniquinha19721972
ADB_DSN=oradb23ai_high
ADB_WALLET_LOCATION=/mnt/d/Dropbox/ORACLE/LatinoAmerica/Wallet_ORADB23ai
ADB_WALLET_PASSWORD=Moniquinha1972
ADB_TABLE_PREFIX=AGENTFW
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017
MONGODB_DATABASE=agent_platform
# Redis
REDIS_URL=redis://localhost:6379/0
ENABLE_REDIS_CACHE=false
###############################################################################
# RAG / Vector / Graph
###############################################################################
VECTOR_STORE_PROVIDER=autonomous
GRAPH_STORE_PROVIDER=autonomous
RAG_TOP_K=5
EMBEDDING_PROVIDER=oci
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
###############################################################################
# Observabilidade
###############################################################################
ENABLE_LANGFUSE=true
# Opcional: verbose, compact
LANGFUSE_TRACE_MODE=compact
# Nome customizado do trace pai, ex.: backoffice.checklist.workflow ou backoffice.emulador.workflow
LANGFUSE_COMPACT_VISIBLE_EVENT_PREFIXES=AGA.,NOC., IC.
LANGFUSE_COMPACT_SUPPRESSED_PREFIXES=llm.chat_completion
LANGFUSE_IGNORE_HEALTHCHECKS=true
LANGFUSE_IGNORED_PATHS=/health,/ready,/metrics
LANGFUSE_PUBLIC_KEY=pk-lf-4a1e3921-5158-4fd3-a16d-7a77549fb312
LANGFUSE_SECRET_KEY=sk-lf-efc6fd59-c5ec-4858-b6ec-4aa129734915
LANGFUSE_HOST=http://localhost:3005
ENABLE_OTEL=false
OTEL_EXPORTER_OTLP_ENDPOINT=
OTEL_SERVICE_NAME=ai-agent-template
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
###############################################################################
# Analytics / Observer corporativo
###############################################################################
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
ENABLE_ANALYTICS=true
# Providers aceitos: oci_streaming,pubsub,noop
ANALYTICS_PROVIDERS=pubsub
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
AGENT_PUBSUB_TOPIC=teste_pubsub
PUBSUB_TOPIC_PATH=projects/project-7f829b68-bf43-442c-ad3/topics/teste_pubsub
PROJECT_ID=project-7f829b68-bf43-442c-ad3
GCP_PUBSUB_TOPIC_PATH=projects/project-7f829b68-bf43-442c-ad3/topics/teste_pubsub
GCP_PROJECT_ID=project-7f829b68-bf43-442c-ad3
GCP_PUBSUB_TOPIC=teste_pubsub
GCP_PUBSUB_TIMEOUT_SECONDS=30
# Credencial GCP segue padrão Google:
GOOGLE_APPLICATION_CREDENTIALS=/mnt/d/Dropbox/ORACLE/TIM/FY27/Testes/sa-hoshikawa.json
PUBSUB_SEQUENCE_ENABLED=true
PUBSUB_SEQUENCE_PROVIDER=mongodb
#PUBSUB_SEQUENCE_MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017
#PUBSUB_SEQUENCE_MONGODB_DATABASE=agent_platform
PUBSUB_SEQUENCE_MONGODB_URI=mongodb://ADMIN:Moniquinha1972@srlrz1eg.adb.sa-saopaulo-1.oraclecloudapps.com:37017/ADMIN?authMechanism=PLAIN&authSource=$external&ssl=true&retryWrites=false&loadBalanced=true
PUBSUB_SEQUENCE_MONGODB_DATABASE=ADMIN
PUBSUB_SEQUENCE_MONGODB_COLLECTION=observer_event_counters
PUBSUB_SEQUENCE_TTL_SECONDS=86400
PUBSUB_SEQUENCE_KEY_PREFIX=observer:sequence
PUBSUB_SEQUENCE_MEMORY_FALLBACK=false
SIMULATE_AGA_MISSING_TRANSACTION_ID=true
###############################################################################
# OCI Streaming
###############################################################################
ENABLE_OCI_STREAMING=false
OCI_STREAM_ENDPOINT=
OCI_STREAM_OCID=
OCI_STREAM_PARTITION_KEY=agent-events
###############################################################################
# Guardrails, Judges, Supervisor
###############################################################################
ENABLE_INPUT_GUARDRAILS=true
ENABLE_OUTPUT_GUARDRAILS=true
ENABLE_JUDGES=true
ENABLE_SUPERVISOR=true
ENABLE_OUTPUT_SUPERVISOR=true
ENABLE_PARALLEL_GUARDRAILS=true
GUARDRAILS_FAIL_FAST=true
OUTPUT_SUPERVISOR_MAX_RETRIES=3
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
JUDGES_CONFIG_PATH=./config/judges.yaml
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
###############################################################################
# Gateway de canais
###############################################################################
DEFAULT_CHANNEL=web
# embedded = backend may parse simple/native channel payloads.
# external = backend only accepts GatewayRequest normalized by an external Channel Gateway.
FRAMEWORK_CHANNEL_INPUT_MODE=embedded
ENABLE_VOICE_ADAPTER=true
ENABLE_WHATSAPP_ADAPTER=true
ENABLE_TEXT_ADAPTER=true
#################################################
# ENTERPRISE ROUTING
#################################################
# Arquivo YAML com intents, keywords, políticas de estado e fallback.
ROUTING_CONFIG_PATH=./config/routing.yaml
# true = usa LLM para classificar quando keywords/estado não resolverem.
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
ENABLE_LLM_ROUTER=true
# Semantic route stickiness (optional).
# Uses a lightweight LLM profile to decide only CONTINUE vs ROUTE.
# There are no regexes or deterministic language rules.
ENABLE_ROUTE_STICKINESS=true
ROUTE_STICKINESS_LLM_PROFILE=route_continuity
ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90
ROUTE_STICKINESS_HISTORY_TURNS=2
ROUTE_STICKINESS_MAX_TOKENS=80
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
###############################################################################
# MCP / Tools
###############################################################################
ENABLE_MCP_TOOLS=true
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
TOOLS_CONFIG_PATH=./config/tools.yaml
MCP_TOOL_TIMEOUT_SECONDS=30
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
ROUTING_MODE=router
# Usage/cost accounting
USAGE_REPOSITORY_PROVIDER=autonomous
IDENTITY_CONFIG_PATH=./config/identity.yaml
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
# -----------------------------------------------------------------------------
# ConversationSummaryMemory / compressão de contexto conversacional
# -----------------------------------------------------------------------------
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
MEMORY_CONTEXT_STRATEGY=summary
MEMORY_HISTORY_LIMIT=80
MEMORY_RECENT_MESSAGES_LIMIT=8
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
MEMORY_MAX_SUMMARY_CHARS=6000
MEMORY_SUMMARY_USE_LLM=true
MEMORY_INJECT_RECENT_MESSAGES=true
MEMORY_INJECT_SUMMARY=true
###############################################################################
# LONG-TERM MEMORY
###############################################################################
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
# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY
# LONG_TERM_MEMORY_ORACLE_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
###############################################################################
# RAG provider selection (mutually exclusive at runtime)
###############################################################################
# standard = RAG original do agent_framework_oci (default, backward compatible)
# kbdb = KBDB enterprise via PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE
RAG_PROVIDER=kbdb
# Somente usados quando RAG_PROVIDER=kbdb. Se vazios, credenciais caem para ADB_*.
KBDB_DB_USER=KB_USER
KBDB_DB_PASSWORD=SuperStr0ngPa##w0rd
KBDB_DB_DSN=kbdb_public_high
KBDB_DB_WALLET_LOCATION=/mnt/d/Dropbox/ORACLE/TIM/FY27/kb/Wallet_kbdb
KBDB_DB_WALLET_PASSWORD=SuperStr0ngPa##w0rd
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=0.5
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3

View File

@@ -172,6 +172,15 @@ config/agents/<agent_id>/judges.yaml
---
## MCP multi-item — não implemente loops no agente
Se uma mesma solicitação envolve vários produtos, pedidos ou outros itens, use o mecanismo multi-item do framework. Declare o parâmetro como `array/list`, use pre-validation para canonicalização quando necessário e faça a tool MCP primária devolver `results[]` com `success`/`ok` por item. O runtime preserva sucesso parcial e produz `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`.
Guia copiável e completo neste template: [`docs/MCP_MULTI_ITEM.md`](docs/MCP_MULTI_ITEM.md).
> Anti-pattern: executar `tool_router.call(...)` em um `for` dentro do agente para simular uma operação em lote. Se o backend só expõe API unitária, encapsule o fan-out no MCP Server/workflow.
## 3. Pré-requisitos
### 3.1. Requisitos locais

View File

@@ -0,0 +1,363 @@
# MCP Multi-item — Guia do Desenvolvedor
## Objetivo
Use este guia quando uma única intenção do usuário precisa operar sobre **mais de um item do mesmo tipo** em uma chamada MCP, por exemplo:
- cancelar vários VAS/produtos;
- devolver ou consultar vários pedidos;
- alterar vários ativos;
- processar uma lista de faturas, linhas, contratos ou recursos.
O framework já possui contratos genéricos para **coleta de listas**, **pré-validação/canonicalização**, **confirmação transacional**, **execução MCP** e **normalização do resultado por item**. O desenvolvedor não deve criar um segundo motor de multi-item dentro do agente.
## Regra principal
**Não faça loop de negócio no agente.** O agente declara a tool e seus parâmetros; o runtime mantém estado, confirmação e evidência. A operação em lote deve ser exposta como uma tool MCP que aceite uma coleção, ou como uma tool transacional apoiada por um workflow/MCP que processe a coleção.
Fluxo recomendado:
```text
Usuário cita N itens
-> Router/Agent seleciona a tool
-> Runtime coleta/reconcilia parâmetro array/list
-> (opcional) pre-validation canonicaliza itens
-> Runtime pede UMA confirmação coerente para o conjunto
-> MCP recebe a coleção
-> MCP/workflow processa item a item
-> MCP devolve results[] com success/ok por item
-> Framework normaliza SUCCESS / PARTIAL_SUCCESS / FAILED
-> Composição final relata sucessos e falhas individualmente
```
## 1. Declarar um parâmetro de lista em `tools.yaml`
O reconciliador transacional do framework aceita `type: array` ou `type: list`.
```yaml
tools:
cancelar_produtos:
description: Cancela um ou mais produtos do cliente.
mcp_server: contas
enabled: true
tool_type: action
confirmation_required: true
requires: [items]
args_schema:
items:
type: array
label: os produtos
description: Lista de produtos que o cliente deseja cancelar.
user_prompt: Informe quais produtos deseja cancelar.
```
O runtime pode reconciliar uma frase como:
```text
quero cancelar TIM Fashion, Aya Audiobooks e Neymar Jr
```
para um argumento lógico semelhante a:
```json
{
"items": ["TIM Fashion", "Aya Audiobooks", "Neymar Jr"]
}
```
A semântica de domínio e a validação dos nomes continuam pertencendo ao MCP/pre-validator, não ao core.
## 2. Quando usar pre-validation
Use `tool_policies.yaml` quando a lista precisa ser resolvida/canonicalizada antes da confirmação.
```yaml
tool_policies:
cancelar_produtos:
operation_type: transactional
require_confirmation: true
requires: [items]
pre_validation:
enabled: true
tool: validar_produtos_cancelamento
fail_open: false
```
O pre-validator pode retornar `eligible: true` e substituir argumentos por valores canônicos em `transaction_decision.resolved_arguments`:
```json
{
"eligible": true,
"transaction_decision": {
"resolved_arguments": {
"items": [
{"name": "TIM Fashion Mensal", "id": "1001"},
{"name": "Aya Audiobooks Premium", "id": "1002"},
{"name": "Neymar Jr", "id": "1003"}
]
},
"target_tool": "cancelar_produtos",
"confirmation_message": "Você confirma o cancelamento de TIM Fashion Mensal, Aya Audiobooks Premium e Neymar Jr?"
}
}
```
### Importante para validação parcial
O contrato `eligible` da pre-validation é global. Portanto, **não retorne `eligible: false` apenas porque um item da lista falhou**, caso os demais ainda possam ser processados. Isso encerraria toda a transação.
Para permitir resultado parcial, a estratégia recomendada é:
1. retornar `eligible: true` quando existe pelo menos um item acionável;
2. preservar/canonicalizar a coleção em `resolved_arguments`;
3. deixar a tool primária devolver o resultado terminal de **cada item** em `results[]`.
Se nenhum item puder ser processado, então `eligible: false` é apropriado.
## 3. Contrato de entrada do MCP
Prefira uma única chamada MCP com uma coleção explícita:
```json
{
"tool_name": "cancelar_produtos",
"arguments": {
"customer_id": "123",
"items": [
{"name": "TIM Fashion Mensal", "id": "1001"},
{"name": "Aya Audiobooks Premium", "id": "1002"},
{"name": "Neymar Jr", "id": "1003"}
]
}
}
```
O framework **não deve ser usado como um loop improvisado que chama N vezes uma tool escalar a partir do agente**. Se o backend só possui API unitária, o fan-out pode ficar encapsulado no MCP Server ou em um workflow de domínio, mantendo uma única tool lógica para o agente.
## 4. Contrato obrigatório de resultado por item
Para o framework reconhecer multi-item automaticamente, a **tool primária solicitada** deve expor uma lista `results` com **dois ou mais itens**, e cada item deve possuir um booleano `success` ou `ok`.
Exemplo:
```json
{
"ok": true,
"result": {
"results": [
{"id": "1001", "subject": "TIM Fashion Mensal", "success": true},
{"id": "1002", "subject": "Aya Audiobooks Premium", "success": false, "reason": "not_found"},
{"id": "1003", "subject": "Neymar Jr", "success": true}
]
}
}
```
Campos como `id`, `subject`, `name`, `reason`, `error`, `protocol` e outros são de domínio e opcionais. O sinal estrutural exigido pelo normalizador é `success: true|false` ou `ok: true|false` por item.
### Workflow-backed tool
Se a tool é executada por workflow, o caminho preferencial e autoritativo é:
```text
workflow.output[tool_name].results
```
Exemplo:
```json
{
"ok": false,
"result": {
"status": "COMPLETED",
"output": {
"cancelar_produtos": {
"results": [
{"subject": "A", "success": true},
{"subject": "B", "success": false, "reason": "not_found"},
{"subject": "C", "success": true}
]
},
"etapa_auxiliar": {
"success": false,
"error": "falha em pós-processamento"
}
}
},
"error": "falha em pós-processamento"
}
```
O runtime preserva o resultado da operação primária e não permite que uma falha auxiliar apague itens já concluídos.
## 5. O que o framework produz automaticamente
Com `results[]` válido, o runtime adiciona:
```json
{
"multi_item_status": "PARTIAL_SUCCESS",
"partial_success": true,
"metadata": {
"multi_item": {
"status": "PARTIAL_SUCCESS",
"items_count": 3,
"items_succeeded_count": 2,
"items_failed_count": 1,
"primary_tool": "cancelar_produtos"
}
}
}
```
Estados possíveis:
| Situação | `multi_item_status` |
|---|---|
| todos os itens concluíram | `SUCCESS` |
| parte concluiu e parte falhou | `PARTIAL_SUCCESS` |
| todos falharam | `FAILED` |
Se uma etapa posterior fizer o wrapper retornar `ok=false`, mas pelo menos um item primário tiver sucesso, o framework preserva para auditoria:
```json
{
"ok": true,
"original_ok": false,
"secondary_error": "erro da etapa posterior"
}
```
Isso evita transformar sucesso parcial/real em falha total na resposta ao usuário.
## 6. O que NÃO fazer
Evite estes padrões:
```python
# NÃO: motor multi-item paralelo dentro do agente
for product in products:
await tool_router.call("cancelar_produto", {"product": product})
```
```json
// NÃO: apenas um approved global sem evidência por item
{
"approved": false,
"error": "um item falhou"
}
```
```json
// NÃO: um único success agregado quando houve resultados mistos
{
"success": false,
"message": "operação bloqueada"
}
```
Esses formatos perdem a evidência terminal individual e podem fazer itens concluídos desaparecerem da composição final.
## 7. Exemplo completo: vários pedidos
### `tools.yaml`
```yaml
tools:
cancelar_pedidos:
description: Cancela um ou mais pedidos.
mcp_server: retail
enabled: true
tool_type: action
confirmation_required: true
requires: [order_ids]
args_schema:
order_ids:
type: array
label: os pedidos
description: Lista de identificadores de pedidos a cancelar.
user_prompt: Quais pedidos deseja cancelar?
```
### Retorno MCP
```json
{
"ok": true,
"result": {
"results": [
{"order_id": "P-100", "success": true, "protocol": "ABC1"},
{"order_id": "P-101", "success": false, "reason": "already_shipped"},
{"order_id": "P-102", "success": true, "protocol": "ABC3"}
]
}
}
```
O framework classificará automaticamente como `PARTIAL_SUCCESS` e fornecerá ao LLM a instrução de relatar os resultados item a item.
## 8. Read-only multi-item
A mesma estrutura serve para consultas em lote. A diferença é que normalmente não existe confirmação:
```yaml
tool_policies:
consultar_pedidos:
operation_type: read_only
require_confirmation: false
```
A tool ainda pode retornar `results[]` com `success/ok` por item para que uma consulta parcialmente indisponível não esconda as consultas bem-sucedidas.
## 9. Idempotência e protocolos
Em operações com efeito colateral:
- use uma chave de idempotência de requisição e, quando necessário, uma chave por item;
- devolva protocolo/operation id no próprio item;
- não dependa apenas de uma mensagem textual agregada;
- em retry, preserve os itens já concluídos e execute apenas o que a regra de domínio permitir.
O framework preserva evidência; a política de idempotência do sistema de destino continua pertencendo ao MCP/workflow de domínio.
## 10. Checklist para o desenvolvedor
- [ ] A tool aceita `array/list` quando o caso de uso é multi-item.
- [ ] Não existe loop MCP de negócio dentro da classe do agente.
- [ ] Se houver canonicalização, ela está em pre-validation/MCP, não hardcoded no agente.
- [ ] A confirmação mostra o conjunto efetivo de itens.
- [ ] A tool primária retorna `results[]` com `success` ou `ok` por item.
- [ ] Resultado parcial não é reduzido a um único `approved=false` ou `success=false` global.
- [ ] Workflow usa `output[tool_name].results` para a evidência terminal da tool primária.
- [ ] Testes cobrem todos-sucesso, parcial, todos-falha e falha auxiliar posterior.
- [ ] A resposta final menciona sucessos e falhas quando houver `PARTIAL_SUCCESS`.
## 11. Onde o comportamento existe no framework
Referências atuais:
```text
libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py
- coerção de type=array/list
libs/agent_framework/src/agent_framework/runtime/agent_runtime.py
- pre-validation e resolved_arguments
- _primary_multi_item_outcomes()
- _normalize_multi_item_tool_result()
- instrução de composição multi-item para o LLM
tests/test_multi_item_result_normalization.py
- regressões de SUCCESS/PARTIAL_SUCCESS e proteção contra falha auxiliar
```
## 12. Resumo de responsabilidade
| Camada | Responsabilidade |
|---|---|
| Agente | declarar intenção/tool e usar o runtime; não implementar motor multi-item |
| `tools.yaml` | declarar parâmetro `array/list` e schema |
| `tool_policies.yaml` | confirmação e pre-validation |
| Pre-validator MCP | resolver/canonicalizar entidades e argumentos |
| Tool MCP/workflow | executar a coleção e produzir resultado por item |
| Framework runtime | manter estado, confirmação, normalizar resultado multi-item e preservar evidência |
| LLM/presentation | transformar evidências em resposta, sem apagar sucesso/falha individual |

View File

@@ -87,3 +87,8 @@ Esses arquivos são o esqueleto de execução usando o framework.
# Política opcional de tools
O arquivo `config/tool_policies.yaml` classifica tools como `read_only` ou `transactional`. Para uma transação real, ative `require_confirmation: true`; chamadas sem `confirmed: true` ou `confirmation: true` serão bloqueadas antes do MCP. A ausência do arquivo preserva o comportamento de templates anteriores.
## MCP multi-item
Para listas de produtos/pedidos/itens, não crie loop MCP no agente. O runtime aceita parâmetros `array/list` e normaliza `results[]` por item em `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`. Consulte [`docs/MCP_MULTI_ITEM.md`](docs/MCP_MULTI_ITEM.md) antes de criar qualquer mecanismo próprio de lote.

View File

@@ -0,0 +1,363 @@
# MCP Multi-item — Guia do Desenvolvedor
## Objetivo
Use este guia quando uma única intenção do usuário precisa operar sobre **mais de um item do mesmo tipo** em uma chamada MCP, por exemplo:
- cancelar vários VAS/produtos;
- devolver ou consultar vários pedidos;
- alterar vários ativos;
- processar uma lista de faturas, linhas, contratos ou recursos.
O framework já possui contratos genéricos para **coleta de listas**, **pré-validação/canonicalização**, **confirmação transacional**, **execução MCP** e **normalização do resultado por item**. O desenvolvedor não deve criar um segundo motor de multi-item dentro do agente.
## Regra principal
**Não faça loop de negócio no agente.** O agente declara a tool e seus parâmetros; o runtime mantém estado, confirmação e evidência. A operação em lote deve ser exposta como uma tool MCP que aceite uma coleção, ou como uma tool transacional apoiada por um workflow/MCP que processe a coleção.
Fluxo recomendado:
```text
Usuário cita N itens
-> Router/Agent seleciona a tool
-> Runtime coleta/reconcilia parâmetro array/list
-> (opcional) pre-validation canonicaliza itens
-> Runtime pede UMA confirmação coerente para o conjunto
-> MCP recebe a coleção
-> MCP/workflow processa item a item
-> MCP devolve results[] com success/ok por item
-> Framework normaliza SUCCESS / PARTIAL_SUCCESS / FAILED
-> Composição final relata sucessos e falhas individualmente
```
## 1. Declarar um parâmetro de lista em `tools.yaml`
O reconciliador transacional do framework aceita `type: array` ou `type: list`.
```yaml
tools:
cancelar_produtos:
description: Cancela um ou mais produtos do cliente.
mcp_server: contas
enabled: true
tool_type: action
confirmation_required: true
requires: [items]
args_schema:
items:
type: array
label: os produtos
description: Lista de produtos que o cliente deseja cancelar.
user_prompt: Informe quais produtos deseja cancelar.
```
O runtime pode reconciliar uma frase como:
```text
quero cancelar TIM Fashion, Aya Audiobooks e Neymar Jr
```
para um argumento lógico semelhante a:
```json
{
"items": ["TIM Fashion", "Aya Audiobooks", "Neymar Jr"]
}
```
A semântica de domínio e a validação dos nomes continuam pertencendo ao MCP/pre-validator, não ao core.
## 2. Quando usar pre-validation
Use `tool_policies.yaml` quando a lista precisa ser resolvida/canonicalizada antes da confirmação.
```yaml
tool_policies:
cancelar_produtos:
operation_type: transactional
require_confirmation: true
requires: [items]
pre_validation:
enabled: true
tool: validar_produtos_cancelamento
fail_open: false
```
O pre-validator pode retornar `eligible: true` e substituir argumentos por valores canônicos em `transaction_decision.resolved_arguments`:
```json
{
"eligible": true,
"transaction_decision": {
"resolved_arguments": {
"items": [
{"name": "TIM Fashion Mensal", "id": "1001"},
{"name": "Aya Audiobooks Premium", "id": "1002"},
{"name": "Neymar Jr", "id": "1003"}
]
},
"target_tool": "cancelar_produtos",
"confirmation_message": "Você confirma o cancelamento de TIM Fashion Mensal, Aya Audiobooks Premium e Neymar Jr?"
}
}
```
### Importante para validação parcial
O contrato `eligible` da pre-validation é global. Portanto, **não retorne `eligible: false` apenas porque um item da lista falhou**, caso os demais ainda possam ser processados. Isso encerraria toda a transação.
Para permitir resultado parcial, a estratégia recomendada é:
1. retornar `eligible: true` quando existe pelo menos um item acionável;
2. preservar/canonicalizar a coleção em `resolved_arguments`;
3. deixar a tool primária devolver o resultado terminal de **cada item** em `results[]`.
Se nenhum item puder ser processado, então `eligible: false` é apropriado.
## 3. Contrato de entrada do MCP
Prefira uma única chamada MCP com uma coleção explícita:
```json
{
"tool_name": "cancelar_produtos",
"arguments": {
"customer_id": "123",
"items": [
{"name": "TIM Fashion Mensal", "id": "1001"},
{"name": "Aya Audiobooks Premium", "id": "1002"},
{"name": "Neymar Jr", "id": "1003"}
]
}
}
```
O framework **não deve ser usado como um loop improvisado que chama N vezes uma tool escalar a partir do agente**. Se o backend só possui API unitária, o fan-out pode ficar encapsulado no MCP Server ou em um workflow de domínio, mantendo uma única tool lógica para o agente.
## 4. Contrato obrigatório de resultado por item
Para o framework reconhecer multi-item automaticamente, a **tool primária solicitada** deve expor uma lista `results` com **dois ou mais itens**, e cada item deve possuir um booleano `success` ou `ok`.
Exemplo:
```json
{
"ok": true,
"result": {
"results": [
{"id": "1001", "subject": "TIM Fashion Mensal", "success": true},
{"id": "1002", "subject": "Aya Audiobooks Premium", "success": false, "reason": "not_found"},
{"id": "1003", "subject": "Neymar Jr", "success": true}
]
}
}
```
Campos como `id`, `subject`, `name`, `reason`, `error`, `protocol` e outros são de domínio e opcionais. O sinal estrutural exigido pelo normalizador é `success: true|false` ou `ok: true|false` por item.
### Workflow-backed tool
Se a tool é executada por workflow, o caminho preferencial e autoritativo é:
```text
workflow.output[tool_name].results
```
Exemplo:
```json
{
"ok": false,
"result": {
"status": "COMPLETED",
"output": {
"cancelar_produtos": {
"results": [
{"subject": "A", "success": true},
{"subject": "B", "success": false, "reason": "not_found"},
{"subject": "C", "success": true}
]
},
"etapa_auxiliar": {
"success": false,
"error": "falha em pós-processamento"
}
}
},
"error": "falha em pós-processamento"
}
```
O runtime preserva o resultado da operação primária e não permite que uma falha auxiliar apague itens já concluídos.
## 5. O que o framework produz automaticamente
Com `results[]` válido, o runtime adiciona:
```json
{
"multi_item_status": "PARTIAL_SUCCESS",
"partial_success": true,
"metadata": {
"multi_item": {
"status": "PARTIAL_SUCCESS",
"items_count": 3,
"items_succeeded_count": 2,
"items_failed_count": 1,
"primary_tool": "cancelar_produtos"
}
}
}
```
Estados possíveis:
| Situação | `multi_item_status` |
|---|---|
| todos os itens concluíram | `SUCCESS` |
| parte concluiu e parte falhou | `PARTIAL_SUCCESS` |
| todos falharam | `FAILED` |
Se uma etapa posterior fizer o wrapper retornar `ok=false`, mas pelo menos um item primário tiver sucesso, o framework preserva para auditoria:
```json
{
"ok": true,
"original_ok": false,
"secondary_error": "erro da etapa posterior"
}
```
Isso evita transformar sucesso parcial/real em falha total na resposta ao usuário.
## 6. O que NÃO fazer
Evite estes padrões:
```python
# NÃO: motor multi-item paralelo dentro do agente
for product in products:
await tool_router.call("cancelar_produto", {"product": product})
```
```json
// NÃO: apenas um approved global sem evidência por item
{
"approved": false,
"error": "um item falhou"
}
```
```json
// NÃO: um único success agregado quando houve resultados mistos
{
"success": false,
"message": "operação bloqueada"
}
```
Esses formatos perdem a evidência terminal individual e podem fazer itens concluídos desaparecerem da composição final.
## 7. Exemplo completo: vários pedidos
### `tools.yaml`
```yaml
tools:
cancelar_pedidos:
description: Cancela um ou mais pedidos.
mcp_server: retail
enabled: true
tool_type: action
confirmation_required: true
requires: [order_ids]
args_schema:
order_ids:
type: array
label: os pedidos
description: Lista de identificadores de pedidos a cancelar.
user_prompt: Quais pedidos deseja cancelar?
```
### Retorno MCP
```json
{
"ok": true,
"result": {
"results": [
{"order_id": "P-100", "success": true, "protocol": "ABC1"},
{"order_id": "P-101", "success": false, "reason": "already_shipped"},
{"order_id": "P-102", "success": true, "protocol": "ABC3"}
]
}
}
```
O framework classificará automaticamente como `PARTIAL_SUCCESS` e fornecerá ao LLM a instrução de relatar os resultados item a item.
## 8. Read-only multi-item
A mesma estrutura serve para consultas em lote. A diferença é que normalmente não existe confirmação:
```yaml
tool_policies:
consultar_pedidos:
operation_type: read_only
require_confirmation: false
```
A tool ainda pode retornar `results[]` com `success/ok` por item para que uma consulta parcialmente indisponível não esconda as consultas bem-sucedidas.
## 9. Idempotência e protocolos
Em operações com efeito colateral:
- use uma chave de idempotência de requisição e, quando necessário, uma chave por item;
- devolva protocolo/operation id no próprio item;
- não dependa apenas de uma mensagem textual agregada;
- em retry, preserve os itens já concluídos e execute apenas o que a regra de domínio permitir.
O framework preserva evidência; a política de idempotência do sistema de destino continua pertencendo ao MCP/workflow de domínio.
## 10. Checklist para o desenvolvedor
- [ ] A tool aceita `array/list` quando o caso de uso é multi-item.
- [ ] Não existe loop MCP de negócio dentro da classe do agente.
- [ ] Se houver canonicalização, ela está em pre-validation/MCP, não hardcoded no agente.
- [ ] A confirmação mostra o conjunto efetivo de itens.
- [ ] A tool primária retorna `results[]` com `success` ou `ok` por item.
- [ ] Resultado parcial não é reduzido a um único `approved=false` ou `success=false` global.
- [ ] Workflow usa `output[tool_name].results` para a evidência terminal da tool primária.
- [ ] Testes cobrem todos-sucesso, parcial, todos-falha e falha auxiliar posterior.
- [ ] A resposta final menciona sucessos e falhas quando houver `PARTIAL_SUCCESS`.
## 11. Onde o comportamento existe no framework
Referências atuais:
```text
libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py
- coerção de type=array/list
libs/agent_framework/src/agent_framework/runtime/agent_runtime.py
- pre-validation e resolved_arguments
- _primary_multi_item_outcomes()
- _normalize_multi_item_tool_result()
- instrução de composição multi-item para o LLM
tests/test_multi_item_result_normalization.py
- regressões de SUCCESS/PARTIAL_SUCCESS e proteção contra falha auxiliar
```
## 12. Resumo de responsabilidade
| Camada | Responsabilidade |
|---|---|
| Agente | declarar intenção/tool e usar o runtime; não implementar motor multi-item |
| `tools.yaml` | declarar parâmetro `array/list` e schema |
| `tool_policies.yaml` | confirmação e pre-validation |
| Pre-validator MCP | resolver/canonicalizar entidades e argumentos |
| Tool MCP/workflow | executar a coleção e produzir resultado por item |
| Framework runtime | manter estado, confirmação, normalizar resultado multi-item e preservar evidência |
| LLM/presentation | transformar evidências em resposta, sem apagar sucesso/falha individual |