\n\n\n\n Estou construindo agentes confiáveis a partir de peças pouco confiáveis: minha estratégia de desenvolvimento - AgntDev \n

Estou construindo agentes confiáveis a partir de peças pouco confiáveis: minha estratégia de desenvolvimento

📖 13 min read2,546 wordsUpdated Mar 31, 2026

Olá a todos, Leo aqui do agntdev.com! Hoje, quero falar sobre algo que tem me preocupado muito ultimamente, especialmente enquanto trabalho em alguns novos projetos de agentes. Trata-se da parte “Dev” do desenvolvimento de agentes, especificamente, como vamos construir agentes confiáveis a partir de componentes não confiáveis. Sim, você ouviu direito. Porque, para ser honesto, essa é a realidade para a maioria de nós, não é?

Geralmente, não trabalhamos com APIs e serviços perfeitamente elaborados e de nível empresarial. Mais frequentemente do que não, estamos montando modelos de código aberto, APIs de terceiros com limites de taxa duvidosos, e talvez até mesmo alguns microserviços caseiros que, digamos, têm uma personalidade própria. E, ainda assim, a expectativa é que nossos agentes simplesmente… funcionem. De maneira consistente. Confiável. Mesmo quando os componentes subjacentes estão agindo de forma inconsistente.

Eu já percorri esse caminho tantas vezes. Lembro de um projeto no ano passado em que estava construindo um agente para ajudar a gerenciar minhas contribuições de código aberto. Ele precisava interagir com a API do GitHub, um modelo de análise de sentimentos hospedado em um nível gratuito, e um serviço de notificação personalizado que eu havia elaborado em um fim de semana. Cada um deles tinha suas particularidades. O GitHub às vezes me limitava de maneira inesperada, o modelo de sentimentos podia expirar de vez em quando, e meu serviço de notificação… bem, digamos que ele costumava esquecer suas maneiras após uma hora de funcionamento. Se eu não tivesse implementado proteções sérias, tudo isso teria desmoronado como um castelo de cartas.

Hoje, quero compartilhar algumas estratégias e modelos práticos que adotei para tornar meus agentes mais resilientes, mesmo quando os elementos dos quais são feitos são tudo, menos confiáveis.

A verdade inevitável: as coisas vão quebrar

Primeiro, vamos aceitar isso como uma confissão. Nenhuma API está disponível a 100%. Nenhum modelo é 100% preciso. Nenhuma rede é 100% estável. Uma vez que você aceita isso, pode começar a projetar para a falha, o que, contraintuitivamente, torna seu agente mais eficaz.

O problema que eu vejo frequentemente, especialmente com os desenvolvedores mais novos explorando o trabalho de agentes, é que eles presumem o sucesso em cada chamada externa. Eles escrevem código assim:


response = external_api.call_method(data)
# Presumir que a resposta é sempre perfeita e continuar
processed_data = process_response(response)

E então, quando external_api.call_method levanta um erro de conexão, ou retorna um 500, ou simplesmente envia um JSON malformado, o agente inteiro para. Podemos fazer melhor.

Estratégia 1: tentativas sólidas com retorno progressivo

Essa é provavelmente a técnica mais fundamental, e, no entanto, é frequentemente mal implementada ou não utilizada. Tentar imediatamente após uma falha geralmente é uma má ideia. Se o serviço externo está fora do ar, você só está o pressionando mais, o que pode piorar as coisas ou fazer com que você enfrente um limite de taxa.

A chave é o retorno progressivo. Isso significa esperar períodos cada vez mais longos entre as tentativas. Isso dá ao serviço externo uma chance de se recuperar e reduz a carga que você impõe a ele.

Exemplo: Python com Tenacity

Para Python, minha biblioteca de escolha para isso é Tenacity. Ela facilita a adição da lógica de tentativas de uma maneira incrivelmente limpa.


import random
import logging
from tenacity import retry, wait_exponential, stop_after_attempt, retry_if_exception_type

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ExternalServiceError(Exception):
 """Exceção personalizada para falhas de serviços externos."""
 pass

