mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-11-06 11:24:13 +00:00
bugfix: Multi-item
This commit is contained in:
@@ -77,3 +77,12 @@ No compose, o backend usa `config/mcp_servers.docker.yaml` para apontar para `te
|
||||
## 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).
|
||||
|
||||
|
||||
## 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`.
|
||||
|
||||
@@ -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`.
|
||||
229
Tuning-Performance/Multi_Item_MCP/README.md
Normal file
229
Tuning-Performance/Multi_Item_MCP/README.md
Normal 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.
|
||||
69
Tuning-Performance/Multi_Item_MCP/README_en.md
Normal file
69
Tuning-Performance/Multi_Item_MCP/README_en.md
Normal 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`
|
||||
@@ -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.
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
363
docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md
Normal file
363
docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md
Normal 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 |
|
||||
252
docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md
Normal file
252
docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md
Normal 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 |
|
||||
@@ -82,6 +82,31 @@ projeto_multi_agent_isolado/
|
||||
### 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
|
||||
|
||||
```
|
||||
|
||||
@@ -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) |
|
||||
| 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 | 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 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) |
|
||||
@@ -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)
|
||||
|
||||
**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)
|
||||
|
||||
|
||||
@@ -83,6 +83,31 @@ projeto_multi_agent_isolado/
|
||||
### 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
|
||||
|
||||
```
|
||||
|
||||
@@ -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) |
|
||||
| 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 | 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 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) |
|
||||
@@ -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)
|
||||
|
||||
**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)
|
||||
|
||||
|
||||
@@ -1351,6 +1351,101 @@ class AgentRuntimeMixin:
|
||||
# Compatibilidade com versões antigas do router.
|
||||
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(
|
||||
self,
|
||||
tool_name: str,
|
||||
@@ -1384,6 +1479,7 @@ class AgentRuntimeMixin:
|
||||
)
|
||||
result = res.model_dump(mode="json") if hasattr(res, "model_dump") else dict(res)
|
||||
if isinstance(result, dict):
|
||||
result = self._normalize_multi_item_tool_result(tool_name, result)
|
||||
result.setdefault("cached", False)
|
||||
await self._emit_ic(
|
||||
"IC.MCP_TOOL_EXECUTED",
|
||||
@@ -3839,6 +3935,21 @@ class AgentRuntimeMixin:
|
||||
"Resultados MCP normalizados pelo framework:\n"
|
||||
+ 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)
|
||||
if transaction_evidence:
|
||||
sections.append(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user