Multi-item documentation
This commit is contained in:
@@ -41,6 +41,7 @@ As funcionalidades mais recentes estão consolidadas em:
|
||||
- [15 — Apresentação, recuperação e observabilidade](docs/developer/pt/15_apresentacao_recuperacao_e_observabilidade.md)
|
||||
- [16 — Multiagente, composição e ciclo de vida](docs/developer/pt/16_multiagente_composicao_e_ciclo_de_vida.md)
|
||||
- [Guardrails e judges externos — implementação completa](docs/EXTERNAL_GUARDRAILS_JUDGES.md)
|
||||
- [MCP Multi-item — listas de produtos, pedidos e recursos](docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md)
|
||||
|
||||
Se você está começando um novo agente, siga este `README.md` desde o início. Para aprofundamento ou troubleshooting, use os links abaixo.
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ The newest capabilities are consolidated in:
|
||||
- [15 — Presentation, recovery, and observability](docs/developer/en/15_presentation_recovery_and_observability.md)
|
||||
- [16 — Multi-agent composition and lifecycle](docs/developer/en/16_multi_agent_composition_and_lifecycle.md)
|
||||
- [External guardrails and judges — complete implementation](docs/EXTERNAL_GUARDRAILS_JUDGES_en.md)
|
||||
- [MCP Multi-item — product, order, and resource lists](docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md)
|
||||
|
||||
If you are creating a new agent, follow this `README_en.md` from the beginning. For deeper implementation details or troubleshooting, use the links below.
|
||||
|
||||
|
||||
@@ -1,85 +1,90 @@
|
||||
# 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.
|
||||
Este documento mostra como adicionar uma operação multi-item sem criar lógica paralela no agente e sem alterar desnecessariamente contratos já existentes.
|
||||
|
||||
## 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ó:
|
||||
A operação lógica continua sendo uma só. Não crie tools/intents por item nem loops no Agent.
|
||||
|
||||
```text
|
||||
cancelar_produtos
|
||||
## 2. Escolha o contrato natural da operação
|
||||
|
||||
### Opção A — parâmetro escalar
|
||||
|
||||
Use quando a interface já recebe um `subject`, `id`, `query` ou expressão que pode representar uma ou várias entidades:
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
cancelar_vas_avulso:
|
||||
args_schema:
|
||||
subject:
|
||||
type: string
|
||||
```
|
||||
|
||||
Não crie `cancelar_produto_1`, `cancelar_produto_2`, loops no agent ou intents artificiais por item.
|
||||
Entrada:
|
||||
|
||||
## 2. Declare a coleção em `tools.yaml`
|
||||
```text
|
||||
subject = "Produto A, Produto B, Produto C"
|
||||
```
|
||||
|
||||
### Opção B — coleção explícita
|
||||
|
||||
Use quando a API/tool é naturalmente batch:
|
||||
|
||||
```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.
|
||||
**Não transforme uma tool escalar em array apenas para obter suporte multi-item.**
|
||||
|
||||
## 3. Use a policy transacional existente
|
||||
## 3. Use a policy existente
|
||||
|
||||
Exemplo escalar:
|
||||
|
||||
```yaml
|
||||
tool_policies:
|
||||
cancelar_produtos:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [items]
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_produtos_cancelamento
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
Nenhuma chave específica de multi-item é necessária.
|
||||
|
||||
## 4. Canonicalize os itens no MCP validator
|
||||
## 4. Canonicalize/expanda no validator quando necessário
|
||||
|
||||
Entrada possível:
|
||||
Entrada:
|
||||
|
||||
```json
|
||||
{
|
||||
"items": ["produto a", "produto b", "produto c"]
|
||||
}
|
||||
{"subject": "produto a, produto b, produto c"}
|
||||
```
|
||||
|
||||
Saída recomendada:
|
||||
Saída possível:
|
||||
|
||||
```json
|
||||
{
|
||||
"eligible": true,
|
||||
"status": "ELIGIBLE",
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": "Produto A, Produto B, Produto C",
|
||||
"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?"
|
||||
}
|
||||
}
|
||||
@@ -97,30 +102,19 @@ B = inválido
|
||||
C = válido
|
||||
```
|
||||
|
||||
Se A e C ainda podem ser executados, não use simplesmente:
|
||||
Com `fail_open: false`, isto bloquearia tudo:
|
||||
|
||||
```json
|
||||
{
|
||||
"eligible": false,
|
||||
"reason": "B inválido"
|
||||
}
|
||||
{"eligible": false, "reason": "B inválido"}
|
||||
```
|
||||
|
||||
Isso encerraria a transação inteira.
|
||||
Quando A e C ainda podem ser executados, mantenha a transação globalmente elegível e preserve o estado individual dos itens.
|
||||
|
||||
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 no MCP Server ou workflow
|
||||
|
||||
## 6. Execute a coleção no MCP Server ou workflow
|
||||
A tool/workflow pode receber `subject` e expandir internamente, ou receber `items[]` já canonicalizado.
|
||||
|
||||
### 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
|
||||
### Backend com API unitária
|
||||
|
||||
```python
|
||||
async def cancelar_produtos(items):
|
||||
@@ -128,26 +122,16 @@ async def cancelar_produtos(items):
|
||||
for item in items:
|
||||
try:
|
||||
response = await backend.cancel_one(item)
|
||||
results.append({
|
||||
"name": item["name"],
|
||||
"success": True,
|
||||
"response": response,
|
||||
})
|
||||
results.append({"name": item["name"], "success": True, "response": response})
|
||||
except Exception as exc:
|
||||
results.append({
|
||||
"name": item["name"],
|
||||
"success": False,
|
||||
"error": str(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**.
|
||||
O loop pertence ao **MCP Server/workflow**, não ao Agent.
|
||||
|
||||
## 7. Retorne `results[]` por item
|
||||
|
||||
Contrato mínimo:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
@@ -162,8 +146,6 @@ 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
|
||||
@@ -171,46 +153,24 @@ 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
|
||||
workflow.output[tool_name].results
|
||||
```
|
||||
|
||||
Assim uma falha auxiliar não converte A e B em falha operacional.
|
||||
Assim uma falha auxiliar posterior não apaga itens já concluídos.
|
||||
|
||||
## 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;
|
||||
- [ ] `tools.yaml` declara o contrato natural, escalar ou coleção;
|
||||
- [ ] não existe requisito de `type: array` para habilitar multi-item;
|
||||
- [ ] `tool_policies.yaml` mantém os `requires` reais do domínio;
|
||||
- [ ] se um escalar representa vários itens, validator/tool/workflow faz a expansão;
|
||||
- [ ] com `fail_open: false`, falha individual não bloqueia automaticamente todos os itens acionáveis;
|
||||
- [ ] fan-out, se necessário, pertence ao MCP Server/workflow;
|
||||
- [ ] a tool primária devolve `results[]`;
|
||||
- [ ] cada item contém `success` ou `ok` booleano;
|
||||
@@ -220,13 +180,13 @@ Antes de criar código adicional, verifique:
|
||||
|
||||
## 11. Testes mínimos recomendados
|
||||
|
||||
Cubra ao menos:
|
||||
|
||||
```text
|
||||
1 item / sucesso
|
||||
N itens / todos sucesso
|
||||
N itens / sucesso parcial
|
||||
N itens / todos falha
|
||||
entrada escalar expandida para N itens
|
||||
entrada batch explícita
|
||||
item não canonicalizado
|
||||
confirmação única para N itens
|
||||
falha auxiliar após sucesso primário
|
||||
|
||||
@@ -4,6 +4,8 @@ Capability de referência para operações MCP que precisam processar **mais de
|
||||
|
||||
> 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`.
|
||||
|
||||
> **Importante:** multi-item não é definido por `type: array`. O parâmetro de entrada pode ser escalar (`subject`, `id`, `query`) ou uma coleção. O que torna a execução multi-item é a resolução/execução de vários itens e o retorno por item em `results[]`.
|
||||
|
||||
## Quando usar
|
||||
|
||||
Use quando o usuário puder solicitar, por exemplo:
|
||||
@@ -18,7 +20,7 @@ ou:
|
||||
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.
|
||||
O desenvolvedor deve modelar **uma operação lógica**. O framework cuida de estado, confirmação e interpretação de resultados; validator/MCP Server/workflow cuidam da resolução e execução dos itens.
|
||||
|
||||
## Divisão de responsabilidades
|
||||
|
||||
@@ -30,25 +32,27 @@ Agent
|
||||
↓
|
||||
|
||||
tools.yaml
|
||||
declara array/list
|
||||
declara o contrato natural da operação
|
||||
pode ser escalar OU coleção
|
||||
NÃO exige chave multi_item
|
||||
|
||||
↓
|
||||
|
||||
tool_policies.yaml
|
||||
confirmação + pre-validation
|
||||
usa o mesmo contrato transacional existente
|
||||
confirmação + requires + pre-validation
|
||||
NÃO possui sintaxe especial de multi-item
|
||||
|
||||
↓
|
||||
|
||||
MCP validator
|
||||
resolve/canonicaliza os itens
|
||||
resolve/canonicaliza itens
|
||||
pode expandir subject/id/query em items[]
|
||||
pode preencher transaction_decision.resolved_arguments
|
||||
|
||||
↓
|
||||
|
||||
MCP tool / workflow
|
||||
executa os itens
|
||||
executa 1 ou N itens
|
||||
devolve results[] com success/ok por item
|
||||
|
||||
↓
|
||||
@@ -66,70 +70,75 @@ LLM / Presentation
|
||||
apresenta cada sucesso/falha
|
||||
```
|
||||
|
||||
## Configuração mínima
|
||||
## Configuração mínima — padrão escalar
|
||||
|
||||
Este padrão é útil quando a interface natural já usa `subject` e o validator expande múltiplas entidades.
|
||||
|
||||
### `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.
|
||||
cancelar_vas_avulso:
|
||||
description: Cancela um ou mais VAS.
|
||||
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.
|
||||
subject:
|
||||
type: string
|
||||
```
|
||||
|
||||
Não é necessário criar configuração como:
|
||||
### `tool_policies.yaml`
|
||||
|
||||
```yaml
|
||||
tool_policies:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
Nenhuma chave abaixo existe ou é necessária:
|
||||
|
||||
```yaml
|
||||
# NÃO EXISTE / NÃO É NECESSÁRIO
|
||||
multi_item: true
|
||||
multi_item_strategy: partial
|
||||
fan_out: framework
|
||||
```
|
||||
|
||||
### `tool_policies.yaml`
|
||||
## Configuração alternativa — API batch
|
||||
|
||||
O contrato também continua o mesmo:
|
||||
Quando a tool MCP recebe naturalmente uma coleção, `array/list` continua válido:
|
||||
|
||||
```yaml
|
||||
tool_policies:
|
||||
cancelar_produtos:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [items]
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_produtos_cancelamento
|
||||
fail_open: false
|
||||
args_schema:
|
||||
items:
|
||||
type: array
|
||||
```
|
||||
|
||||
## Pre-validation e lista canônica
|
||||
Isso é **uma opção de contrato**, não um pré-requisito para multi-item.
|
||||
|
||||
O validator pode transformar nomes informais em uma coleção canônica antes da confirmação:
|
||||
## Pre-validation e expansão de itens
|
||||
|
||||
Um `subject` escalar pode ser transformado em argumentos canônicos:
|
||||
|
||||
```json
|
||||
{
|
||||
"eligible": true,
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": "Produto A, Produto B, Produto C",
|
||||
"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?"
|
||||
}
|
||||
}
|
||||
@@ -137,21 +146,17 @@ O validator pode transformar nomes informais em uma coleção canônica antes da
|
||||
|
||||
### 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**.
|
||||
`eligible` é global. Com `fail_open: false`, não retorne `eligible: false` apenas porque um item falhou se outros ainda são acionáveis.
|
||||
|
||||
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`;
|
||||
1. `eligible: true` enquanto houver pelo menos um item acionável;
|
||||
2. preservar resolução/eligibilidade individual;
|
||||
3. executar a operação primária;
|
||||
4. retornar o resultado terminal individual em `results[]`.
|
||||
|
||||
Se nenhum item puder ser processado, `eligible: false` é apropriado.
|
||||
4. produzir o resultado terminal individual em `results[]`.
|
||||
|
||||
## Resultado MCP esperado
|
||||
|
||||
A tool primária deve retornar `results[]` com `success` ou `ok` booleano por item:
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
@@ -162,50 +167,23 @@ A tool primária deve retornar `results[]` com `success` ou `ok` booleano por it
|
||||
}
|
||||
```
|
||||
|
||||
O framework deriva automaticamente:
|
||||
O framework deriva automaticamente `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`.
|
||||
|
||||
## Uma operação lógica, sem N chamadas no agente
|
||||
|
||||
São válidos:
|
||||
|
||||
```text
|
||||
PARTIAL_SUCCESS
|
||||
2 succeeded
|
||||
1 failed
|
||||
Agent -> cancelar(subject="A, B, C") -> validator/workflow -> results[]
|
||||
```
|
||||
|
||||
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:
|
||||
ou:
|
||||
|
||||
```text
|
||||
Agent -> cancelar_produtos(items=[A,B,C]) -> MCP
|
||||
Agent -> cancelar(items=[A,B,C]) -> MCP/workflow -> results[]
|
||||
```
|
||||
|
||||
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})
|
||||
```
|
||||
Se o backend possui apenas API unitária, o fan-out fica no MCP Server/workflow. Evite loop MCP de negócio dentro do agente.
|
||||
|
||||
## Workflow-backed tools
|
||||
|
||||
@@ -215,15 +193,13 @@ Quando a operação é implementada por workflow, o caminho preferencial do resu
|
||||
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.
|
||||
Uma falha posterior não deve apagar 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.
|
||||
- `specs/SPEC-010-Agent-Development.md` — regra arquitetural.
|
||||
|
||||
Para um passo a passo prático, leia `IMPLEMENTACAO_MULTI_ITEM_MCP.md` nesta pasta.
|
||||
Para o passo a passo prático, leia `IMPLEMENTACAO_MULTI_ITEM_MCP.md`.
|
||||
|
||||
@@ -1,69 +1,58 @@
|
||||
# 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.
|
||||
Reference capability for MCP operations that process more than one item under a single logical user intent.
|
||||
|
||||
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`.
|
||||
> Multi-item support is **not enabled by `type: array`**. The input may be scalar (`subject`, `id`, `query`) or a collection. Multi-item behavior comes from resolving/executing several items and returning per-item `results[]`.
|
||||
|
||||
```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
|
||||
Agent selects the operation; no business fan-out
|
||||
-> tools.yaml declares the natural input contract: scalar OR collection
|
||||
-> tool_policies confirmation/requires/pre-validation; no special multi-item syntax
|
||||
-> validator resolves/canonicalizes and may expand one value into items[]
|
||||
-> MCP/workflow executes 1 or N items and returns results[]
|
||||
-> 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:
|
||||
## Scalar pattern
|
||||
|
||||
```yaml
|
||||
tool_policies:
|
||||
cancel_products:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [items]
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validate_products_for_cancellation
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
No `multi_item: true`, framework fan-out flag, or extra agent code is required.
|
||||
`subject` may represent one or many entities. The validator/workflow may expand it into canonical `items[]`.
|
||||
|
||||
## MCP result contract
|
||||
## Explicit batch pattern
|
||||
|
||||
The primary tool should return `results[]` with boolean `success` or `ok` per item:
|
||||
```yaml
|
||||
args_schema:
|
||||
items:
|
||||
type: array
|
||||
```
|
||||
|
||||
This is supported, but it is **an interface option, not a requirement**.
|
||||
|
||||
## Result contract
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"name": "A", "success": true},
|
||||
{"name": "B", "success": false, "error": "not_found"},
|
||||
{"name": "B", "success": false},
|
||||
{"name": "C", "success": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The framework derives `PARTIAL_SUCCESS` for the example above and preserves the successful items.
|
||||
The runtime derives `PARTIAL_SUCCESS` 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`
|
||||
For full details, read `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md`.
|
||||
|
||||
@@ -2,37 +2,69 @@
|
||||
|
||||
## 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:
|
||||
Use este guia quando uma única intenção do usuário precisa operar sobre **mais de um item do mesmo tipo** — por exemplo, cancelar vários VAS/produtos, devolver ou consultar vários pedidos, alterar vários ativos ou processar várias faturas/linhas/contratos.
|
||||
|
||||
- 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 suporte genérico para **resolução/canonicalização de múltiplos itens**, **confirmação transacional**, **execução MCP/workflow**, **preservação de evidência por item** e **normalização de `SUCCESS` / `PARTIAL_SUCCESS` / `FAILED`**. O desenvolvedor não deve criar um segundo motor de multi-item dentro do agente.
|
||||
|
||||
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 central:** multi-item é uma característica da resolução, execução e resultado da operação. **Não é definido pelo tipo do parâmetro de entrada.** Uma tool pode receber `subject: string`, `id: string`, `items: array` ou outro contrato natural de domínio e ainda assim processar múltiplos itens.
|
||||
|
||||
## 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.
|
||||
**Não faça loop de negócio no agente.** O agente seleciona a intenção/tool e usa o runtime. A expansão para múltiplos itens pode ocorrer no pre-validator, na própria tool MCP ou em um workflow de domínio.
|
||||
|
||||
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
|
||||
-> Runtime coleta os parâmetros declarados pela tool
|
||||
-> Entrada pode ser escalar ou coleção
|
||||
-> (opcional) pre-validation resolve/expande/canonicaliza N itens
|
||||
-> Runtime pede UMA confirmação coerente para o conjunto efetivo
|
||||
-> MCP tool/workflow processa 1 ou N itens
|
||||
-> Tool primária 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`
|
||||
## 1. Declare o contrato natural da operação em `tools.yaml`
|
||||
|
||||
O reconciliador transacional do framework aceita `type: array` ou `type: list`.
|
||||
O framework **não exige `array/list`** para que uma operação seja multi-item.
|
||||
|
||||
Use o contrato que faz sentido para a tool e para o domínio:
|
||||
|
||||
- **parâmetro escalar** (`subject`, `id`, `query`, etc.) quando a operação recebe uma expressão que pode representar uma ou várias entidades;
|
||||
- **`array/list`** quando a interface MCP é naturalmente batch e recebe explicitamente uma coleção.
|
||||
|
||||
### Padrão A — parâmetro escalar que pode representar vários itens
|
||||
|
||||
Este é o padrão usado pelo agente de Contas para cancelamento de VAS.
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
cancelar_vas_avulso:
|
||||
description: Cancela um ou mais serviços VAS.
|
||||
mcp_server: contas
|
||||
enabled: true
|
||||
tool_type: action
|
||||
args_schema:
|
||||
subject:
|
||||
type: string
|
||||
label: o serviço ou os serviços
|
||||
description: Serviço ou expressão com um ou mais serviços a cancelar.
|
||||
```
|
||||
|
||||
Entrada possível:
|
||||
|
||||
```text
|
||||
subject = "TIM Fashion, Aya Audiobooks, Neymar Jr"
|
||||
```
|
||||
|
||||
O `subject` continua sendo uma `string`. O suporte multi-item aparece posteriormente quando o validator/tool resolve essa expressão em itens independentes.
|
||||
|
||||
### Padrão B — coleção explícita
|
||||
|
||||
Também é válido quando a API/tool é naturalmente batch:
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
@@ -41,119 +73,156 @@ tools:
|
||||
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:
|
||||
Os dois padrões são suportados. **Não altere uma interface escalar existente para array apenas para “habilitar multi-item”.**
|
||||
|
||||
```text
|
||||
quero cancelar TIM Fashion, Aya Audiobooks e Neymar Jr
|
||||
```
|
||||
## 2. `tool_policies.yaml` não possui sintaxe especial de multi-item
|
||||
|
||||
para um argumento lógico semelhante a:
|
||||
`tool_policies.yaml` continua definindo tipo de operação, confirmação, parâmetros obrigatórios e pre-validation. Não é necessário criar `multi_item: true`, trocar `subject` por `items` ou introduzir outra configuração.
|
||||
|
||||
```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.
|
||||
Exemplo realista do agente de Contas:
|
||||
|
||||
```yaml
|
||||
version: 1
|
||||
defaults:
|
||||
operation_type: read_only
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_produtos:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [items]
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_produtos_cancelamento
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
O pre-validator pode retornar `eligible: true` e substituir argumentos por valores canônicos em `transaction_decision.resolved_arguments`:
|
||||
`requires: [subject]` significa apenas que a operação precisa saber **o que** será processado. Esse `subject` pode representar um item ou vários.
|
||||
|
||||
```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?"
|
||||
}
|
||||
}
|
||||
```
|
||||
## 3. Pre-validation pode transformar um parâmetro escalar em múltiplos itens
|
||||
|
||||
### 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`.
|
||||
Quando necessário, o pre-validator resolve nomes, canonicaliza entidades e pode materializar uma lista estrutural em `transaction_decision.resolved_arguments`.
|
||||
|
||||
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}
|
||||
"eligible": true,
|
||||
"subject": "TIM Fashion, Aya Audiobooks, Neymar Jr",
|
||||
"resolved_subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr",
|
||||
"resolved_subjects": [
|
||||
"TIM Fashion Mensal",
|
||||
"Aya Audiobooks Premium",
|
||||
"Neymar Jr"
|
||||
],
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr",
|
||||
"items": [
|
||||
{"name": "TIM Fashion Mensal"},
|
||||
{"name": "Aya Audiobooks Premium"},
|
||||
{"name": "Neymar Jr"}
|
||||
]
|
||||
},
|
||||
"target_tool": "cancelar_vas_avulso",
|
||||
"confirmation_message": "Você confirma o cancelamento de TIM Fashion Mensal, Aya Audiobooks Premium e Neymar Jr?"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`resolved_arguments.items[]` é uma representação canônica útil para a execução, mas **não obriga** `tools.yaml` a declarar `items` como parâmetro de entrada.
|
||||
|
||||
### Validação parcial e `fail_open: false`
|
||||
|
||||
`eligible` é global. Com `fail_open: false`, retornar `eligible: false` encerra a transação inteira.
|
||||
|
||||
Portanto, quando alguns itens são válidos e outros não:
|
||||
|
||||
1. mantenha `eligible: true` se existe pelo menos um item acionável;
|
||||
2. preserve o estado individual dos itens resolvidos/rejeitados;
|
||||
3. deixe a tool primária produzir o resultado terminal por item em `results[]`.
|
||||
|
||||
Exemplo conceitual:
|
||||
|
||||
```json
|
||||
{
|
||||
"eligible": true,
|
||||
"status": "PARTIALLY_ELIGIBLE",
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"items": [
|
||||
{"name": "TIM Fashion Mensal", "eligible": true},
|
||||
{"name": "Aya Audiobooks Premium", "eligible": false, "reason": "item_nao_encontrado"},
|
||||
{"name": "Neymar Jr", "eligible": true}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Se nenhum item puder ser processado, `eligible: false` é apropriado.
|
||||
|
||||
## 4. Contrato de entrada do MCP: uma operação lógica, não necessariamente uma coleção explícita
|
||||
|
||||
Prefira **uma única operação lógica MCP para o agente**. Ela pode ser implementada de três formas:
|
||||
|
||||
1. receber um parâmetro escalar e expandi-lo no validator/workflow;
|
||||
2. receber uma coleção explícita (`array/list`);
|
||||
3. receber uma expressão/identificador e descobrir os itens dentro da própria tool/workflow.
|
||||
|
||||
O agente não deve fazer fan-out chamando a mesma tool repetidamente por conta própria.
|
||||
|
||||
### Exemplo — escalar expandido
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "cancelar_vas_avulso",
|
||||
"arguments": {
|
||||
"subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Exemplo — batch explícito
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "cancelar_produtos",
|
||||
"arguments": {
|
||||
"items": [
|
||||
{"name": "A", "id": "1001"},
|
||||
{"name": "B", "id": "1002"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Se o backend só possui API unitária, o fan-out deve ficar encapsulado no **MCP Server ou workflow de domínio**, preservando uma única operação lógica para o agente.
|
||||
|
||||
## 5. Contrato obrigatório de resultado por item
|
||||
|
||||
Para o framework reconhecer automaticamente o resultado como multi-item, a **tool primária solicitada** deve expor uma lista `results` com **dois ou mais itens**, e cada item deve possuir `success` ou `ok` booleano.
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"subject": "TIM Fashion Mensal", "success": true},
|
||||
{"subject": "Aya Audiobooks Premium", "success": false, "reason": "not_found"},
|
||||
{"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 do normalizador é `success: true|false` ou `ok: true|false` por item.
|
||||
|
||||
### Workflow-backed tool
|
||||
|
||||
@@ -163,37 +232,20 @@ Se a tool é executada por workflow, o caminho preferencial e autoritativo é:
|
||||
workflow.output[tool_name].results
|
||||
```
|
||||
|
||||
Uma falha auxiliar posterior não deve apagar itens já concluídos pela tool primária.
|
||||
|
||||
## 6. O que o framework produz automaticamente
|
||||
|
||||
Com `results[]` válido, o runtime classifica o agregado como:
|
||||
|
||||
| Situação | `multi_item_status` |
|
||||
|---|---|
|
||||
| todos os itens concluíram | `SUCCESS` |
|
||||
| parte concluiu e parte falhou | `PARTIAL_SUCCESS` |
|
||||
| todos falharam | `FAILED` |
|
||||
|
||||
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",
|
||||
@@ -204,141 +256,178 @@ Com `results[]` válido, o runtime adiciona:
|
||||
"items_count": 3,
|
||||
"items_succeeded_count": 2,
|
||||
"items_failed_count": 1,
|
||||
"primary_tool": "cancelar_produtos"
|
||||
"primary_tool": "cancelar_vas_avulso"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Estados possíveis:
|
||||
Se um wrapper/etapa posterior retornar erro depois de existirem resultados terminais válidos, o framework preserva os sucessos individuais e pode registrar o erro como secundário, em vez de converter tudo para falha total.
|
||||
|
||||
| Situação | `multi_item_status` |
|
||||
|---|---|
|
||||
| todos os itens concluíram | `SUCCESS` |
|
||||
| parte concluiu e parte falhou | `PARTIAL_SUCCESS` |
|
||||
| todos falharam | `FAILED` |
|
||||
## 7. O que NÃO fazer
|
||||
|
||||
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:
|
||||
Não crie motor multi-item dentro do agente:
|
||||
|
||||
```python
|
||||
# NÃO: motor multi-item paralelo dentro do agente
|
||||
# NÃO
|
||||
for product in products:
|
||||
await tool_router.call("cancelar_produto", {"product": product})
|
||||
```
|
||||
|
||||
Não reduza resultado misto a um único booleano global sem evidência por item:
|
||||
|
||||
```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"
|
||||
}
|
||||
Também não altere uma interface existente apenas para trocar:
|
||||
|
||||
```text
|
||||
subject: string
|
||||
```
|
||||
|
||||
Esses formatos perdem a evidência terminal individual e podem fazer itens concluídos desaparecerem da composição final.
|
||||
por:
|
||||
|
||||
## 7. Exemplo completo: vários pedidos
|
||||
```text
|
||||
items: array
|
||||
```
|
||||
|
||||
quando o validator/workflow já consegue expandir corretamente o `subject`.
|
||||
|
||||
## 8. Exemplo completo — padrão escalar do Contas
|
||||
|
||||
### `tools.yaml`
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
cancelar_pedidos:
|
||||
description: Cancela um ou mais pedidos.
|
||||
mcp_server: retail
|
||||
cancelar_vas_avulso:
|
||||
description: Cancela um ou mais VAS.
|
||||
mcp_server: contas
|
||||
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?
|
||||
subject:
|
||||
type: string
|
||||
```
|
||||
|
||||
### Retorno MCP
|
||||
### `tool_policies.yaml`
|
||||
|
||||
```yaml
|
||||
tool_policies:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
### Validator
|
||||
|
||||
```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"}
|
||||
]
|
||||
"eligible": true,
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr",
|
||||
"items": [
|
||||
{"name": "TIM Fashion Mensal"},
|
||||
{"name": "Aya Audiobooks Premium"},
|
||||
{"name": "Neymar Jr"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
O framework classificará automaticamente como `PARTIAL_SUCCESS` e fornecerá ao LLM a instrução de relatar os resultados item a item.
|
||||
### Resultado MCP/workflow
|
||||
|
||||
## 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
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"subject": "TIM Fashion Mensal", "success": true},
|
||||
{"subject": "Aya Audiobooks Premium", "success": false, "reason": "not_found"},
|
||||
{"subject": "Neymar Jr", "success": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Resultado agregado: `PARTIAL_SUCCESS`.
|
||||
|
||||
## 9. Idempotência e protocolos
|
||||
## 9. Exemplo alternativo — API naturalmente batch
|
||||
|
||||
Quando o MCP já define coleção como contrato de entrada, `array/list` continua válido:
|
||||
|
||||
```yaml
|
||||
args_schema:
|
||||
order_ids:
|
||||
type: array
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"order_id": "P-100", "success": true},
|
||||
{"order_id": "P-101", "success": false, "reason": "already_shipped"},
|
||||
{"order_id": "P-102", "success": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
O ponto importante é: **array é uma opção de contrato, não o gatilho do mecanismo multi-item.**
|
||||
|
||||
## 10. Read-only, conversational e transactional podem ser multi-item
|
||||
|
||||
Multi-item é independente do `operation_type`.
|
||||
|
||||
É possível ter:
|
||||
|
||||
```text
|
||||
read_only + multi-item
|
||||
conversational + multi-item
|
||||
transactional + multi-item
|
||||
internal + multi-item
|
||||
```
|
||||
|
||||
O que muda é a política de confirmação e execução, não a capacidade de representar vários itens.
|
||||
|
||||
## 11. 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;
|
||||
- use chave de idempotência de requisição e, quando necessário, 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.
|
||||
- preserve itens já concluídos em retry;
|
||||
- não dependa apenas de mensagem textual agregada.
|
||||
|
||||
O framework preserva evidência; a política de idempotência do sistema de destino continua pertencendo ao MCP/workflow de domínio.
|
||||
A política específica de idempotência do sistema de destino continua pertencendo ao MCP/workflow de domínio.
|
||||
|
||||
## 10. Checklist para o desenvolvedor
|
||||
## 12. Checklist do 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.
|
||||
- [ ] O agente seleciona a operação e **não** implementa fan-out/loop MCP de negócio.
|
||||
- [ ] `tools.yaml` declara o contrato natural da operação, que pode ser escalar ou coleção.
|
||||
- [ ] Nenhuma chave especial `multi_item: true` foi criada ou é necessária.
|
||||
- [ ] `tool_policies.yaml` usa os `requires` reais do domínio; não é necessário trocar `subject` por `items`.
|
||||
- [ ] Se um escalar representar múltiplas entidades, validator/tool/workflow expande e canonicaliza os itens.
|
||||
- [ ] A confirmação representa o conjunto efetivo resolvido.
|
||||
- [ ] Com `fail_open: false`, um item inválido não força `eligible: false` global quando outros ainda são acionáveis.
|
||||
- [ ] A tool primária retorna `results[]` com `success` ou `ok` por item quando há múltiplos resultados.
|
||||
- [ ] Workflow usa `output[tool_name].results` como evidência terminal preferencial da tool primária.
|
||||
- [ ] 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`.
|
||||
- [ ] A apresentação final menciona sucessos e falhas individualmente quando houver `PARTIAL_SUCCESS`.
|
||||
|
||||
## 11. Onde o comportamento existe no framework
|
||||
|
||||
Referências atuais:
|
||||
## 13. Onde o comportamento existe no framework
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py
|
||||
- coerção de type=array/list
|
||||
- suporte existente a coerção de array/list quando esse for o contrato declarado
|
||||
|
||||
libs/agent_framework/src/agent_framework/runtime/agent_runtime.py
|
||||
- pre-validation e resolved_arguments
|
||||
@@ -350,14 +439,14 @@ tests/test_multi_item_result_normalization.py
|
||||
- regressões de SUCCESS/PARTIAL_SUCCESS e proteção contra falha auxiliar
|
||||
```
|
||||
|
||||
## 12. Resumo de responsabilidade
|
||||
## 14. 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 |
|
||||
| Agente | selecionar intenção/tool e usar o runtime; não implementar motor multi-item |
|
||||
| `tools.yaml` | declarar o contrato natural da operação; pode ser escalar ou coleção |
|
||||
| `tool_policies.yaml` | confirmação, `requires` e pre-validation; sem sintaxe especial de multi-item |
|
||||
| Pre-validator MCP | identificar, resolver, canonicalizar e, quando necessário, expandir 1 parâmetro em N itens |
|
||||
| Tool MCP/workflow | executar 1 ou N itens e produzir resultado por item |
|
||||
| Framework runtime | manter estado/confirmação, interpretar `results[]`, preservar evidência e calcular `SUCCESS` / `PARTIAL_SUCCESS` / `FAILED` |
|
||||
| LLM/presentation | transformar evidências em resposta sem apagar sucesso/falha individual |
|
||||
|
||||
@@ -2,251 +2,169 @@
|
||||
|
||||
## 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.
|
||||
Use this guide when one user intent must operate on **more than one item of the same kind**. The framework already supports item resolution/canonicalization, transactional confirmation, MCP/workflow execution, per-item evidence preservation, and `SUCCESS` / `PARTIAL_SUCCESS` / `FAILED` normalization.
|
||||
|
||||
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.
|
||||
> **Core rule:** multi-item is a property of resolution, execution, and results. **It is not defined by the input parameter type.** A tool may receive `subject: string`, `id: string`, `items: array`, or another natural domain contract and still process multiple items.
|
||||
|
||||
## 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:
|
||||
**Do not implement business fan-out in the agent.** The agent selects the intent/tool and uses the runtime. Expansion to multiple items may happen in the pre-validator, MCP tool, or domain workflow.
|
||||
|
||||
```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
|
||||
-> Runtime collects the parameters declared by the tool
|
||||
-> Input may be scalar or a collection
|
||||
-> optional pre-validation resolves/expands/canonicalizes N items
|
||||
-> Runtime requests ONE coherent confirmation for the effective set
|
||||
-> MCP tool/workflow processes 1 or N items
|
||||
-> Primary tool 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`
|
||||
## 1. Declare the natural operation contract in `tools.yaml`
|
||||
|
||||
The transaction parameter reconciler accepts `type: array` and `type: list`.
|
||||
The framework **does not require `array/list`** for multi-item support.
|
||||
|
||||
Use:
|
||||
- a scalar (`subject`, `id`, `query`, etc.) when the natural interface accepts an expression that may represent one or many entities;
|
||||
- `array/list` when the MCP interface is naturally batch-oriented.
|
||||
|
||||
### Pattern A — scalar expanded later
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
cancel_products:
|
||||
description: Cancels one or more customer products.
|
||||
mcp_server: accounts
|
||||
enabled: true
|
||||
tool_type: action
|
||||
confirmation_required: true
|
||||
requires: [items]
|
||||
cancelar_vas_avulso:
|
||||
args_schema:
|
||||
subject:
|
||||
type: string
|
||||
```
|
||||
|
||||
Possible input:
|
||||
|
||||
```text
|
||||
subject = "TIM Fashion, Aya Audiobooks, Neymar Jr"
|
||||
```
|
||||
|
||||
### Pattern B — explicit collection
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
cancelar_produtos:
|
||||
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.
|
||||
Both patterns are valid. **Do not change an existing scalar interface to array only to enable multi-item.**
|
||||
|
||||
## 2. Use pre-validation for canonicalization
|
||||
## 2. `tool_policies.yaml` has no special multi-item syntax
|
||||
|
||||
Configure `tool_policies.yaml` when the list must be resolved before confirmation:
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
tool_policies:
|
||||
cancel_products:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [items]
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validate_products_for_cancellation
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
A validator may return `eligible: true` and replace the raw list through `transaction_decision.resolved_arguments`:
|
||||
`subject` may represent one or multiple entities.
|
||||
|
||||
## 3. Pre-validation may expand a scalar into multiple canonical items
|
||||
|
||||
```json
|
||||
{
|
||||
"eligible": true,
|
||||
"resolved_subjects": ["A", "B", "C"],
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": "A, B, C",
|
||||
"items": [
|
||||
{"name": "Product A", "id": "1001"},
|
||||
{"name": "Product B", "id": "1002"}
|
||||
{"name": "A"},
|
||||
{"name": "B"},
|
||||
{"name": "C"}
|
||||
]
|
||||
},
|
||||
"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` |
|
||||
`resolved_arguments.items[]` is a useful canonical execution representation, but it does **not** require `tools.yaml` to expose `items` as its input parameter.
|
||||
|
||||
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.
|
||||
With `fail_open: false`, do not return global `eligible: false` merely because one item fails while other items remain actionable. Keep `eligible: true`, preserve per-item eligibility, and let the primary tool produce terminal `results[]`.
|
||||
|
||||
## 6. Anti-patterns
|
||||
## 4. MCP input contract: one logical operation, not necessarily an explicit collection
|
||||
|
||||
Do not implement a business loop in the agent:
|
||||
Prefer one logical MCP operation for the agent. It may:
|
||||
1. receive a scalar and expand it in validator/workflow;
|
||||
2. receive an explicit collection;
|
||||
3. receive an expression/identifier and discover items inside the tool/workflow.
|
||||
|
||||
```python
|
||||
for product in products:
|
||||
await tool_router.call("cancel_product", {"product": product})
|
||||
```
|
||||
If the backend only exposes a unit API, keep fan-out inside the **MCP Server or domain workflow**, not inside the agent.
|
||||
|
||||
Do not return only a global decision such as:
|
||||
## 5. Required per-item result contract
|
||||
|
||||
Automatic multi-item normalization is driven by the **primary tool result**, not by the input schema. The primary tool must expose `results` with at least two items and each item must contain boolean `success` or `ok`.
|
||||
|
||||
```json
|
||||
{"approved": false, "error": "one item failed"}
|
||||
{
|
||||
"results": [
|
||||
{"subject": "A", "success": true},
|
||||
{"subject": "B", "success": false, "reason": "not_found"},
|
||||
{"subject": "C", "success": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
For workflow-backed tools, the preferred authoritative path is:
|
||||
|
||||
```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
|
||||
workflow.output[tool_name].results
|
||||
```
|
||||
|
||||
## 11. Responsibility summary
|
||||
## 6. Framework normalization
|
||||
|
||||
| Situation | `multi_item_status` |
|
||||
|---|---|
|
||||
| all items succeed | `SUCCESS` |
|
||||
| some succeed and some fail | `PARTIAL_SUCCESS` |
|
||||
| all fail | `FAILED` |
|
||||
|
||||
A later auxiliary error must not erase terminal per-item evidence already produced by the primary tool.
|
||||
|
||||
## 7. Do not
|
||||
|
||||
Do not create an MCP loop in the agent, reduce mixed results to one global boolean, or redesign a working scalar contract merely to make it an array.
|
||||
|
||||
## 8. Checklist
|
||||
|
||||
- [ ] Agent selects the operation and does not implement business fan-out.
|
||||
- [ ] `tools.yaml` declares the natural contract, scalar or collection.
|
||||
- [ ] No special `multi_item: true` setting is required.
|
||||
- [ ] `tool_policies.yaml` keeps the real domain `requires`; `subject` does not need to become `items`.
|
||||
- [ ] Validator/tool/workflow expands and canonicalizes multiple entities when needed.
|
||||
- [ ] Confirmation represents the effective resolved set.
|
||||
- [ ] With `fail_open: false`, one invalid item does not force global `eligible: false` if other items remain actionable.
|
||||
- [ ] Primary tool returns `results[]` with `success`/`ok` per item.
|
||||
- [ ] Workflow prefers `output[tool_name].results` for terminal evidence.
|
||||
- [ ] Partial results remain visible and are reported individually.
|
||||
|
||||
## 9. 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 |
|
||||
| Agent | select intent/tool; do not implement a multi-item engine |
|
||||
| `tools.yaml` | declare the natural input contract; scalar or collection |
|
||||
| `tool_policies.yaml` | confirmation, `requires`, pre-validation; no special multi-item syntax |
|
||||
| MCP pre-validator | resolve/canonicalize and optionally expand one parameter into N items |
|
||||
| MCP tool/workflow | execute 1 or N items and return per-item results |
|
||||
| Framework runtime | maintain state/confirmation, interpret `results[]`, preserve evidence, calculate aggregate status |
|
||||
| LLM/presentation | report per-item outcomes without erasing successful/failed evidence |
|
||||
|
||||
@@ -86,7 +86,7 @@ projeto_multi_agent_isolado/
|
||||
|
||||
### 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`.
|
||||
When one intent must operate on several items, **do not create an MCP loop inside the agent**. The framework supports scalar or `array/list` contracts, pre-validation with `resolved_arguments`, set-level transaction confirmation, and automatic normalization of per-item `results[]` into `SUCCESS`, `PARTIAL_SUCCESS`, or `FAILED`. Multi-item support does not depend on the input parameter type.
|
||||
|
||||
The complete contract, `tools.yaml` / `tool_policies.yaml` examples, MCP payloads, per-item output, workflow behavior, partial validation, and anti-patterns are documented in:
|
||||
|
||||
|
||||
@@ -27,7 +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) |
|
||||
| I need to run one operation for multiple products/orders/items | scalar or collection contract, pre-validation, single confirmation, per-item outcome | [MCP Multi-item](../../MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md) and [MCP/Tools](./04_mcp_integration_tools_and_policies.md) |
|
||||
| A transactional operation executes without confirmation | tool policy, `require_confirmation` | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
|
||||
| A name search requires an overly exact match | parameter extraction/mapping and agent logic | [MCP/Tools](docs/developer/en/04_mcp_integration_tools_and_policies.md) |
|
||||
| I receive 401 between gateway/backend/MCP | Basic Auth, credentials per hop | [Gateways and Auth](docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md) |
|
||||
|
||||
@@ -87,7 +87,7 @@ projeto_multi_agent_isolado/
|
||||
|
||||
### 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`.
|
||||
Quando uma única intenção precisa operar sobre vários itens, **não crie um loop MCP dentro do agente**. O framework suporta contratos escalares ou `array/list`, pre-validation com `resolved_arguments`, confirmação transacional do conjunto e normalização de `results[]` por item em `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`. Multi-item não depende do tipo do parâmetro de entrada.
|
||||
|
||||
O contrato completo, exemplos de `tools.yaml`, `tool_policies.yaml`, payload MCP, retorno por item, workflows, validação parcial e anti-patterns estão em:
|
||||
|
||||
|
||||
@@ -28,7 +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) |
|
||||
| Preciso chamar a mesma operação para vários produtos/pedidos/itens | contrato escalar ou coleção, pre-validation, confirmação única, resultado por item | [MCP Multi-item](../../MCP_MULTI_ITEM_DEVELOPER_GUIDE.md) e [MCP/Tools](./04_mcp_integration_tools_and_policies.md) |
|
||||
| Uma operação transacional executa sem confirmação | tool policy, `require_confirmation` | [MCP/Tools](./04_mcp_integration_tools_and_policies.md) |
|
||||
| Uma busca por nome exige correspondência exata demais | extração/mapeamento de parâmetros e lógica do agente | [MCP/Tools](./04_mcp_integration_tools_and_policies.md) |
|
||||
| Recebo 401 entre gateway/backend/MCP | Basic Auth, credenciais por hop | [Gateways e Auth](./05_agent_gateway_mcp_gateway_and_auth.md) |
|
||||
|
||||
@@ -232,7 +232,7 @@ Antes de publicar um novo template/host, execute os cenários multi-turno descri
|
||||
|
||||
## Contrato MCP Multi-item
|
||||
|
||||
Para uma intenção que opere sobre múltiplos itens homogêneos, o agente **não deve implementar fan-out/loop de chamadas MCP por conta própria**. Deve reutilizar o runtime e declarar a coleção no schema da tool (`type: array` ou `type: list`).
|
||||
Para uma intenção que opere sobre múltiplos itens homogêneos, o agente **não deve implementar fan-out/loop de chamadas MCP por conta própria**. Deve reutilizar o runtime e declarar o contrato natural da tool. A entrada pode ser escalar ou coleção; `type: array/list` não é requisito para suporte multi-item.
|
||||
|
||||
Regras:
|
||||
|
||||
@@ -309,7 +309,7 @@ Referência de implementação: [`MCP Multi-item — Guia do Desenvolvedor`](../
|
||||
- [ ] Configuração ocorre por YAML e `.env`.
|
||||
- [ ] Agente usa BusinessContext.
|
||||
- [ ] Agente acessa MCP por router/gateway.
|
||||
- [ ] Casos multi-item usam array/list + resultado por item, sem loop MCP de negócio dentro do agente.
|
||||
- [ ] Casos multi-item usam o contrato natural da operação (escalar ou coleção) + resultado por item, sem loop MCP de negócio dentro do agente.
|
||||
- [ ] Agente não conhece payload bruto de canal.
|
||||
- [ ] Guardrails e judges são configurados.
|
||||
- [ ] Dataset de eval existe.
|
||||
|
||||
@@ -175,7 +175,7 @@ config/agents/<agent_id>/judges.yaml
|
||||
|
||||
## MCP multi-item — não implemente loops no agente
|
||||
|
||||
Se uma mesma solicitação envolve vários produtos, pedidos ou outros itens, use o mecanismo multi-item do framework. Declare o parâmetro como `array/list`, use pre-validation para canonicalização quando necessário e faça a tool MCP primária devolver `results[]` com `success`/`ok` por item. O runtime preserva sucesso parcial e produz `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`.
|
||||
Se uma mesma solicitação envolve vários produtos, pedidos ou outros itens, use o mecanismo multi-item do framework. Declare o contrato natural da operação (escalar ou coleção), use pre-validation para expandir/canonicalizar itens quando necessário e faça a tool MCP primária devolver `results[]` com `success`/`ok` por item. Multi-item não exige `array/list`. O runtime preserva sucesso parcial e produz `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`.
|
||||
|
||||
Guia copiável e completo neste template: [`docs/MCP_MULTI_ITEM.md`](docs/MCP_MULTI_ITEM.md).
|
||||
|
||||
|
||||
@@ -2,37 +2,69 @@
|
||||
|
||||
## 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:
|
||||
Use este guia quando uma única intenção do usuário precisa operar sobre **mais de um item do mesmo tipo** — por exemplo, cancelar vários VAS/produtos, devolver ou consultar vários pedidos, alterar vários ativos ou processar várias faturas/linhas/contratos.
|
||||
|
||||
- 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 suporte genérico para **resolução/canonicalização de múltiplos itens**, **confirmação transacional**, **execução MCP/workflow**, **preservação de evidência por item** e **normalização de `SUCCESS` / `PARTIAL_SUCCESS` / `FAILED`**. O desenvolvedor não deve criar um segundo motor de multi-item dentro do agente.
|
||||
|
||||
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 central:** multi-item é uma característica da resolução, execução e resultado da operação. **Não é definido pelo tipo do parâmetro de entrada.** Uma tool pode receber `subject: string`, `id: string`, `items: array` ou outro contrato natural de domínio e ainda assim processar múltiplos itens.
|
||||
|
||||
## 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.
|
||||
**Não faça loop de negócio no agente.** O agente seleciona a intenção/tool e usa o runtime. A expansão para múltiplos itens pode ocorrer no pre-validator, na própria tool MCP ou em um workflow de domínio.
|
||||
|
||||
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
|
||||
-> Runtime coleta os parâmetros declarados pela tool
|
||||
-> Entrada pode ser escalar ou coleção
|
||||
-> (opcional) pre-validation resolve/expande/canonicaliza N itens
|
||||
-> Runtime pede UMA confirmação coerente para o conjunto efetivo
|
||||
-> MCP tool/workflow processa 1 ou N itens
|
||||
-> Tool primária 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`
|
||||
## 1. Declare o contrato natural da operação em `tools.yaml`
|
||||
|
||||
O reconciliador transacional do framework aceita `type: array` ou `type: list`.
|
||||
O framework **não exige `array/list`** para que uma operação seja multi-item.
|
||||
|
||||
Use o contrato que faz sentido para a tool e para o domínio:
|
||||
|
||||
- **parâmetro escalar** (`subject`, `id`, `query`, etc.) quando a operação recebe uma expressão que pode representar uma ou várias entidades;
|
||||
- **`array/list`** quando a interface MCP é naturalmente batch e recebe explicitamente uma coleção.
|
||||
|
||||
### Padrão A — parâmetro escalar que pode representar vários itens
|
||||
|
||||
Este é o padrão usado pelo agente de Contas para cancelamento de VAS.
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
cancelar_vas_avulso:
|
||||
description: Cancela um ou mais serviços VAS.
|
||||
mcp_server: contas
|
||||
enabled: true
|
||||
tool_type: action
|
||||
args_schema:
|
||||
subject:
|
||||
type: string
|
||||
label: o serviço ou os serviços
|
||||
description: Serviço ou expressão com um ou mais serviços a cancelar.
|
||||
```
|
||||
|
||||
Entrada possível:
|
||||
|
||||
```text
|
||||
subject = "TIM Fashion, Aya Audiobooks, Neymar Jr"
|
||||
```
|
||||
|
||||
O `subject` continua sendo uma `string`. O suporte multi-item aparece posteriormente quando o validator/tool resolve essa expressão em itens independentes.
|
||||
|
||||
### Padrão B — coleção explícita
|
||||
|
||||
Também é válido quando a API/tool é naturalmente batch:
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
@@ -41,119 +73,156 @@ tools:
|
||||
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:
|
||||
Os dois padrões são suportados. **Não altere uma interface escalar existente para array apenas para “habilitar multi-item”.**
|
||||
|
||||
```text
|
||||
quero cancelar TIM Fashion, Aya Audiobooks e Neymar Jr
|
||||
```
|
||||
## 2. `tool_policies.yaml` não possui sintaxe especial de multi-item
|
||||
|
||||
para um argumento lógico semelhante a:
|
||||
`tool_policies.yaml` continua definindo tipo de operação, confirmação, parâmetros obrigatórios e pre-validation. Não é necessário criar `multi_item: true`, trocar `subject` por `items` ou introduzir outra configuração.
|
||||
|
||||
```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.
|
||||
Exemplo realista do agente de Contas:
|
||||
|
||||
```yaml
|
||||
version: 1
|
||||
defaults:
|
||||
operation_type: read_only
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_produtos:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [items]
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_produtos_cancelamento
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
O pre-validator pode retornar `eligible: true` e substituir argumentos por valores canônicos em `transaction_decision.resolved_arguments`:
|
||||
`requires: [subject]` significa apenas que a operação precisa saber **o que** será processado. Esse `subject` pode representar um item ou vários.
|
||||
|
||||
```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?"
|
||||
}
|
||||
}
|
||||
```
|
||||
## 3. Pre-validation pode transformar um parâmetro escalar em múltiplos itens
|
||||
|
||||
### 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`.
|
||||
Quando necessário, o pre-validator resolve nomes, canonicaliza entidades e pode materializar uma lista estrutural em `transaction_decision.resolved_arguments`.
|
||||
|
||||
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}
|
||||
"eligible": true,
|
||||
"subject": "TIM Fashion, Aya Audiobooks, Neymar Jr",
|
||||
"resolved_subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr",
|
||||
"resolved_subjects": [
|
||||
"TIM Fashion Mensal",
|
||||
"Aya Audiobooks Premium",
|
||||
"Neymar Jr"
|
||||
],
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr",
|
||||
"items": [
|
||||
{"name": "TIM Fashion Mensal"},
|
||||
{"name": "Aya Audiobooks Premium"},
|
||||
{"name": "Neymar Jr"}
|
||||
]
|
||||
},
|
||||
"target_tool": "cancelar_vas_avulso",
|
||||
"confirmation_message": "Você confirma o cancelamento de TIM Fashion Mensal, Aya Audiobooks Premium e Neymar Jr?"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`resolved_arguments.items[]` é uma representação canônica útil para a execução, mas **não obriga** `tools.yaml` a declarar `items` como parâmetro de entrada.
|
||||
|
||||
### Validação parcial e `fail_open: false`
|
||||
|
||||
`eligible` é global. Com `fail_open: false`, retornar `eligible: false` encerra a transação inteira.
|
||||
|
||||
Portanto, quando alguns itens são válidos e outros não:
|
||||
|
||||
1. mantenha `eligible: true` se existe pelo menos um item acionável;
|
||||
2. preserve o estado individual dos itens resolvidos/rejeitados;
|
||||
3. deixe a tool primária produzir o resultado terminal por item em `results[]`.
|
||||
|
||||
Exemplo conceitual:
|
||||
|
||||
```json
|
||||
{
|
||||
"eligible": true,
|
||||
"status": "PARTIALLY_ELIGIBLE",
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"items": [
|
||||
{"name": "TIM Fashion Mensal", "eligible": true},
|
||||
{"name": "Aya Audiobooks Premium", "eligible": false, "reason": "item_nao_encontrado"},
|
||||
{"name": "Neymar Jr", "eligible": true}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Se nenhum item puder ser processado, `eligible: false` é apropriado.
|
||||
|
||||
## 4. Contrato de entrada do MCP: uma operação lógica, não necessariamente uma coleção explícita
|
||||
|
||||
Prefira **uma única operação lógica MCP para o agente**. Ela pode ser implementada de três formas:
|
||||
|
||||
1. receber um parâmetro escalar e expandi-lo no validator/workflow;
|
||||
2. receber uma coleção explícita (`array/list`);
|
||||
3. receber uma expressão/identificador e descobrir os itens dentro da própria tool/workflow.
|
||||
|
||||
O agente não deve fazer fan-out chamando a mesma tool repetidamente por conta própria.
|
||||
|
||||
### Exemplo — escalar expandido
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "cancelar_vas_avulso",
|
||||
"arguments": {
|
||||
"subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Exemplo — batch explícito
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "cancelar_produtos",
|
||||
"arguments": {
|
||||
"items": [
|
||||
{"name": "A", "id": "1001"},
|
||||
{"name": "B", "id": "1002"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Se o backend só possui API unitária, o fan-out deve ficar encapsulado no **MCP Server ou workflow de domínio**, preservando uma única operação lógica para o agente.
|
||||
|
||||
## 5. Contrato obrigatório de resultado por item
|
||||
|
||||
Para o framework reconhecer automaticamente o resultado como multi-item, a **tool primária solicitada** deve expor uma lista `results` com **dois ou mais itens**, e cada item deve possuir `success` ou `ok` booleano.
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"subject": "TIM Fashion Mensal", "success": true},
|
||||
{"subject": "Aya Audiobooks Premium", "success": false, "reason": "not_found"},
|
||||
{"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 do normalizador é `success: true|false` ou `ok: true|false` por item.
|
||||
|
||||
### Workflow-backed tool
|
||||
|
||||
@@ -163,37 +232,20 @@ Se a tool é executada por workflow, o caminho preferencial e autoritativo é:
|
||||
workflow.output[tool_name].results
|
||||
```
|
||||
|
||||
Uma falha auxiliar posterior não deve apagar itens já concluídos pela tool primária.
|
||||
|
||||
## 6. O que o framework produz automaticamente
|
||||
|
||||
Com `results[]` válido, o runtime classifica o agregado como:
|
||||
|
||||
| Situação | `multi_item_status` |
|
||||
|---|---|
|
||||
| todos os itens concluíram | `SUCCESS` |
|
||||
| parte concluiu e parte falhou | `PARTIAL_SUCCESS` |
|
||||
| todos falharam | `FAILED` |
|
||||
|
||||
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",
|
||||
@@ -204,141 +256,178 @@ Com `results[]` válido, o runtime adiciona:
|
||||
"items_count": 3,
|
||||
"items_succeeded_count": 2,
|
||||
"items_failed_count": 1,
|
||||
"primary_tool": "cancelar_produtos"
|
||||
"primary_tool": "cancelar_vas_avulso"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Estados possíveis:
|
||||
Se um wrapper/etapa posterior retornar erro depois de existirem resultados terminais válidos, o framework preserva os sucessos individuais e pode registrar o erro como secundário, em vez de converter tudo para falha total.
|
||||
|
||||
| Situação | `multi_item_status` |
|
||||
|---|---|
|
||||
| todos os itens concluíram | `SUCCESS` |
|
||||
| parte concluiu e parte falhou | `PARTIAL_SUCCESS` |
|
||||
| todos falharam | `FAILED` |
|
||||
## 7. O que NÃO fazer
|
||||
|
||||
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:
|
||||
Não crie motor multi-item dentro do agente:
|
||||
|
||||
```python
|
||||
# NÃO: motor multi-item paralelo dentro do agente
|
||||
# NÃO
|
||||
for product in products:
|
||||
await tool_router.call("cancelar_produto", {"product": product})
|
||||
```
|
||||
|
||||
Não reduza resultado misto a um único booleano global sem evidência por item:
|
||||
|
||||
```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"
|
||||
}
|
||||
Também não altere uma interface existente apenas para trocar:
|
||||
|
||||
```text
|
||||
subject: string
|
||||
```
|
||||
|
||||
Esses formatos perdem a evidência terminal individual e podem fazer itens concluídos desaparecerem da composição final.
|
||||
por:
|
||||
|
||||
## 7. Exemplo completo: vários pedidos
|
||||
```text
|
||||
items: array
|
||||
```
|
||||
|
||||
quando o validator/workflow já consegue expandir corretamente o `subject`.
|
||||
|
||||
## 8. Exemplo completo — padrão escalar do Contas
|
||||
|
||||
### `tools.yaml`
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
cancelar_pedidos:
|
||||
description: Cancela um ou mais pedidos.
|
||||
mcp_server: retail
|
||||
cancelar_vas_avulso:
|
||||
description: Cancela um ou mais VAS.
|
||||
mcp_server: contas
|
||||
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?
|
||||
subject:
|
||||
type: string
|
||||
```
|
||||
|
||||
### Retorno MCP
|
||||
### `tool_policies.yaml`
|
||||
|
||||
```yaml
|
||||
tool_policies:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
### Validator
|
||||
|
||||
```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"}
|
||||
]
|
||||
"eligible": true,
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr",
|
||||
"items": [
|
||||
{"name": "TIM Fashion Mensal"},
|
||||
{"name": "Aya Audiobooks Premium"},
|
||||
{"name": "Neymar Jr"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
O framework classificará automaticamente como `PARTIAL_SUCCESS` e fornecerá ao LLM a instrução de relatar os resultados item a item.
|
||||
### Resultado MCP/workflow
|
||||
|
||||
## 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
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"subject": "TIM Fashion Mensal", "success": true},
|
||||
{"subject": "Aya Audiobooks Premium", "success": false, "reason": "not_found"},
|
||||
{"subject": "Neymar Jr", "success": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Resultado agregado: `PARTIAL_SUCCESS`.
|
||||
|
||||
## 9. Idempotência e protocolos
|
||||
## 9. Exemplo alternativo — API naturalmente batch
|
||||
|
||||
Quando o MCP já define coleção como contrato de entrada, `array/list` continua válido:
|
||||
|
||||
```yaml
|
||||
args_schema:
|
||||
order_ids:
|
||||
type: array
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"order_id": "P-100", "success": true},
|
||||
{"order_id": "P-101", "success": false, "reason": "already_shipped"},
|
||||
{"order_id": "P-102", "success": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
O ponto importante é: **array é uma opção de contrato, não o gatilho do mecanismo multi-item.**
|
||||
|
||||
## 10. Read-only, conversational e transactional podem ser multi-item
|
||||
|
||||
Multi-item é independente do `operation_type`.
|
||||
|
||||
É possível ter:
|
||||
|
||||
```text
|
||||
read_only + multi-item
|
||||
conversational + multi-item
|
||||
transactional + multi-item
|
||||
internal + multi-item
|
||||
```
|
||||
|
||||
O que muda é a política de confirmação e execução, não a capacidade de representar vários itens.
|
||||
|
||||
## 11. 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;
|
||||
- use chave de idempotência de requisição e, quando necessário, 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.
|
||||
- preserve itens já concluídos em retry;
|
||||
- não dependa apenas de mensagem textual agregada.
|
||||
|
||||
O framework preserva evidência; a política de idempotência do sistema de destino continua pertencendo ao MCP/workflow de domínio.
|
||||
A política específica de idempotência do sistema de destino continua pertencendo ao MCP/workflow de domínio.
|
||||
|
||||
## 10. Checklist para o desenvolvedor
|
||||
## 12. Checklist do 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.
|
||||
- [ ] O agente seleciona a operação e **não** implementa fan-out/loop MCP de negócio.
|
||||
- [ ] `tools.yaml` declara o contrato natural da operação, que pode ser escalar ou coleção.
|
||||
- [ ] Nenhuma chave especial `multi_item: true` foi criada ou é necessária.
|
||||
- [ ] `tool_policies.yaml` usa os `requires` reais do domínio; não é necessário trocar `subject` por `items`.
|
||||
- [ ] Se um escalar representar múltiplas entidades, validator/tool/workflow expande e canonicaliza os itens.
|
||||
- [ ] A confirmação representa o conjunto efetivo resolvido.
|
||||
- [ ] Com `fail_open: false`, um item inválido não força `eligible: false` global quando outros ainda são acionáveis.
|
||||
- [ ] A tool primária retorna `results[]` com `success` ou `ok` por item quando há múltiplos resultados.
|
||||
- [ ] Workflow usa `output[tool_name].results` como evidência terminal preferencial da tool primária.
|
||||
- [ ] 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`.
|
||||
- [ ] A apresentação final menciona sucessos e falhas individualmente quando houver `PARTIAL_SUCCESS`.
|
||||
|
||||
## 11. Onde o comportamento existe no framework
|
||||
|
||||
Referências atuais:
|
||||
## 13. Onde o comportamento existe no framework
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py
|
||||
- coerção de type=array/list
|
||||
- suporte existente a coerção de array/list quando esse for o contrato declarado
|
||||
|
||||
libs/agent_framework/src/agent_framework/runtime/agent_runtime.py
|
||||
- pre-validation e resolved_arguments
|
||||
@@ -350,14 +439,14 @@ tests/test_multi_item_result_normalization.py
|
||||
- regressões de SUCCESS/PARTIAL_SUCCESS e proteção contra falha auxiliar
|
||||
```
|
||||
|
||||
## 12. Resumo de responsabilidade
|
||||
## 14. 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 |
|
||||
| Agente | selecionar intenção/tool e usar o runtime; não implementar motor multi-item |
|
||||
| `tools.yaml` | declarar o contrato natural da operação; pode ser escalar ou coleção |
|
||||
| `tool_policies.yaml` | confirmação, `requires` e pre-validation; sem sintaxe especial de multi-item |
|
||||
| Pre-validator MCP | identificar, resolver, canonicalizar e, quando necessário, expandir 1 parâmetro em N itens |
|
||||
| Tool MCP/workflow | executar 1 ou N itens e produzir resultado por item |
|
||||
| Framework runtime | manter estado/confirmação, interpretar `results[]`, preservar evidência e calcular `SUCCESS` / `PARTIAL_SUCCESS` / `FAILED` |
|
||||
| LLM/presentation | transformar evidências em resposta sem apagar sucesso/falha individual |
|
||||
|
||||
@@ -91,4 +91,4 @@ O arquivo `config/tool_policies.yaml` classifica tools como `read_only` ou `tran
|
||||
|
||||
## MCP multi-item
|
||||
|
||||
Para listas de produtos/pedidos/itens, não crie loop MCP no agente. O runtime aceita parâmetros `array/list` e normaliza `results[]` por item em `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`. Consulte [`docs/MCP_MULTI_ITEM.md`](docs/MCP_MULTI_ITEM.md) antes de criar qualquer mecanismo próprio de lote.
|
||||
Para listas de produtos/pedidos/itens, não crie loop MCP no agente. O runtime aceita contratos escalares ou coleções e normaliza `results[]` por item em `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`; `array/list` não é requisito para multi-item. Consulte [`docs/MCP_MULTI_ITEM.md`](docs/MCP_MULTI_ITEM.md) antes de criar qualquer mecanismo próprio de lote.
|
||||
|
||||
@@ -2,37 +2,69 @@
|
||||
|
||||
## 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:
|
||||
Use este guia quando uma única intenção do usuário precisa operar sobre **mais de um item do mesmo tipo** — por exemplo, cancelar vários VAS/produtos, devolver ou consultar vários pedidos, alterar vários ativos ou processar várias faturas/linhas/contratos.
|
||||
|
||||
- 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 suporte genérico para **resolução/canonicalização de múltiplos itens**, **confirmação transacional**, **execução MCP/workflow**, **preservação de evidência por item** e **normalização de `SUCCESS` / `PARTIAL_SUCCESS` / `FAILED`**. O desenvolvedor não deve criar um segundo motor de multi-item dentro do agente.
|
||||
|
||||
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 central:** multi-item é uma característica da resolução, execução e resultado da operação. **Não é definido pelo tipo do parâmetro de entrada.** Uma tool pode receber `subject: string`, `id: string`, `items: array` ou outro contrato natural de domínio e ainda assim processar múltiplos itens.
|
||||
|
||||
## 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.
|
||||
**Não faça loop de negócio no agente.** O agente seleciona a intenção/tool e usa o runtime. A expansão para múltiplos itens pode ocorrer no pre-validator, na própria tool MCP ou em um workflow de domínio.
|
||||
|
||||
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
|
||||
-> Runtime coleta os parâmetros declarados pela tool
|
||||
-> Entrada pode ser escalar ou coleção
|
||||
-> (opcional) pre-validation resolve/expande/canonicaliza N itens
|
||||
-> Runtime pede UMA confirmação coerente para o conjunto efetivo
|
||||
-> MCP tool/workflow processa 1 ou N itens
|
||||
-> Tool primária 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`
|
||||
## 1. Declare o contrato natural da operação em `tools.yaml`
|
||||
|
||||
O reconciliador transacional do framework aceita `type: array` ou `type: list`.
|
||||
O framework **não exige `array/list`** para que uma operação seja multi-item.
|
||||
|
||||
Use o contrato que faz sentido para a tool e para o domínio:
|
||||
|
||||
- **parâmetro escalar** (`subject`, `id`, `query`, etc.) quando a operação recebe uma expressão que pode representar uma ou várias entidades;
|
||||
- **`array/list`** quando a interface MCP é naturalmente batch e recebe explicitamente uma coleção.
|
||||
|
||||
### Padrão A — parâmetro escalar que pode representar vários itens
|
||||
|
||||
Este é o padrão usado pelo agente de Contas para cancelamento de VAS.
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
cancelar_vas_avulso:
|
||||
description: Cancela um ou mais serviços VAS.
|
||||
mcp_server: contas
|
||||
enabled: true
|
||||
tool_type: action
|
||||
args_schema:
|
||||
subject:
|
||||
type: string
|
||||
label: o serviço ou os serviços
|
||||
description: Serviço ou expressão com um ou mais serviços a cancelar.
|
||||
```
|
||||
|
||||
Entrada possível:
|
||||
|
||||
```text
|
||||
subject = "TIM Fashion, Aya Audiobooks, Neymar Jr"
|
||||
```
|
||||
|
||||
O `subject` continua sendo uma `string`. O suporte multi-item aparece posteriormente quando o validator/tool resolve essa expressão em itens independentes.
|
||||
|
||||
### Padrão B — coleção explícita
|
||||
|
||||
Também é válido quando a API/tool é naturalmente batch:
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
@@ -41,119 +73,156 @@ tools:
|
||||
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:
|
||||
Os dois padrões são suportados. **Não altere uma interface escalar existente para array apenas para “habilitar multi-item”.**
|
||||
|
||||
```text
|
||||
quero cancelar TIM Fashion, Aya Audiobooks e Neymar Jr
|
||||
```
|
||||
## 2. `tool_policies.yaml` não possui sintaxe especial de multi-item
|
||||
|
||||
para um argumento lógico semelhante a:
|
||||
`tool_policies.yaml` continua definindo tipo de operação, confirmação, parâmetros obrigatórios e pre-validation. Não é necessário criar `multi_item: true`, trocar `subject` por `items` ou introduzir outra configuração.
|
||||
|
||||
```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.
|
||||
Exemplo realista do agente de Contas:
|
||||
|
||||
```yaml
|
||||
version: 1
|
||||
defaults:
|
||||
operation_type: read_only
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
cancelar_produtos:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires: [items]
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_produtos_cancelamento
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
O pre-validator pode retornar `eligible: true` e substituir argumentos por valores canônicos em `transaction_decision.resolved_arguments`:
|
||||
`requires: [subject]` significa apenas que a operação precisa saber **o que** será processado. Esse `subject` pode representar um item ou vários.
|
||||
|
||||
```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?"
|
||||
}
|
||||
}
|
||||
```
|
||||
## 3. Pre-validation pode transformar um parâmetro escalar em múltiplos itens
|
||||
|
||||
### 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`.
|
||||
Quando necessário, o pre-validator resolve nomes, canonicaliza entidades e pode materializar uma lista estrutural em `transaction_decision.resolved_arguments`.
|
||||
|
||||
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}
|
||||
"eligible": true,
|
||||
"subject": "TIM Fashion, Aya Audiobooks, Neymar Jr",
|
||||
"resolved_subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr",
|
||||
"resolved_subjects": [
|
||||
"TIM Fashion Mensal",
|
||||
"Aya Audiobooks Premium",
|
||||
"Neymar Jr"
|
||||
],
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr",
|
||||
"items": [
|
||||
{"name": "TIM Fashion Mensal"},
|
||||
{"name": "Aya Audiobooks Premium"},
|
||||
{"name": "Neymar Jr"}
|
||||
]
|
||||
},
|
||||
"target_tool": "cancelar_vas_avulso",
|
||||
"confirmation_message": "Você confirma o cancelamento de TIM Fashion Mensal, Aya Audiobooks Premium e Neymar Jr?"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`resolved_arguments.items[]` é uma representação canônica útil para a execução, mas **não obriga** `tools.yaml` a declarar `items` como parâmetro de entrada.
|
||||
|
||||
### Validação parcial e `fail_open: false`
|
||||
|
||||
`eligible` é global. Com `fail_open: false`, retornar `eligible: false` encerra a transação inteira.
|
||||
|
||||
Portanto, quando alguns itens são válidos e outros não:
|
||||
|
||||
1. mantenha `eligible: true` se existe pelo menos um item acionável;
|
||||
2. preserve o estado individual dos itens resolvidos/rejeitados;
|
||||
3. deixe a tool primária produzir o resultado terminal por item em `results[]`.
|
||||
|
||||
Exemplo conceitual:
|
||||
|
||||
```json
|
||||
{
|
||||
"eligible": true,
|
||||
"status": "PARTIALLY_ELIGIBLE",
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"items": [
|
||||
{"name": "TIM Fashion Mensal", "eligible": true},
|
||||
{"name": "Aya Audiobooks Premium", "eligible": false, "reason": "item_nao_encontrado"},
|
||||
{"name": "Neymar Jr", "eligible": true}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Se nenhum item puder ser processado, `eligible: false` é apropriado.
|
||||
|
||||
## 4. Contrato de entrada do MCP: uma operação lógica, não necessariamente uma coleção explícita
|
||||
|
||||
Prefira **uma única operação lógica MCP para o agente**. Ela pode ser implementada de três formas:
|
||||
|
||||
1. receber um parâmetro escalar e expandi-lo no validator/workflow;
|
||||
2. receber uma coleção explícita (`array/list`);
|
||||
3. receber uma expressão/identificador e descobrir os itens dentro da própria tool/workflow.
|
||||
|
||||
O agente não deve fazer fan-out chamando a mesma tool repetidamente por conta própria.
|
||||
|
||||
### Exemplo — escalar expandido
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "cancelar_vas_avulso",
|
||||
"arguments": {
|
||||
"subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Exemplo — batch explícito
|
||||
|
||||
```json
|
||||
{
|
||||
"tool_name": "cancelar_produtos",
|
||||
"arguments": {
|
||||
"items": [
|
||||
{"name": "A", "id": "1001"},
|
||||
{"name": "B", "id": "1002"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Se o backend só possui API unitária, o fan-out deve ficar encapsulado no **MCP Server ou workflow de domínio**, preservando uma única operação lógica para o agente.
|
||||
|
||||
## 5. Contrato obrigatório de resultado por item
|
||||
|
||||
Para o framework reconhecer automaticamente o resultado como multi-item, a **tool primária solicitada** deve expor uma lista `results` com **dois ou mais itens**, e cada item deve possuir `success` ou `ok` booleano.
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"subject": "TIM Fashion Mensal", "success": true},
|
||||
{"subject": "Aya Audiobooks Premium", "success": false, "reason": "not_found"},
|
||||
{"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 do normalizador é `success: true|false` ou `ok: true|false` por item.
|
||||
|
||||
### Workflow-backed tool
|
||||
|
||||
@@ -163,37 +232,20 @@ Se a tool é executada por workflow, o caminho preferencial e autoritativo é:
|
||||
workflow.output[tool_name].results
|
||||
```
|
||||
|
||||
Uma falha auxiliar posterior não deve apagar itens já concluídos pela tool primária.
|
||||
|
||||
## 6. O que o framework produz automaticamente
|
||||
|
||||
Com `results[]` válido, o runtime classifica o agregado como:
|
||||
|
||||
| Situação | `multi_item_status` |
|
||||
|---|---|
|
||||
| todos os itens concluíram | `SUCCESS` |
|
||||
| parte concluiu e parte falhou | `PARTIAL_SUCCESS` |
|
||||
| todos falharam | `FAILED` |
|
||||
|
||||
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",
|
||||
@@ -204,141 +256,178 @@ Com `results[]` válido, o runtime adiciona:
|
||||
"items_count": 3,
|
||||
"items_succeeded_count": 2,
|
||||
"items_failed_count": 1,
|
||||
"primary_tool": "cancelar_produtos"
|
||||
"primary_tool": "cancelar_vas_avulso"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Estados possíveis:
|
||||
Se um wrapper/etapa posterior retornar erro depois de existirem resultados terminais válidos, o framework preserva os sucessos individuais e pode registrar o erro como secundário, em vez de converter tudo para falha total.
|
||||
|
||||
| Situação | `multi_item_status` |
|
||||
|---|---|
|
||||
| todos os itens concluíram | `SUCCESS` |
|
||||
| parte concluiu e parte falhou | `PARTIAL_SUCCESS` |
|
||||
| todos falharam | `FAILED` |
|
||||
## 7. O que NÃO fazer
|
||||
|
||||
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:
|
||||
Não crie motor multi-item dentro do agente:
|
||||
|
||||
```python
|
||||
# NÃO: motor multi-item paralelo dentro do agente
|
||||
# NÃO
|
||||
for product in products:
|
||||
await tool_router.call("cancelar_produto", {"product": product})
|
||||
```
|
||||
|
||||
Não reduza resultado misto a um único booleano global sem evidência por item:
|
||||
|
||||
```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"
|
||||
}
|
||||
Também não altere uma interface existente apenas para trocar:
|
||||
|
||||
```text
|
||||
subject: string
|
||||
```
|
||||
|
||||
Esses formatos perdem a evidência terminal individual e podem fazer itens concluídos desaparecerem da composição final.
|
||||
por:
|
||||
|
||||
## 7. Exemplo completo: vários pedidos
|
||||
```text
|
||||
items: array
|
||||
```
|
||||
|
||||
quando o validator/workflow já consegue expandir corretamente o `subject`.
|
||||
|
||||
## 8. Exemplo completo — padrão escalar do Contas
|
||||
|
||||
### `tools.yaml`
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
cancelar_pedidos:
|
||||
description: Cancela um ou mais pedidos.
|
||||
mcp_server: retail
|
||||
cancelar_vas_avulso:
|
||||
description: Cancela um ou mais VAS.
|
||||
mcp_server: contas
|
||||
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?
|
||||
subject:
|
||||
type: string
|
||||
```
|
||||
|
||||
### Retorno MCP
|
||||
### `tool_policies.yaml`
|
||||
|
||||
```yaml
|
||||
tool_policies:
|
||||
cancelar_vas_avulso:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
requires:
|
||||
- subject
|
||||
pre_validation:
|
||||
enabled: true
|
||||
tool: validar_vas_subject
|
||||
fail_open: false
|
||||
```
|
||||
|
||||
### Validator
|
||||
|
||||
```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"}
|
||||
]
|
||||
"eligible": true,
|
||||
"transaction_decision": {
|
||||
"resolved_arguments": {
|
||||
"subject": "TIM Fashion Mensal, Aya Audiobooks Premium, Neymar Jr",
|
||||
"items": [
|
||||
{"name": "TIM Fashion Mensal"},
|
||||
{"name": "Aya Audiobooks Premium"},
|
||||
{"name": "Neymar Jr"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
O framework classificará automaticamente como `PARTIAL_SUCCESS` e fornecerá ao LLM a instrução de relatar os resultados item a item.
|
||||
### Resultado MCP/workflow
|
||||
|
||||
## 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
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"subject": "TIM Fashion Mensal", "success": true},
|
||||
{"subject": "Aya Audiobooks Premium", "success": false, "reason": "not_found"},
|
||||
{"subject": "Neymar Jr", "success": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Resultado agregado: `PARTIAL_SUCCESS`.
|
||||
|
||||
## 9. Idempotência e protocolos
|
||||
## 9. Exemplo alternativo — API naturalmente batch
|
||||
|
||||
Quando o MCP já define coleção como contrato de entrada, `array/list` continua válido:
|
||||
|
||||
```yaml
|
||||
args_schema:
|
||||
order_ids:
|
||||
type: array
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{"order_id": "P-100", "success": true},
|
||||
{"order_id": "P-101", "success": false, "reason": "already_shipped"},
|
||||
{"order_id": "P-102", "success": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
O ponto importante é: **array é uma opção de contrato, não o gatilho do mecanismo multi-item.**
|
||||
|
||||
## 10. Read-only, conversational e transactional podem ser multi-item
|
||||
|
||||
Multi-item é independente do `operation_type`.
|
||||
|
||||
É possível ter:
|
||||
|
||||
```text
|
||||
read_only + multi-item
|
||||
conversational + multi-item
|
||||
transactional + multi-item
|
||||
internal + multi-item
|
||||
```
|
||||
|
||||
O que muda é a política de confirmação e execução, não a capacidade de representar vários itens.
|
||||
|
||||
## 11. 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;
|
||||
- use chave de idempotência de requisição e, quando necessário, 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.
|
||||
- preserve itens já concluídos em retry;
|
||||
- não dependa apenas de mensagem textual agregada.
|
||||
|
||||
O framework preserva evidência; a política de idempotência do sistema de destino continua pertencendo ao MCP/workflow de domínio.
|
||||
A política específica de idempotência do sistema de destino continua pertencendo ao MCP/workflow de domínio.
|
||||
|
||||
## 10. Checklist para o desenvolvedor
|
||||
## 12. Checklist do 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.
|
||||
- [ ] O agente seleciona a operação e **não** implementa fan-out/loop MCP de negócio.
|
||||
- [ ] `tools.yaml` declara o contrato natural da operação, que pode ser escalar ou coleção.
|
||||
- [ ] Nenhuma chave especial `multi_item: true` foi criada ou é necessária.
|
||||
- [ ] `tool_policies.yaml` usa os `requires` reais do domínio; não é necessário trocar `subject` por `items`.
|
||||
- [ ] Se um escalar representar múltiplas entidades, validator/tool/workflow expande e canonicaliza os itens.
|
||||
- [ ] A confirmação representa o conjunto efetivo resolvido.
|
||||
- [ ] Com `fail_open: false`, um item inválido não força `eligible: false` global quando outros ainda são acionáveis.
|
||||
- [ ] A tool primária retorna `results[]` com `success` ou `ok` por item quando há múltiplos resultados.
|
||||
- [ ] Workflow usa `output[tool_name].results` como evidência terminal preferencial da tool primária.
|
||||
- [ ] 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`.
|
||||
- [ ] A apresentação final menciona sucessos e falhas individualmente quando houver `PARTIAL_SUCCESS`.
|
||||
|
||||
## 11. Onde o comportamento existe no framework
|
||||
|
||||
Referências atuais:
|
||||
## 13. Onde o comportamento existe no framework
|
||||
|
||||
```text
|
||||
libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py
|
||||
- coerção de type=array/list
|
||||
- suporte existente a coerção de array/list quando esse for o contrato declarado
|
||||
|
||||
libs/agent_framework/src/agent_framework/runtime/agent_runtime.py
|
||||
- pre-validation e resolved_arguments
|
||||
@@ -350,14 +439,14 @@ tests/test_multi_item_result_normalization.py
|
||||
- regressões de SUCCESS/PARTIAL_SUCCESS e proteção contra falha auxiliar
|
||||
```
|
||||
|
||||
## 12. Resumo de responsabilidade
|
||||
## 14. 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 |
|
||||
| Agente | selecionar intenção/tool e usar o runtime; não implementar motor multi-item |
|
||||
| `tools.yaml` | declarar o contrato natural da operação; pode ser escalar ou coleção |
|
||||
| `tool_policies.yaml` | confirmação, `requires` e pre-validation; sem sintaxe especial de multi-item |
|
||||
| Pre-validator MCP | identificar, resolver, canonicalizar e, quando necessário, expandir 1 parâmetro em N itens |
|
||||
| Tool MCP/workflow | executar 1 ou N itens e produzir resultado por item |
|
||||
| Framework runtime | manter estado/confirmação, interpretar `results[]`, preservar evidência e calcular `SUCCESS` / `PARTIAL_SUCCESS` / `FAILED` |
|
||||
| LLM/presentation | transformar evidências em resposta sem apagar sucesso/falha individual |
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,8 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
def test_multi_item_guide_documents_runtime_contract():
|
||||
text = (ROOT / "docs" / "MCP_MULTI_ITEM_DEVELOPER_GUIDE.md").read_text(encoding="utf-8")
|
||||
for token in (
|
||||
"type: array",
|
||||
"não exige `array/list`",
|
||||
"subject",
|
||||
"resolved_arguments",
|
||||
"results[]",
|
||||
"PARTIAL_SUCCESS",
|
||||
|
||||
Reference in New Issue
Block a user