Tool calling não funciona? Por que seu agente chama a ferramenta errada (e como consertar)
O agente tinha a ferramenta certa na mão. E escolheu a errada.
Você definiu seis tools, escreveu o prompt, rodou o teste. Em vez de chamar get_customer, o modelo chamou search_customers com um argumento que nem existia no schema. Aí veio o reflexo: "esse modelo é burro". Quase nunca é. Quando o tool calling não funciona, na imensa maioria dos casos o problema está na descrição da ferramenta, no schema frouxo e no nome ambíguo que você escreveu.
Tool calling é o que transforma um chat em agente. É o pedaço que mais cresceu em interesse no último ano, e também o que mais gente coloca em produção sem entender por que falha. Neste tutorial você vai aprender a diagnosticar e consertar os três pontos onde o tool calling não funciona: descrição, schema e fallback. Com código que roda na API da Claude e os números que a própria Anthropic publicou medindo cada ajuste.
TL;DR
- O que é: guia prático para consertar tool calling (function calling) quando o agente escolhe a ferramenta errada ou passa argumento inválido.
- Stack/Modelos: Python, Anthropic SDK,
claude-opus-4-8. Os princípios valem para qualquer provider com function calling. - Custo/Acesso: requer chave da API da Claude. Os ajustes principais (descrição, schema,
strict,tool_choice) são GA, sem header beta. - Link útil: Writing effective tools for AI agents e Advanced tool use, da engenharia da Anthropic.
Por que o tool calling não funciona (e o problema quase nunca é o modelo)
Existem só dois jeitos de o tool calling quebrar:
- O modelo escolhe a ferramenta errada.
- O modelo escolhe a certa, mas passa argumento inválido — JSON malformado, campo obrigatório faltando, tipo errado.
A Anthropic é direta sobre isso: as falhas mais comuns em tool calling são exatamente "wrong tool selection and incorrect parameters" (Advanced tool use). E a causa raiz dessas duas falhas raramente é a capacidade do modelo. É ambiguidade.
Pensa pelo lado do modelo. Ele não enxerga sua arquitetura. Ele enxerga uma lista de tools com nome, descrição e schema, e tem que decidir. Se você tem notification-send-user e notification-send-channel lado a lado com descrições genéricas, o modelo vai chutar. Se o parâmetro se chama user em vez de user_id, ele vai mandar um nome quando você esperava um ID. A informação que faltava para decidir certo não estava no modelo. Estava no contrato que você definiu.
A boa notícia: como o problema é o contrato, o conserto está sob seu controle. A Anthropic mostrou que refinar só as descrições das tools levou o Claude Sonnet 3.5 ao topo do SWE-bench Verified, derrubando a taxa de erro (Advanced tool use). Não mudaram o modelo. Mudaram o texto. É engenharia de contrato, não sorte com prompt.
Pré-requisitos
Para acompanhar os exemplos você vai precisar de:
- [ ] Chave de API da Claude (
ANTHROPIC_API_KEYno ambiente) - [ ]
pip install anthropic - [ ] Conhecimento básico de tool use: como declarar uma tool e devolver o
tool_result. Se isso ainda é novo, comece pelo tool calling na prática e volta aqui. - [ ] Python 3.10+
Mão na massa: consertando o tool calling
Passo 1: a descrição que escolhe a ferramenta por você
A regra que mais entrega resultado: escreva a descrição como se estivesse treinando um funcionário novo, e seja prescritivo sobre quando chamar, não só sobre o que a tool faz.
Modelos recentes (Opus 4.7, 4.8) acionam tools com mais cautela e seguem a descrição mais à risca. Isso significa que uma condição de gatilho explícita na descrição dá ganho mensurável de acerto. Compare:
# Ruim: o modelo tem que adivinhar quando usar
{
"name": "search",
"description": "Busca dados.",
"input_schema": {
"type": "object",
"properties": {"user": {"type": "string"}},
"required": ["user"],
},
}
# Bom: o "quando" está no texto, o parâmetro é específico
{
"name": "get_customer_context",
"description": (
"Retorna o perfil do cliente, últimas transações e notas internas. "
"Chame esta tool quando o usuário perguntar sobre o histórico, status "
"ou pendências de um cliente específico. NÃO use para listar vários "
"clientes — para isso use search_customers."
),
"input_schema": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "ID do cliente, formato cus_XXXX (não é o nome).",
}
},
"required": ["customer_id"],
},
}
Duas mudanças carregam o resultado. Primeiro: a descrição diz quando usar e quando não usar, apontando a alternativa. Segundo: user virou customer_id com formato explícito no description do parâmetro. Nome genérico convida argumento genérico. Se você quer descer ao design das tools em si — contrato, exemplos few-shot, erros úteis —, o tool use na prática destrincha isso.
Passo 2: o schema que rejeita argumento inválido na origem
Descrição resolve a escolha. Schema resolve o argumento. E aqui tem uma alavanca que muita gente ignora: strict tool use. Com strict: true, o modelo só consegue gerar tool calls que batem com o seu schema — chega de campo faltando ou tipo errado.
import anthropic
client = anthropic.Anthropic()
tools = [{
"name": "book_flight",
"description": "Reserva um voo. Chame quando o usuário confirmar destino e data.",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"destination": {"type": "string"},
"date": {"type": "string", "format": "date"},
"passengers": {"type": "integer", "enum": [1, 2, 3, 4, 5, 6]},
},
"required": ["destination", "date", "passengers"],
"additionalProperties": False,
},
}]
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "Quero voar pra Tóquio dia 15/03, 2 pessoas"}],
)
Três detalhes que fazem diferença: enum fecha o conjunto de valores aceitos (o modelo não inventa passengers: 9), format: "date" orienta o formato, e additionalProperties: false é obrigatório no modo strict.
Para tools com input complexo — objetos aninhados, formatos sensíveis — o JSON Schema tem um limite: ele define estrutura, mas não consegue expressar padrões de uso. É aí que entra o campo input_examples, com exemplos de chamada validados contra o schema. O número da Anthropic é difícil de ignorar: o acerto em parâmetros complexos subiu de 72% para 90% com exemplos (Advanced tool use).
E uma regra de ouro que vale para qualquer modelo: três níveis de aninhamento é o limite prático para tool calls confiáveis. Se o seu schema tem objetos dentro de objetos dentro de objetos, achate. Promova os campos críticos para o topo e empacote o resto de config opcional em um único parâmetro que a tool desempacota por dentro.
Passo 3: nomes ambíguos e excesso de tools
O modelo se confunde quando duas tools se sobrepõem. notification-send-user versus notification-send-channel com descrições parecidas é pedir para errar. Dois consertos:
Namespacing por prefixo. Quando suas tools cruzam vários serviços, prefixe pelo serviço: asana_search, jira_search, slack_send_message. A escolha entre prefixo e sufixo teve impacto mensurável nas avaliações da Anthropic — prefixo na frente ajuda o modelo a navegar a biblioteca.
Consolidação. Em vez de list_users, list_events e create_event, exponha um schedule_event. Em vez de get_customer_by_id, list_transactions e list_notes, um get_customer_context que já devolve tudo enriquecido. Menos tools, mais capazes — menos ambiguidade na escolha e menos round-trips.
E se você tem dezenas de tools? O custo aparece antes da conversa começar: um caso real tinha 58 tools consumindo ~55.000 tokens só de definição. A Tool Search Tool carrega só as tools relevantes sob demanda, e o ganho de acerto foi concreto: o Opus 4.5 saltou de 79,5% para 88,1% (Advanced tool use). É o padrão de progressive disclosure: carregar tool sob demanda em vez de afogar o modelo em cinquenta definições. Tool demais não é robustez. É distração.
Passo 4: o fallback para quando falha mesmo assim
Nenhum schema é perfeito. Quando a tool falha em runtime — ID não existe, API externa fora do ar — devolva um erro que o modelo consegue usar para se corrigir, não um código críptico:
tool_result = {
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": "Erro: cliente 'cus_999' não encontrado. Confirme o ID com o usuário ou use search_customers para localizar pelo nome.",
"is_error": True,
}
A mensagem aponta o problema e a saída. O modelo lê isso, pede confirmação ou troca de estratégia. Erro acionável fecha o loop; código de erro nu trava o agente.
Quando você precisa de garantia de que uma tool específica vai ser chamada, force com tool_choice:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "book_flight"}, # obriga essa tool
messages=[{"role": "user", "content": "..."}],
)
As opções são auto (padrão), any (qualquer tool, mas obriga uma), tool (essa tool) e none (proíbe tools). Para o caminho onde o argumento ainda escapa, a estratégia de repair funciona: reenvie o histórico mais o schema para um modelo mais forte gerar o input válido, ou use structured outputs para constranger a saída.
Limitações e pontos de atenção
Onde você ainda vai se queimar:
- Strict tem limites de schema. Modo strict não suporta schema recursivo, nem restrições numéricas (
minimum/maximum) ou de string (minLength). Valide essas regras do seu lado. - Tier do modelo importa. Modelos da faixa mini/nano/flash/Haiku têm confiabilidade de tool call mensuravelmente menor. Se a precisão é crítica no agente, não economize justo na decisão de qual tool chamar.
- Cache e contexto estourado. Mudou o schema e não reiniciou o processo? O agente pode estar usando a versão antiga enquanto a validação roda contra a nova. E se prompt + tools + mensagem estouram o contexto, o modelo pode ver um schema parcial e gerar uma chamada que não bate com a definição completa.
- Não exagere na descrição. Nos modelos recentes, instrução agressiva tipo "CRITICAL: você DEVE usar esta tool sempre" faz a tool disparar quando não devia. Seja prescritivo, não autoritário.
E o ponto que sustenta credibilidade: monte um harness de avaliação. Rode 50 a 100 prompts representativos pelo caminho de tool calling e valide a saída contra o schema. Tool com menos de 95% de sucesso nesse teste não sobe para produção. Sem isso, você está consertando no escuro.
FAQ rápido
Por que o modelo chama a ferramenta certa mas com argumento errado?
Quase sempre o schema está frouxo. Adicione strict: true, feche valores com enum, marque os required e dê description específico em cada parâmetro. Para inputs complexos, input_examples resolve o que o schema sozinho não expressa.
Mais tools deixam o agente mais capaz? Não. Tools demais distraem o modelo e inflam o contexto. Consolide tools relacionadas e, acima de algumas dezenas, use Tool Search para carregar só o que importa por chamada.
Strict mode resolve tudo?
Resolve a validade do argumento, não a escolha da tool. Escolha errada é problema de descrição e nome — strict não toca nisso. Os dois andam juntos: descrição para escolher, schema para preencher.
Funciona com OpenAI, Gemini e outros? O princípio é o mesmo em qualquer function calling: nome claro, descrição prescritiva, schema apertado, fallback acionável. A sintaxe muda; a causa raiz da falha, não.
Conclusão
O modelo raramente é o gargalo do seu tool calling. O contrato é. Descrição que diz quando usar, schema que rejeita lixo na origem, nomes que não se confundem e fallback que ensina o modelo a se corrigir — esses quatro ajustes derrubam a maior parte das falhas de "chamou a ferramenta errada". E o melhor: dá para medir cada um deles, do jeito que a Anthropic mediu.
Esse é exatamente o tipo de decisão que separa um agente de demo de um agente de produção, e é o que destrinchamos com código rodando no Workshop Arquitetando Soluções de IA: como arquitetar soluções reais com agents, sem terceirizar o raciocínio para a sorte. Depois de fechar o tool calling, o próximo passo é avaliação contínua — porque schema bom hoje não garante schema bom depois da próxima tool que você adicionar.
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
Você também pode gostar
Tool use na prática: desenhando ferramentas que o LLM realmente consegue usar
Você plugou doze tools no agente e ele continua chamando a errada, inventando IDs ou pulando etapas. O gargalo quase nunca é o modelo: é o design das ferramentas. Veja por que descrição mal escrita destrói tool use e quais são os princípios concretos (nome, descrição, schema strict, exemplos few-shot, erros úteis) para desenhar tools que o LLM realmente sabe chamar em produção.
Tool calling na prática: como o agente decide chamar uma ferramenta
Anatomia do loop ReAct e do tool calling: quando o agente decide buscar/agir vs. responder direto, com design de tools (contratos, schemas, idempotência) e um exemplo de tool de busca em banco no Laravel via Claude API.
Progressive disclosure: como não afogar seu agente em 50 ferramentas
Colocar 50 ferramentas no contexto do agente degrada a escolha e estoura tokens. Progressive disclosure carrega tools sob demanda: o agente descobre o que precisa quando precisa. Padrão central de arquitetura de agentes que escalam.
Montando um agente mínimo viável com Claude API + Laravel
Um walkthrough de um agente funcional em PHP puro com Laravel, batendo direto na Claude API, sem framework de agente nenhum. Só o loop prompt, tool use e resposta.