Erros estruturados em tool_result e MCP
Objetivos de aprendizagem
- Sinalizar falhas com
is_error: truenotool_resultda Claude API e com o flagisErrorno MCP. - Classificar erros em transient, validation, business e permission — e decidir quais são retryable.
- Explicar por que respostas genéricas ("Operation failed") impedem o agente de tomar decisões de recuperação.
- Distinguir falha de acesso (exige decisão de retry) de resultado vazio válido (consulta bem-sucedida sem matches).
- Decidir entre recuperação local no subagente e propagação ao coordenador com resultados parciais.
Sinalizando erro: is_error (API) e isError (MCP)
Na Claude API, o resultado de uma ferramenta que falhou volta como tool_result com is_error: true e uma mensagem informativa — assim o modelo reconhece a falha e tenta outra abordagem ou pede esclarecimento:
{
"type": "tool_result",
"tool_use_id": "toolu_01ABC",
"content": "Error: cidade 'xyz' nao encontrada. Informe um nome de cidade valido.",
"is_error": true
}
No MCP, o padrão equivalente é o flag isError no resultado da tool — a falha volta como dado para o agente raciocinar, em vez de explodir como exceção de protocolo:
{
"content": [
{
"type": "text",
"text": "{\"errorCategory\": \"transient\", \"isRetryable\": true, \"message\": \"Upstream API timeout apos 10s\"}"
}
],
"isError": true
}
Categorias de erro e retryability
| Categoria | Exemplos | Retryable? | Ação esperada do agente |
|---|---|---|---|
transient | Timeout, serviço indisponível, 429/529 | Sim (com backoff) | Repetir; se persistir, tentar alternativa |
validation | Entrada inválida, formato errado | Não (igual); sim com entrada corrigida | Corrigir o input e chamar de novo |
business | Violação de política/regra de negócio (ex.: reembolso acima do limite) | Não (retriable: false) | Comunicar ao usuário com explicação amigável; escalar |
permission | Sem autorização para o recurso | Não | Não repetir; escalar ou pedir credencial |
Um erro genérico "Operation failed" esconde a categoria — o agente não sabe se deve repetir, corrigir o input, desistir ou escalar, e desperdiça turnos em retries inúteis. Metadados estruturados (errorCategory, isRetryable, descrição legível) tornam a decisão de recuperação trivial e evitam retries em erros não-retryable.
Para violações de regra de negócio, inclua retriable: false e uma explicação amigável para o cliente final — o agente pode então comunicar a recusa adequadamente em vez de tentar de novo.
Falha de acesso ≠ resultado vazio
Um erro comum de design: retornar erro quando a consulta funcionou mas não encontrou nada — ou pior, mascarar uma falha de acesso como lista vazia. São situações distintas:
- Resultado vazio válido = consulta bem-sucedida sem matches →
is_errorausente/false, com payload explícito:{"results": [], "note": "0 pedidos no período"}. - Falha de acesso = a consulta não pôde ser executada →
is_error: truecom categoria, para o agente decidir sobre retry.
Capturar um timeout e devolver lista vazia marcada como sucesso é o pior dos mundos: suprime o erro, impede qualquer recuperação e produz relatórios silenciosamente incompletos.
Recuperação local vs propagação ao coordenador
Em sistemas multi-agente, o subagente deve resolver localmente o que consegue e propagar com contexto o que não consegue:
- Local: falhas transitórias → retry com backoff dentro do subagente, sem incomodar o coordenador.
- Propagação: quando não resolve localmente, envie ao coordenador contexto estruturado: tipo da falha, o que foi tentado (query usada), resultados parciais obtidos e alternativas possíveis — nunca um genérico "search unavailable", que esconde tudo o que o coordenador precisaria para decidir entre repetir com outra query, tentar outra rota ou seguir com o parcial.
flowchart TD
T[Falha na ferramenta] --> C{Categoria?}
C -- transient --> R[Retry local com backoff]
R -- resolveu --> OK[Continua a tarefa]
R -- esgotou tentativas --> P[Propaga ao coordenador:\ntipo, query tentada,\nresultados parciais, alternativas]
C -- validation --> F[Corrige input e rechama]
C -- business / permission --> N[Não repete:\ncomunica / escala]
Construindo o tool_result estruturado em Python
import json
import anthropic
client = anthropic.Anthropic()
def build_tool_result(tool_use_id, outcome):
"""Converte o resultado da execucao em um tool_result honesto."""
if outcome["status"] == "ok" and not outcome["rows"]:
# Resultado vazio VALIDO: sucesso, sem is_error
return {
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps({
"results": [],
"note": "Consulta executada com sucesso: 0 registros no periodo.",
}),
}
if outcome["status"] == "ok":
return {
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps({"results": outcome["rows"]}),
}
# Falha: erro estruturado com categoria e retryability
return {
"type": "tool_result",
"tool_use_id": tool_use_id,
"is_error": True,
"content": json.dumps({
"errorCategory": outcome["category"], # transient | validation | business | permission
"isRetryable": outcome["category"] == "transient",
"message": outcome["message"],
"attempted": outcome.get("attempted"), # o que foi tentado
"partialResults": outcome.get("partial", []),
}),
}
# Exemplo: timeout transitorio com resultados parciais
result_block = build_tool_result("toolu_01XYZ", {
"status": "error",
"category": "transient",
"message": "Upstream timeout apos 10s na pagina 3 de 5.",
"attempted": "GET /orders?page=3",
"partial": [{"order_id": "ORD-1"}, {"order_id": "ORD-2"}],
})
followup = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
thinking={"type": "adaptive"},
messages=[
{"role": "user", "content": "Liste meus pedidos do trimestre."},
{"role": "assistant", "content": [{
"type": "tool_use", "id": "toolu_01XYZ",
"name": "list_orders", "input": {"period": "2026-Q3"},
}]},
{"role": "user", "content": [result_block]},
],
)
print(followup.content[0])
Pegadinhas da prova
- Distrator típico: "retorne lista vazia marcada como sucesso quando a busca der timeout". Suprime o erro e impede recuperação — nunca é a resposta certa.
- Distrator típico: "faça retry com backoff e retorne só 'search unavailable' ao final". O retry local está certo, mas o status genérico esconde do coordenador a query tentada, os parciais e as alternativas.
- Distrator típico: "propague a exceção ao handler top-level e encerre o workflow". Mata o fluxo inteiro quando estratégias de recuperação poderiam funcionar.
- Pegadinha: na API o campo é
is_error(snake_case, notool_result); no MCP éisError(camelCase, no resultado da tool). A prova pode trocar um pelo outro. - Pegadinha: erro de
validationnão é "não-retryable para sempre" — é não-retryable com o mesmo input; o agente deve corrigir o input e tentar de novo.
Resumo em 5 linhas
- Falha de ferramenta =
tool_resultcomis_error: true(API) ouisError: true(MCP), sempre com mensagem informativa. - Categorize:
transient(retry com backoff),validation(corrigir input),business(retriable: false+ explicação amigável),permission(não repetir, escalar). - "Operation failed" genérico impede decisões de recuperação; metadados estruturados (
errorCategory,isRetryable) evitam retries desperdiçados. - Resultado vazio válido é sucesso explícito (
results: []), nunca erro — e falha de acesso nunca deve virar lista vazia "de sucesso". - Subagente recupera transientes localmente; o que não resolver, propaga ao coordenador com tipo da falha, o que foi tentado, resultados parciais e alternativas.
Documentação oficial
Questões de fixação
1. Como uma tool MCP deve comunicar uma falha de execução ao agente?
Gabarito: B. O padrão MCP é o flag isError no resultado: a falha volta como dado para o agente raciocinar. A derruba a sessão por uma falha de uma tool. C mistura camada de transporte com semântica da tool. D é ambíguo — vazio pode ser resultado válido sem matches.
2. Logs mostram o agente repetindo 5 vezes uma chamada que sempre retorna "Operation failed" — o erro real é falta de permissão no recurso. Qual mudança resolve o desperdício de retries?
Gabarito: C. Com categoria e retryability explícitas o agente sabe que repetir é inútil e escala. A trata o sintoma: ainda desperdiça retries e também bloquearia retries legítimos de erros transitórios. B proíbe retries que são corretos para erros transient. D suprime o erro e gera resultados silenciosamente errados.
3. Uma ferramenta de busca de pedidos executa com sucesso, mas não há pedidos no período consultado. Qual é o retorno correto?
Gabarito: A. Resultado vazio válido é uma consulta bem-sucedida sem matches — sucesso explícito. B e C tratam ausência de dados como falha, levando o agente a retries ou "correções" desnecessárias. D é inválido: todo tool_use exige um tool_result com o tool_use_id correspondente.
4. O subagente de busca esgota os retries locais de um timeout. O que ele deve devolver ao coordenador?
Gabarito: D. O coordenador só decide bem (repetir com outra query, rota alternativa, seguir com parciais) com contexto completo. A esconde exatamente essa informação. B suprime a falha e gera pesquisa incompleta silenciosa. C mata o workflow quando havia estratégias de recuperação viáveis.