O problema real
Você montou um LLM que responde perguntas bem. Mas aí chega uma pergunta que ele não consegue responder sozinho: "quanto de disk eu gastei no mês com backups?". Isso exige chamar uma API, processar resultado, reformular, chamar outra API. Um agente faz isso: pensa, chama ferramenta, observa, repete até resolver.
A primeira tentativa, você deixa o agente rodar até achar a resposta. Roda 50 vezes. Custo explosivo. Aí você coloca limite de 5 iterações. Funciona, mas às vezes não completa a tarefa. Bem-vindo ao caos dos agentes em produção. Este artigo mostra as arquiteturas que funcionam de verdade.
O loop agêntico
Um agente é um loop simples:
1. PENSAR: LLM recebe a tarefa e histórico
2. PLANEJAR: LLM decide: "Preciso chamar a ferramenta X com parâmetro Y"
3. AGIR: Seu código executa a ferramenta
4. OBSERVAR: Resultado retorna pro LLM
5. LOOP: Se resolveu, termina. Se não, volta ao passo 1
Controlar esse loop é a arte.
Três padrões de arquitetura
Padrão 1: Agente simples com ferramentas
Um LLM, várias ferramentas. Melhor para tarefas bem-definidas.
import json
from openai import OpenAI
from typing import Callable, Any
class SimplifiedAgent:
def __init__(self, model: str = "gpt-4o-mini"):
self.client = OpenAI()
self.model = model
self.tools = {}
self.iterations = 0
self.max_iterations = 5
self.cost = 0.0
def register_tool(self, name: str, func: Callable, description: str):
"""Registra uma ferramenta disponível para o agente."""
self.tools[name] = {
"func": func,
"description": description
}
def _execute_tool(self, tool_name: str, params: dict) -> Any:
"""Executa uma ferramenta e retorna resultado."""
if tool_name not in self.tools:
return f"Erro: ferramenta '{tool_name}' não existe"
try:
result = self.tools[tool_name]["func"](**params)
return result
except Exception as e:
return f"Erro ao executar {tool_name}: {str(e)}"
def _build_system_prompt(self) -> str:
"""Constrói prompt com lista de ferramentas disponíveis."""
tools_description = "\n".join([
f"- {name}: {info['description']}"
for name, info in self.tools.items()
])
return f"""Você é um assistente que pode executar ações.
Quando precisar chamar uma ferramenta, responda com JSON no formato:
{{"action": "ferramenta_name", "params": {{"param1": "valor1"}}}}
Ferramentas disponíveis:
{tools_description}
Se conseguir responder sem ferramentas, faça isso.
Se não conseguir, use as ferramentas.
Seja conciso."""
def run(self, task: str) -> str:
"""Executa a tarefa usando o loop agêntico."""
self.iterations = 0
self.cost = 0.0
conversation = []
print(f"\n[AGENTE] Tarefa: {task}")
while self.iterations < self.max_iterations:
self.iterations += 1
# Cria mensagem pro LLM
system_prompt = self._build_system_prompt()
conversation.append({"role": "user", "content": task})
# Chama LLM
response = self.client.chat.completions.create(
model=self.model,
system=system_prompt,
messages=conversation,
temperature=0 # Determinístico
)
assistant_message = response.choices[0].message.content
conversation.append({"role": "assistant", "content": assistant_message})
# Estima custo (simplificado)
input_tokens = response.usage.prompt_tokens
output_tokens = response.usage.completion_tokens
self.cost += (input_tokens * 0.00015 + output_tokens * 0.0006) / 1000
print(f"[Iteração {self.iterations}] {assistant_message[:100]}...")
# Verifica se é uma ação ou resposta final
try:
action = json.loads(assistant_message)
if "action" in action:
tool_name = action["action"]
params = action.get("params", {})
# Executa ferramenta
result = self._execute_tool(tool_name, params)
print(f"[Ferramenta {tool_name}] {str(result)[:100]}...")
# Retorna resultado pro agente
task = f"Resultado da ferramenta {tool_name}: {result}"
else:
# Resposta final
print(f"[RESPOSTA] {assistant_message}")
return assistant_message
except json.JSONDecodeError:
# Não é JSON, é resposta final
print(f"[RESPOSTA] {assistant_message}")
return assistant_message
# Limite de iterações atingido
return f"Atingido limite de iterações ({self.max_iterations})"
# Exemplo prático
def query_database(table: str, where: str = "") -> list:
"""Simula uma query em banco de dados."""
if table == "disks" and "month" in where:
return [
{"disk": "backup_01", "size_gb": 250},
{"disk": "backup_02", "size_gb": 180}
]
return []
def calculate_cost(size_gb: int, price_per_gb: float = 0.05) -> float:
"""Calcula custo de armazenamento."""
return size_gb * price_per_gb
# Configura agente
agent = SimplifiedAgent()
agent.register_tool(
"query_db",
query_database,
"Query banco de dados. Parâmetros: table, where"
)
agent.register_tool(
"calc_cost",
calculate_cost,
"Calcula custo. Parâmetros: size_gb, price_per_gb"
)
# Executa
result = agent.run("Quanto de disk gastei em backup esse mês? Considera USD 0.05 por GB")
print(f"\nCusto de execução: USD {agent.cost:.4f}")
print(f"Iterações: {agent.iterations}")
Vantagens:
- Simples de implementar
- Fácil debugar
- Adequado para tarefas < 10 passos
Desvantagens:
- Uma ferramenta quebra, tudo quebra
- Sem priorização
Padrão 2: Roteador (agente supervisor)
Um agente decide qual tarefa delegar para qual suboagente. Melhor para tarefas complexas com ramificações.
class RouterAgent:
"""Agente supervisor que roteia para subagenges especializados."""
def __init__(self):
self.client = OpenAI()
self.subagentos = {}
def register_subagenote(self, name: str, agent):
"""Registra um subagenote especializado."""
self.subagentos[name] = agent
def route(self, task: str) -> str:
"""Decide qual subagenote executa a tarefa."""
routing_prompt = f"""Dado esta tarefa, qual especialista deve resolver?
Especialistas:
- billing: consulta custos, faturas, orçamentos
- infrastructure: consulta servidores, disks, CPUs
- security: consulta logs de acesso, permissões
- support: consulta tickets de clientes
Responda com APENAS o nome do especialista:"""
response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": routing_prompt + task}],
temperature=0
)
specialist = response.choices[0].message.content.strip().lower()
if specialist not in self.subagentos:
specialist = "support" # fallback
print(f"[ROTEADOR] Tarefa delegada para: {specialist}")
return self.subagentos[specialist].run(task)
Vantagens:
- Cada subagenote é especialista em seu domínio
- Falha de um não afeta outros
- Escalável
Desvantagens:
- Mais complexo de debugar
- Necessário treinar roteador bem
Padrão 3: Agentes em cascata (multi-etapa)
Um agente chama outro. Útil para tarefas que precisam de contexto progressivo.
class CascadeAgent:
"""Executa agentes em sequência, passando contexto."""
def run_cascade(self, task: str, agents: list) -> str:
"""Executa lista de agentes em ordem."""
context = task
for i, agent in enumerate(agents):
print(f"\n[Etapa {i+1}] Executando {agent.__class__.__name__}")
result = agent.run(context)
# Próximo agente recebe resultado do anterior
context = f"Contexto anterior: {result}\n\nProxima tarefa: {task}"
return result
Controlando custo e loops infinitos
Limite de iterações
Sempre tenha:
max_iterations = 5 # Maioria dos casos resolve em 2-3
Limite de custo
def run(self, task: str, max_cost: float = 0.5) -> str: # USD 0.50 max
self.cost = 0.0
while self.iterations < self.max_iterations:
# ... executa LLM ...
if self.cost > max_cost:
print(f"Limite de custo atingido: USD {self.cost}")
break
Detecção de loop infinito
def detect_loop(self, last_n_messages: int = 3) -> bool:
"""Detecta se agente está repetindo."""
if len(self.conversation) < last_n_messages * 2:
return False
# Compara últimas N ações
recent = self.conversation[-last_n_messages:]
if len(set(recent)) == 1:
# Todas iguais
return True
return False
Tratamento de falha de ferramenta
Ferramentas falham. Prepare-se:
def execute_with_retry(self, tool_name: str, params: dict, max_retries: int = 2):
"""Tenta executar ferramenta com retry."""
for attempt in range(max_retries):
try:
return self.tools[tool_name]["func"](**params)
except Exception as e:
if attempt == max_retries - 1:
return f"Ferramenta falhou após {max_retries} tentativas: {str(e)}"
# Avisa o LLM que falhou
return f"Tentativa {attempt + 1} falhou: {str(e)}. Tentando novamente..."
Observabilidade: rastreando a execução
Em produção, você quer saber por que o agente falhou. Trace cada chamada:
import uuid
from datetime import datetime
class TracedAgent:
def run(self, task: str):
trace_id = str(uuid.uuid4())[:8]
log = {
"trace_id": trace_id,
"task": task,
"start_time": datetime.now().isoformat(),
"iterations": [],
"total_cost": 0.0,
"status": "running"
}
# Para cada iteração:
iteration_log = {
"number": self.iterations,
"llm_input": "...",
"llm_output": "...",
"tool_called": "query_db",
"tool_params": {"table": "disks"},
"tool_result": "[...]",
"timestamp": datetime.now().isoformat()
}
log["iterations"].append(iteration_log)
# Salva em JSON, CloudWatch, ou banco
print(json.dumps(log, indent=2))
return log
Armadilhas comuns
"Agenetes sempre precisam ferramentas"
Falso. Um agente que só chama LLM e pensa melhor pode ser mais barato.
"Mais iterações = melhor resultado"
Falso. Após 5-10 iterações, começam loops. Diminua o limite se resolver em menos.
"Agente resolve 100% dos casos"
Falso. Agentes falham ~5-10% do tempo. Tenha fallback manual.
"Rastrear tudo é grátis"
Logging excessivo custa tokens (LLM precisa processar logs grandes). Trace essencial apenas.
Quando NOT usar agentes
- Tarefa é simples (< 2 passos) → prompt estruturado é melhor
- Latência < 200ms é obrigatória → agentes adicionam 500ms+
- Você não tem ferramentas bem-definidas → agente fica confuso
- Custo é crítico e tarefa é simples → desnecessário
Próximos passos
- Estruture seus prompts de agente com Engenharia de prompt em produção.
- Monitore custos com FinOps para IA.
- Implemente observabilidade com Observabilidade em aplicações com LLM.
- Garanta segurança das ferramentas com Segurança em aplicações com LLM.
Resumo: checklist de produção para agentes
- Defini limite de iterações (máx. 10)
- Defini limite de custo em dólares
- Cada ferramenta tem tratamento de erro
- Ferramenta sempre retorna JSON ou texto estruturado
- Testei com cases que quebram (falha de API, timeout)
- Tenho fallback se agente não resolve
- Trace completo de cada execução
- Latência testada em produção
- Detecta loops infinitos
Agentes são poderosos mas exigem disciplina. Sem ela, explodem custo.