Ao final deste artigo você vai saber escrever o loop de um agente de IA com ferramentas, colocar limites que seguram custo e repetição, tratar falhas de ferramenta e decidir quando um agente nem deveria existir.
O problema real
Seu assistente responde bem a perguntas simples. Aí chega: "quanto eu gastei com backups neste mês?". Para responder, ele precisa consultar uma API, somar os valores, talvez consultar outra API e só então redigir a resposta. Um agente é isso: um modelo de linguagem que decide qual ferramenta chamar, recebe o resultado e repete até concluir.
Na primeira versão, quase todo mundo deixa o loop rodar até o modelo "achar" a resposta. Em algum dia ele fica preso repetindo a mesma chamada e a conta do mês vai junto. Aí vem o limite de 5 passos, que segura o custo mas às vezes corta tarefas legítimas no meio. A solução está em desenhar o loop com limites explícitos e saídas de emergência.
O loop agêntico
O ciclo é curto:
- O modelo recebe a tarefa, as ferramentas disponíveis e o histórico.
- Ele responde com texto final ou com um pedido de ferramenta.
- Seu código executa a ferramenta (o modelo nunca executa nada).
- O resultado volta ao histórico como mensagem.
- Repete até haver resposta final ou até um limite ser atingido.
O que torna isso confiável em produção é o código ao redor do modelo: limites, validação e registro de tudo.
Um agente simples e completo
O exemplo abaixo usa o SDK da Anthropic, mas a estrutura é a mesma em qualquer provedor com tool use. O nome do modelo vem de uma variável de ambiente para você escolher o que fizer sentido (um modelo pequeno costuma bastar para agentes com poucas ferramentas).
import json
import os
import anthropic
client = anthropic.Anthropic()
MODEL = os.environ["AGENT_MODEL"] # defina o modelo que você usa
# Ferramentas de mentira, só para o exemplo rodar
def consultar_discos(mes: str) -> list[dict]:
return [
{"disco": "backup_01", "tamanho_gb": 250},
{"disco": "backup_02", "tamanho_gb": 180},
]
def custo_armazenamento(tamanho_gb: float, preco_por_gb: float) -> float:
return round(tamanho_gb * preco_por_gb, 2)
FERRAMENTAS = {
"consultar_discos": consultar_discos,
"custo_armazenamento": custo_armazenamento,
}
SCHEMAS = [
{
"name": "consultar_discos",
"description": "Lista os discos de backup usados em um mês (formato AAAA-MM).",
"input_schema": {
"type": "object",
"properties": {"mes": {"type": "string"}},
"required": ["mes"],
},
},
{
"name": "custo_armazenamento",
"description": "Calcula o custo de armazenar tamanho_gb a um preço por GB.",
"input_schema": {
"type": "object",
"properties": {
"tamanho_gb": {"type": "number"},
"preco_por_gb": {"type": "number"},
},
"required": ["tamanho_gb", "preco_por_gb"],
},
},
]
def executar_ferramenta(nome: str, args: dict) -> str:
funcao = FERRAMENTAS.get(nome)
if funcao is None:
return f"Erro: a ferramenta '{nome}' não existe."
try:
return json.dumps(funcao(**args), ensure_ascii=False)
except Exception as e: # o erro volta ao modelo, que pode se corrigir
return f"Erro ao executar {nome}: {e}"
def rodar_agente(tarefa: str, max_passos: int = 6, max_tokens_total: int = 20_000) -> str:
mensagens = [{"role": "user", "content": tarefa}]
tokens_gastos = 0
chamadas_vistas = []
for passo in range(1, max_passos + 1):
resposta = client.messages.create(
model=MODEL,
max_tokens=1024,
system="Você é um assistente de FinOps. Use as ferramentas quando precisar de dados.",
tools=SCHEMAS,
messages=mensagens,
)
tokens_gastos += resposta.usage.input_tokens + resposta.usage.output_tokens
mensagens.append({"role": "assistant", "content": resposta.content})
if resposta.stop_reason != "tool_use":
return "".join(b.text for b in resposta.content if b.type == "text")
resultados = []
for bloco in resposta.content:
if bloco.type != "tool_use":
continue
assinatura = (bloco.name, json.dumps(bloco.input, sort_keys=True))
if chamadas_vistas.count(assinatura) >= 2:
return "Parei: o agente repetiu a mesma chamada várias vezes."
chamadas_vistas.append(assinatura)
resultados.append({
"type": "tool_result",
"tool_use_id": bloco.id,
"content": executar_ferramenta(bloco.name, bloco.input),
})
mensagens.append({"role": "user", "content": resultados})
if tokens_gastos > max_tokens_total:
return "Parei: orçamento de tokens da tarefa esgotado."
return "Parei: limite de passos atingido sem resposta final."
print(rodar_agente("Quanto gastei com discos de backup em 2026-09? Considere 0,05 por GB."))
Repare nos detalhes que fazem diferença: o erro da ferramenta volta como texto para o modelo tentar de novo, o histórico guarda o tool_use_id de cada chamada, e o loop tem três saídas de emergência (passos, tokens e repetição). Orçamento em tokens é mais estável que em dinheiro, porque preço muda; converta para valor no seu painel de custos.
Três arquiteturas, quando usar cada uma
Agente único com ferramentas
Um modelo, poucas ferramentas bem descritas. É o ponto de partida e resolve a maioria dos casos de até uns dez passos. Fica fácil de depurar porque existe um único histórico para ler.
O risco é o contexto inchar e o modelo se confundir quando as ferramentas passam de umas quinze. Quando isso acontecer, divida.
Roteador com especialistas
Uma primeira chamada, barata, só classifica o pedido (cobrança, infraestrutura, suporte) e entrega a um agente especializado, cada um com poucas ferramentas e um prompt curto.
def rotear(pedido: str) -> str:
r = client.messages.create(
model=MODEL,
max_tokens=10,
system="Classifique o pedido em uma palavra: cobranca, infra ou suporte.",
messages=[{"role": "user", "content": pedido}],
)
area = r.content[0].text.strip().lower()
return area if area in {"cobranca", "infra", "suporte"} else "suporte"
O fallback para "suporte" evita que uma resposta inesperada do classificador derrube o fluxo. O custo é ter mais prompts para manter e testar.
Pipeline em etapas
Quando a tarefa tem fases fixas (coletar, analisar, redigir), encadeie chamadas com código determinístico entre elas. Aqui o agente nem decide o fluxo: você decide. Isso costuma ser mais barato e previsível que um agente livre, e é a primeira coisa a considerar antes de soltar um modelo no comando.
Falhas de ferramenta e repetição de chamadas
Ferramentas falham: timeout, 500, argumento inválido. Três regras ajudam.
- Devolva o erro ao modelo em texto claro, como no exemplo. Muitas vezes ele corrige o argumento sozinho.
- Faça retry com espera crescente só para erros transitórios (rede, 429, 503), dentro da própria ferramenta. Erro de validação não melhora tentando de novo.
- Ferramentas que alteram o mundo (enviar e-mail, pagar, apagar) precisam de confirmação humana ou de idempotência, para que uma repetição não duplique o efeito.
A detecção de repetição do exemplo compara nome e argumentos das chamadas. É simples e pega o caso clássico do agente que pergunta a mesma coisa em círculo.
Rastreie cada execução
Sem registro, você descobre que o agente falhou pelo cliente reclamando. Grave por execução um identificador, a tarefa, cada passo (ferramenta, argumentos, resultado resumido, tokens, duração) e o motivo de parada. O artigo sobre observabilidade de LLM mostra como transformar isso em traces. Cuidado com dados pessoais nesses registros: mascare antes de gravar.
Armadilhas comuns
- Achar que mais passos dão resposta melhor. Depois de alguns passos sem progresso, o mais provável é loop. Meça a distribuição de passos reais e ajuste o limite com base nela.
- Descrever mal as ferramentas. A descrição é o prompt da ferramenta. Nome vago e parâmetros sem explicação geram chamadas erradas.
- Dar ferramentas demais. Cada ferramenta extra aumenta a chance de escolha errada. Comece com o mínimo.
- Não ter caminho manual. Nenhum agente resolve tudo. Defina o que acontece quando ele desiste, por exemplo abrir um ticket com o histórico anexado.
- Esquecer segurança. Texto vindo de ferramentas e documentos pode conter instruções maliciosas. Veja segurança em aplicações com LLM.
Quando não usar um agente
- A tarefa tem um ou dois passos conhecidos: um prompt estruturado ou function calling direto resolve.
- Você precisa de latência baixa e previsível: cada volta do loop soma uma chamada ao modelo.
- O fluxo é fixo: um pipeline em código é mais barato e mais fácil de testar.
Checklist de produção
- Limite de passos e de tokens por tarefa
- Detecção de chamadas repetidas
- Erro de ferramenta devolvido ao modelo em texto
- Ações destrutivas com confirmação ou idempotência
- Trace completo de cada execução, sem dados pessoais
- Testes com falha de API, timeout e entrada hostil (veja avaliação de LLM com evals)
- Caminho manual quando o agente desiste
Para acompanhar os gastos, siga com FinOps para IA. Quer aplicar isso na sua empresa? Marque uma conversa de 45 minutos em https://iauaicloud.com.br/consultoria