Tool Use Basics
10 examples to get you started with Tool Use & Function Calling - 7 basic and 3 intermediate.
Prerequisites
- Install the SDK:
pip install anthropic. - Set
ANTHROPIC_API_KEYin your environment - the client reads it automatically. from anthropic import Anthropic; client = Anthropic()is all the setup these examples need.
Basic Examples
1. Defining a Tool Schema
Describe a tool with a name, a description, and a JSON Schema for its input.
weather_tool = {
"name": "get_weather",
"description": "Get current weather for a location. Call this when the user asks about current conditions.",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state, e.g., San Francisco, CA",
}
},
"required": ["location"],
},
}nameis the identifier Claude uses to request the tool and that you match against in your own code.descriptionis the main signal Claude uses to decide when to call the tool - write it like documentation for a teammate, not a label.input_schemais standard JSON Schema;requiredand per-fielddescriptionvalues both improve how reliably Claude fills in arguments.- One tool definition is a plain Python dict - no special SDK class is required to declare it.
Related: Defining Tool Schemas - the full schema reference
2. Sending a Message with Tools
Pass your tool list to messages.create alongside the conversation.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[weather_tool],
messages=[
{"role": "user", "content": "What's the weather in San Francisco, CA?"}
],
)- The
toolsparameter accepts a list, so you can offer Claude several tools in the same call. - Claude only uses a tool when the conversation actually calls for it - passing
toolsdoes not force a call. max_tokensaround 1024 is plenty for a single tool-call turn; raise it if the final answer will be long.model="claude-sonnet-5"is the default model for general-purpose tool use on this stack.
3. Detecting a tool_use Stop Reason
Check stop_reason before assuming you have a plain text answer.
if response.stop_reason == "tool_use":
print("Claude wants to call a tool")
else:
print(response.content[0].text)stop_reason == "tool_use"is how the SDK tells you Claude paused to request a tool call instead of finishing its answer.- Always branch on
stop_reasonrather than guessing from the content shape - it is the documented signal for this state. - If Claude didn't need a tool,
response.contentholds ordinary text blocks you can read directly. - Skipping this check is the most common source of crashes when a tool call is optional.
Related: How Claude Decides When to Call a Tool - why some turns skip tool use entirely
4. Extracting the Tool Input
Find the tool_use content block and read its generated arguments.
tool_use_block = next(
block for block in response.content if block.type == "tool_use"
)
tool_name = tool_use_block.name
tool_input = tool_use_block.input
tool_id = tool_use_block.id
print(tool_name, tool_input, tool_id)response.contentis a list of content blocks; atool_useturn can mix atextblock with one or moretool_useblocks.block.inputis already a parsed Python dict matching yourinput_schema- no JSON decoding needed.block.idis the value you must echo back later in the matchingtool_result, so capture it now.block.nametells you which tool to run when you have more than one defined.
5. Executing Your Tool Function
Run your own code with the arguments Claude generated.
def get_weather(location: str) -> str:
# Replace with a real weather API call.
return f"72F and sunny in {location}"
if tool_name == "get_weather":
result = get_weather(**tool_input)- Claude never executes code itself - it only produces the tool name and input; running the tool is entirely your responsibility.
- Unpacking
tool_inputwith**works cleanly when your function's parameter names match the schema'spropertieskeys. - Treat
tool_inputlike any other external input - validate it before using it in file paths, queries, or shell commands. - Keep the function's return value a simple string or JSON-serializable structure so it's easy to hand back as a
tool_result.
6. Returning a tool_result Block
Send the tool's output back to Claude as a new user message.
messages = [
{"role": "user", "content": "What's the weather in San Francisco, CA?"},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_id,
"content": result,
}
],
},
]
final_response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[weather_tool],
messages=messages,
)
print(final_response.content[0].text)- The assistant turn you append must be
response.contentverbatim - it preserves the exacttool_useblock Claude generated, including itsid. tool_use_idmust match theidof thetool_useblock it answers, so Claude can pair the request with its result.- The
tool_resultblock always goes in auser-role message, even though it's really a machine response, not something the human typed. - The second
messages.createcall is a fresh request with the full history attached - Claude only sees prior turns you explicitly include.
Related: Handling Tool Use Requests and Returning tool_result Blocks - the full round-trip reference
7. Handling Tool Execution Errors
Tell Claude a tool call failed instead of silently dropping it.
try:
result = get_weather(**tool_input)
tool_result_content = result
is_error = False
except Exception as exc:
tool_result_content = f"Tool execution failed: {exc}"
is_error = True
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_id,
"content": tool_result_content,
"is_error": is_error,
}- Set
"is_error": trueon thetool_resultblock when your function raises or the underlying API call fails. - An informative error string lets Claude decide what to do next - retry, ask the user for clarification, or explain the failure.
- Never omit the
tool_resultfor atool_useblock, even on failure - the conversation is malformed if a tool call is left unanswered. - Wrapping tool execution in
try/exceptkeeps a single bad API call from crashing your whole request handler.
Intermediate Examples
8. Full Round Trip: From User Question to Final Answer
Chain every step above into one working loop.
from anthropic import Anthropic
client = Anthropic()
weather_tool = {
"name": "get_weather",
"description": "Get current weather for a location. Call this when the user asks about current conditions.",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City and state, e.g., San Francisco, CA"}
},
"required": ["location"],
},
}
def get_weather(location: str) -> str:
return f"72F and sunny in {location}"
messages = [{"role": "user", "content": "Should I bring an umbrella in Austin, TX today?"}]
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[weather_tool],
messages=messages,
)
if response.stop_reason == "tool_use":
tool_use_block = next(b for b in response.content if b.type == "tool_use")
result = get_weather(**tool_use_block.input)
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": tool_use_block.id, "content": result}
],
})
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[weather_tool],
messages=messages,
)
print(response.content[0].text)- This is the same five-step round trip - create, detect
tool_use, execute, appendtool_result, create again - written as one script. - Reusing the
messageslist with.append()keeps the full conversation history intact for the second call. - The second
messages.createcall still passestools=[weather_tool], since Claude may legitimately need to call the tool again. - In production this logic is usually wrapped in a
while response.stop_reason == "tool_use":loop to handle any number of tool calls.
Related: Building a Multi-Turn Tool Use Loop - generalizing this into a reusable loop
9. Handling Multiple tool_use Blocks in One Turn
Loop over every tool_use block, since Claude can request several tools in a single turn.
tool_result_blocks = []
for block in response.content:
if block.type != "tool_use":
continue
if block.name == "get_weather":
output = get_weather(**block.input)
else:
output = f"Unknown tool: {block.name}"
tool_result_blocks.append(
{"type": "tool_result", "tool_use_id": block.id, "content": output}
)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_result_blocks})- A single assistant turn can contain more than one
tool_useblock - filterresponse.contentfortype == "tool_use"rather than assuming exactly one. - Every
tool_useblock needs its own matchingtool_resultblock in the follow-up user message, each with its owntool_use_id. - All the
tool_resultblocks for one turn go together in a single user message'scontentlist, not as separate messages. - Branching on
block.nameis how you dispatch to the right Python function when multiple tools are defined.
Related: Executing Parallel Tool Calls in a Single Turn - more on batching tool results
10. Choosing Between Tools with tool_choice
Use tool_choice to force, restrict, or leave open which tool Claude picks.
calculate_tool = {
"name": "calculate",
"description": "Evaluate a basic arithmetic expression. Call this for any math question.",
"input_schema": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "e.g., 12 * (4 + 1)"}
},
"required": ["expression"],
},
}
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[weather_tool, calculate_tool],
tool_choice={"type": "tool", "name": "calculate"},
messages=[{"role": "user", "content": "What is 12 times 5?"}],
)tool_choice={"type": "tool", "name": "calculate"}forces Claude to call that specific tool instead of choosing on its own.- Leaving
tool_choiceunset is equivalent to{"type": "auto"}- Claude decides whether and which tool to use based on the descriptions. {"type": "any"}requires some tool call but lets Claude pick which one;{"type": "none"}disables tool calls for that turn.- Forcing a tool is useful for testing a single tool's schema in isolation, without depending on Claude's own routing decision.
Related: Tool Choice Options Reference - every tool_choice mode in detail
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.