# Simular uma chamada a uma API externa pouco confiável
def call_unreliable_api(data):
 if random.random() < 0.6: # 60% de chances de falha
 logger.warning(f"A chamada à API falhou para os dados: {data}")
 raise ExternalServiceError("Falha ou timeout simulado da API")
 logger.info(f"A chamada à API foi bem-sucedida para os dados: {data}")
 return {"status": "success", "result": f"processed_{data}"}

@retry(wait=wait_exponential(multiplier=1, min=4, max=10),
 stop=stop_after_attempt(5),
 retry=retry_if_exception_type(ExternalServiceError))
def get_processed_data_with_retries(input_data):
 logger.info(f"Tentando chamar a API para: {input_data}")
 return call_unreliable_api(input_data)

if __name__ == "__main__":
 try:
 result = get_processed_data_with_retries("some_important_item")
 print(f"Resultado final: {result}")
 except ExternalServiceError as e:
 print(f"Falhou após várias tentativas: {e}")
 except Exception as e:
 print(f"Ocorreu um erro inesperado: {e}")

Neste trecho:

  • wait_exponential aumenta os intervalos a cada tentativa (4s, depois ~8s, depois ~16s, etc., até 10s no máximo).
  • stop_after_attempt(5) significa que tentará no máximo 5 vezes.
  • retry_if_exception_type(ExternalServiceError) garante que só tente novamente para erros específicos, e não, digamos, por um KeyboardInterrupt.

Este modelo é um verdadeiro salvador. Eu o utilizo para conexões de banco de dados, requisições HTTP e até mesmo para a comunicação interna entre módulos de agentes quando sei que um deles pode estar temporariamente sobrecarregado.

Estratégia 2: Disjuntores para prevenir falhas em cascata

Tentar novamente é ótimo para erros transitórios. Mas e se o serviço estiver completamente fora do ar? Tentar repetidamente esgotará seus recursos e pode piorar o problema para o serviço externo, caso ele esteja com dificuldades para se recuperar. É aqui que entra o modelo de disjuntor.

Pense nisso como um disjuntor elétrico na sua casa. Se houver um defeito (muitos fracassos), ele “desarma”, impedindo que mais corrente passe e protegendo o sistema. Após um certo tempo, ele pode ser reiniciado, mas não continuará a tentar passar corrente por um fio curto-circuitado.

Para os agentes, um disjuntor monitora as chamadas a um serviço externo. Se a taxa de falhas ultrapassar um certo limite em uma janela de tempo dada, o circuito “se abre”. Quando está aberto, todas as chamadas seguintes a esse serviço falham imediatamente sem sequer tentar a chamada. Após um período de “delay” configurável, o circuito passa para um estado “semi-aberto”, permitindo um número limitado de chamadas de teste para ver se o serviço se recuperou. Se essas chamadas forem bem-sucedidas, ele se fecha; se falharem, ele se abre novamente.

Por que isso importa para os agentes:

  • Conservação de recursos: Seu agente não perde tempo e recursos tentando chamar um serviço que falhou.
  • Falha mais rápida: Em vez de esperar um timeout, seu agente recebe um sinal de falha imediato, permitindo que ele gerencie a situação (por exemplo, usar uma solução alternativa, registrar o problema, notificar um operador).
  • Protege os serviços externos: Impede que seu agente faça um DDOS em um serviço com problemas.

Eu geralmente implemento isso usando bibliotecas. Para Python, Pybreaker é excelente.


