O que é Retry Cego?
Retry Cego (Blind Retry) é um smell de IA, ou seja, um sinal de problema em sistemas com LLM, do grupo Abusos de controle. Qualquer erro dispara nova tentativa, inclusive erros que nunca vão passar.
São duas da manhã. Um assistente financeiro tenta agendar um Pix para um cliente. O banco diz não.
O código pega o erro, espera um pouco e tenta de novo. E de novo. Quatro vezes, a mesma resposta: o limite noturno foi excedido.
Na quinta tentativa, o limite tinha acabado de virar, e a transferência passou. Mas o cliente já tinha sido avisado da falha. Então pagou de novo, pelo app do banco. Pagou duas vezes.
O retry funcionou. Esse é o problema. Nesta aula, você vai aprender a separar o erro que vale tentar de novo do erro que é um não, e por que essa diferença tem que morar no seu código.
O formato é este. A chamada sai.
Qualquer erro volta, e todo erro é tratado do mesmo jeito.
A mesma chamada é repetida, com os mesmos argumentos, sem ninguém perguntar por que falhou.
E quando finalmente passa, o efeito pode acontecer duas vezes, ou na hora errada.
O limite noturno não é uma oscilação de rede. É uma regra de negócio que quer dizer: agora não. Insistir até o relógio virar transforma esse não num efeito que ninguém pediu.
Esse smell se esconde em dois lugares. No código, é um tratamento que pega qualquer exceção e repete.
Num agente, é o modelo. Ele lê a palavra erro e chama a mesma ferramenta de novo, com os mesmos argumentos. Nos dois casos, quem decidiu repetir nunca classificou o erro.
Embrulhar uma chamada num loop de retry é a receita mais conhecida para rede instável. Parece proteção. E a receita vaza para erros que ela nunca deveria pegar: sem saldo, sem permissão, entrada inválida.
O erro volta como texto, sem tipo. O seu código não distingue tente de novo de isso nunca vai funcionar.
O agente aprende a insistir. Se a ferramenta só diz erro, ela parece quebrada, e o modelo faz o razoável. Tenta de novo.
E ninguém pensou em idempotência. A ferramenta foi escrita como se toda chamada fosse única. Aí um timeout que aconteceu depois de o banco aceitar a transferência vira uma segunda transferência.
O pior custo é o efeito duplicado. Dois pagamentos, dois emails, dois pedidos. Num pagamento, o estorno vira horas de suporte, e um cliente que deixa de confiar em você.
Depois, o efeito na hora errada. Um retry que se recupera de um limite noturno agenda um pagamento que o usuário já considerava cancelado.
Depois, desperdício. Imagine dez mil pedidos recusados por dia, cada um tentado cinco vezes. São quarenta mil chamadas a mais, todas inúteis. E martelar um serviço com problema pode te render um bloqueio, ou piorar a queda dele.
E o diagnóstico fica confuso. O trace mostra cinco falhas e um sucesso, e ninguém lê a primeira mensagem, a que dizia o porquê.
Uma pergunta para você. Você já corrigiu a parte óbvia. Só repete em timeout e erro do servidor, e manda uma chave de idempotência. E mesmo assim um cliente recebeu dois emails de cobrança. O que você olha primeiro?
Se a chave muda a cada tentativa. A idempotência só vale se a chave for a mesma em todas as tentativas da mesma operação. Se cada retry gera uma chave nova, o serviço de email fez exatamente o que você pediu. Mandou dois emails.
Trocar de provedor de email não ajuda, nem aumentar o limite de tentativas. O duplicado já passou. A correção é uma chave que pertence à operação, não à tentativa.
O tratamento é uma refatoração chamada classify errors before retry. Ela tem três partes, e pular qualquer uma deixa um buraco. A primeira é classificar.
Timeout, servidor fora do ar ou limite de requisições é transitório. Repita, com espera crescente, um limite baixo, e se o servidor diz quanto esperar, espere isso.
Entrada inválida é permanente. O pedido está errado, e vai estar errado na oitava tentativa também. Uma armadilha clássica é repetir um pedido inválido porque a API às vezes oscila. Oscilação de rede aparece como timeout e erro de servidor. Pedido inválido quer dizer corrija o pedido.
Sem saldo, limite ou sem permissão é permanente, pelo menos por agora. Não repita. Avise o usuário, ou reagende de propósito.
E timeout numa escrita é o caso difícil. Você não sabe se aconteceu. Repita, mas só com a mesma chave de idempotência, para o serviço poder dizer: essa eu já fiz.
Num agente, a classificação também precisa chegar ao modelo. A ferramenta devolve um erro que ele consegue usar. Um código, se dá para repetir, e o que está faltando. Um erro seco empurra o modelo de volta para a mesma chamada.
E coloque um detector de repetição no código. A mesma ferramenta, com os mesmos argumentos, três vezes seguidas, encerra o loop. Isso pega o caso em que o modelo ignora a mensagem.
Quando um retry simples está ok? Numa leitura sem efeito colateral, repetir erro transitório com limite e espera é boa prática. Uma busca que deu timeout pode rodar de novo.
E o SDK do provedor do modelo que repete duas vezes em limite de requisições e erro de servidor não é esse smell. Ele é seletivo. Só confira o que ele repete. Um SDK que cuida da rede não desculpa um tratamento genérico que você colocou em volta de um pagamento.
Mas repare: o smell não precisa de efeito duplicado. Um job de leitura que repete acesso negado e arquivo inválido oito vezes não tem efeito colateral. Continua cego. Só queima cota em vez de dinheiro.
Então, a ideia para levar. Um retry não é uma rede de proteção que você joga sobre tudo. É uma decisão que você toma sobre um tipo de erro, o tipo que o tempo resolve de verdade. O resto precisa de uma resposta, não de mais uma tentativa. E tudo que muda o mundo carrega uma chave que deixa a segunda tentativa inofensiva.
Na próxima aula, a gente faz essa refatoração passo a passo, num fluxo de pagamento de verdade: classify errors before retry. Até lá.
Blind Retry
Retry Cego (Blind Retry) é um smell de IA, ou seja, um sinal de problema em sistemas com LLM, do grupo Abusos de controle. Qualquer erro dispara nova tentativa, inclusive erros que nunca vão passar.
Um assistente financeiro agenda Pix (o sistema brasileiro de pagamento instantâneo). O código que chama o banco (anonimizado):
for attempt in range(5):
try:
return bank.schedule_pix(key, amount, date)
except Exception as e:
log.warning(f"tentativa {attempt} falhou: {e}")
time.sleep(2 ** attempt)
raise PaymentFailed()
O log de uma madrugada:
02:14:03 tentativa 0 falhou: LIMITE_NOTURNO_EXCEDIDO
02:14:05 tentativa 1 falhou: LIMITE_NOTURNO_EXCEDIDO
02:14:09 tentativa 2 falhou: LIMITE_NOTURNO_EXCEDIDO
02:14:17 tentativa 3 falhou: LIMITE_NOTURNO_EXCEDIDO
02:14:33 tentativa 4 ok
Na quinta tentativa o limite noturno tinha virado, e o Pix passou. O cliente, avisado da falha minutos antes, já tinha feito o pagamento pelo app. Pagou duas vezes.
O retry “funcionou”. Esse é o problema. LIMITE_NOTURNO_EXCEDIDO não é falha de rede. É uma regra de negócio: agora não. Insistir até o relógio mudar transforma um “não” em um efeito que ninguém pediu de novo.
Sinais de Retry Cego:
flowchart LR
C["1. Chamada"]:::tool --> E{"2. Erro?"}
E -->|qualquer erro| R["3. Tentar de novo"]:::tool
R --> C
E -->|"passou por acaso"| D[("4. Efeito duplicado")]:::store
Há dois desenhos que produzem o mesmo cheiro. No código, um except Exception envolve a chamada. No agente, o modelo lê “erro” e chama a mesma ferramenta de novo. Nos dois casos, quem decide repetir não classificou o erro.
retryable: false, a ferramenta parece quebrada, e o modelo tenta a mesma coisa.Suponha que a API do banco (ou o HTTP por baixo) devolva códigos. A tabela abaixo é o mapa que o laço cego não tem:
| Sinal | Tipo | O que fazer |
|---|---|---|
| Timeout, 503, conexão resetada | Transitório | Retry com espera, limite baixo |
429 com Retry-After |
Transitório | Esperar o que o servidor pediu |
| 400, validação, chave Pix inválida | Permanente | Não repetir. Corrigir o pedido |
| Saldo, limite, permissão | Permanente (agora) | Não repetir. Avisar ou remarcar |
| 409, “já existe” | Idempotente | Tratar como sucesso |
| Timeout em escrita, resultado desconhecido | Indeterminado | Só repetir com a mesma chave de idempotência |
A armadilha clássica de quem está começando: tratar 400 como transitório “porque a API às vezes oscila”. Oscilação de rede vem como timeout ou 5xx. 400 quer dizer que o pedido está errado.
| Refatoração | Escolha quando |
|---|---|
| Classificar Erro Antes de Tentar de Novo | Sempre. Erros transitórios (rede, 429, 503) têm retry com espera; erros permanentes voltam ao usuário ou ao modelo com explicação. Chamadas com efeito usam chave de idempotência. Destinos: Disjuntor e Ação Idempotente. |
O tratamento tem três peças, e pular qualquer uma deixa um furo:
Retry-After. Sem repetir o mesmo argumento depois de um permanente.No loop do agente, a ferramenta precisa devolver um erro que o modelo consiga usar: código, se é repetível, e o que falta. “ok” e “erro” empurram o modelo de volta para a mesma chamada. Um detector de repetição (a mesma ferramenta, os mesmos argumentos, três vezes) corta o caso em que o modelo ignora o retryable: false.
Antes e depois, no laço:
# antes: qualquer exceção vira nova tentativa
# depois: só transitório, e a chave não muda entre tentativas
result = bank.schedule_pix(key, amount, date, idempotency_key=key)
# se LIMITE_NOTURNO_EXCEDIDO: return erro ao usuário, sem loop
# se timeout: retry com a mesma idempotency_key
except Exception em cima do Pix.Responda as 6 perguntas para concluir a aula. Só assistir não conclui: a checagem é o que conta para a trilha e para o certificado.
Retry Cego (Blind Retry) é um smell de IA, ou seja, um sinal de problema em sistemas com LLM, do grupo Abusos de controle. Qualquer erro dispara nova tentativa, inclusive erros que nunca vão passar.
Com Classificar Erro Antes de Tentar de Novo. Cada refatoração traz o passo a passo seguro, com eval antes e depois.
Nos cenários reais Gerador de testes e Assistente financeiro com aprovação.