\n\n\n\n Eu Construo Agentes Confiáveis com Peças Não Confiáveis: Minha Estratégia de Desenvolvimento - AgntDev \n

Eu Construo Agentes Confiáveis com Peças Não Confiáveis: Minha Estratégia de Desenvolvimento

📖 13 min read2,532 wordsUpdated Mar 31, 2026

Oi pessoal, Leo aqui do agntdev.com! Hoje, quero falar sobre algo que tem me ocupado muito a mente ultimamente, especialmente enquanto estou lidando com alguns novos projetos de agentes. É sobre o lado “Dev” do desenvolvimento de agentes, especificamente, como construímos agentes confiáveis a partir de partes não confiáveis. Sim, você ouviu certo. Porque sejamos honestos, essa é a realidade para a maioria de nós, certo?

Geralmente, não estamos trabalhando com APIs e serviços perfeitamente curados e de nível empresarial. Na maioria das vezes, estamos costurando modelos de código aberto, APIs de terceiros com limites de taxa questionáveis e talvez até alguns microserviços caseiros que, digamos, têm personalidade. E mesmo assim, a expectativa é sempre que nossos agentes simplesmente… funcionem. De forma consistente. Confiável. Mesmo quando os componentes subjacentes estão tendo um ataque de nervos.

Já passei por isso 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 plano gratuito e um serviço de notificações personalizado que preparei em um fim de semana. Cada um deles tinha suas peculiaridades. O GitHub às vezes me limitava inesperadamente, o modelo de sentimentos ocasionalmente demorava para responder, e meu serviço de notificações… bem, vamos apenas dizer que ele tinha o hábito de esquecer as boas maneiras após uma hora de funcionamento. Se eu não tivesse implementado algumas proteções sérias, tudo teria desmoronado como um castelo de cartas.

Então, hoje, quero compartilhar algumas estratégias e padrões práticos que adotei para tornar meus agentes mais resilientes, mesmo quando as partes das quais são construídos são tudo, menos.

A Verdade Inegável: As Coisas Vão Quebrar

Primeiro de tudo, vamos aceitar isso como um fato. Nenhuma API está 100% disponível. 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 bem-sucedido.

O problema que vejo com frequência, especialmente com desenvolvedores mais novos explorando o trabalho com agentes, é que eles assumem sucesso em cada chamada externa. Eles escrevem código assim:


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

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

Estratégia 1: Tentativas Sólidas com Retardo

Essa é provavelmente a técnica mais fundamental, e ainda assim, muitas vezes é mal implementada ou não é implementada de forma alguma. Simplesmente tentar novamente imediatamente após uma falha geralmente é uma má ideia. Se o serviço externo estiver fora do ar, você está apenas pressionando mais, potencialmente piorando a situação ou se limitando.

A chave é retardo exponencial. Isso significa esperar períodos progressivamente mais longos entre as tentativas. Isso dá ao serviço externo uma chance de se recuperar e reduz a carga que você está impondo a ele.

Exemplo: Python com Tenacity

Para Python, minha biblioteca preferida para isso é Tenacity. Ela torna a adição da lógica de repetição 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

# Simula uma chamada de API externa não confiável
def call_unreliable_api(data):
 if random.random() < 0.6: # 60% de chance de falha
 logger.warning(f"Chamada de API falhou para os dados: {data}")
 raise ExternalServiceError("Falha ou timeout simulados da API")
 logger.info(f"Chamada de API 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 faz as esperas se tornarem mais longas 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ó tentará novamente para erros específicos, não, digamos, um KeyboardInterrupt.

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

Estratégia 2: Disjuntores para Prevenir Falhas Cumulativas

Tentativas são ótimas para erros transitórios. Mas e se o serviço estiver completamente fora do ar? Tentar repetidamente apenas esgotará seus recursos e poderá piorar o problema para o serviço externo se ele estiver lutando para se recuperar. É aí que entra o Padrão do Disjuntor.

Pense nisso como um disjuntor elétrico em sua casa. Se houver uma falha (demais falhas), ele “desarma”, impedindo que mais corrente flua e protegendo o sistema. Depois de um tempo, pode ser reiniciado, mas não continuará tentando enviar corrente através de um fio em curto.

Para agentes, um disjuntor monitora as chamadas a um serviço externo. Se a taxa de falhas ultrapassar um determinado limite dentro de uma janela de tempo específica, o circuito “abre”. Quando aberto, todas as chamadas subsequentes para aquele serviço falham imediatamente, sem sequer tentar a chamada. Após um período de “timeout” configurável, o circuito passa para um estado “meio-aberto”, permitindo um número limitado de chamadas de teste para ver se o serviço se recuperou. Se essas tiverem sucesso, ele fecha; se falharem, ele abre novamente.

Por que isso importa para agentes:

  • Conservação de recursos: Seu agente não está desperdiçando tempo e recursos tentando chamar um serviço inativo.
  • Falha mais rápida: Em vez de esperar por um timeout, seu agente recebe um sinal de falha imediato, permitindo que ele lide com a situação (por exemplo, use um plano de contingência, registre o problema, notifique um operador).
  • Protege serviços externos: Impede que seu agente faça um ataque de negação de serviço a um serviço que está lutando.

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):
 # Configura 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 quebrar em TypeErrors

 def _unreliable_call(self, data):
 if random.random() < 0.7: # 70% de chance de falha
 logger.warning(f"Simulando erro interno da API para dados: {data}")
 raise ConnectionError("Serviço inacessível")
 logger.info(f"Chamada de API 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! Não chamando a API para os dados: {data}")
 # Lógica de fallback aqui: retornar dados em cache, valor padrão ou gerar um erro mais específico
 return {"result": "fallback_data", "source": "circuit_breaker"}
 except Exception as e:
 logger.error(f"Erro durante a chamada da 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 tratado: {e}")
 time.sleep(1) # Simulando algum atraso entre chamadas

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

