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