Tool use / function calling
Advanced AI Workflows & the Claude API
Tool use — also called function calling — is the mechanism that lets Claude interact with the outside world. Instead of just generating text, Claude can decide to call a function you've defined: search a database, fetch a URL, run a calculation, send a notification. Your code executes the function and feeds the result back to Claude, which then continues its response. This turns Claude from a text generator into an agent that can take actions.
The Tool Use Loop
Tool use is a multi-turn exchange, not a single request. Understanding the loop is the foundation for everything else in this chapter:
Defining a Tool
Each tool is a Python dict (or Pydantic model) that tells Claude what the function does and what parameters it takes. The description is the most important field — Claude uses it to decide when to call the tool.
"get_weather", "search_products", "send_email"
"required": [...] to mark mandatory parameters. Add a "description" to each property.
A Complete Tool Use Example
client = anthropic.Anthropic()
# 1. Define the tool
tools = [{
"name": "get_weather",
"description": "Returns current weather for a city. Use when the user "
"asks about weather in a specific location.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, e.g. 'London'"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}]
# 2. Send initial request
messages = [{"role": "user", "content": "What's the weather in Tokyo?"}]
response = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024,
tools=tools, messages=messages
)
# 3. Handle tool call
if response.stop_reason == "tool_use":
tool_block = response.content[0]
args = tool_block.input # {"city": "Tokyo"}
# 4. Execute your actual function
result = get_weather(args["city"], args.get("units", "celsius"))
# 5. Append assistant turn + tool result, resend
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_block.id, # must match
"content": json.dumps(result)
}]
})
# 6. Get final response
final = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024,
tools=tools, messages=messages
)
print(final.content[0].text)
Real-World Tool Examples
category: string (optional)
max_results: integer (optional, default 5)
start_datetime: string ISO8601 (required)
duration_minutes: integer (required)
description: string (optional)
channel: enum ["push","email","sms"] (required)
scheduled_at: string ISO8601 (optional)
precision: integer decimal places (optional, default 2)
Handling Multiple Tool Calls
Claude may call multiple tools in a single response, or call tools sequentially across multiple turns. A robust tool loop handles both:
messages = initial_messages.copy()
for _ in range(max_turns):
response = client.messages.create(
model="claude-sonnet-4-6", max_tokens=4096,
tools=tools, messages=messages
)
if response.stop_reason == "end_turn":
return response # done
if response.stop_reason != "tool_use":
break # unexpected stop reason
# Append assistant turn
messages.append({"role": "assistant", "content": response.content})
# Execute all tool calls in this response
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = dispatch_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
messages.append({"role": "user", "content": tool_results})
raise RuntimeError("Tool loop exceeded max_turns")
max_turns limit, a tool loop can run indefinitely if Claude keeps deciding to call tools. Set a reasonable ceiling — 5–10 turns covers almost all real use cases. Log and alert when the limit is hit so you can investigate whether the loop is behaving as expected.
Tool Design Principles
- Write the description for Claude, not for humans. The description is Claude's only guide for when to call the tool. Include: what the tool does, what it returns, and when to use it vs not use it. If two tools are similar, explicitly distinguish them in the descriptions.
- Describe each parameter. Every property in the input schema should have a
"description"field. Claude uses these to understand what values are expected — missing descriptions lead to wrong arguments. - Use enums for constrained choices. If a parameter only accepts certain values, list them as an enum. Claude will pick from the list reliably rather than inventing a value that your code doesn't handle.
- Keep tools focused. One tool should do one thing. A
manage_usertool with ten modes is harder for Claude to invoke correctly than three separate tools:get_user,update_user,delete_user. - Validate inputs before executing. Claude follows the schema but can still pass unexpected values. Validate and sanitise all tool arguments before running them — especially for tools that write data or call external services.
- Return rich results. Claude uses the tool result to formulate its answer. A result like
{"temperature": 18, "condition": "partly cloudy", "humidity": 72, "wind_kph": 15}lets Claude give a better answer than"18°C". - Handle errors gracefully in the result. If a tool fails, return an error description in the result rather than raising an exception and crashing the loop. Claude can then tell the user what went wrong and potentially try a different approach.