Before you start
Finish lesson 1 first: this lesson reuses its client, its key and its loop over content blocks. Tool use runs on the same Messages endpoint, with a tools list added to the request.
The model never runs anything. For client tools, the ones you define, your code runs the function and reports back. Server tools such as web search, web fetch and code execution run on Anthropic's side, so a response can arrive with their results already inside. This lesson covers client tools. The tool use overview draws the same line.
Build the tool_use and tool_result round trip
One request, one tool, two calls. The code below defines get_weather, asks a question that needs it and handles a single round trip. Lesson 4 wraps the same steps in a loop. The weather function is a stand-in written for this lesson, so replace it with a real lookup.
Two rules decide whether the second request is accepted. The user message with the results must come right after the assistant message that holds the tool_use blocks, and the tool_result blocks must come first in its content, before any text. Break either one and the API returns a 400.
import json
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": (
"Get the current weather for a city. Use it when the user asks about "
"weather, temperature or whether to bring an umbrella. It returns the "
"temperature in Celsius and a short condition. It does not forecast."
),
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "A city name, for example Lisbon"},
},
"required": ["city"],
},
}
]
def get_weather(city: str) -> str:
# Stand-in for a real weather API call.
return json.dumps({"city": city, "temp_c": 18, "condition": "light rain"})
messages = [{"role": "user", "content": "Do I need an umbrella in Lisbon today?"}]
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, tools=tools, messages=messages
)
if response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
results = []
for block in response.content:
if block.type == "tool_use":
results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": get_weather(**block.input),
}
)
messages.append({"role": "user", "content": results})
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, tools=tools, messages=messages
)
for block in response.content:
if block.type == "text":
print(block.text)- Define the tool with a
name, adescriptionand aninput_schema, then pass the list astools. - Send the user message. Claude replies with
stop_reasonset totool_useand one or moretool_useblocks, each with anid, anameand aninput. - Append
response.contentto the history as the assistant message, whole and unedited. The docs want every thinking block back unchanged when tools are in play. - Run your function for each
tool_useblock and build atool_resultblock with the same id astool_use_id. - Send all the results in one user message and call the API again. Claude now writes the final answer.
Write tool definitions Claude can use
The description decides most of the outcome. The docs call it the most important factor in tool performance. They ask for at least three or four sentences that say what the tool does, when to use it, what each parameter means and what it returns.
Fewer, broader tools work better than many narrow ones, so fold related operations into one tool with an action parameter. Prefix names by service when several tools sit side by side, as in github_list_prs. Return only high-signal data from the function, because everything it returns is input tokens on the next call.
| Field | Needed | What to know |
|---|---|---|
name | Yes | Must match ^[a-zA-Z0-9_-]{1,128}$ |
description | Strongly advised | Plain text, three or four sentences at least |
input_schema | Yes | A JSON Schema object with properties and required |
input_examples | No | Sample inputs that must validate against the schema, about 20 to 50 tokens each for a simple tool and 100 to 200 for a nested one |
strict | No | Set to true to make Claude's inputs follow the schema |
cache_control | No | A cache breakpoint on the tool list, covered in lesson 3 |
Steer the call with tool_choice
By default Claude decides for itself whether a request needs a tool. tool_choice overrides that. Its four values behave differently on current models, and the release notes show the forced ones being turned off one model at a time since September.
Parallel calls are controlled from the same field. With auto, setting disable_parallel_tool_use to true limits Claude to at most one tool call per turn.
Tool use leaves beta
No beta header on the Claude API, Bedrock or Vertex AI.
disable_parallel_tool_usearrivesSet it inside
tool_choiceto keep Claude to at most one tool per turn.tool_choicegainsnoneYou can stop Claude from calling any tool.
Tool helpers and the tool runner in beta
The Python and TypeScript SDKs can run the loop for you.
Tool use examples drop the beta header
So do tool search, memory, code execution and web fetch.
Fable 5.1 rejects forced
tool_choiceOn Fable 5.1 and Mythos 5.1,
anyandtoolreturn an error.Opus 5.5 rejects it too
Tools can also be defined in a mid-conversation system message, in beta.
Sonnet 5.5 follows
Forced tool use returns an error here as well.
Source: Claude Docs: Release notes (read November 5, 2026)
| tool_choice | Behavior | Watch for |
|---|---|---|
{"type": "auto"} | Claude decides whether to call a tool | The default when you pass tools |
{"type": "any"} | Claude must call one of your tools | A 400 on the models in the box below |
{"type": "tool", "name": "get_weather"} | Claude must call that tool | The same 400, and a forced call skips thinking |
{"type": "none"} | Claude may not call a tool | The default when you pass no tools |
Several tool calls in one response
Ask about the weather in two cities and Claude can return two tool_use blocks in the same response. Your code decides whether to run them one after the other or at the same time. The reply matters more. Put every tool_result into a single user message.
Sending each result as its own user message works at first, but the parallel tool use page warns that it teaches Claude to stop making parallel calls. Each result still needs the id of its own call. The API matches them by id, not by position.
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "{\"temp_c\": 18}" },
{ "type": "tool_result", "tool_use_id": "toolu_02", "content": "{\"temp_c\": 24}" }
]
}Report errors so Claude can recover
A tool fails in two ways: your function raises, or Claude sent input that does not fit. Report both as a tool_result with is_error set to true and a message that says how to fix the call. For a call with invalid or missing parameters, the docs say Claude retries two or three times with corrections before it apologizes. A message such as the one below gives it something to correct with, and a bare "Error" does not.
A call you decide not to run still needs a tool_result, with is_error and the reason. Leave one out and the next request fails.
Treat everything a tool returns as untrusted. A web page or a file can carry text written to steer the model, which the docs call indirect prompt injection. The handle tool calls page is why you enforce what a tool may touch in your own code, not in the prompt.
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"is_error": true,
"content": "City not found. Pass a city name such as Lisbon, not a country."
}What Anthropic and Alex Albert posted about designing tools
Anthropic posted on X on 11 September 2025 to share a guide from its engineering blog, writing effective tools for agents. The guide holds that more tools do not always mean better results. Its example: a search_logs tool that returns the matching lines beats a read_logs tool that returns everything. The define tools page still links to it for naming and response shape, so give get_weather the same treatment and return two fields, not the whole weather API response.
Alex Albert, who works in research at Anthropic, posted on 17 February 2026 that tool use examples, tool search and programmatic tool calling had become generally available. Tool use examples are the input_examples field in the table of tool fields, and they need no beta header now. Get the description right first. Then add two or three examples to any tool with nested input.
code execution, web fetch, memory, programmatic tool calling, tool search, and tool use examples are now generally available.
Screenshots of X's own embeds of the posts, taken on 5 November 2026, with the time in UTC. Select one to open the post on X. Both embeds leave out the link that ends the post.
Common mistakes
- Sending each tool_result in a separate user message, which cuts down parallel calls.
- Putting text before the tool_result blocks in the same message.
- Adding a text block right after the results, which can make Claude end the turn with an empty reply.
- Rebuilding the assistant message from the text blocks only, and so dropping the thinking blocks the API wants back.
- Writing a one-line description and wondering why Claude picks the wrong tool.
- Forcing a tool with tool_choice on a model that rejects it.
- Returning a whole database row when Claude needs two fields.
Try it yourself
- Add a second tool,
get_time, and ask a question that needs both. How manytool_useblocks come back, and do you reply in one user message? - Make
get_weatherraise for the city "Atlantis" and return the error JSON from this lesson. Does Claude retry with a different city or tell the user? - Delete all but one sentence of the description and compare which questions still trigger the tool.
- Set
disable_parallel_tool_usetotrueand repeat the two-city question.
Next
The loop in this lesson stops after one round, and every extra round resends the same tool list and system prompt. Lesson 3 caches them. If you would rather connect tools that already exist than write the schemas, the MCP lesson on connecting servers shows how.
Check yourself
A response ends with stop_reason "tool_use". What do you send next?
Show answer
C. A user message with tool_result blocks that carry the tool_use ids Claude never runs the tool, so your code runs it and answers in a user message, one tool_result per tool_use id.
Claude returns two tool_use blocks in one response. How should you send the results?
Show answer
A. In one user message holding both tool_result blocks Separate user messages teach Claude to stop making parallel calls, so every result goes into a single message.
A tool raises an exception in your code. What should the tool_result carry?
Show answer
D. is_error true and a message that says how to fix the call An instructive error says what went wrong and what to try next, so Claude can recover without guessing.
A request with tool_choice set to type "tool" returns a 400 on Opus 5.5. What do the docs suggest?
Show answer
B. Use auto with strict set to true on the tool, or structured outputs Forced tool_choice is rejected on those models, so the docs point to auto with strict or to structured outputs.
Frequently asked questions
Should I use the tool runner or write the loop myself?
The tool runner is in beta in the Python and TypeScript SDKs and runs the loop for you, turning a tool exception into an is_error result. The docs recommend a manual loop when you need human approval, custom logging or conditional execution.
What error do I get if a tool_result is missing?
An invalid_request_error that reads tool_use ids were found without tool_result blocks immediately after. Check that the results sit in the next user message and that every tool_use id has a match.
Why does the API want my thinking blocks back?
With tool use, the docs require every thinking and redacted_thinking block from the assistant turn to go back exactly as received, empty ones included. Editing, reordering or filtering them makes the request fail, so append the whole response content.
Do tool definitions cost tokens?
Yes. Definitions, tool_use blocks and tool_result blocks all count toward your tokens. On Opus 5.5 the docs also list a 286-token tool-use system prompt, added when tool_choice is auto or none.
Can I cache my tool definitions?
Yes. The cache order is tools, then system, then messages, and a cache_control breakpoint can sit on the last tool. Changing any tool definition resets the whole cache, which lesson 3 covers.
Sources
- Anthropic Docs: Tool use with Claude https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview
- Anthropic Docs: Define tools https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools
- Anthropic Docs: Handle tool calls https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls
- Anthropic Docs: Parallel tool use https://platform.claude.com/docs/en/agents-and-tools/tool-use/parallel-tool-use
- Anthropic Docs: How tool use works https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works
- Anthropic Docs: Tool runner https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-runner
- Anthropic Docs: Stop reasons and fallback https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons
- Anthropic Docs: Prompt caching https://platform.claude.com/docs/en/build-with-claude/prompt-caching
- Anthropic Docs: Claude API errors https://platform.claude.com/docs/en/api/errors
- Claude Docs: Release notes https://platform.claude.com/docs/en/release-notes/overview
- Anthropic Engineering: Writing effective tools for agents https://www.anthropic.com/engineering/writing-tools-for-agents
- X: Anthropic shares its guide to writing tools for agents https://x.com/AnthropicAI/status/1966236220868247701
- X: Alex Albert on tool use examples and tool search becoming generally available https://x.com/alexalbert__/status/2023834875678298535


