From 2ad9afd9745348c2a8e5779e7abe4dfd42c00ee5 Mon Sep 17 00:00:00 2001 From: "cristiano.hoshikawa" Date: Fri, 18 Sep 2026 23:35:05 -0300 Subject: [PATCH] Multi-item documentation --- agent_framework_oci/README.md | 1 + agent_framework_oci/README_en.md | 1 + .../IMPLEMENTACAO_MULTI_ITEM_MCP.md | 146 ++--- .../Multi_Item_MCP/README.md | 142 ++--- .../Multi_Item_MCP/README_en.md | 67 +-- .../docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md | 511 ++++++++++-------- .../docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md | 290 ++++------ .../04_mcp_integration_tools_and_policies.md | 2 +- .../developer/en/INDEX_DEVELOPER_GUIDE.md | 2 +- .../04_mcp_integration_tools_and_policies.md | 2 +- .../developer/pt/INDEX_DEVELOPER_GUIDE.md | 2 +- .../specs/SPEC-010-Agent-Development.md | 4 +- .../agent_template_backend/README.md | 2 +- .../docs/MCP_MULTI_ITEM.md | 511 ++++++++++-------- .../README_DAY_ZERO.md | 2 +- .../docs/MCP_MULTI_ITEM.md | 511 ++++++++++-------- ...documentation.cpython-313-pytest-9.0.2.pyc | Bin 8387 -> 8423 bytes ...normalization.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 8923 bytes .../tests/test_multi_item_documentation.py | 3 +- 19 files changed, 1156 insertions(+), 1043 deletions(-) create mode 100644 agent_framework_oci/tests/__pycache__/test_multi_item_result_normalization.cpython-313-pytest-9.0.2.pyc diff --git a/agent_framework_oci/README.md b/agent_framework_oci/README.md index 693f51c5..ba6739e7 100644 --- a/agent_framework_oci/README.md +++ b/agent_framework_oci/README.md @@ -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. diff --git a/agent_framework_oci/README_en.md b/agent_framework_oci/README_en.md index 78accc5c..43423c7a 100644 --- a/agent_framework_oci/README_en.md +++ b/agent_framework_oci/README_en.md @@ -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. diff --git a/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/IMPLEMENTACAO_MULTI_ITEM_MCP.md b/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/IMPLEMENTACAO_MULTI_ITEM_MCP.md index 8c0662cc..4dfd01f3 100644 --- a/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/IMPLEMENTACAO_MULTI_ITEM_MCP.md +++ b/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/IMPLEMENTACAO_MULTI_ITEM_MCP.md @@ -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 diff --git a/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/README.md b/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/README.md index 629eb417..27027476 100644 --- a/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/README.md +++ b/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/README.md @@ -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`. diff --git a/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/README_en.md b/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/README_en.md index afe5a5e7..572f5259 100644 --- a/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/README_en.md +++ b/agent_framework_oci/Tuning-Performance/Multi_Item_MCP/README_en.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`. diff --git a/agent_framework_oci/docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md b/agent_framework_oci/docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md index 6d5244f3..af8ca5d0 100644 --- a/agent_framework_oci/docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md +++ b/agent_framework_oci/docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.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 | diff --git a/agent_framework_oci/docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md b/agent_framework_oci/docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md index a032f42a..17ffa08e 100644 --- a/agent_framework_oci/docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md +++ b/agent_framework_oci/docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md @@ -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 | diff --git a/agent_framework_oci/docs/developer/en/04_mcp_integration_tools_and_policies.md b/agent_framework_oci/docs/developer/en/04_mcp_integration_tools_and_policies.md index d5d433e6..256d39ba 100644 --- a/agent_framework_oci/docs/developer/en/04_mcp_integration_tools_and_policies.md +++ b/agent_framework_oci/docs/developer/en/04_mcp_integration_tools_and_policies.md @@ -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: diff --git a/agent_framework_oci/docs/developer/en/INDEX_DEVELOPER_GUIDE.md b/agent_framework_oci/docs/developer/en/INDEX_DEVELOPER_GUIDE.md index b803ac5c..5545308e 100644 --- a/agent_framework_oci/docs/developer/en/INDEX_DEVELOPER_GUIDE.md +++ b/agent_framework_oci/docs/developer/en/INDEX_DEVELOPER_GUIDE.md @@ -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) | diff --git a/agent_framework_oci/docs/developer/pt/04_mcp_integration_tools_and_policies.md b/agent_framework_oci/docs/developer/pt/04_mcp_integration_tools_and_policies.md index d9942074..2c2effec 100644 --- a/agent_framework_oci/docs/developer/pt/04_mcp_integration_tools_and_policies.md +++ b/agent_framework_oci/docs/developer/pt/04_mcp_integration_tools_and_policies.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: diff --git a/agent_framework_oci/docs/developer/pt/INDEX_DEVELOPER_GUIDE.md b/agent_framework_oci/docs/developer/pt/INDEX_DEVELOPER_GUIDE.md index 67d0cfa8..c2614864 100644 --- a/agent_framework_oci/docs/developer/pt/INDEX_DEVELOPER_GUIDE.md +++ b/agent_framework_oci/docs/developer/pt/INDEX_DEVELOPER_GUIDE.md @@ -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) | diff --git a/agent_framework_oci/specs/SPEC-010-Agent-Development.md b/agent_framework_oci/specs/SPEC-010-Agent-Development.md index 65b37c9e..597e15b6 100644 --- a/agent_framework_oci/specs/SPEC-010-Agent-Development.md +++ b/agent_framework_oci/specs/SPEC-010-Agent-Development.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. diff --git a/agent_framework_oci/templates/agent_template_backend/README.md b/agent_framework_oci/templates/agent_template_backend/README.md index 055597a0..dcfc1558 100644 --- a/agent_framework_oci/templates/agent_template_backend/README.md +++ b/agent_framework_oci/templates/agent_template_backend/README.md @@ -175,7 +175,7 @@ config/agents//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). diff --git a/agent_framework_oci/templates/agent_template_backend/docs/MCP_MULTI_ITEM.md b/agent_framework_oci/templates/agent_template_backend/docs/MCP_MULTI_ITEM.md index 6d5244f3..af8ca5d0 100644 --- a/agent_framework_oci/templates/agent_template_backend/docs/MCP_MULTI_ITEM.md +++ b/agent_framework_oci/templates/agent_template_backend/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 | diff --git a/agent_framework_oci/templates/agent_template_backend_day_zero/README_DAY_ZERO.md b/agent_framework_oci/templates/agent_template_backend_day_zero/README_DAY_ZERO.md index 2e9f3349..2332b9b6 100644 --- a/agent_framework_oci/templates/agent_template_backend_day_zero/README_DAY_ZERO.md +++ b/agent_framework_oci/templates/agent_template_backend_day_zero/README_DAY_ZERO.md @@ -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. diff --git a/agent_framework_oci/templates/agent_template_backend_day_zero/docs/MCP_MULTI_ITEM.md b/agent_framework_oci/templates/agent_template_backend_day_zero/docs/MCP_MULTI_ITEM.md index 6d5244f3..af8ca5d0 100644 --- a/agent_framework_oci/templates/agent_template_backend_day_zero/docs/MCP_MULTI_ITEM.md +++ b/agent_framework_oci/templates/agent_template_backend_day_zero/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 | diff --git a/agent_framework_oci/tests/__pycache__/test_multi_item_documentation.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_multi_item_documentation.cpython-313-pytest-9.0.2.pyc index e4de9c84118a4b21e2b7c9f65f4415d3ea690164..ba8134dc190895a62831518aa91a9b7a8684c874 100644 GIT binary patch delta 586 zcma*kJxtp`6bEpQU?(JT$OqvgIEf)&O)KF-p<;mYF|e>R5=?Rgqu55zXDR8@p$jXz zvFg+bsU0dav#_wSRvuZ9U_r^$slAtlj=kZhcjx~*J)Lj1AGVc&qC`i;`rQ9FH)Yt%Vv=r zGKUlq70Dw7K9N+@8kX~ddJf)Vz2I*oZ9*#idum5I5GPrGkm`p>?c#dY|C4=`NS!~< zZIT3kt?E-tBJeR`xGn{zRbHL-R_iV-b4RVpalB38U(`}LiDgOyWIe4v@8l4 z{4j+~^Lw>M6duhlfBK){u}4FOv&X=|x`|L^57`w2v8}US zd{gb5mOm;ack>p5T+oLc@%kpn`yoM5PY8W`e8T> zBX~>+OVC*Whw4VkO95jx{+`*AK8T%iI;UUdSo`F>mNu)G5?e;ic*9hDYdF;v5jfI# z9D9&ht>tLE)`?*i(P+yB>Nbtvjiz0svLwjVWE2d7gC2O?^*zw(ph8qpHka5mcFkwI z5B#TlvlrkaHR%&Bu}AFSsabE)ccDQ%A)11iX0z@V*!a(UB^`*}dfKgDFug6xPBByZ zqros41TidP(R$8mxNUWx-pErO5q{j|{!*)-)sH15vS>+mw28b@Y}u(}$+n`OMouibqBd}=6a>9X$(!tw z!Xwv?d@_n2oLdhK;9CQ8ldxcPrT>}?` zMnEJNc^VS&PT-aB#R!c6AC!10lnzMYUh+nOMx{tEp)n~6G%m$}3Q`N`wWez3Qb{xOMfFntt-Ln(S*M|9buFjR4bd>wb#Yb67t~xTUqzB>e5Y24s+k zFdcmK%l(9CTw_WZZV4xR;E;$rHTQa*yaPtQbc7HA##-ZE2(EHqhlo+iW>v#j0G_|9 z>jf*grkZG=^q9rxO|@tQU~EyeemYvz%+Z`;Dx<|x!OY8eQ_ktx)%;td3YuPCrAkq~ zq0={IJ)0jjRl_tGm3udbg)Ke6}_?Q68N5p!2^5 z4_ZHaCIxRx1z@d+J*Y9~2q;<__$c0N_7;qc3uY@|g$+|NONJHJOXhmXv|7B$**8)l zD`tY>WDQ=PhH#Lu{RB3!6)mc!f@Xsy8dM5}RL}}ByE0v-W8fDQMUl2*@79B_PY;6_ z-@?FEd5hfZe74@)davuqCt)HERt6UT(peo?toAR~`-dKcg2Tz(C>b4pPpu5hJP^W% znjeH>?eX0Xk~~!JJhmy6JH{XMwKRoyNlPfa2P2X!68G@7!0&;3hcs521Y1E0!qSGV zDTQH?2c@Vj84XIYGn__NaH+6k38#DD7$8zSLryt58g4BC&7}pTfPNwqe3j@k!%m-^ zPRI&=7j`V+r#W!IXiXV@oRbn)2FC-Zoag!aFqgEy(*Vf?-2L#Snn71nYEB1^!(6nG z)FQQJLSr0jg;x^p=)SR9u05n4saJYRic3$Y*BOabr{aBa5LK_QO@eAu4h=^r{T5U3**#dyub(+ntn$%eA3qQq*RV&()~Jd5#()efHxz_KX=IxZ+0L(E$dD@c_aw3odl3@zK=V9XL4 zjg$Vy3AQ*7=b`FGQO@<{@ivhW;}!hKu1 z>zA_rsLP~mAg0|QUcXPVACvDklcuC=!Sm(e^JN}sML1vMopT!`XhUr7`1mT*^wc~C zw)ys%GT(P6WD}vU#yjUW-*@|r-P&oeRn+x#JO(r^Ldp zMMn@Voe6w}_$YcHK8iWN@Lw-JiZc`_eCsH%$%~H?QuDVGAGJ7kX#JWL%dAYV!l+o8Cy^{0QQL&Y3V5F%|n7yR7wVyWU_DewRG{a@?ix(?||LU8Of@-ZWKBzPced zKrLcW-2fOYBM2^0m13?Qr)>NK(6oc$hVL#{qA%7vM&9eFM9F)~0m6J9~I=PuD^FS)X$r|sR+x$s}k+8YrS4i`QGXY5RMy|9r(!5f1#Y^ulVJYCiG2m5b&cdA>M2laVqUGGw ztUEUaB1m5^OwAuSAhGN9TI3)Z9{a{;<&|+BYgS$_m{km_Pj>n`C<%}<4?5{S?qiTSa6VkG(v3p5{^BMWCn=UgkkR0QXUFX8q{X&`jFKv8`tzwA;&^% zd99#dg`5Mc%gkcWIV3*>vK3-8cqn{+-X9mAq?bU|z@OBILSrMZtrb+e1ZD^LPzi%* z82xPanxd_#5UuA8nA{pT+M)tA$*e(T80tKZ`AZ4?O;B^!VovhRuz-5-^A=-g%_OS$g4<1mz;l9*0@NTaquNLG=2!Rn2 z%r!Lca}6ykQCDZla}urud%4A1&owlj@<>(~ zy}&iJN^PENNJ{NK*U;wLYo+m)NmtgmexYmVSYWPTj^Yxs;<|ZFr7WLD(ee1*#t^nU zf!u6&@@^t#&fH=>PkT@S_o3}P7}R3OY1xyZL!lWYSCBCG5ogJ{1D=Ea81MfC2`*5& z-!ahFL3VJpWF>buz(wf0&W*cJ_ChYAq*f(d{nON zJNFc+gcWstFte-GBimWdw4IK`+CEhgbV7#H6JZ6O$(TR4jvn~ufi$)Mg(GdtotFJA zu@cTQj0~bOhf2}3TZFkTJ9`