Tratando Requisições de tool_use e Retornando Blocos tool_result
Execute a ferramenta solicitada e retorne um bloco de conteúdo tool_result correspondente.
Resumo
Quando stop_reason em uma Message retorna "tool_use", Claude pausou no meio da vez para pedir que seu código execute algo. Ele ainda não respondeu ao usuário - está esperando por você.
Seu trabalho é um processo fixo de cinco etapas: encontrar os blocos tool_use em response.content, executar a função Python correspondente com block.input, anexar o response.content do assistente de volta a messages literalmente, envolver a saída de cada ferramenta em um bloco tool_result com a chave tool_use_id, e chamar messages.create novamente.
Esta página trata de realizar essa transição corretamente e de forma defensiva - lendo block.input como o dicionário analisado que o SDK fornece (nunca analisando novamente ou correspondendo strings de JSON bruto), tratando cada bloco tool_use em uma vez (não apenas o primeiro), e relatando falhas com is_error em vez de deixar uma exceção encerrar a requisição.
Acertar este loop uma vez o transforma em uma função pequena e reutilizável que todas as requisições que usam ferramentas em sua aplicação podem chamar.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
if response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type != "tool_use":
continue
try:
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
except Exception as e:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(e),
"is_error": True,
})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=tools,
messages=messages,
)Quando usar isso:
- Qualquer requisição onde você passou uma lista
toolsnão vazia e precisa realmente honrar uma respostatool_use. - Vezes em que Claude pode solicitar mais de uma ferramenta por vez - o loop acima lida com todas elas antes de responder.
- Em qualquer lugar onde uma chamada de ferramenta possa falhar plausivelmente (uma API instável, uma consulta incorreta) e você queira que Claude veja a falha em vez de seu processo travar.
- Construindo um helper de propósito geral "execute esta conversa até que Claude pare de pedir ferramentas" que você reutilizará em vários endpoints.
Exemplo Funcional
import anthropic
client = anthropic.Anthropic()
weather_tool = {
"name": "get_weather",
"description": "Obtém o clima atual para um local. Chame isso quando o usuário perguntar sobre condições atuais.",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "Cidade e estado, por exemplo, São Paulo, SP",
}
},
"required": ["location"],
},
}
stock_price_tool = {
"name": "get_stock_price",
"description": "Obtém o preço mais recente para um símbolo de ticker de ação.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Símbolo do ticker da ação, por exemplo, PETR4",
}
},
"required": ["ticker"],
},
}
tools = [weather_tool, stock_price_tool]
def get_weather(location: str) -> str:
# Substitua por uma chamada real de API de clima.
return f"72F e ensolarado em {location}"
def get_stock_price(ticker: str) -> str:
# Substitua por uma chamada real de API de dados de mercado.
if not ticker.isalpha():
raise ValueError(f"'{ticker}' não é um símbolo de ticker válido")
return f"{ticker.upper()} está sendo negociado a R$ 184,32"
TOOL_FUNCTIONS = {
"get_weather": get_weather,
"get_stock_price": get_stock_price,
}
def execute_tool(name: str, tool_input: dict) -> str:
if name not in TOOL_FUNCTIONS:
raise ValueError(f"Ferramenta desconhecida: {name}")
return TOOL_FUNCTIONS[name](**tool_input)
def run_turn(messages: list) -> list:
"""Envia mensagens, lida com qualquer ciclo de ida e volta de tool_use, retorna a lista atualizada."""
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
while response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type != "tool_use":
continue
try:
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
except Exception as e:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(e),
"is_error": True,
})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
return messages
if __name__ == "__main__":
conversation = [
{
"role": "user",
"content": "Qual o clima em São Paulo, SP, e qual o preço de AAPL?",
}
]
conversation = run_turn(conversation)
final_text = next(
block.text for block in conversation[-1]["content"] if block.type == "text"
)
print(final_text)O que isso demonstra:
- Um loop
while response.stop_reason == "tool_use":, para que o manipulador continue resolvendo chamadas de ferramentas até que Claude produza uma resposta de texto final, não apenas uma única viagem de ida e volta. - Uma tabela de despacho (
TOOL_FUNCTIONS) que mapeiablock.namea uma função Python real, comexecute_toollevantando um erro limpo para qualquer nome que não reconheça. - Duas ferramentas solicitadas na mesma pergunta do usuário, ambas resolvidas e retornadas juntas em uma única mensagem
usercontendotool_result. - Um
try/exceptem torno de cada execução de ferramenta, para que um símbolo incorreto (get_stock_pricelevantandoValueError) não derrube a consulta de clima executada ao lado dela.
Mergulho Profundo
Como Funciona
response.stop_reason == "tool_use"significa que Claude parou de gerar especificamente para devolver o controle a você - não é um estado de erro nem a resposta final.response.contenté uma lista de blocos de conteúdo. Uma veztool_usepode conter um blocotextinicial (Claude "pensando alto") mais um ou mais blocostool_use- sempre filtre porblock.type == "tool_use"em vez de assumir uma forma fixa.- Cada bloco
tool_usecarrega.id(um identificador único para esta chamada específica),.name(qual ferramenta) e.input(os argumentos, já analisados em um dicionário Python que corresponde ao seuinput_schema). - A mensagem do assistente que você anexa a
messagesdeve serresponse.contentexatamente como retornado, não uma reconstrução - é o que preserva oiddo blocotool_usepara que otool_resultsubsequente possa referenciá-lo corretamente. - Os blocos
tool_resultsempre vivem dentro de uma mensagem de roleuser, nunca deassistant, mesmo que nada sobre eles tenha sido digitado por um humano.
Estruturando Conteúdo de tool_result
Valor de content | Quando usar | Exemplo |
|---|---|---|
| Uma string simples | A maioria das saídas de ferramentas - texto, JSON como string, resumos curtos | "72F e ensolarado em Austin, TX" |
| Uma lista de blocos de conteúdo | A saída da ferramenta inclui várias partes, como texto mais uma imagem | [{"type": "text", "text": "..."}, {"type": "image", "source": {...}}] |
Uma string de erro com is_error: True | A ferramenta levantou uma exceção ou a chamada subjacente falhou | {"content": "Ticker não encontrado", "is_error": True} |
Notas Python
# Nunca corresponda strings de JSON bruto para descobrir o que Claude pediu.
# Os modelos da família Claude 4 podem escapar Unicode ou barras invertidas de forma diferente
# entre as respostas, então a correspondência de texto bruto em block.input é frágil.
# Sempre use o dicionário já analisado do SDK:
for block in response.content:
if block.type == "tool_use":
# block.input é um dicionário - acesse-o diretamente, não use json.loads()
location = block.input["location"]Parâmetros e Valores de Retorno
| Campo | Tipo | Descrição |
|---|---|---|
type | str | Sempre "tool_result" para este bloco. |
tool_use_id | str | Deve ser igual ao .id do bloco tool_use que está sendo respondido. |
content | str ou list[dict] | A saída da ferramenta - uma string ou uma lista de blocos de conteúdo. |
is_error | bool (opcional) | Defina como True quando a ferramenta levantou uma exceção ou falhou de outra forma; omita ou defina como False em caso de sucesso. |
Armadilhas
- Correspondência de texto JSON bruto em vez de
block.input. Algumas equipes tentam usar regex ou correspondência de strings da chamada da ferramenta antes de analisá-la. Os modelos da família Claude 4 podem variar como escapam Unicode ou barras invertidas em JSON gerado, então essa abordagem falha intermitentemente. Correção: sempre leiablock.input- o SDK já o analisou em um dicionário Python para você. - Anexar apenas o texto, não o
response.contentcompleto, como a vez do assistente. Se você extrairresponse.content[0].texte empurrar apenas essa string de volta paramessages, oiddo blocotool_useé perdido e a próxima requisição falha na validação. Correção: anexeresponse.contentliteralmente:messages.append({"role": "assistant", "content": response.content}). - Dividir múltiplos blocos
tool_resultem mensagens de usuário separadas. Quando Claude solicita duas ferramentas em uma vez, enviar duas mensagens deuserde acompanhamento (uma por resultado) em vez de uma mensagem com dois blocos produz uma conversa malformada. Correção: colete todos ostool_resultem uma lista e envie-os juntos:messages.append({"role": "user", "content": tool_results}). - Deixar um bloco
tool_usesem resposta. Se Claude solicitar três ferramentas e você retornar apenas dois blocostool_result(por exemplo, porque o terceiro falhou silenciosamente e você engoliu a exceção), a próxima chamadamessages.createrejeitará a requisição. Correção: envolva cada chamada de ferramenta emtry/excepte sempre anexe umtool_result- em caso de falha, definais_error: Trueem vez de pular. - Esquecer
is_errorem uma chamada de ferramenta falha. Retornar a string da exceção comocontentcomum semis_error: Truefaz com que Claude trate um stack trace ou mensagem de erro como dados legítimos, que ele então pode repetir ao usuário como se fosse uma resposta real. Correção: sempre defina"is_error": Truequando a ferramenta levantou uma exceção ou retornou um status de falha. - Não tratar um
block.namenão reconhecido. Se Claude chamar um nome de ferramenta para o qual seu código de despacho não tem um ramo, uma cadeiaif/elifsem guarda cairá e não fará nada ou levantará umKeyErrornão tratado. Correção: faça o caso de "ferramenta desconhecida" um ramo explícito que retorna umtool_resultcomis_error: True, comoexecute_toolfaz no Exemplo Funcional acima. - Passar um objeto não serializável como
content. Retornar uma instância de classe personalizada, umdatetimeou um objeto de linha de banco de dados diretamente comotool_result["content"]falha quando a requisição é serializada. Correção: converta a saída da ferramenta para uma string ou estrutura serializável JSON simples antes de construir o blocotool_result.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Loop manual de ida e volta (esta página) | Você quer controle total sobre a ordem de execução, retentativas e tratamento de erros por ferramenta | O conjunto de ferramentas é grande e dinâmico o suficiente para que um loop gerenciado economize código repetitivo real |
Loop de agente gerenciado pelo SDK (client.beta.messages.tool_runner, beta) | Você quer que o SDK conduza o ciclo de criação-execução-reenvio para você em vez de rolar o loop while manualmente | Você precisa de lógica personalizada por ferramenta - diferentes políticas de retentativa, logging ou formas de resultado não padrão |
| Framework de agente de terceiros | Você precisa de memória, recuperação ou orquestração multiagente integradas sobre o uso de ferramentas | Você quer dependências mínimas e visibilidade total de cada requisição enviada à API |
| Sem uso de ferramentas | A tarefa requer apenas geração e nenhum dado ao vivo ou efeitos colaterais | A resposta depende de dados em tempo real, estado específico do usuário ou uma ação que o modelo não pode executar por conta própria |
FAQs
Como sei quando Claude quer que eu execute uma ferramenta?
Verifique response.stop_reason == "tool_use" após cada chamada messages.create. Se for verdadeiro, response.content contém pelo menos um bloco tool_use para tratar antes de você ter uma resposta final.
block.input já está analisado ou preciso usar json.loads() nele?
Já está analisado. block.input é um dicionário Python que corresponde ao seu input_schema - nenhuma etapa de decodificação é necessária, e você nunca deve recorrer à correspondência do texto bruto da resposta.
Por que a mensagem do assistente precisa de response.content em vez de apenas o texto?
O id do bloco tool_use está dentro de response.content. Se você empurrar apenas o texto extraído para messages, esse id é perdido, e o tool_result que você envia a seguir não tem nada válido para referenciar.
Qual role a mensagem tool_result usa - assistant ou user?
user. Mesmo que um tool_result seja gerado pelo seu código em vez de digitado por uma pessoa, a API o modela como a próxima vez user na conversa.
O que acontece se Claude pedir duas ferramentas na mesma vez?
Itere sobre cada bloco em response.content, executando cada bloco tool_use que você encontrar, e colete todos os blocos tool_result resultantes em uma única lista. Envie essa lista como o content de uma única mensagem user - não como duas mensagens separadas.
O que o content deve ser dentro de um bloco tool_result?
Geralmente uma string simples. Também pode ser uma lista de blocos de conteúdo (por exemplo, texto mais uma imagem) quando a saída da ferramenta tem mais de uma parte.
O que devo fazer se minha função de ferramenta levantar uma exceção?
try:
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
except Exception as e:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(e),
"is_error": True,
})Retorne um tool_result de qualquer maneira, com is_error: True em caso de falha, para que Claude possa ver o que aconteceu e se adaptar.
Posso pular o retorno de um tool_result se a chamada da ferramenta não fizer sentido?
Não. Cada bloco tool_use na mensagem do assistente precisa de um tool_result correspondente na próxima mensagem user, mesmo que esse resultado seja apenas uma string de erro explicando por que a chamada não pôde ser concluída.
E se Claude solicitar um nome de ferramenta que eu não reconheço?
Trate como qualquer outra falha: retorne um tool_result com is_error: True e uma mensagem como "Ferramenta desconhecida: <nome>", em vez de deixar um ramo não tratado travar seu loop.
Preciso continuar passando tools= nas chamadas messages.create subsequentes?
Sim. Claude pode precisar chamar uma ferramenta novamente com base no resultado que você acabou de retornar, então a requisição subsequente deve incluir a mesma lista tools da chamada original.
Por que tool_use_id é tão importante?
É a única coisa que associa um tool_result ao bloco tool_use específico que ele está respondendo. Um tool_use_id ausente ou incorreto produz uma conversa malformada que a API rejeitará.
A execução da ferramenta deve ser sequencial ou posso executar ferramentas em paralelo?
A API não se importa com a ordem de execução - ela apenas exige que cada bloco tool_use receba um tool_result de volta na mesma mensagem de acompanhamento. Você pode executar várias ferramentas simultaneamente (por exemplo, com um pool de threads), desde que ainda colete todos os resultados em uma única mensagem user antes de chamar messages.create novamente.
Relacionado
- Noções Básicas de Uso de Ferramentas - o tutorial completo com 10 exemplos do qual o ciclo de ida e volta desta página é extraído
- Definindo Esquemas de Ferramentas com name, description e input_schema - escrevendo o
input_schemaque produz valoresblock.inputconfiáveis - Construindo um Loop de Uso de Ferramentas Multi-Turno - generalizando o loop
whilemostrado aqui em um helper reutilizável - Executando Chamadas de Ferramentas Paralelas em uma Única Vez - mais sobre o agrupamento de vários blocos
tool_resultjuntos
Versões da Stack: Escrito contra a linha de modelos Claude atual a partir de ~junho de 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (o padrão), e Claude Haiku 4.5 - e o SDK oficial
anthropicPython (última versão 0.x). Nomes de modelos, versões de SDK e preços mudam rapidamente - verifique os detalhes atuais em platform.claude.com/docs antes de confiar neles.