O problema
Um assistente de suporte recebe uma pergunta que exige duas informações: o status do pedido e o histórico de pagamento do cliente. O modelo responde com stop_reason: "tool_use" pedindo duas ferramentas, get_order_status e get_payment_history, no mesmo turno. O time que construiu o harness tem duas escolhas na hora de montar a resposta: executar as duas chamadas e devolver os resultados como um parágrafo corrido resumindo os dois, ou executar cada uma separadamente e devolver cada resultado amarrado ao tool_use_id que o originou.
A primeira opção parece mais limpa — é só um texto explicando o que foi encontrado. Mas ela quebra o contrato do protocolo: se uma das chamadas falhar, não há como o modelo saber qual delas, porque a identidade de cada chamada se perdeu no resumo. A segunda opção é mais verbosa, mas é a única que preserva informação suficiente para o loop continuar de forma confiável.
Esse é o tipo de decisão que esta lição trata: um loop de ferramentas não é "deixar o modelo trabalhar sozinho". É delegação controlada, onde o harness que você escreve é responsável por manter o protocolo íntegro em cada volta.
O ciclo de vida, sem atalho
O ciclo de uma chamada de ferramenta tem um formato fixo:
- O modelo responde com
stop_reason: "tool_use"e um ou mais blocostool_use, cada um com umidúnico. - O harness executa a ferramenta correspondente a cada bloco.
- O harness devolve um
tool_resultportool_use_id, na mesma ordem de turno, como parte da próxima mensagem do usuário. - O modelo continua a conversa com esse resultado disponível — e pode pedir outra ferramenta, ou terminar com
stop_reason: "end_turn".
O ponto que mais gera erro em produção: quando o modelo pede duas ou mais ferramentas independentes no mesmo turno, elas podem ser executadas em paralelo (se não houver dependência entre elas), mas os resultados precisam manter a associação com o id original. Combinar os resultados num texto solto, ou devolvê-los fora de ordem sem a referência ao id, é a causa mais comum de loops que "esquecem" o que já foi perguntado.
Onde a decisão pode ir errada
Um padrão que aparece com frequência em cenários de prova: dado que uma chamada é determinística (como calcular um checksum ou formatar uma data), a resposta "óbvia" costuma ser delegar isso para outra chamada de ferramenta, ou pior, para um subagente. Mas se a operação tem uma implementação exata e sem ambiguidade, ela deve ser resolvida em código convencional dentro do próprio harness — não vale a pena pagar o custo de uma chamada de modelo, e menos ainda o de um subagente inteiro, para algo que uma função pura resolve.
A regra prática: reserve o loop de ferramentas (e, com mais razão, subagentes) para passos que exigem julgamento, ambiguidade ou acesso a um sistema externo. Everything else é código.
Outro erro comum é não ter uma condição de parada clara. Um loop que continua pedindo ferramentas indefinidamente porque cada resposta "parece incompleta" para o modelo drena orçamento e tempo sem necessidade. O harness deve impor um limite de iterações e, quando esse limite é atingido, devolver um estado explícito de "não concluído" em vez de deixar o loop rodar até estourar o tempo da requisição.
Erros, limites e cancelamento também são parte do loop
Em produção, um harness de tool use robusto trata como estados de primeira classe:
- Erro na execução da ferramenta: o
tool_resultdeve indicar a falha explicitamente (por exemplo, com um campo de erro), não silenciar o problema devolvendo uma string vazia. - Cancelamento pelo usuário: a requisição em andamento precisa ser interrompida de forma limpa, sem deixar o histórico da conversa em um estado inconsistente (um
tool_usesemtool_resultcorrespondente quebra a próxima chamada). - Limite de tempo ou de iterações: como já mencionado, precisa de um teto explícito, não implícito.
Coloque em prática
O laboratório desta lição pede pra você implementar um harness mínimo de loop de ferramentas em Python: ele recebe uma lista de chamadas de ferramenta simuladas (algumas independentes, uma dependente de outra), precisa executar as independentes preservando a associação por id, detectar quando uma chamada determinística deveria ter sido resolvida em código puro, e impor um limite de iterações. Os testes verificam que a associação entre chamada e resultado nunca se perde, mesmo quando a ordem de execução não é a ordem de chegada.