Lesson 2.2
The Tool Use Request/Result Cycle
The exact message shapes Claude and your code exchange when a tool is called, including parallel calls, tool_choice and server-side tools.
Tool use is a protocol layered on ordinary messages. Claude asks for a tool by emitting a content block; you answer with another content block in the next user message. Nothing executes unless your code executes it. The exam tests the block shapes, the pairing rules, and the differences between client-side tools you run and server-side tools Anthropic runs.
Step by step
- Request. You send
tools(an array of definitions) alongsidemessages. - Response with
stop_reason: "tool_use". The assistant content contains one or moretool_useblocks, often preceded by atextblock explaining the intent. - Execute. Your code runs each requested tool with the supplied
input. - Return. You append the assistant message, then a user message containing one
tool_resultblock pertool_use, each referencing the originatingtool_use_id. - Continue. Claude reads the results and either calls more tools or answers with
end_turn.
The two block shapes:
{"type": "tool_use", "id": "toolu_01A", "name": "get_weather",
"input": {"city": "Antwerp"}}
{"type": "tool_result", "tool_use_id": "toolu_01A",
"content": "12°C, light rain"}
content on a result may be a string or an array of blocks, which allows images (a screenshot, a chart) to be returned to the model. Add "is_error": true when the tool failed so Claude treats the content as an error message rather than data.
Pairing rules the API enforces
- Every
tool_resultmust reference atool_use_idthat appears in the immediately preceding assistant message. - Every
tool_usein that assistant message must receive a result before the conversation continues; an unanswered call is a validation error. - The results go in a user message, and tool results should come first in that message if you also include text.
Omitting a result because a tool "did nothing" is a classic bug; return an explicit "ok" instead.
Parallel tool use
By default Claude may emit several tool_use blocks in a single turn when the calls are independent. Execute them concurrently if you can, then return all results in one user message, in any order (the IDs do the matching). Returning them one per message works but teaches the model to serialise its calls. If parallelism causes trouble for a specific tool set, set disable_parallel_tool_use: true inside tool_choice to cap calls at one per turn.
Controlling whether tools are called
tool_choice governs the model's freedom:
| Value | Effect |
|---|---|
{"type": "auto"} | Default. Claude decides whether and which tools to call |
{"type": "any"} | Claude must call some tool |
{"type": "tool", "name": "x"} | Claude must call tool x |
{"type": "none"} | No tool calls this turn |
Forced modes (any, tool) were historically the way to guarantee structured output: define one tool whose schema is your desired JSON and force it. Newer model generations reject forced tool choice with a 400 error and expect structured outputs (output_config.format) instead, so check the model documentation before relying on it. Under auto, a tool call is not guaranteed; if your workflow requires one, check the response and retry with a firmer instruction.
Client-side versus server-side tools
Everything above describes client-side tools: you define them, you run them. Anthropic also offers two other kinds.
Anthropic-defined client tools (bash, text editor, computer use, memory) are declared by type and name only; the schema is built into the model. You still execute them locally and return results.
Server-side tools (web search, web fetch, code execution, tool search) run on Anthropic's infrastructure. You declare them in tools, and the results arrive as blocks in the same response (server_tool_use followed by a result block such as web_search_tool_result). There is no round trip for your code to perform. Two consequences follow: errors from server tools are reported inside the result block rather than as HTTP errors, and a long server-side loop may return stop_reason: "pause_turn", which you resume by resending the conversation unchanged.
Streaming tool input
When streaming, a tool_use block arrives as a content_block_start followed by input_json_delta events carrying fragments of partial_json. Accumulate the fragments and parse only when the block stops. Never execute a tool from partially accumulated JSON; a max_tokens cut-off can leave it incomplete.
A compact TypeScript loop
let res = await client.messages.create({ model, max_tokens: 4096, tools, messages });
while (res.stop_reason === "tool_use") {
messages.push({ role: "assistant", content: res.content });
const results = await Promise.all(
res.content.filter(b => b.type === "tool_use").map(async b => ({
type: "tool_result" as const, tool_use_id: b.id,
content: await runTool(b.name, b.input),
})));
messages.push({ role: "user", content: results });
res = await client.messages.create({ model, max_tokens: 4096, tools, messages });
}
Exam focus
- A
tool_useblock carriesid,nameandinput; the matchingtool_resultcarriestool_use_idandcontent, and lives in the next user message. - Every
tool_usein an assistant turn must be answered; results for parallel calls belong in a single user message. is_error: truemarks a failed tool so the model treats the content as an error.tool_choicevalues areauto,any,toolandnone; forced modes are rejected by newer models, which use structured outputs instead.- Server-side tools run on Anthropic's side with no client round trip and may return
pause_turn; client-side tools always need your code to execute them. - Streaming delivers tool input as
input_json_deltafragments that must be fully accumulated before parsing.
Check yourself
5 questions
1.After executing a tool that Claude requested, how must the result be sent back to the API?
2.When Claude decides to call a client-side tool, which stop_reason and content block type appear in the response?
3.A developer appends only the text portion of Claude's reply to the conversation history (discarding the tool_use blocks), then sends a user message with a tool_result. The API returns a 400 error. Why?
4.A tool execution fails because a downstream API times out. How should the harness convey this to Claude?
5.For one particular request a team wants Claude to answer directly without calling any tools, while keeping the tool definitions in the request so that the prompt cache is not invalidated. Which setting achieves this?
