Tool calling na prática: como o agente decide chamar uma ferramenta
Entre "pensar" e "agir" existe um loop. No tool calling, esse loop é a coisa toda. Quem entende ele para de brigar com o modelo — e para de ver o agente inventar parâmetro de ferramenta do nada.
A maioria dos bugs de agente que eu vejo não está no prompt. Está na fronteira: o momento em que o modelo decide se vai responder direto ou chamar uma tool. Quando você não controla essa fronteira, o agente alucina argumento, chama a ferramenta errada, ou responde "não tenho acesso a isso" enquanto a tool certa estava ali, na frente dele.
Neste tutorial a gente abre o capô do tool calling: como o agente decide chamar uma ferramenta, qual é a anatomia do loop ReAct por trás disso, e como desenhar tools (contrato, schema, idempotência) que o modelo consegue usar sem se perder. No final, um exemplo que roda: uma tool de busca em banco, em Laravel, chamando a Claude API.
TL;DR
- O que é: tool calling (ou function calling) é o mecanismo que deixa o LLM emitir uma chamada estruturada de função, que o seu código executa e devolve pro modelo.
- Stack/Modelos: Claude API (Messages), PHP 8.3 + Laravel 11, Eloquent.
- Custo/Acesso: requer chave da Anthropic (paga, por token). O exemplo é client-side, sem custo extra além dos tokens.
- Conceito-base: loop ReAct — Yao et al., 2022.
O loop ReAct: por que o agente "pensa" antes de "agir"
Antes de existir tool calling na API, existia um paper. Em 2022, Yao e colegas publicaram o ReAct — Synergizing Reasoning and Acting in Language Models. A ideia é simples e mudou tudo: em vez de o modelo só raciocinar (chain-of-thought puro) ou só agir, ele intercala os dois. Pensa um passo, age um passo, observa o resultado, pensa de novo.
Por que isso importa? Porque chain-of-thought sozinho alucina. O modelo raciocina lindamente sobre fatos que ele inventou. O ReAct quebra isso: a cada passo de ação, o modelo busca informação real no mundo — uma API, um banco, uma busca — e o resultado dessa busca ancora o próximo passo de raciocínio. No paper, isso reduziu alucinação e erro em cima de tarefas de QA, e bateu métodos de RL por +34% de sucesso no ALFWorld e +10% no WebShop, com um ou dois exemplos no prompt.
O loop tem três tempos, sempre os mesmos:
- Reason — o modelo pensa: "pra responder isso eu preciso saber o preço atual do produto X".
- Act — ele emite uma ação:
buscar_produto(nome: "X"). - Observe — seu código executa, devolve o resultado, e o modelo lê isso como novo contexto.
Repete até ele ter o que precisa pra fechar a resposta. Tool calling é a versão dessa dança formalizada dentro da API — em vez de você parsear texto livre procurando "Act: ...", o modelo devolve um bloco estruturado. É a diferença entre adivinhar a intenção com regex e receber a intenção tipada.
A decisão central do tool calling: chamar a tool ou responder direto?
Aqui mora o ponto que confunde quase todo mundo. Quem decide chamar a ferramenta é o modelo, não você. Você descreve o que existe; ele escolhe quando usar.
Com o tool_choice no default — {"type": "auto"} — a documentação da Anthropic é direta sobre o critério: a cada turno, Claude chama uma tool quando o pedido mapeia na capability descrita pela tool e a resposta ainda não está no contexto. Ele responde direto quando é conhecimento estável, tarefa criativa, ou conversa fiada. Ou seja: a descrição da sua tool é o classificador. Descrição vaga, decisão ruim.
Essa fronteira é dirigível pelo system prompt. Se o agente não chama a tool quando você espera, uma instrução leve já mexe o ponteiro de forma mensurável — algo como "Use as ferramentas para investigar antes de responder.". Quer mais agressivo? "Sempre chame uma ferramenta antes de responder.". Quer conservador? "Use seu julgamento sobre chamar uma ferramenta ou responder direto.". E quando você precisa de garantia dura, não de empurrãozinho, troca o tool_choice para any (chama alguma tool) ou tool (chama uma específica).
Tem um detalhe de comportamento que vale ouro saber: quando falta um parâmetro obrigatório, o modelo Opus tende a perguntar; o Sonnet tende a chutar um valor plausível. A própria doc avisa isso. Se você está vendo o agente inventar um location: "New York" que ninguém pediu, não é azar — é o modelo preenchendo lacuna. A correção não é xingar o prompt. É desenhar a tool pra não deixar a lacuna existir.
Pré-requisitos
- [ ] Chave de API da Anthropic (
ANTHROPIC_API_KEYno.env). - [ ] PHP 8.3+, Laravel 11, um banco com uma tabela
products(id, name, price, stock). - [ ] HTTP client — vou usar o
Httpdo Laravel direto, sem SDK, pra você ver o JSON cru da API. - [ ] Noção básica de JSON Schema (é como a tool descreve os argumentos que aceita).
Mão na massa: uma tool de busca em banco no Laravel
Vamos construir um agente que responde perguntas sobre o catálogo consultando o banco — não a memória do modelo. Esse "responder preço e estoque olhando o mundo real" é justamente o teste que separa um chatbot de um agente de verdade. A peça central é uma tool buscar_produto. Três passos: definir o contrato, rodar o loop, executar a chamada.
Passo 1: definir a tool (o contrato)
A tool é um objeto com name, description e input_schema (JSON Schema). Essa descrição não é documentação pra humano — é o que o modelo lê pra decidir se e como chamar. Escreva como se explicasse pra alguém que entrou ontem no time.
$tools = [[
'name' => 'buscar_produto',
'description' => 'Busca produtos no catálogo da loja pelo nome ou parte do nome. '
. 'Use sempre que o usuário perguntar sobre preço, estoque ou disponibilidade '
. 'de um produto específico. Retorna nome, preço em reais e quantidade em estoque. '
. 'Não use para perguntas gerais que não citam um produto.',
'input_schema' => [
'type' => 'object',
'properties' => [
'termo' => [
'type' => 'string',
'description' => 'Nome ou trecho do nome do produto, ex: "caneca" ou "teclado mecânico".',
],
],
'required' => ['termo'],
],
]];
Repare na descrição: ela diz quando usar ("perguntar sobre preço, estoque...") e quando não usar ("não use para perguntas gerais"). Isso afina a decisão lá do passo anterior. O required força o termo a existir — é assim que você fecha a porta pro modelo chutar.
Passo 2: rodar o loop agêntico
O contrato do tool calling é um while ancorado no stop_reason. Você manda a mensagem; se o modelo responder com stop_reason: "tool_use", ele quer uma ferramenta. Você executa, devolve o resultado como tool_result, e manda de novo. Sai do loop em qualquer outro stop_reason (end_turn, max_tokens, stop_sequence, refusal).
use Illuminate\Support\Facades\Http;
function perguntarAoAgente(string $pergunta, array $tools): string
{
$messages = [
['role' => 'user', 'content' => $pergunta],
];
while (true) {
$resposta = Http::withHeaders([
'x-api-key' => config('services.anthropic.key'),
'anthropic-version' => '2023-06-01',
])->post('https://api.anthropic.com/v1/messages', [
'model' => 'claude-opus-4-8',
'max_tokens' => 1024,
'tools' => $tools,
'messages' => $messages,
])->json();
// O modelo terminou? Devolve o texto e encerra o loop.
if ($resposta['stop_reason'] !== 'tool_use') {
return collect($resposta['content'])
->firstWhere('type', 'text')['text'] ?? '';
}
// Guarda a vez do assistant (precisa ir de volta na próxima mensagem).
$messages[] = ['role' => 'assistant', 'content' => $resposta['content']];
// Executa cada tool_use e monta os tool_result.
$resultados = [];
foreach ($resposta['content'] as $bloco) {
if (($bloco['type'] ?? null) !== 'tool_use') {
continue;
}
$saida = executarTool($bloco['name'], $bloco['input']);
$resultados[] = [
'type' => 'tool_result',
'tool_use_id' => $bloco['id'],
'content' => $saida,
];
}
$messages[] = ['role' => 'user', 'content' => $resultados];
}
}
Três campos que você não pode errar: o bloco que volta tem type, id, name, input. O tool_result que você devolve precisa do tool_use_id casando com aquele id — é o que amarra a resposta à chamada. Erra o id e o modelo recebe a observação fora de ordem.
Passo 3: executar a tool de verdade
Agora o código determinístico. Aqui é PHP normal — Eloquent, validação, o que for. O modelo nunca vê essa implementação; só vê o que você devolve.
use App\Models\Product;
function executarTool(string $nome, array $input): string
{
if ($nome !== 'buscar_produto') {
return "Ferramenta desconhecida: {$nome}";
}
$produtos = Product::query()
->where('name', 'like', '%' . $input['termo'] . '%')
->limit(5)
->get(['name', 'price', 'stock']);
if ($produtos->isEmpty()) {
return "Nenhum produto encontrado para \"{$input['termo']}\".";
}
// Devolve linguagem natural, não um dump de IDs crus.
return $produtos->map(fn ($p) =>
"{$p->name}: R$ " . number_format($p->price, 2, ',', '.')
. " — {$p->stock} em estoque"
)->implode("\n");
}
Olha o return. Eu não devolvo {"id": 4821, "sku": "X9-22"}. Devolvo "Caneca Beer & Code: R$ 49,90 — 12 em estoque". Isso é proposital, e é o gancho pra próxima seção.
Desenhando tools que o modelo consegue usar
Aqui é onde os dois ângulos — o loop e o design — viram um só. Tool boa não é tool que "funciona quando você testa". É tool que o modelo usa certo mesmo quando o pedido é ambíguo. A própria Anthropic resume isso numa frase que vale colar na parede: "Tools are a new kind of software which reflects a contract between deterministic systems and non-deterministic agents" (Writing effective tools for AI agents). Você está escrevendo um contrato pra um chamador que pode entender errado. Projete pra isso.
Quatro princípios que eu aplico em todo agente que vai pra produção:
1. Contrato explícito na descrição. "Pense em como você descreveria a tool pra um novato no time", diz o guia. Termos de domínio, formato de query, relação entre recursos — tudo que você traria implícito, escreva. A descrição é o classificador da decisão; ambiguidade ali vira tool chamada na hora errada.
2. Schema que fecha as lacunas. Marque required no que é obrigatório. Use enum quando o valor é de um conjunto fixo (status: ["pago", "pendente", "cancelado"]) — isso elimina o chute. E se você quer garantia de que a chamada bate com o schema exatamente, a API tem strict: true na definição da tool. Lembra do Opus-pergunta-vs-Sonnet-chuta? Schema apertado é o que neutraliza o chute.
3. Resultado em linguagem natural. O guia é explícito: "agents tend to grapple with natural language names, terms, or identifiers significantly more successfully than they do with cryptic identifiers". Por isso devolvi "Caneca — R$ 49,90" e não um SKU. O modelo raciocina muito melhor sobre nome do que sobre id 4821. E controle o tamanho: a Anthropic limita resposta de tool a 25.000 tokens por padrão no Claude Code. Pagine, filtre, trunque — não jogue 500 linhas de banco no contexto.
4. Idempotência onde tem efeito colateral. A buscar_produto é só leitura — chamar duas vezes não quebra nada. Mas no minuto que você cria uma tool criar_pedido, o jogo muda. O modelo pode chamar de novo se a primeira observação não voltou clara, ou repetir num retry. Desenhe a operação pra ser idempotente: aceite uma chave de idempotência (pedido_uuid), faça upsert em vez de insert cego, e devolva o mesmo resultado se a chave já existe. Agente não-determinístico chamando operação não-idempotente é receita de pedido duplicado.
E quando você tem muitas tools: namespacing. Agrupe sob prefixo comum (catalogo_buscar, catalogo_filtrar, pedido_criar) pra delimitar fronteira. O modelo erra menos a escolha quando os nomes contam a história.
Limitações e pontos de atenção
Onde isso te queima se você não souber:
- O modelo decide errado, e a culpa é da descrição. 90% dos "o agente não chamou a tool" que eu depuro se resolvem reescrevendo a
description, não o system prompt. A tool é o classificador. - Loop infinito é real. Se a sua tool sempre devolve algo que o modelo lê como insuficiente, ele chama de novo, e de novo. Ponha um teto de iterações no
whilee ummax_tokenssensato. Nunca confie que oend_turnsempre vem. - Argumento alucinado existe mesmo com schema. Schema reduz, não zera. Valide o
inputno seu código (é o que o passo 3 faz com oif) e devolvais_error: truenotool_resultquando vier lixo — o modelo lê o erro e tenta de novo, melhor. - Não mande dado sensível cru pro contexto. O
tool_resultvira contexto do modelo e pode ir pro log do provedor. Mascare PII antes de devolver. "Cliente #fra... saldo R$ X" em vez do CPF inteiro. - Custo escondido no
tools. O array de tools entra no input a cada chamada do loop. Tool com descrição gigante, chamada cinco vezes, é cinco vezes o custo. Enxugue.
FAQ rápido
Tool calling e function calling são a mesma coisa?
Na prática, sim. "Function calling" foi o nome que a OpenAI popularizou; "tool use / tool calling" é como a Anthropic chama. O mecanismo é o mesmo: o modelo emite uma chamada estruturada, você executa, devolve o resultado. A Claude API usa os blocos tool_use / tool_result.
Preciso de um framework tipo LangChain pra isso?
Não. O exemplo acima é Http::post puro. Framework ajuda a orquestrar loop, memória e múltiplas tools, mas o tool calling em si é só JSON na request. Comece sem framework pra entender o contrato; adote um quando a orquestração doer.
O modelo executa a minha função? Não. Ele nunca roda seu código. Ele devolve a intenção — nome e argumentos — e seu código executa (client tools). A exceção são as server tools da Anthropic (web_search, code_execution), que rodam na infra deles e já voltam com o resultado.
Como forço o modelo a sempre usar uma tool?
tool_choice: {"type": "any"} força alguma tool; {"type": "tool", "name": "x"} força aquela. Mas cuidado: forçar tool em pergunta que não precede de tool degrada a resposta. Na maioria dos casos, auto + descrição boa ganha.
Fechando o loop
O que a gente construiu é pequeno de propósito: uma tool, um while, três campos que não podem errar. Mas é a anatomia inteira. Reason, act, observe — o loop do ReAct virou stop_reason: "tool_use", e a decisão de chamar ou não a ferramenta mora na descrição que você escreve, não na sorte. Se a peça "agente" ainda está difusa na sua cabeça, vale voltar um passo e ler o que é um agente de IA (e o que é só wrapper de prompt) — tool calling é uma das quatro peças que sustentam ele.
O próximo salto é multi-tool: várias ferramentas, o modelo encadeando chamadas, e aí o design de contrato (schema, idempotência, namespacing) deixa de ser higiene e vira a diferença entre um agente que aguenta produção e um que duplica pedido na primeira concorrência. Se você quer ver esse tipo de arquitetura sendo montada na mesa — com código rodando e decisão de design sendo defendida ao vivo — é exatamente o que a gente faz no Workshop Arquitetando Soluções de IA, um encontro prático de como arquitetar software com agents de IA.
Entender esse loop é o que separa quem briga com o modelo de quem projeta pra ele. Não é prompt mágico. É contrato. E contrato bem-feito é engenharia.
{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
Roadmap AI Engineer em 90 dias: do dev backend ao primeiro agente em produção
Caminho real de 13 semanas para dev backend experiente virar AI engineer aplicada. Tool use, harness próprio, RAG, memória, evals e um projeto fim-a-fim que cabe no portfólio. Sem refazer fundamentos, sem detour por framework da moda. Entregáveis por semana e foco no que recrutador olha de verdade.
Anatomia de um Agent Harness: state, tool execution, feedback loops e guardrails
Harness é o software que envolve o LLM e separa um demo bonito de um agente que aguenta produção. Quebro a anatomia em cinco peças obrigatórias: estado persistente, roteador de ferramentas, validação de I/O, loop de raciocínio e limites de segurança. É o mapa mental que abre a série de posts sobre engenharia de agentes.
Construindo seu primeiro harness em Laravel: do prompt isolado ao loop autônomo
Construa do zero um harness em Laravel mais Claude API: um service PHP que recebe a tarefa, escolhe qual tool chamar, executa em loop ate concluir e reporta. Inclui handling de erros com is_error, limite de iteracoes e logging real. Codigo executavel, sem framework de agente.
Programmatic Tool Calling: por que executar suas ferramentas em código é o futuro do agente
Function calling clássico vai virar legado. Programmatic tool calling do Claude troca o loop turno-a-turno por código Python no sandbox: 37% menos tokens, paralelismo nativo via asyncio.gather e composição em um único script. A gente compara latência, tokens, debug, e fecha com um agente que escreve o próprio orquestrador.