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

@@ -77,3 +77,12 @@ No compose, o backend usa `config/mcp_servers.docker.yaml` para apontar para `te
## Operações read-only e transacionais ## 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). 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).
## Chamadas MCP multi-item
Para operações com listas de produtos, pedidos, ativos ou outros recursos, **não implemente loops de chamadas MCP dentro do agente**. O framework possui contrato genérico para parâmetros `array/list`, pre-validation, confirmação e resultado por item (`SUCCESS`, `PARTIAL_SUCCESS`, `FAILED`).
Guia completo: [MCP Multi-item — Guia do Desenvolvedor](../docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md).
A tool primária deve retornar `results[]` com `success` ou `ok` booleano por item. Em workflows, use preferencialmente `output[tool_name].results`.

View File

@@ -0,0 +1,236 @@
# Implementação prática — MCP Multi-Item
Este documento mostra o caminho mínimo para adicionar uma operação multi-item a um agente sem criar lógica paralela no código do agente.
## 1. Modele uma única operação lógica
Exemplo de requisito:
```text
quero cancelar Produto A, Produto B e Produto C
```
A operação lógica deve continuar sendo uma só:
```text
cancelar_produtos
```
Não crie `cancelar_produto_1`, `cancelar_produto_2`, loops no agent ou intents artificiais por item.
## 2. Declare a coleção em `tools.yaml`
```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 a cancelar.
user_prompt: Quais produtos deseja cancelar?
```
Também é aceito `type: list` quando o projeto já usa essa forma.
## 3. Use a policy transacional existente
```yaml
tool_policies:
cancelar_produtos:
operation_type: transactional
require_confirmation: true
requires: [items]
pre_validation:
enabled: true
tool: validar_produtos_cancelamento
fail_open: false
```
Nenhuma chave específica de multi-item é necessária.
## 4. Canonicalize os itens no MCP validator
Entrada possível:
```json
{
"items": ["produto a", "produto b", "produto c"]
}
```
Saída recomendada:
```json
{
"eligible": true,
"status": "ELIGIBLE",
"transaction_decision": {
"resolved_arguments": {
"items": [
{"name": "Produto A", "id": "1001"},
{"name": "Produto B", "id": "1002"},
{"name": "Produto C", "id": "1003"}
]
},
"target_tool": "cancelar_produtos",
"confirmation_message": "Você confirma o cancelamento de Produto A, Produto B e Produto C?"
}
}
```
O validator deve ser side-effect-free.
## 5. Não transforme uma falha individual em rejeição global
Caso:
```text
A = válido
B = inválido
C = válido
```
Se A e C ainda podem ser executados, não use simplesmente:
```json
{
"eligible": false,
"reason": "B inválido"
}
```
Isso encerraria a transação inteira.
Prefira manter a transação elegível quando houver item acionável e preservar informação suficiente para a tool primária produzir o resultado final por item.
## 6. Execute a coleção no MCP Server ou workflow
### Backend com API batch
```python
async def cancelar_produtos(items):
response = await backend.cancel_batch(items)
return normalize_results(response)
```
### Backend com API apenas unitária
```python
async def cancelar_produtos(items):
results = []
for item in items:
try:
response = await backend.cancel_one(item)
results.append({
"name": item["name"],
"success": True,
"response": response,
})
except Exception as exc:
results.append({
"name": item["name"],
"success": False,
"error": str(exc),
})
return {"results": results}
```
O loop acima pertence ao MCP/workflow, **não ao Agent**.
## 7. Retorne `results[]` por item
Contrato mínimo:
```json
{
"results": [
{"name": "Produto A", "success": true},
{"name": "Produto B", "success": false, "error": "not_found"},
{"name": "Produto C", "success": true}
]
}
```
O framework aceita `success` ou `ok` booleano por item.
## 8. Resultado interpretado pelo framework
No exemplo anterior:
```text
items_count = 3
items_succeeded_count = 2
items_failed_count = 1
multi_item_status = PARTIAL_SUCCESS
```
O framework preserva os resultados terminais e fornece esse contexto para composição/presentation.
## 9. Workflow com etapa auxiliar
Estrutura recomendada:
```json
{
"status": "COMPLETED",
"output": {
"cancelar_produtos": {
"results": [
{"name": "A", "success": true},
{"name": "B", "success": true}
]
},
"registrar_analitica": {
"success": false,
"error": "analytics unavailable"
}
}
}
```
O resultado autoritativo da operação principal deve estar em:
```text
workflow.output[cancelar_produtos].results
```
Assim uma falha auxiliar não converte A e B em falha operacional.
## 10. Checklist do desenvolvedor
Antes de criar código adicional, verifique:
- [ ] existe uma única tool lógica para a operação;
- [ ] a coleção está declarada como `array` ou `list` em `tools.yaml`;
- [ ] confirmação/pre-validation usam `tool_policies.yaml` existente;
- [ ] canonicalização pertence ao MCP validator;
- [ ] fan-out, se necessário, pertence ao MCP Server/workflow;
- [ ] a tool primária devolve `results[]`;
- [ ] cada item contém `success` ou `ok` booleano;
- [ ] sucessos parciais não são apagados por uma falha global posterior;
- [ ] a apresentação comunica sucesso/falha por item;
- [ ] não foi criado um segundo motor multi-item no Agent.
## 11. Testes mínimos recomendados
Cubra ao menos:
```text
1 item / sucesso
N itens / todos sucesso
N itens / sucesso parcial
N itens / todos falha
item não canonicalizado
confirmação única para N itens
falha auxiliar após sucesso primário
replay/idempotência da mesma operação
```
Para detalhes do contrato interno do runtime, consulte `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md`.

View File

@@ -0,0 +1,229 @@
# Multi-Item MCP
Capability de referência para operações MCP que precisam processar **mais de um item do mesmo tipo** em uma única intenção: produtos, pedidos, VAS, ativos, faturas, linhas, contratos ou recursos.
> Esta capability é **framework-native**. Ela não exige um motor multi-item dentro do agente e não adiciona novos atributos obrigatórios a `tools.yaml` ou `tool_policies.yaml`.
## Quando usar
Use quando o usuário puder solicitar, por exemplo:
```text
cancele Produto A, Produto B e Produto C
```
ou:
```text
consulte os pedidos 1001, 1002 e 1003
```
O desenvolvedor deve modelar **uma operação lógica** que receba a coleção. O framework cuida de estado, confirmação e interpretação de resultados; o MCP Server ou workflow de domínio cuida da execução dos itens.
## Divisão de responsabilidades
```text
Agent
seleciona a operação
NÃO cria motor multi-item
↓
tools.yaml
declara array/list
NÃO exige chave multi_item
↓
tool_policies.yaml
confirmação + pre-validation
usa o mesmo contrato transacional existente
↓
MCP validator
resolve/canonicaliza os itens
pode preencher transaction_decision.resolved_arguments
↓
MCP tool / workflow
executa os itens
devolve results[] com success/ok por item
↓
Agent Framework
mantém estado
controla confirmação
interpreta results[]
preserva evidência por item
calcula SUCCESS / PARTIAL_SUCCESS / FAILED
↓
LLM / Presentation
apresenta cada sucesso/falha
```
## Configuração mínima
### `tools.yaml`
O modo de configuração continua o mesmo. Declare o parâmetro como `array` ou `list`:
```yaml
tools:
cancelar_produtos:
description: Cancela um ou mais produtos.
mcp_server: contas
enabled: true
tool_type: action
confirmation_required: true
requires: [items]
args_schema:
items:
type: array
label: os produtos
description: Produtos solicitados pelo cliente.
user_prompt: Informe quais produtos deseja cancelar.
```
Não é necessário criar configuração como:
```yaml
# NÃO EXISTE / NÃO É NECESSÁRIO
multi_item: true
multi_item_strategy: partial
fan_out: framework
```
### `tool_policies.yaml`
O contrato também continua o mesmo:
```yaml
tool_policies:
cancelar_produtos:
operation_type: transactional
require_confirmation: true
requires: [items]
pre_validation:
enabled: true
tool: validar_produtos_cancelamento
fail_open: false
```
## Pre-validation e lista canônica
O validator pode transformar nomes informais em uma coleção canônica antes da confirmação:
```json
{
"eligible": true,
"transaction_decision": {
"resolved_arguments": {
"items": [
{"name": "Produto A", "id": "1001"},
{"name": "Produto B", "id": "1002"},
{"name": "Produto C", "id": "1003"}
]
},
"target_tool": "cancelar_produtos",
"confirmation_message": "Você confirma o cancelamento de Produto A, Produto B e Produto C?"
}
}
```
### Validação parcial
`eligible` é global para a transação. Portanto, se pelo menos um item puder ser processado, o validator **não deve reprovar o lote inteiro apenas porque outro item falhou**.
Padrão recomendado:
1. `eligible: true` quando existe pelo menos um item acionável;
2. preservar/canonicalizar todos os itens necessários em `resolved_arguments`;
3. executar a operação primária;
4. retornar o resultado terminal individual em `results[]`.
Se nenhum item puder ser processado, `eligible: false` é apropriado.
## Resultado MCP esperado
A tool primária deve retornar `results[]` com `success` ou `ok` booleano por item:
```json
{
"results": [
{"name": "Produto A", "success": true},
{"name": "Produto B", "success": false, "error": "not_found"},
{"name": "Produto C", "success": true}
]
}
```
O framework deriva automaticamente:
```text
PARTIAL_SUCCESS
2 succeeded
1 failed
```
Estados possíveis:
| Resultado dos itens | Status do framework |
|---|---|
| todos concluídos | `SUCCESS` |
| alguns concluídos e alguns falharam | `PARTIAL_SUCCESS` |
| todos falharam | `FAILED` |
## Uma chamada lógica, não N chamadas no agente
Se o backend suporta batch, prefira:
```text
Agent -> cancelar_produtos(items=[A,B,C]) -> MCP
```
Se o backend possui apenas API unitária, o fan-out deve ficar encapsulado no MCP Server ou no workflow de domínio:
```text
Agent -> cancelar_produtos(items=[A,B,C])
|
v
MCP/workflow
/ | \
A B C
\ | /
results[]
```
Evite:
```python
# NÃO: lógica multi-item dentro do agente
for item in items:
await tool_router.call("cancelar_produto", {"item": item})
```
## Workflow-backed tools
Quando a operação é implementada por workflow, o caminho preferencial do resultado primário é:
```text
workflow.output[tool_name].results
```
Isso permite distinguir a operação primária de etapas auxiliares. Uma falha posterior não deve apagar o sucesso terminal de itens já executados.
## Documentação completa
Consulte também:
- `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md` — guia canônico em português;
- `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md` — guia canônico em inglês;
- `templates/agent_template_backend/docs/MCP_MULTI_ITEM.md` — referência junto ao template;
- `specs/SPEC-010-Agent-Development.md` — regra arquitetural de desenvolvimento.
Para um passo a passo prático, leia `IMPLEMENTACAO_MULTI_ITEM_MCP.md` nesta pasta.

View File

@@ -0,0 +1,69 @@
# Multi-Item MCP
Reference capability for MCP operations that process **multiple items of the same kind** in one user intent: products, orders, services, assets, invoices, lines, contracts, or resources.
This capability is **framework-native**. It does not require a multi-item engine in the agent and does not introduce new mandatory keys in `tools.yaml` or `tool_policies.yaml`.
```text
Agent selects the operation; no fan-out engine
-> tools.yaml declares array/list
-> tool_policies.yaml confirmation + pre-validation
-> MCP validator resolves/canonicalizes items
-> MCP tool/workflow executes items and returns results[]
-> Agent Framework preserves per-item evidence and derives
SUCCESS / PARTIAL_SUCCESS / FAILED
-> Presentation reports each success/failure
```
## Minimal configuration
`tools.yaml` keeps the existing list contract:
```yaml
tools:
cancel_products:
requires: [items]
args_schema:
items:
type: array
```
`tool_policies.yaml` also keeps the current transaction contract:
```yaml
tool_policies:
cancel_products:
operation_type: transactional
require_confirmation: true
requires: [items]
pre_validation:
enabled: true
tool: validate_products_for_cancellation
fail_open: false
```
No `multi_item: true`, framework fan-out flag, or extra agent code is required.
## MCP result contract
The primary tool should return `results[]` with boolean `success` or `ok` per item:
```json
{
"results": [
{"name": "A", "success": true},
{"name": "B", "success": false, "error": "not_found"},
{"name": "C", "success": true}
]
}
```
The framework derives `PARTIAL_SUCCESS` for the example above and preserves the successful items.
If the downstream system only supports single-item APIs, put the fan-out inside the MCP Server or domain workflow, not inside the agent.
For the complete contract see:
- `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md`
- `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md`
- `IMPLEMENTACAO_MULTI_ITEM_MCP.md`

View File

@@ -8,3 +8,4 @@ Variantes e documentos de referência para comparar funcionalidades e impacto de
- `Deterministic_Transactional_Workflow`: transações multi-etapas executadas por workflow LangGraph determinístico após clarification e confirmação. - `Deterministic_Transactional_Workflow`: transações multi-etapas executadas por workflow LangGraph determinístico após clarification e confirmação.
- `Transaction_Evidence`: persistência e correlação de resultados transacionais como evidência operacional para turnos posteriores e groundedness. - `Transaction_Evidence`: persistência e correlação de resultados transacionais como evidência operacional para turnos posteriores e groundedness.
- `Transaction_Pre_Validation`: pré-validação MCP side-effect-free antes da confirmação transacional; regras de elegibilidade permanecem no domínio. - `Transaction_Pre_Validation`: pré-validação MCP side-effect-free antes da confirmação transacional; regras de elegibilidade permanecem no domínio.
- `Multi_Item_MCP`: operações MCP sobre listas de produtos/pedidos/serviços/ativos com confirmação única, canonicalização e resultado por item (`SUCCESS` / `PARTIAL_SUCCESS` / `FAILED`), sem criar motor multi-item no agente.

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

@@ -0,0 +1,252 @@
# MCP Multi-item — Developer Guide
## Purpose
Use this guide when a single user intent must operate on **more than one item of the same kind** through MCP, such as multiple products, orders, assets, invoices, lines, contracts, or resources.
The framework already provides generic contracts for **list parameter collection**, **pre-validation/canonicalization**, **transaction confirmation**, **MCP execution**, and **per-item result normalization**. Do not create a second multi-item engine inside an agent.
## Main rule
**Do not implement business fan-out loops in the agent.** The agent declares the tool and parameters; the runtime owns state, confirmation, and evidence. A bulk operation should be exposed as an MCP tool that accepts a collection, or as a transactional tool backed by a domain MCP/workflow that processes the collection.
Recommended flow:
```text
User names N items
-> Router/Agent selects the tool
-> Runtime collects/reconciles an array/list parameter
-> optional pre-validation canonicalizes items
-> Runtime asks ONE coherent confirmation for the set
-> MCP receives the collection
-> MCP/workflow processes each item
-> MCP returns results[] with success/ok per item
-> Framework normalizes SUCCESS / PARTIAL_SUCCESS / FAILED
-> Final composition reports successes and failures individually
```
## 1. Declare a list parameter in `tools.yaml`
The transaction parameter reconciler accepts `type: array` and `type: list`.
```yaml
tools:
cancel_products:
description: Cancels one or more customer products.
mcp_server: accounts
enabled: true
tool_type: action
confirmation_required: true
requires: [items]
args_schema:
items:
type: array
label: the products
description: List of products the customer wants to cancel.
user_prompt: Which products do you want to cancel?
```
A phrase naming several products can therefore be reconciled into a logical list argument. Domain semantics and canonical name validation remain in MCP/pre-validation, not in the core.
## 2. Use pre-validation for canonicalization
Configure `tool_policies.yaml` when the list must be resolved before confirmation:
```yaml
tool_policies:
cancel_products:
operation_type: transactional
require_confirmation: true
requires: [items]
pre_validation:
enabled: true
tool: validate_products_for_cancellation
fail_open: false
```
A validator may return `eligible: true` and replace the raw list through `transaction_decision.resolved_arguments`:
```json
{
"eligible": true,
"transaction_decision": {
"resolved_arguments": {
"items": [
{"name": "Product A", "id": "1001"},
{"name": "Product B", "id": "1002"}
]
},
"target_tool": "cancel_products",
"confirmation_message": "Do you confirm cancellation of Product A and Product B?"
}
}
```
### Partial validation
The pre-validation `eligible` flag is global. **Do not return `eligible: false` only because one list item failed** when other items can still run; that would terminate the entire transaction.
For partial outcomes, the recommended pattern is:
1. return `eligible: true` when at least one item is actionable;
2. canonicalize/preserve the collection in `resolved_arguments`;
3. let the primary tool publish terminal status for **every item** in `results[]`.
If no item can be processed, `eligible: false` is appropriate.
## 3. MCP input contract
Prefer one logical MCP call containing the collection:
```json
{
"tool_name": "cancel_products",
"arguments": {
"customer_id": "123",
"items": [
{"name": "Product A", "id": "1001"},
{"name": "Product B", "id": "1002"}
]
}
}
```
Do not use the agent as an improvised loop that invokes a scalar tool N times. If the downstream backend only offers a single-item API, encapsulate fan-out in the MCP Server or domain workflow while keeping one logical tool for the agent.
## 4. Required per-item result contract
For automatic multi-item recognition, the **requested primary tool** must expose a `results` list with **two or more entries**, and every entry must contain boolean `success` or `ok`.
```json
{
"ok": true,
"result": {
"results": [
{"id": "1001", "subject": "Product A", "success": true},
{"id": "1002", "subject": "Product B", "success": false, "reason": "not_found"},
{"id": "1003", "subject": "Product C", "success": true}
]
}
}
```
Fields such as `id`, `subject`, `name`, `reason`, `error`, and `protocol` are domain-specific and optional. The structural signal required by the normalizer is per-item `success: true|false` or `ok: true|false`.
### Workflow-backed tools
For workflow-backed execution, the preferred authoritative location is:
```text
workflow.output[tool_name].results
```
This prevents an auxiliary/post-processing failure from overwriting terminal outcomes of the requested primary operation.
## 5. What the runtime adds automatically
With a valid `results[]`, the runtime derives:
```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": "cancel_products"
}
}
}
```
| Situation | `multi_item_status` |
|---|---|
| all items succeeded | `SUCCESS` |
| mixed success/failure | `PARTIAL_SUCCESS` |
| all items failed | `FAILED` |
If a later step makes the outer wrapper `ok=false` while at least one primary item succeeded, the framework preserves audit information as `original_ok=false` and `secondary_error`, while keeping the executed item outcomes authoritative.
## 6. Anti-patterns
Do not implement a business loop in the agent:
```python
for product in products:
await tool_router.call("cancel_product", {"product": product})
```
Do not return only a global decision such as:
```json
{"approved": false, "error": "one item failed"}
```
or a single aggregate `success=false` when the actual outcomes are mixed. These formats lose per-item terminal evidence.
## 7. Read-only multi-item calls
The same result contract can be used for bulk queries. Usually only the policy changes:
```yaml
tool_policies:
query_orders:
operation_type: read_only
require_confirmation: false
```
Returning `results[]` with `success/ok` per item ensures one unavailable item does not hide successful queries.
## 8. Idempotency and protocols
For side-effecting operations:
- use a request idempotency key and, when necessary, an item-level key;
- return protocol/operation id on the item itself;
- do not rely only on an aggregate text message;
- on retry, preserve completed items and execute only what domain policy allows.
The runtime preserves evidence; downstream idempotency remains the responsibility of the domain MCP/workflow.
## 9. Developer checklist
- [ ] The tool accepts `array/list` for a multi-item use case.
- [ ] There is no business MCP loop inside the agent class.
- [ ] Canonicalization belongs to pre-validation/MCP, not hardcoded agent logic.
- [ ] Confirmation describes the effective item set.
- [ ] The primary tool returns `results[]` with per-item `success` or `ok`.
- [ ] Partial outcome is not collapsed into a single global failure flag.
- [ ] Workflow-backed tools use `output[tool_name].results` for primary terminal evidence.
- [ ] Tests cover all-success, partial, all-failure, and later auxiliary failure.
- [ ] Final response reports both successes and failures for `PARTIAL_SUCCESS`.
## 10. Framework implementation references
```text
libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py
- array/list coercion
libs/agent_framework/src/agent_framework/runtime/agent_runtime.py
- pre-validation and resolved_arguments
- _primary_multi_item_outcomes()
- _normalize_multi_item_tool_result()
- multi-item LLM composition instruction
tests/test_multi_item_result_normalization.py
- SUCCESS/PARTIAL_SUCCESS regressions and auxiliary-failure protection
```
## 11. Responsibility summary
| Layer | Responsibility |
|---|---|
| Agent | select/declaratively use the tool; do not implement a multi-item engine |
| `tools.yaml` | declare `array/list` parameter and schema |
| `tool_policies.yaml` | confirmation and pre-validation |
| MCP pre-validator | resolve/canonicalize entities and arguments |
| MCP tool/workflow | execute the collection and return per-item outcomes |
| Framework runtime | state, confirmation, multi-item normalization, evidence preservation |
| LLM/presentation | verbalize evidence without hiding per-item success/failure |

View File

@@ -82,6 +82,31 @@ projeto_multi_agent_isolado/
### Main components ### 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 `array/list` parameters, pre-validation with `resolved_arguments`, set-level transaction confirmation, and automatic normalization of per-item `results[]` into `SUCCESS`, `PARTIAL_SUCCESS`, or `FAILED`.
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 ### Simplified HTTP contract used by the project
``` ```

View File

@@ -27,6 +27,7 @@ If something is not working, use **Search by problem** below.
| 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) | | 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) | | 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) | | 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 | array/list parameter, 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 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) | | 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 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) |
@@ -73,9 +74,9 @@ If something is not working, use **Search by problem** below.
### [04 — MCP, Tools, Policies, and Parameter Extraction](docs/developer/en/04_mcp_integration_tools_and_policies.md) ### [04 — MCP, Tools, Policies, and Parameter Extraction](docs/developer/en/04_mcp_integration_tools_and_policies.md)
**What it is:** reference for tools, MCP Servers, mappings, policies, and parameter extraction. **What it is:** reference for tools, MCP Servers, mappings, policies, parameter extraction, and multi-item calls.
**Use when:** tool integration/execution is incorrect or needs to be created. **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) ### [05 — Agent Gateway, MCP Gateway, and Authentication](docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md)

View File

@@ -83,6 +83,31 @@ projeto_multi_agent_isolado/
### Componentes principais ### 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 parâmetros `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`.
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 ### Contrato HTTP simplificado usado no projeto
``` ```

View File

@@ -28,6 +28,7 @@ Se algo não está funcionando, use **Buscar pelo problema** abaixo.
| Uma tool não aparece ou não é encontrada | `tools.yaml`, catálogo MCP, discovery | [MCP/Tools](./04_mcp_integration_tools_and_policies.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) | | 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) | | 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 | parâmetro array/list, 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 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) | | 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) | | Recebo 401 entre gateway/backend/MCP | Basic Auth, credenciais por hop | [Gateways e Auth](./05_agent_gateway_mcp_gateway_and_auth.md) |
@@ -74,9 +75,9 @@ Se algo não está funcionando, use **Buscar pelo problema** abaixo.
### [04 — MCP, Tools, Policies e Extração de Parâmetros](./04_mcp_integration_tools_and_policies.md) ### [04 — MCP, Tools, Policies e Extração de Parâmetros](./04_mcp_integration_tools_and_policies.md)
**O que é:** referência de tools, MCP Servers, mappings, policies e parameter extraction. **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 ou precisa ser criada. **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) ### [05 — Agent Gateway, MCP Gateway e Autenticação](./05_agent_gateway_mcp_gateway_and_auth.md)

View File

@@ -1351,6 +1351,101 @@ class AgentRuntimeMixin:
# Compatibilidade com versões antigas do router. # Compatibilidade com versões antigas do router.
return None, arguments or {}, None return None, arguments or {}, None
@staticmethod
def _primary_multi_item_outcomes(tool_name: str, result: dict[str, Any]) -> list[dict[str, Any]]:
"""Return explicit per-item outcomes for the requested tool, when present.
The contract is intentionally structural and domain-agnostic: the framework
only recognizes an array named ``results`` whose entries expose an explicit
boolean ``success``/``ok`` signal. For workflow-backed tools, preference is
given to ``workflow.output[tool_name].results`` so a later auxiliary/composite
step cannot overwrite the already executed primary operation.
"""
if not isinstance(result, dict):
return []
body = result.get("result") if isinstance(result.get("result"), dict) else result
candidates: list[Any] = []
if isinstance(body, dict):
output = body.get("output")
if isinstance(output, dict):
primary = output.get(tool_name)
if isinstance(primary, dict):
candidates.append(primary.get("results"))
candidates.append(body.get("results"))
candidates.append(result.get("results"))
for candidate in candidates:
if not isinstance(candidate, list) or len(candidate) < 2:
continue
normalized: list[dict[str, Any]] = []
valid = True
for item in candidate:
if not isinstance(item, dict):
valid = False
break
signal = item.get("success") if isinstance(item.get("success"), bool) else item.get("ok")
if not isinstance(signal, bool):
valid = False
break
normalized.append(dict(item))
if valid and normalized:
return normalized
return []
@classmethod
def _normalize_multi_item_tool_result(cls, tool_name: str, result: dict[str, Any]) -> dict[str, Any]:
"""Preserve partial/full success of a multi-item primary operation.
Some composite workflows execute the requested tool successfully for one or
more items and then fail in a secondary validation/post-processing step. A
wrapper-level ``ok=false`` must not erase those terminal item outcomes.
This normalizer never invents business semantics. It activates only when
the requested tool itself exposes two or more explicit item outcomes with a
boolean success signal.
"""
if not isinstance(result, dict):
return result
outcomes = cls._primary_multi_item_outcomes(tool_name, result)
if not outcomes:
return result
succeeded = sum(1 for item in outcomes if bool(item.get("success") if isinstance(item.get("success"), bool) else item.get("ok")))
failed = len(outcomes) - succeeded
if succeeded and failed:
status = "PARTIAL_SUCCESS"
elif succeeded:
status = "SUCCESS"
else:
status = "FAILED"
normalized = dict(result)
metadata = dict(normalized.get("metadata") or {})
metadata["multi_item"] = {
"status": status,
"items_count": len(outcomes),
"items_succeeded_count": succeeded,
"items_failed_count": failed,
"primary_tool": tool_name,
}
normalized["metadata"] = metadata
normalized["multi_item_status"] = status
normalized["partial_success"] = status == "PARTIAL_SUCCESS"
# The requested action is considered operationally successful whenever at
# least one explicitly identified item completed. Preserve the original
# wrapper status/error for audit instead of discarding it.
if succeeded > 0 and normalized.get("ok") is False:
normalized["original_ok"] = False
normalized["ok"] = True
if normalized.get("error"):
normalized["secondary_error"] = normalized.get("error")
return normalized
async def _call_mcp_tool_uncached( async def _call_mcp_tool_uncached(
self, self,
tool_name: str, tool_name: str,
@@ -1384,6 +1479,7 @@ class AgentRuntimeMixin:
) )
result = res.model_dump(mode="json") if hasattr(res, "model_dump") else dict(res) result = res.model_dump(mode="json") if hasattr(res, "model_dump") else dict(res)
if isinstance(result, dict): if isinstance(result, dict):
result = self._normalize_multi_item_tool_result(tool_name, result)
result.setdefault("cached", False) result.setdefault("cached", False)
await self._emit_ic( await self._emit_ic(
"IC.MCP_TOOL_EXECUTED", "IC.MCP_TOOL_EXECUTED",
@@ -3839,6 +3935,21 @@ class AgentRuntimeMixin:
"Resultados MCP normalizados pelo framework:\n" "Resultados MCP normalizados pelo framework:\n"
+ self._compact_llm_value(mcp_results, max_chars=18000) + self._compact_llm_value(mcp_results, max_chars=18000)
) )
multi_item_summaries = []
for item in mcp_results:
metadata = item.get("metadata") if isinstance(item, dict) else None
summary = metadata.get("multi_item") if isinstance(metadata, dict) else None
if isinstance(summary, dict):
multi_item_summaries.append(summary)
if multi_item_summaries:
sections.append(
"Semântica operacional multi-item do framework:\n"
+ self._compact_llm_value(multi_item_summaries, max_chars=4000)
+ "\nOs resultados explícitos por item da operação primária são evidência terminal e "
"devem ser relatados individualmente. Um erro de etapa auxiliar/pós-processamento "
"não pode converter itens já concluídos com sucesso em falha, nem omiti-los. "
"Quando houver sucessos e falhas, descreva o resultado como parcial e informe ambos."
)
transaction_evidence = self.transaction_evidence_for_turn(state, mcp_results) transaction_evidence = self.transaction_evidence_for_turn(state, mcp_results)
if transaction_evidence: if transaction_evidence:
sections.append( sections.append(

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