import time
import random
import logging
from pybreaker import CircuitBreaker, CircuitBreakerError

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ExternalAPIClient:
 def __init__(self):
 # Configurar o disjuntor:
 # 3 falhas consecutivas em 60 segundos abrirão o circuito.
 # Ele permanece aberto por 5 segundos.
 self.breaker = CircuitBreaker(fail_max=3, reset_timeout=5, exclude=[TypeError]) # Não cortar em TypeErrors

 def _unreliable_call(self, data):
 if random.random() < 0.7: # 70% de chances de falha
 logger.warning(f"Simulação de erro interno da API para os dados: {data}")
 raise ConnectionError("Serviço inacessível")
 logger.info(f"A chamada API foi bem-sucedida para os dados: {data}")
 return {"result": f"processed_{data}"}

 def process_data(self, data):
 try:
 return self.breaker.call(self._unreliable_call, data)
 except CircuitBreakerError:
 logger.error(f"O circuito está aberto! Sem chamada API para os dados: {data}")
 # Lógica de fallback aqui: retornar dados em cache, um valor padrão, ou levantar um erro mais específico
 return {"result": "fallback_data", "source": "circuit_breaker"}
 except Exception as e:
 logger.error(f"Erro ao chamar API (não relacionado ao disjuntor): {e}")
 raise

if __name__ == "__main__":
 client = ExternalAPIClient()
 for i in range(15):
 print(f"\n--- Tentativa {i+1} ---")
 try:
 result = client.process_data(f"item_{i}")
 print(f"Resultado: {result}")
 except Exception as e:
 print(f"Erro gerenciado: {e}")
 time.sleep(1) # Simular um atraso entre as chamadas

Execute isso, e você verá o circuito se abrir após algumas falhas, e então, eventualmente, tentar se semi-abri, e talvez até se fechar novamente se o serviço simulado começar a se comportar.

Estratégia 3: Idempotência para operações que alteram o estado

É crucial para qualquer agente que modifica o estado externo (por exemplo, criar um registro, enviar um e-mail, iniciar um pagamento). Se o seu agente tenta realizar uma ação, e a rede está instável, ou o serviço externo expira, como você sabe se a ação realmente ocorreu?

Se você simplesmente tentar novamente sem considerar a idempotência, poderá acidentalmente realizar a ação duas vezes. Imagine enviar o mesmo e-mail duas vezes, ou pior, cobrar um cliente duas vezes. Isso não é bom.

Uma operação é idempotente se realizá-la várias vezes tem o mesmo efeito que realizá-la uma única vez. Por exemplo, definir um valor (SET x = 5) é idempotente. Incrementar um valor (x = x + 1) não é.

Como alcançar a idempotência:

  • Use identificadores de requisição únicos: Ao fazer uma chamada API que altera o estado, inclua um identificador único gerado pelo cliente no cabeçalho da requisição (por exemplo, X-Idempotency-Key). O serviço externo pode então usar essa chave para detectar requisições duplicadas e retornar a resposta original sem reprocessamento.
  • Projete APIs idempotentes: Se você controla a API, projete endpoints que sejam naturalmente idempotentes. Por exemplo, ao invés de um endpoint “criar um pedido”, tenha um endpoint “upsert pedido” que pode criar ou atualizar com base em um identificador de pedido único.
  • Verifique o estado antes de tentar novamente: Após uma operação de alteração de estado falhada, se a API permitir, consulte o estado do recurso usando o identificador único antes de tentar novamente.

Embora eu não tenha um trecho de código direto para isso (é mais sobre design de API e lógica do lado do cliente), aqui está como poderia ser o raciocínio do seu agente:


# Pseudo-código do agente para uma operação idempotente
transaction_id = generate_unique_id()
payload = {"data": "some_value", "idempotency_key": transaction_id}

try:
 response = external_payment_api.process_charge(payload)
 # Sucesso! Armazenar transaction_id e response.
except (ConnectionError, TimeoutError, APIError) as e:
 # Oh não, falhou. O pagamento foi realizado mesmo assim?
 logger.warning(f"O pagamento falhou, verificando o estado com o ID: {transaction_id}")
 try:
 status_response = external_payment_api.get_transaction_status(transaction_id)
 if status_response.get("status") == "completed":
 logger.info(f"O pagamento {transaction_id} foi realmente completado durante a verificação de retry.")
 # Considerar como bem-sucedido, armazenar as informações.
 else:
 logger.info(f"O pagamento {transaction_id} realmente falhou, tentando novamente (com a mesma chave de idempotência).")
 # É aqui que você tentaria novamente com o *mesmo* transaction_id
 # A API de pagamento deve reconhecê-lo e não cobrar duas vezes.
 response = external_payment_api.process_charge(payload)
 # ... gerenciar o sucesso/falha do retry
 except Exception as check_e:
 logger.error(f"Não foi possível verificar o estado da transação para {transaction_id}: {check_e}")
 # Necessário para registrar para uma revisão manual, ou passar para a fila de mensagens mortas

