Tool Use (Function Calling)
Tools let Claude ask your code to do something — look up a record, call an API, run a calculation — and use the result to keep reasoning. This page covers how to define a tool, control when Claude reaches for one, and run the request/response loop correctly.
Imagine texting a really smart friend who's stuck in a windowless room with just a phone. They can't check the weather themselves — but they can text you: "Hey, can you look outside and tell me what it's like?" You look, text back "sunny, 15 degrees," and they use that to finish their sentence. Claude works the same way: it can't touch the internet or your database, so it asks your code to go look, waits for the answer to come back, and keeps going from there.
What "tool use" actually means
☺ Like you're 10: Claude is the brain, not the hands — it can only describe what it wants done and wait for someone else (your code) to actually go do it.
Claude cannot execute code or reach the network on its own. When you hand it a tools array, you're describing capabilities it's allowed to ask for — it never runs anything itself. Instead, the model emits a special content block asking you to run a function with specific arguments; your application executes that function and sends the result back in the next request. Claude then keeps reasoning with that result in hand.
This is the same mechanism whether the "tool" is a weather lookup, a database query, or one step inside a multi-step agent — it's the foundation everything from RAG pipelines to autonomous coding agents is built on.
Defining a tool
☺ Like you're 10: A tool definition is like a job posting — its name, what it does, and exactly what information it needs from the applicant (Claude) before it can be hired for the task.
A tool definition has three fields that matter most: name, description, and input_schema. name must match ^[a-zA-Z0-9_-]{1,64}$. input_schema is standard JSON Schema — type: "object", a properties map, and a required array. description is plain text explaining what the tool does, when to use it, what each parameter means, and any caveats.
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The unit of temperature, either 'celsius' or 'fahrenheit'"
}
},
"required": ["location"]
}
}
Anthropic's own docs call the description "by far the most important factor in tool performance," and recommend at least 3-4 sentences for anything non-trivial. Write it the way you'd document a function for a new engineer joining your team who has never seen your codebase.
A handful of optional fields can sit alongside those three on the same tool definition: cache_control to mark a prompt-cache breakpoint, strict to turn on grammar-constrained validation so the model's input is guaranteed to match your schema exactly, input_examples for complex tools where a few example inputs clarify usage faster than more prose, and defer_loading plus allowed_callers for larger tool libraries managed with the tool search tool. You won't need most of these on day one — name, description, and input_schema cover the vast majority of tools.
Pattern vs. anti-pattern: the description is the interface
"Get the current weather for a location. Use this whenever the user asks about current conditions, temperature, or forecast for a specific place. location must be a city and state or a city and country (e.g. 'Austin, TX' or 'Nairobi, Kenya') — do not pass zip codes or coordinates. unit defaults to celsius if omitted. Returns current conditions only; this tool cannot answer questions about future dates beyond the next 24 hours."
"Gets weather." No guidance on when to call it, what format location expects, what unit defaults to, or what the tool can't do — Claude is left guessing, which shows up as malformed arguments, wrong-tool selection, or unnecessary retries.
tool_choice: controlling when Claude calls a tool
The tool_choice parameter tells Claude how aggressively to use the tools you've provided. There are exactly four types.
| tool_choice | Behavior | When to use it |
|---|---|---|
{"type": "auto"} | Claude decides whether to call a tool or just reply in text. Default when tools is present. | General-purpose assistants where tool use is one option among several. |
{"type": "any"} | Claude must call some tool, but you don't pick which one. | You know a tool call is needed but want Claude to choose which tool fits the request. |
{"type": "tool", "name": "get_weather"} | Forces that exact tool. | Extraction-style tasks — using a tool's schema purely to force structured output. |
{"type": "none"} | Blocks all tool use, even if tools are defined. Default when no tools are passed. | Temporarily disabling tools without removing their definitions from the request. |
Any of these also accepts disable_parallel_tool_use: true, which caps the turn at a single tool call — useful when your execution side can only safely handle one action at a time, e.g. {"type": "auto", "disable_parallel_tool_use": true}.
When tool_choice is any or tool, the API prefills the assistant turn to force a tool call, so you won't get a natural-language preamble. Changing tool_choice between requests also invalidates cached message blocks (your tool definitions and system prompt stay cached, though), and manual, non-adaptive extended thinking is only compatible with auto and none — forcing a tool with thinking enabled on those models returns an error.
The tool_use → tool_result round trip
☺ Like you're 10: Every request gets a matching receipt — Claude's tool_use hands out an order number, and your tool_result has to quote that exact number back so Claude knows which order it's picking up.
When Claude wants to call a tool, the response comes back with stop_reason: "tool_use" and one or more content blocks of type "tool_use":
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": {
"location": "San Francisco, CA",
"unit": "celsius"
}
}
id is a unique identifier for this specific call — you'll echo it back so the model knows which call your result answers. name is the tool being invoked, and input is an object that (absent strict mode) should conform to your input_schema but is worth validating before you act on it.
You continue the conversation with a new user-role message containing a tool_result block keyed by tool_use_id:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "15 degrees"
}
]
}
Two formatting rules here are strict and will 400 if you violate them: a tool_result block must immediately follow the assistant's tool_use turn — you cannot insert any messages in between — and within that user message, every tool_result block must come before any text content. If you want to add commentary alongside a tool result, the text goes after the result blocks, never before.
The full loop, worked end to end
☺ Like you're 10: It's a relay race, not a one-way pass — Claude and your code keep handing the baton back and forth until Claude has everything it needs to answer.
Putting it together, the canonical client-tool loop is: send the request with tools and a user message; if stop_reason comes back "tool_use", execute each requested tool, build tool_result blocks, and send a new request containing the full history — the original messages, the assistant's response verbatim (including its tool_use blocks), and a user message carrying the results; repeat while stop_reason is "tool_use". The loop ends on end_turn, max_tokens, stop_sequence, or refusal.
Foxy: Okay wait, so Claude just calls the weather API itself, right? Like any normal program would?
Professor Owl: Not quite. Claude can't touch the network or execute code — it can only write a tool_use block describing what it wants, then stop and wait.
Foxy: So who actually makes the call and brings the answer back?
Pip: That's my job. I carry the request over to your code, wait for the real answer, and fly the tool_result straight back — in the very next message, always before any of Claude's own words. Then Claude picks up right where it left off.
Here's the same get_weather tool from earlier, wired into a real request/response cycle. The "execution" is a stand-in lookup — in production this is where you'd call your actual weather provider.
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from env
tools = [
{
"name": "get_weather",
"description": (
"Get the current weather for a location. Use this whenever the user "
"asks about current conditions or temperature for a specific place. "
"location must be a city and state or city and country, e.g. "
"'San Francisco, CA'. unit defaults to celsius if omitted."
),
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit, celsius or fahrenheit",
},
},
"required": ["location"],
},
}
]
def get_weather(location: str, unit: str = "celsius") -> str:
# Stand-in for a real weather API call.
return f"15 degrees {unit[0]}, partly cloudy in {location}"
messages = [{"role": "user", "content": "What's the weather in San Francisco?"}]
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
if block.name == "get_weather":
try:
result = get_weather(**block.input)
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
}
)
except Exception as exc:
tool_results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": f"Error: {exc}",
"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,
)
for block in response.content:
if block.type == "text":
print(block.text)import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // reads ANTHROPIC_API_KEY from env
const tools = [
{
name: "get_weather",
description:
"Get the current weather for a location. Use this whenever the user " +
"asks about current conditions or temperature for a specific place. " +
"location must be a city and state or city and country, e.g. " +
"'San Francisco, CA'. unit defaults to celsius if omitted.",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature unit, celsius or fahrenheit",
},
},
required: ["location"],
},
},
];
function getWeather(location, unit = "celsius") {
// Stand-in for a real weather API call.
return `15 degrees ${unit[0]}, partly cloudy in ${location}`;
}
let messages = [
{ role: "user", content: "What's the weather in San Francisco?" },
];
let response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
tools,
messages,
});
while (response.stop_reason === "tool_use") {
messages.push({ role: "assistant", content: response.content });
const toolResults = [];
for (const block of response.content) {
if (block.type !== "tool_use") continue;
if (block.name === "get_weather") {
try {
const result = getWeather(block.input.location, block.input.unit);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: result,
});
} catch (err) {
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: `Error: ${err}`,
is_error: true,
});
}
}
}
messages.push({ role: "user", content: toolResults });
response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
tools,
messages,
});
}
for (const block of response.content) {
if (block.type === "text") {
console.log(block.text);
}
}Handling errors and parallel calls
☺ Like you're 10: If the errand fails, don't just shrug — send a note back explaining what went wrong, so your friend can try a different approach instead of guessing blindly.
When your tool code fails — a network error, an invalid argument, a downstream 500 — don't drop the turn silently. Return a tool_result with is_error: true and a message that tells Claude what actually went wrong:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "ConnectionError: the weather service API is not available (HTTP 500)",
"is_error": true
}
]
}
Write instructive error text rather than a bare "failed" — something like "Rate limit exceeded. Retry after 60 seconds" tells Claude what to try next instead of leaving it to guess. If Claude called the tool with an invalid name or missed a required parameter, that's usually a sign the description needs to be clearer; returning an is_error result describing the missing field lets Claude retry with corrections (it will typically retry two or three times before giving up and telling the user). strict: true on the tool definition eliminates this entire class of error by constraining generation so the arguments always match your schema.
By default, a single assistant turn can contain more than one tool_use block — Claude might ask for the weather in three cities at once, for example. The API doesn't prescribe how you execute them: independent, read-only calls are usually safe to run concurrently, while calls with side effects or shared state may need to run sequentially. Whatever you choose, the rule for the response is fixed: return one tool_result per tool_use block, matched by tool_use_id, all together in the next user message, with every result appearing before any text.
If you skip a call — say, a later call in a sequential batch that depended on an earlier one that failed — you still owe it a result, marked as an error:
{
"type": "tool_result",
"tool_use_id": "toolu_02",
"is_error": true,
"content": "Not executed: the preceding write_file call failed."
}
To turn parallel calling off entirely and force at most one tool call per turn, set disable_parallel_tool_use: true inside your tool_choice object.
Best practices for tool design
Don't expose too many tools at once. Anthropic's own guidance is that Claude's ability to pick the right tool degrades once you exceed roughly 30-50 available tools in a single request — a typical multi-server setup can burn tens of thousands of tokens on tool definitions alone before Claude does any work. Consolidate related operations into one tool with an action parameter (e.g. one manage_pr tool with action: "create" | "review" | "merge") rather than three separate tools, and use clear, namespaced names like github_list_prs as your tool count grows.
Always validate tool arguments before executing anything with side effects — writing a file, sending an email, charging a card, deleting a record. Claude's input is generated text, not a trusted, pre-authorized command. Check types, ranges, and required fields yourself (or use strict: true to guarantee schema conformance), and apply the same authorization checks you would to any other untrusted input reaching that code path.
Extend the worked get_weather example with a second tool, get_forecast(location, days), that returns a short multi-day forecast string. Send a user message that naturally needs both tools in one turn (e.g. "What's the weather right now in Austin, and what's the 3-day forecast?"), and confirm in your logs that Claude returns two tool_use blocks in a single response, that you execute both, and that you send both results back together — with the results ordered before any text — in the next request.
You should now be able to explain why Claude never runs code itself, define a tool with a description good enough to steer Claude reliably, pick the right tool_choice for a given task, and run the tool_use → tool_result loop — including the strict ordering rules and error handling — without a request bouncing back as a 400. Next, see how this same tool-calling mechanism gets repurposed to force reliably structured output in Structured Outputs.
Check your answers
- Why doesn't Claude just run the tool itself? Claude has no ability to execute code or reach the network — it can only emit a
tool_useblock naming the tool and the arguments it wants. Your application is the one that actually runs the function and reports the result back. - What's the difference between
tool_choice: {"type": "auto"}and{"type": "any"}?autolets Claude decide whether to call a tool at all or just answer in text (it's the default whenever tools are present);anyforces Claude to call some tool this turn, but leaves the choice of which one up to Claude. - What happens if a
tool_resultdoesn't immediately follow itstool_useturn, or a text block comes before it? Both are strict formatting violations that the API rejects with a 400 — atool_resultmust be the very next message after the assistant'stool_useturn, and within that message everytool_resultblock must precede any text content.