Confiabilidade: erros, retries e propagação multi-agente
Objetivos de aprendizagem
- Classificar erros HTTP da API por retryability: 429 (respeitar
retry-after), 5xx/529 (retryable), 400 (não retryable — corrigir o request). - Implementar retry com backoff exponencial + jitter, sabendo que o SDK já retenta 2x por padrão.
- Projetar propagação de erro estruturada em sistemas multi-agente: tipo da falha, query tentada, resultados parciais, alternativas.
- Distinguir falha de acesso (timeout que pede decisão de retry) de resultado vazio válido (query bem-sucedida sem matches).
- Evitar os dois anti-padrões: suprimir erro como sucesso e derrubar o workflow inteiro por uma falha isolada.
Erros HTTP: o que retentar e o que corrigir
| Código | Tipo | Retryable? | Ação correta |
|---|---|---|---|
| 400 | invalid_request_error | Não | Corrigir o request (parâmetro inválido, mensagens fora de ordem, beta header inexistente). Retentar o mesmo payload falha para sempre. |
| 401 / 403 | authentication_error / permission_error | Não | Corrigir credencial / permissões. |
| 413 | request_too_large | Não | Reduzir o input (truncar histórico, dividir documentos). |
| 429 | rate_limit_error | Sim | Respeitar o header retry-after; backoff exponencial. |
| 500 | api_error | Sim | Backoff exponencial. |
| 529 | overloaded_error | Sim | Backoff; considerar espaçar requests ou usar outro modelo temporariamente. |
O SDK já retenta por você. Os SDKs oficiais retentam automaticamente erros de conexão, 429 e ≥500 com backoff exponencial — max_retries=2 por padrão, configurável no client ou via with_options(). Timeouts (default de 10 minutos) levantam APITimeoutError e também são retentados. Só implemente retry customizado quando precisar de comportamento além do SDK (ex.: mais tentativas, logging, fila).
Idempotência: antes de retentar operações com efeito colateral (criar pedido, enviar e-mail via tool), garanta que a repetição não duplica o efeito — desenhe as ferramentas para serem idempotentes ou deduplique por chave. Retry seguro pressupõe operação segura de repetir.
Exemplo: exceções tipadas + backoff exponencial
import time
import random
import anthropic
client = anthropic.Anthropic() # o proprio SDK ja retenta 2x (max_retries=2)
def call_with_retry(max_retries: int = 5, **kwargs):
"""Retry com backoff exponencial + jitter para alem do que o SDK cobre."""
last_exception = None
for attempt in range(max_retries):
try:
return client.messages.create(**kwargs)
except anthropic.RateLimitError as e: # 429
retry_after = int(e.response.headers.get("retry-after", "0"))
last_exception = e
delay = max(retry_after, 2 ** attempt + random.uniform(0, 1))
except anthropic.APIStatusError as e:
if e.status_code >= 500: # 500 api_error, 529 overloaded_error
last_exception = e
delay = 2 ** attempt + random.uniform(0, 1)
else:
raise # 4xx (exceto 429): corrigir o request, NUNCA retentar
except anthropic.APIConnectionError as e: # falha de rede antes da resposta
last_exception = e
delay = 2 ** attempt + random.uniform(0, 1)
print(f"Tentativa {attempt + 1}/{max_retries}; aguardando {delay:.1f}s")
time.sleep(min(delay, 60.0))
raise last_exception
response = call_with_retry(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Resuma o status do pipeline."}],
)
print(response.content[0].text)
Propagação de erro em sistemas multi-agente (TS 5.3)
Quando um subagente falha, o que ele reporta ao coordenador determina o que o coordenador consegue decidir. Compare:
| Reporte | O que o coordenador consegue fazer |
|---|---|
Genérico: "search unavailable" | Quase nada — não sabe se retenta, reformula, ou usa outra fonte. |
| Estruturado: tipo da falha (timeout? permissão?), query tentada, resultados parciais obtidos, alternativas possíveis | Decisão inteligente de recuperação: retentar, delegar a outra fonte, prosseguir com parciais anotando lacunas. |
Distinção que a prova cobra: falha de acesso ≠ resultado vazio válido. Um timeout (a fonte não foi consultada) exige decisão de retry; uma query bem-sucedida com zero matches é um resultado, não um erro. Reportá-los da mesma forma faz o coordenador tratar dados inexistentes como dados verificados ausentes — ou vice-versa.
Recuperação local antes de propagar
O subagente deve tentar recuperação local de falhas transitórias (retry do timeout, reformulação da query) e propagar apenas o que não conseguiu resolver — incluindo o que foi tentado e os resultados parciais. Assim o coordenador não é acionado para cada soluço de rede, mas recebe contexto completo quando a falha é real.
Os dois anti-padrões
- Suprimir erro como sucesso: retornar lista vazia quando a busca falhou. O coordenador conclui "não há dados sobre X" — uma afirmação falsa que contamina a síntese.
- Derrubar o workflow inteiro por uma falha isolada: uma fonte fora do ar não deveria cancelar a pesquisa das outras nove. Prossiga com resultados parciais e anote a cobertura.
flowchart TD
A[Subagente executa query] --> B{Falhou?}
B -- "Nao" --> C{Zero resultados?}
C -- "Sim" --> D[Reportar resultado vazio VALIDO
query ok, sem matches]
C -- "Nao" --> E[Reportar achados]
B -- "Sim, transitoria" --> F[Recuperacao local: retry/reformular]
F -- "Resolveu" --> C
F -- "Nao resolveu" --> G[Propagar erro ESTRUTURADO:
tipo da falha + query tentada +
parciais + alternativas]
G --> H{Coordenador decide}
H --> I[Retentar depois / outra fonte /
prosseguir com parciais + anotar lacuna de cobertura]
Erros estruturados também nas suas ferramentas
O mesmo princípio vale para tool_results devolvidos ao modelo: use is_error: true e um corpo estruturado (categoria do erro — transient/validation/permission —, flag de retryability, descrição legível). Isso permite ao agente retentar erros transitórios e explicar erros de negócio ao usuário, em vez de alucinar em cima de uma falha opaca.
Pegadinhas da prova
- Distrator típico: retentar um 400 com backoff. 400 é erro de request — o mesmo payload falhará para sempre; a ação é corrigir o request.
- Distrator típico: implementar retry manual "porque a API não retenta". O SDK já retenta 429/5xx/conexão com backoff (2x por padrão) — a resposta certa costuma ser configurar
max_retries, não reinventar. - Distrator típico: ignorar o header
retry-afterno 429 e usar delay fixo — desperdiça janela ou martela a API cedo demais. - Distrator típico: subagente que retorna
[]quando a fonte deu timeout. Isso é suprimir erro como sucesso — o coordenador registrará "sem dados" como fato. - Distrator típico: abortar toda a pesquisa multi-agente porque uma fonte falhou. O padrão correto é prosseguir com parciais e anotar lacunas de cobertura.
- Distrator típico: reportar tudo como
"search unavailable""para simplificar a interface". Erro genérico esconde do coordenador exatamente o contexto de que ele precisa para decidir a recuperação.
Resumo em 5 linhas
- 429/5xx/529/conexão: retryable com backoff exponencial (respeitando
retry-afterno 429); 400: corrigir o request, nunca retentar. - O SDK oficial já retenta 2x por padrão (
max_retriesconfigurável); timeouts levantamAPITimeoutError; retry pressupõe idempotência. - Erro propagado ao coordenador deve ser estruturado: tipo da falha, query tentada, resultados parciais, alternativas.
- Falha de acesso (timeout) ≠ resultado vazio válido (query ok, zero matches) — reporte-os de forma distinguível.
- Subagente recupera localmente o transitório e propaga só o irrecuperável; nunca suprima erro como sucesso nem derrube o workflow inteiro por uma falha.
Documentação oficial
Questões de fixação
1. Um pipeline recebe HTTP 400 (invalid_request_error) em todas as chamadas de um lote e o código atual retenta cada uma 5 vezes com backoff. Qual é o problema?
Gabarito: B. 400 indica request inválido — parâmetro errado, mensagens fora de ordem, beta header inexistente. Retentar o mesmo payload só queima tempo e cota. A e D discutem como retentar algo que não deve ser retentado. C aplica-se ao 429, não ao 400.
2. Em um sistema de pesquisa multi-agente, o subagente de busca acadêmica sofre timeout na API externa. Qual retorno ao coordenador segue as melhores práticas?
Gabarito: C. Recuperação local primeiro; se irrecuperável, erro estruturado com contexto que permita ao coordenador decidir (retentar depois, usar outra fonte, prosseguir com parciais). A suprime erro como sucesso — o coordenador registraria "não há papers" como fato. B combina os dois anti-padrões: erro genérico + derrubar o workflow. D transforma falha recuperável em crash e não carrega contexto algum.
3. Por que um coordenador precisa distinguir "falha de acesso" de "resultado vazio válido" nos reports dos subagentes?
Gabarito: A. São situações semanticamente opostas: "não consegui olhar" versus "olhei e não há". B inverte a lógica — resultado vazio válido não é falha e retentá-lo é desperdício. C esconde lacunas de cobertura que a síntese deveria anotar. D é falso e irrelevante para a decisão arquitetural.
4. Uma equipe quer tratamento de rate limit "mais robusto" e propõe escrever do zero um loop de retry para TODOS os erros HTTP. O que o arquiteto deve apontar sobre o SDK oficial?
Gabarito: B. O comportamento default do SDK cobre o essencial; customização começa por max_retries, e um loop próprio só se justifica para necessidades além disso — nunca incluindo 4xx não-retryable. A e C descrevem o SDK incorretamente (em sentidos opostos). D confunde: retry de leituras e de erros de transporte é seguro; idempotência é preocupação para operações com efeito colateral, que se desenham para suportar repetição.
5. Uma chamada retorna HTTP 529. O que esse código significa e qual a resposta adequada?
Gabarito: C. 529 = overloaded_error, condição transitória de capacidade — retryable com backoff. A descreve 401. B descreve 413. D trata condição temporária como permanente; a mitigação é backoff/espaçamento, não suporte.