Lição 6: Mão na massa: conectando três ferramentas a um agente
Objetivos de aprendizado:
- Escrever um laço de execução de ferramentas completo que realmente põe um agente para rodar
- Registrar a definição de interface e a implementação de uma ferramenta numa única tabela, para que os dois lados nunca se separem
- Equipar o laço com válvulas de segurança e ler os logs para identificar quando uma ferramenta está mal conectada
Pré-requisitos: Conclua as Lições 1-5 e saiba ler JavaScript / Node.js básico | Anterior: Lição 5 <<
Primeiro o resultado: uma execução completa
É isto que esta lição constrói. Você digita uma frase no terminal, e o agente decide por conta própria quais ferramentas chamar e quantas vezes:
Três turnos, três ferramentas, e os argumentos de cada turno se apoiam no resultado do turno anterior: primeiro descobrir em quais arquivos lodash aparece, depois ler o package.json para confirmar a versão, depois pegar esse nome e perguntar ao GitHub sobre ele. Isto não é um script com valores fixos — o próprio modelo decide qual ferramenta chamar em seguida e quais argumentos passar.
Esta lição constrói tudo do zero: três ferramentas, um registro, um laço de execução, algumas válvulas de segurança.
O que está acontecendo por baixo: uma ida e volta de API atrás da outra
Cada "turno" que você viu acima é, por baixo, uma requisição HTTP completa. A Lição 2, "A ida e volta completa de uma chamada de ferramenta", mostrou como é a ida e volta de uma única chamada de ferramenta; aqui apenas a ligamos num laço — o modelo retorna stop_reason: "tool_use", seu código roda a ferramenta, costura o resultado de volta na conversa e envia outra requisição, até que o modelo pare de pedir chamadas de ferramenta.1
Três turnos de chamada de ferramenta são, na verdade, quatro chamadas a messages.create: nas três primeiras o modelo segue pedindo ferramentas, e na quarta ele já tem os dados do GitHub, decide que tem o suficiente e dá uma resposta em texto diretamente, encerrando o laço. O julgamento sobre continuar pedindo ferramentas ou não vive inteiramente do lado do modelo; seu código apenas executa e devolve resultados.
Passo 1: escreva o contrato de cada ferramenta
A Lição 4, "Projetando interfaces de ferramentas: nome, descrição, parâmetros, valor de retorno", cobriu os três campos centrais de uma interface de ferramenta: name, description e input_schema.2 Aqui nós os transformamos direto em código. As três ferramentas mapeiam para três dos cinco tipos da Lição 3, "Cinco tipos comuns de ferramenta: ler, escrever, executar, buscar, chamar": buscar, ler e chamar — escrever e executar ficam para você conectar nos exercícios.
github_repo_info carrega um prefixo github_ — a orientação oficial é dar namespacing aos nomes de ferramenta com o serviço quando a ferramenta toca um serviço externo, o que reduz bastante a chance de o modelo escolher a ferramenta errada.3 search_files e read_file operam sobre o sistema de arquivos local, onde não há ambiguidade de "qual serviço", então não precisam de prefixo.
As três descrições explicitam que texto volta quando nada é encontrado, e isso não é enfeite. A Lição 4 defendeu que uma boa descrição elimina a ambiguidade em entradas e saídas;4 a ambiguidade aqui não está nos parâmetros, e sim em como a ferramenta expressa "não encontrei nada" — uma armadilha que dispara na seção "Válvulas de segurança".
Passo 2: registre o contrato e a implementação numa única tabela
Uma armadilha comum: se a lista de schemas e a tabela de busca de handlers usada em tempo de execução forem escritas como duas cópias separadas, elas vão se separar mais cedo ou mais tarde. Você renomeia search_files para find_in_files mas esquece de atualizar a chave na tabela de handlers; o modelo emite uma chamada conforme o novo schema, a tabela de handlers não tem nada sob aquela chave, e estoura.
A solução é manter uma única tabela em que name, description, input_schema e a função que de fato roda fiquem todos no mesmo objeto. A lista de schemas de que a API precisa e a tabela de busca de handlers de que a execução precisa são ambas derivadas dessa mesma tabela:
toolSchemas e toolHandlers ficam em sincronia para sempre, porque são duas visões calculadas a partir do mesmo dado, e não duas cópias escritas à mão. Renomear uma ferramenta ou adicionar um parâmetro significa alterar TOOLS em exatamente um lugar.
Passo 3: implemente as três ferramentas, com fronteiras
searchFiles percorre o diretório por conta própria em vez de invocar o grep no shell — isso evita emendar entrada de usuário numa linha de comando e convidar a injeção de comandos. A contagem de acertos tem teto, para que uma única busca não enfie milhares de linhas no contexto:
readFile faz uma coisa: confirmar que o caminho de destino não escapou da raiz do projeto. A ideia de fronteira da Lição 5 aparece aqui como uma única verificação de prefixo com separador. Note que não é um startsWith(PROJECT_ROOT) puro: digamos que a raiz do projeto seja /Users/me/proj e o modelo passe ../proj-backup/x; depois do resolve você tem /Users/me/proj-backup/x, e uma comparação de prefixo pura ainda passaria — acrescente path.sep e a fronteira finalmente cai sobre o separador de diretórios:
githubRepoInfo é a única ferramenta que envia dados para fora do projeto — conteúdo de arquivo local, destilado pelo modelo nas duas strings owner e repo, e então enviado para a internet pública. É exatamente o cenário em que duas condições de alto risco se encontram, "ler dados privados" mais "comunicar-se para fora",5 então ela ganha uma regra de permissão explícita: os argumentos precisam corresponder ao formato de nome válido do GitHub, e nada mais:
GITHUB_TOKEN é lido de uma variável de ambiente e nunca aparece no código; funciona sem ele também, apenas com limites de taxa mais baixos para requisições anônimas. Esta é a mesma ideia das regras de permissão da Lição 5 em outra forma: aquela lição cobriu as regras declarativas allow/deny/ask no arquivo de configuração do Claude Code,6 e esta é a versão imperativa escrita dentro do código da ferramenta — ambas traçam uma linha que uma operação de alto risco não pode cruzar.7
Passo 4: escreva o laço de execução
Com toolSchemas e toolHandlers em mãos, o laço em si não é complicado. A lógica central tem quatro passos: enviar a requisição, olhar o stop_reason, retornar texto se não for tool_use e, se for, rodar cada bloco de chamada de ferramenta e costurar os resultados de volta.1
Há aqui um detalhe fácil de deixar passar: for (const block of response.content) itera sobre todos os blocos de conteúdo retornados neste turno, não apenas sobre o primeiro. O modelo frequentemente pede duas ou três ferramentas em paralelo num mesmo turno; cada uma precisa ser executada e produzir seu próprio tool_result, com tool_use_id casado um a um, e nenhuma pode faltar.8 O exercício de Nível 2 vai levar você pela armadilha de deixar uma faltando na prática.
Válvulas de segurança, e como identificar quando uma ferramenta está mal conectada
O laço acima roda, mas faltam duas salvaguardas. Vamos acrescentá-las:
Salvaguarda um: uma falha de ferramenta tem que ser devolvida como retorno, não derrubar o laço. Embrulhe a chamada crua num try/catch e, em caso de falha, ainda produza um tool_result, apenas marcado com is_error: true — quando o modelo vê essa marca, normalmente ajusta os argumentos e tenta de novo, em vez de repetir o mesmo erro.9 8
Salvaguarda dois: a mesma ferramenta com os mesmos argumentos, chamada três vezes seguidas, deve parar. Isto não é chute — baseia-se em registrar as assinaturas das últimas chamadas:
Junto com MAX_TURNS como chave-mestra, as três válvulas de segurança têm funções distintas: MAX_TURNS protege contra "o modelo segue pedindo ferramentas em novas variações e nunca para"; a detecção de chamadas repetidas protege contra "o modelo fica preso girando nos mesmos argumentos"; e as verificações internas de caminho e formato das ferramentas (aquelas escritas no Passo 3) protegem contra "o modelo inventou um argumento fora dos limites e a ferramenta obedientemente o executou assim mesmo". Tire qualquer uma das três camadas e o laço corre o risco de disparar sem controle ou de ultrapassar seus limites.7
Como você identifica pelos logs que uma ferramenta está mal conectada? Dois dos sinais mais comuns:
- O modelo chama a mesma ferramenta repetidamente, com argumentos variando apenas dentro de uma faixa estreita (mudanças de caixa, acrescentar ou tirar uma palavra). Em nove de cada dez casos o modelo não está sendo burro — o conteúdo do
tool_result é vago demais. O "não encontrado" retorna uma string vazia, o modelo não consegue distinguir "realmente não há nada lá" de "a ferramenta está quebrada", e só lhe resta chutar e tentar de novo.
- O modelo preenche argumentos no chute, por exemplo passando ao
read_file um caminho que não existe. Rastrear para trás normalmente revela uma de duas causas: a description não explicitou de onde o argumento deveria vir (ecoando a Lição 4), ou a saída da ferramenta anterior não deu um caminho preciso, deixando o modelo inventar um.
Recapitulação
- Registre o schema e o handler de uma ferramenta na mesma tabela (
TOOLS), com toolSchemas e toolHandlers ambos derivados dela, para que alterar um lugar nunca deixe o outro sem alteração
- O cerne do laço de execução é: enviar a requisição → verificar se
stop_reason é tool_use → se for, iterar sobre cada bloco de chamada de ferramenta, executar e costurar de volta o tool_result → se não for, retornar texto e encerrar o laço
- Um turno pode ter várias chamadas de ferramenta em paralelo; cada
tool_use precisa de um tool_result correspondente e único, e faltar um faz a requisição seguinte dar erro
- As três válvulas de segurança guardam cada uma sua camada:
MAX_TURNS impede que o modelo peça ferramentas indefinidamente, a detecção de chamadas repetidas impede que o modelo fique girando no mesmo conjunto de argumentos, e as verificações internas de caminho e formato das ferramentas barram argumentos fora dos limites
- O conteúdo do
tool_result tem que declarar com clareza "não encontrado" versus "ocorreu um erro"; um retorno vazio e vago é a causa número um de o modelo repetir a tentativa sem parar e de os logs parecerem indicar uma ferramenta mal conectada
Você concluiu agora todas as seis lições deste curso, de "por que agentes precisam de ferramentas" até escrever você mesmo um laço de execução de ferramentas que funciona. A coisa mais valiosa a fazer em seguida não é ler mais uma lição — é pegar uma tarefa pequena e real do seu próprio projeto, quebrá-la em duas ou três ferramentas e levar este esqueleto de laço com alguns ajustes. Colocar para rodar uma vez vale mais do que ler outras dez explicações. Ao depurar, se ficar em dúvida sobre algum campo específico, volte ao sources.md e consulte S4 e S5, os dois documentos oficiais; são o texto normativo mais primário para este laço de múltiplos turnos.