O assistente agenda um Pix. O banco demora um pouco demais, e a chamada dá timeout. Então o código faz o que sempre faz. Tenta de novo. Cinco vezes.
Mas o banco já tinha registrado a primeira. A resposta só se perdeu na volta. Saem cinco transferências, para um pedido só.
Na aula anterior, você viu esse smell: o retry cego. Agora a gente corrige, passo a passo, com um número antes e um número depois. Você sai sabendo quais erros merecem uma segunda tentativa, e como deixar essa segunda tentativa inofensiva.
A ideia
A ideia cabe numa frase. Antes de repetir, classifique o erro.
Um erro transitório pode sumir sozinho. Rede instável, limite de requisições, um serviço fora do ar por um instante. Esses ganham retry, com espera crescente e um limite.
Um erro permanente não some. Entrada inválida, sem permissão, saldo insuficiente, uma regra de negócio. Esses voltam para quem chamou, com uma explicação que dá para usar.
E toda chamada que muda o mundo carrega uma chave de idempotência. É essa peça que deixa o retry seguro.
Antes e depois
Antes, havia uma seta só. Qualquer erro voltava direto para a chamada. Depois, o tipo do erro escolhe o caminho.
A chamada sai com uma chave. O serviço usa essa chave para reconhecer uma repetição.
O erro é classificado pelo código ou pelo status. Nunca procurando a palavra timeout no texto da mensagem. Mensagem muda. Código não.
Só o erro transitório volta para o começo, com espera crescente e um limite.
O erro permanente vira uma resposta clara. Para o usuário, ou para o modelo, que assim muda de rumo em vez de insistir.
E o sucesso acontece uma vez só, mesmo quando houve retry.
Um caso real
Vamos passar pelo caso perigoso, o timeout que na verdade foi um sucesso.
Um usuário pede para agendar duzentos e cinquenta reais para amanhã.
No momento do pedido, o sistema cria um identificador. Não dentro do loop de retry. No clique.
O banco processa a transferência. Aí a rede derruba a resposta.
O seu código vê um timeout. Isso é transitório, então ele repete. Com a mesma chave.
O banco reconhece a chave e, em vez de agendar de novo, devolve o primeiro resultado.
O trace mostra duas tentativas, e um efeito. É esse o número que você quer ver.
Tratar o timeout como transitório estava certo. O que impediu o duplicado foi uma chave nascida com a intenção do usuário, não com a tentativa. APIs de pagamento sérias aceitam essa chave exatamente por isso.
Um desafio
Uma pergunta para você. Uma API de email pode devolver cinco erros. Requisições demais. Destinatário inválido. Serviço indisponível. Chave de API inválida. E chave duplicada, mensagem já enviada. O que você faz com cada um?
Requisições demais é transitório. Espere o tempo que o serviço pedir e tente de novo, dentro do seu limite.
Destinatário inválido é permanente. Avise o usuário, ou o modelo, que o endereço precisa ser corrigido.
Serviço indisponível é transitório. Repita, com espera crescente. Se continuar falhando depois do limite, pare e use o plano B.
Chave de API inválida é permanente, e é problema de configuração. Nenhuma chamada vai funcionar, então não repita. Avise o time.
E chave duplicada é um sucesso disfarçado. O email já saiu. Trate como feito, e não mande de novo.
Passo a passo seguro
Como em toda refatoração aqui, comece medindo.
Puxe dos logs os erros mais comuns de cada dependência, quantos retries cada um gerou, e qualquer efeito duplicado. Duas transferências para o mesmo pedido é o número que importa.
Classifique os códigos de erro de cada serviço como transitório ou permanente. Na dúvida, trate como permanente. Um não errado sai mais barato que um sim errado.
Coloque uma chave de idempotência em toda chamada com efeito colateral, gerada uma vez por intenção do usuário.
Depois, repita só os erros transitórios, com espera crescente, um limite, e um pouco de aleatoriedade na espera. Essa aleatoriedade se chama jitter, e ela evita que mil clientes repitam no mesmo segundo.
Escreva mensagens úteis para os erros permanentes, para o usuário e para o modelo. Saldo insuficiente na conta é algo que uma pessoa consegue resolver. Erro, não.
No loop do agente, coloque um detector de repetição. A mesma ferramenta, com os mesmos argumentos, depois de um erro permanente, encerra aquele caminho.
E meça de novo. Retries por tipo de erro, tempo de resposta e efeitos duplicados. Esse último deve ser zero.
Riscos
O primeiro risco é o bug mais comum dessa refatoração. A chave é gerada dentro do loop de retry. Cada tentativa ganha uma chave nova, o serviço vê pedidos novos, e a idempotência some. A chave nasce com o clique, o chamado, a conversa, nunca com a tentativa.
O segundo é retry empilhado. O SDK do provedor repete duas vezes. A sua função repete três. O agente chama a ferramenta de novo. São nove tentativas, e ninguém combinou esse número. Desligue o retry do SDK nas chamadas que você mesmo classifica. Um lugar só decide.
E alguns serviços nem aceitam chave. Aí confira o estado antes de repetir. Já existe uma transferência para esse pedido? Se não dá para conferir, não repita um efeito colateral. Passe para uma pessoa.
Fechamento
Então, a ideia para levar. Uma boa política de retry é, quase toda, saber dizer não. Não para erros que o tempo não resolve, não para chave nova em intenção antiga, e não para uma terceira camada de retry. O que sobra é uma segunda tentativa que nunca faz estrago.
Na próxima aula, a gente vê tudo desta trilha funcionando junto num sistema real: extração de notas e contratos. Até lá.
29Ce
RefatoraçãoSegurança e confiabilidade
Classificar Erro Antes de Tentar de Novo
Classify Errors Before Retry
Classificar Erro Antes de Tentar de Novo (Classify Errors Before Retry) é uma refatoração de IA para sistemas com LLM, do grupo Segurança e confiabilidade. Separar erros temporários de erros definitivos e só repetir o que pode passar.
O assistente agenda um Pix. A API do banco devolve timeout. O código tenta de novo, cinco vezes, com a mesma chamada. O banco tinha registrado a primeira. Cinco Pix saem. Todo erro dispara outra tentativa, inclusive os que nunca vão dar certo, e efeitos acabam duplicados. É o smell Retry Cego.
Solução
Antes de tentar de novo, classifique o erro. Erros transitórios (rede, limite de taxa, indisponibilidade momentânea) têm retry com espera crescente e limite. Erros permanentes (validação, permissão, saldo, regra de negócio) voltam para quem chamou com uma explicação. Toda chamada com efeito leva uma chave de idempotência.
Antes e depois
-for attempt in range(5):- try:- return bank.schedule_pix(key, amount, date)- except Exception:- time.sleep(2 ** attempt)+TRANSIENT = {"TIMEOUT", "CONNECTION_RESET", "RATE_LIMITED", "SERVICE_UNAVAILABLE"}++def schedule_pix(key, amount, date, request_id):+ for attempt in range(3):+ try:+ return bank.schedule_pix(key, amount, date, idempotency_key=request_id)+ except BankError as e:+ if e.code not in TRANSIENT:+ return {"ok": False, "error": e.code, "retryable": False,+ "message": HUMAN_MESSAGES.get(e.code, e.message)}+ time.sleep(min(2 ** attempt + random.random(), 10)) # espera com jitter+ return {"ok": False, "error": "UNAVAILABLE", "retryable": True,+ "message": "O banco está instável. Tente de novo em alguns minutos."}
Antes: qualquer erro, mesma resposta.
flowchart LR
C["1. Chamada"]:::tool --> E{"2. Erro?"}
E -->|"qualquer"| C
Depois: o tipo do erro decide.
flowchart LR
C["1. Chamada com chave de idempotência"]:::tool --> E{"2. Tipo do erro"}
E -->|"transitório"| W["3. Esperar e tentar, com limite"]:::tool
W --> C
E -->|"permanente"| R(["4. Explicar a quem chamou"]):::io
E -->|"ok"| OK(["5. Resultado"]):::io
Toda chamada com efeito carrega uma chave que o serviço usa para não repetir o efeito.
O erro é classificado pelo código ou status, nunca pelo texto da mensagem.
Só erros transitórios voltam a ser tentados, com espera crescente e teto.
Erros permanentes viram uma resposta clara, que o modelo ou o usuário pode usar.
O sucesso acontece uma vez, mesmo com retries.
Por que funciona
Repetir só ajuda quando a causa do erro pode desaparecer sozinha. Rede instável e limite de taxa passam; saldo insuficiente e chave Pix inválida, não. Classificar evita gastar tentativas no que não vai funcionar, e evita que uma tentativa “dê certo” na hora errada.
A chave de idempotência resolve o caso mais perigoso: a chamada que deu certo no servidor, mas cuja resposta se perdeu na rede. Sem a chave, o retry repete o efeito. Com ela, o servidor reconhece a repetição e devolve o resultado da primeira vez. APIs de pagamento sérias (a do Stripe, as de Pix dos bancos) aceitam essa chave por esse motivo.
Num agente, o erro permanente bem explicado também ajuda o modelo: em vez de repetir schedule_pix com os mesmos argumentos, ele informa o usuário ou muda de caminho. Classifique pelo código (INSUFFICIENT_FUNDS, 400, 429), nunca por um if "timeout" in str(e). Mensagens mudam; códigos não.
Exemplo trabalhado: timeout que era sucesso
Entrada: agendar R$ 250 para amanhã, request_id=chk_9f3a. O banco processa, a rede cai antes da resposta. Sem chave, o retry cria chk novo e agenda de novo. Com chave, o segundo POST devolve o mesmo agendamento. Saída no trace: attempts=2, effect=1.
Classificar o timeout como transitório está certo. O que impede o duplicado é a chave nascida com a intenção do usuário, não com a tentativa.
Passo a passo seguro
Meça. Levante nos logs os erros mais comuns de cada dependência e quantos retries cada um gerou. Procure efeitos duplicados (dois Pix com o mesmo pedido).
Classifique os códigos de erro de cada serviço em transitórios e permanentes. Na dúvida, trate como permanente.
Acrescente chave de idempotência a todas as chamadas com efeito, gerada uma vez por intenção do usuário.
Implemente o retry só para transitórios, com espera crescente, jitter (um pouco de acaso na espera, para mil clientes não baterem no mesmo segundo) e teto. Respeite Retry-After quando o serviço mandar.
Escreva mensagens úteis para erros permanentes, para o modelo e para o usuário.
Detecte repetição no agente: a mesma ferramenta com os mesmos argumentos depois de erro permanente encerra o caminho.
Meça de novo: retries por erro, efeitos duplicados (devem ir a zero) e tempo até a resposta.
Armadilha: três camadas de retry
O SDK do provedor repete duas vezes. A sua função repete três. O loop do agente chama a ferramenta de novo. Nove tentativas, e ninguém combinou o teto. Desligue o retry do SDK (max_retries=0) nas chamadas em que você classifica. Um só lugar decide.
Outra armadilha: gerar uuid4() dentro do for attempt. Cada volta é uma chave nova, e a idempotência some. A chave nasce com o clique, o ticket, o request_id da conversa.
Se o serviço não aceita chave, consulte o estado antes de repetir (“já existe um Pix com este request_id?”). Sem consulta, não repita efeito.
Riscos
Classificação errada. Um erro permanente tratado como transitório volta ao problema original. Revise a lista quando o serviço mudar.
Chave de idempotência gerada a cada tentativa. Assim ela não protege nada.
Serviço que não suporta idempotência. Nesse caso, consulte o estado antes de repetir.
Retries empilhados. O SDK repete, a sua função repete e o agente repete. Defina um só lugar responsável por retry. Combine com Adicionar Caminho de Fallback quando o transitório persiste: depois do teto, disjuntor, não mais uma volta.
Desafio
Classifique estes erros de uma API de envio de email e diga o que fazer com cada um.
429 Too Many Requests400 Invalid recipient address503 Service Unavailable401 Invalid API key409 Duplicate idempotency key (message already sent)
Ver solução
429: transitório. Esperar (respeitando Retry-After) e tentar de novo, com limite.
400 endereço inválido: permanente. Informar ao modelo ou ao usuário que o endereço precisa ser corrigido.
503: transitório. Retry com espera; se persistir, abrir o disjuntor e usar o fallback.
401: permanente e de configuração. Não repetir; alertar o time, porque nenhuma chamada vai funcionar.
409 chave duplicada: sucesso disfarçado. O email já foi enviado; tratar como resultado positivo, sem reenviar.
Responda as 5 perguntas para concluir a aula. Só assistir não conclui: a checagem é o que conta para a trilha e para o certificado.
Perguntas rápidas
O que é Classificar Erro Antes de Tentar de Novo?
Classificar Erro Antes de Tentar de Novo (Classify Errors Before Retry) é uma refatoração de IA para sistemas com LLM, do grupo Segurança e confiabilidade. Separar erros temporários de erros definitivos e só repetir o que pode passar.
Que problema Classificar Erro Antes de Tentar de Novo resolve?