Estratégia 3: Idempotência para Operações que Alteram Estado

Isso é crucial para qualquer agente que modifica o estado externo (por exemplo, criando um registro, enviando um e-mail, iniciando um pagamento). Se o seu agente tenta realizar uma ação e a rede cai, ou o serviço externo tem um timeout, como você sabe se a ação realmente aconteceu?

Se você simplesmente tentar novamente sem considerar a idempotência, pode acabar realizando a ação duas vezes. Imagine enviar o mesmo e-mail duas vezes, ou pior, cobrar um cliente duas vezes. Não é bom.

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

Como alcançar idempotência:

  • Use IDs de requisições únicos: Ao fazer uma chamada de API que modifica o estado, inclua um ID único, gerado pelo cliente, no cabeçalho da requisição (por exemplo, X-Idempotency-Key). O serviço externo pode usar essa chave para detectar requisições duplicadas e retornar a resposta original sem reprocessar.
  • Projekte APIs idempotentes: Se você controla a API, projete endpoints que sejam naturalmente idempotentes. Por exemplo, em vez de um endpoint de “criar pedido”, tenha um endpoint de “upsert pedido” que possa criar ou atualizar com base em um ID de pedido único.
  • Verifique o status antes de tentar novamente: Após uma operação que muda o estado ter falhado, se a API suportar, consulte o status do recurso usando o ID único antes de tentar uma nova tentativa.

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 o processo de pensamento do seu agente pode parecer:


# 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! Armazene transaction_id e resposta.
except (ConnectionError, TimeoutError, APIError) as e:
 # Oh não, falhou. A cobrança passou de qualquer forma?
 logger.warning(f"Falha no pagamento, verificando status com 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 na verdade bem-sucedido na verificação de retry.")
 # Trate como sucesso, armazene as informações.
 else:
 logger.info(f"O pagamento {transaction_id} realmente falhou, tentando novamente (com a mesma chave de idempotência).")
 # Aqui é onde você faria a nova tentativa 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)
 # ... trate o sucesso/falha da nova tentativa
 except Exception as check_e:
 logger.error(f"Não foi possível verificar o status da transação para {transaction_id}: {check_e}")
 # É necessário registrar para revisão manual ou mover para a fila de mensagens com erro

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

Estratégia 4: Soluções de Contingência e Degradação Elegante

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

Isso pode significar:

  • Usando dados em cache: Se seu agente precisa de dados específicos de um serviço, mas o serviço está fora do ar, você pode usar uma versão antiga de um cache?
  • Fornecendo valores padrão: Se um modelo de IA para análise de sentimento estiver fora do ar, você pode simplesmente classificar toda a entrada como “neutro” ou “desconhecido” por um período, em vez de falhar em todo o fluxo do agente?
  • Trocando para um serviço de backup: Se sua API de tradução primária estiver fora do ar, você pode redirecionar as requisições para uma secundária, talvez menos eficiente ou mais cara?
  • Pulando etapas opcionais: Se uma etapa de enriquecimento não crítica falhar, o agente pode simplesmente prosseguir sem aquele enriquecimento, talvez registrando um aviso?
  • Notificando usuários/operadores: No mínimo, falhe de forma elegante e comunique claramente o problema para o usuário ou operador do sistema.

Minha anedota sobre o serviço de notificações falhando? Minha solução foi simples: se meu serviço de notificações personalizado falhasse, o agente apenas registraria o evento localmente e enviaria um e-mail para *mim* dizendo “Ei, seu serviço de notificações provavelmente está fora do ar de novo, verifique os logs.” Não é ideal para os usuários finais, mas evitou que todo o agente travasse e garantiu que eu soubesse que algo estava errado.

Conselhos Práticos para Seu Próximo Projeto de Agente

  1. Assuma a falha: Projete seu agente desde o início esperando que as dependências externas falhem.
  2. Implemente novas tentativas com backoff exponencial: Use bibliotecas como Tenacity (Python) ou padrões similares em outras linguagens para erros transitórios.
  3. Implante fusíveis: Previna falhas em cascata e conserve recursos ao “desligar” o circuito quando um serviço estiver constantemente falhando. Pybreaker é um bom começo.
  4. Priorize idempotência para mudanças de estado: Garanta que operações como pagamentos ou criação de registros não sejam duplicadas se uma nova tentativa ocorrer. Use IDs únicos.
  5. Planeje para degradação elegante: Identifique dependências críticas vs. não críticas e construa soluções de contingência. Qual é a “menos ruim” 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 que você está coletando e analisando esses logs para entender *por que* as coisas estão falhando e com que frequência.

Construir agentes confiáveis não é apenas sobre algoritmos inteligentes ou modelos poderosos. Trata-se fundamentalmente de engenharia solidez em cada camada, especialmente ao lidar com a realidade bagunçada das dependências externas. Ao aplicar essas estratégias, você passará menos tempo depurando falhas misteriosas de agentes e mais tempo construindo sistemas autônomos realmente úteis e confiáveis.

Quais são suas estratégias preferidas para lidar com serviços externos instáveis? Deixe um comentário abaixo, eu adoraria ouvir suas histórias de guerra e 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