Isso requer a cooperação do serviço externo, mas é um modelo crítico para construir agentes realmente confiáveis que gerenciam operações financeiras ou outras sensíveis.

Estratégia 4: Soluções de fallback e degradação graciosa

Às vezes, um serviço externo está simplesmente completamente indisponível, e não há esperança de tentar novamente ou esperar. Nesses casos, um bom agente não simplesmente falha; ele encontra uma maneira de fornecer uma experiência degradada, mas ainda útil.

Isso pode significar:

  • Usar dados em cache: Se seu agente precisa de dados específicos de um serviço, mas o serviço está fora do ar, você consegue usar uma versão desatualizada de um cache?
  • Fornecer valores padrão: Se um modelo de IA para análise de sentimentos está fora do ar, você pode simplesmente classificar todas as entradas como “neutras” ou “desconhecidas” por um tempo, em vez de fazer o fluxo todo do agente falhar?
  • Mudar para um serviço de fallback: Se sua API de tradução principal está fora do ar, você pode redirecionar as requisições para uma segunda, talvez menos eficiente ou mais cara?
  • Omitir etapas opcionais: Se uma etapa de enriquecimento não crítica falhar, o agente pode simplesmente continuar sem esse enriquecimento, registrando talvez um aviso?
  • Notificar usuários/operações: No mínimo, falhe graciosamente e comunique claramente o problema ao usuário ou ao operador do sistema.

Minha anedota sobre a falha do serviço de notificações? Minha solução de fallback foi simples: se meu serviço de notificações personalizado estivesse fora do ar, o agente apenas registrava o evento localmente e enviava um e-mail para *mim* dizendo “Ei, seu serviço de notificações provavelmente está fora do ar novamente, verifique os logs.” Não é ideal para os usuários finais, mas isso impediu que todo o agente travasse e me garantiu que eu soubesse que havia um problema.

Medidas concretas para seu próximo projeto de agente

  1. Suponha a falha: Projete seu agente desde o início esperando que as dependências externas falhem.
  2. Implemente tentativas com um retorno exponencial: Use bibliotecas como Tenacity (Python) ou modelos semelhantes em outras linguagens para erros transitórios.
  3. Implante disjuntores: Previna falhas em cascata e preserve recursos “disparando” o circuito quando um serviço falha de forma consistente. Pybreaker é um bom começo.
  4. Priorize a idempotência para mudanças de estado: Certifique-se de que operações como pagamentos ou criação de registros não se duplicam se uma nova tentativa ocorrer. Use IDs únicos.
  5. Planeje para uma degradação graciosa: Identifique dependências críticas em relação a dependências não críticas e construa soluções de fallback. Qual é a “menos pior” coisa que seu agente pode fazer quando uma dependência falha?
  6. Monitore de forma agressiva: Todas essas estratégias geram logs. Certifique-se de coletar e analisar esses logs para entender *por que* as coisas falham e com que frequência.

Construir agentes confiáveis não trata apenas de algoritmos engenhosos ou modelos poderosos. É fundamentalmente uma questão de engenharia de solidez em cada camada, especialmente quando se trata da realidade problemática das dependências externas. Ao aplicar essas estratégias, você passará menos tempo depurando falhas misteriosas de agentes e mais tempo criando sistemas autônomos verdadeiramente úteis e confiáveis.

Quais são suas estratégias favoritas para lidar com serviços externos pouco confiáveis? Deixe um comentário abaixo, gostaria de ouvir suas histórias de batalha e suas soluções!

🕒 Published:

✍️
Written by Jake Chen

AI technology writer and researcher.

Learn more →
Browse Topics: Agent Frameworks | Architecture | Dev Tools | Performance | Tutorials
Scroll to Top