Claude is smart, but it cannot check the weather, query your database, or call your APIs. Tool use changes that. You define functions, and Claude decides when to call them.
This is Article 8 in the Claude AI — From Zero to Power User series. You should have completed Article 7: Messages API before this article.
By the end of this article, you will know how to define tools, handle the tool use flow, force tool execution, and build a working multi-tool assistant.
What is Tool Use?
Tool use (also called function calling) lets you define functions that Claude can call. Here is how it works:
- You send a message with tool definitions
- Claude decides which tool to call (if any) and provides the arguments
- You execute the function with those arguments
- You send the result back to Claude
- Claude uses the result to write its final response
Claude never executes your code. It only decides what to call and with what arguments. You run the function and return the result.
Defining Tools
A tool definition has three parts: name, description, and input schema. The input schema uses JSON Schema format.
Python
import anthropic
import json
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a specific city. Returns temperature in Celsius, conditions, and humidity.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. 'London' or 'New York'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit. Defaults to celsius."
}
},
"required": ["city"]
}
}
]
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "What is the weather in Berlin?"}
]
)
print(message.stop_reason) # "tool_use"
print(message.content)
TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description:
"Get the current weather for a specific city. Returns temperature in Celsius, conditions, and humidity.",
input_schema: {
type: "object" as const,
properties: {
city: {
type: "string",
description: "The city name, e.g. 'London' or 'New York'",
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature unit. Defaults to celsius.",
},
},
required: ["city"],
},
},
];
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools,
messages: [{ role: "user", content: "What is the weather in Berlin?" }],
});
console.log(message.stop_reason); // "tool_use"
console.log(message.content);
When Claude decides to use a tool, the response looks like this:
{
"stop_reason": "tool_use",
"content": [
{
"type": "text",
"text": "I'll check the weather in Berlin for you."
},
{
"type": "tool_use",
"id": "toolu_01ABC123",
"name": "get_weather",
"input": {
"city": "Berlin",
"unit": "celsius"
}
}
]
}
Notice two things:
stop_reasonis"tool_use"instead of"end_turn"contenthas both a text block and a tool_use block
The Complete Tool Use Flow
The full flow requires three API calls:
- User message with tools → Claude responds with
tool_use - Tool result → Claude responds with final answer (or more tool calls)
Python — Complete Example
import anthropic
import json
client = anthropic.Anthropic()
# Step 1: Define tools
tools = [
{
"name": "get_weather",
"description": "Get current weather for a city.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
}
]
# Step 2: Your actual function
def get_weather(city: str) -> dict:
# In a real app, call a weather API here
weather_data = {
"Berlin": {"temp": 18, "conditions": "Partly cloudy", "humidity": 65},
"London": {"temp": 14, "conditions": "Rainy", "humidity": 80},
"Tokyo": {"temp": 25, "conditions": "Sunny", "humidity": 55},
}
return weather_data.get(city, {"temp": 20, "conditions": "Unknown", "humidity": 50})
# Step 3: Send initial message
messages = [{"role": "user", "content": "What is the weather like in Berlin today?"}]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
# Step 4: Handle tool use
if response.stop_reason == "tool_use":
# Find the tool_use block
tool_block = next(block for block in response.content if block.type == "tool_use")
# Execute the function
result = get_weather(tool_block.input["city"])
# Step 5: Send the result back
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_block.id,
"content": json.dumps(result)
}
]
})
# Step 6: Get final response
final_response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
print(final_response.content[0].text)
# "The weather in Berlin is 18°C and partly cloudy with 65% humidity."
TypeScript — Complete Example
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description: "Get current weather for a city.",
input_schema: {
type: "object" as const,
properties: {
city: { type: "string", description: "City name" },
},
required: ["city"],
},
},
];
function getWeather(city: string): Record<string, unknown> {
const weatherData: Record<string, Record<string, unknown>> = {
Berlin: { temp: 18, conditions: "Partly cloudy", humidity: 65 },
London: { temp: 14, conditions: "Rainy", humidity: 80 },
Tokyo: { temp: 25, conditions: "Sunny", humidity: 55 },
};
return weatherData[city] ?? { temp: 20, conditions: "Unknown", humidity: 50 };
}
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: "What is the weather like in Berlin today?" },
];
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools,
messages,
});
if (response.stop_reason === "tool_use") {
const toolBlock = response.content.find(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);
if (toolBlock) {
const result = getWeather((toolBlock.input as { city: string }).city);
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolBlock.id,
content: JSON.stringify(result),
},
],
});
const finalResponse = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools,
messages,
});
if (finalResponse.content[0].type === "text") {
console.log(finalResponse.content[0].text);
}
}
}
Multiple Tools
You can define many tools. Claude picks the right one based on the user’s request.
Python
import anthropic
import json
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "Get current weather for a city.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
},
{
"name": "search_database",
"description": "Search the product database by name or category.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "books"],
"description": "Product category to filter by"
},
"max_results": {
"type": "integer",
"description": "Maximum results to return. Default: 5"
}
},
"required": ["query"]
}
},
{
"name": "send_email",
"description": "Send an email to a recipient.",
"input_schema": {
"type": "object",
"properties": {
"to": {"type": "string", "description": "Recipient email"},
"subject": {"type": "string", "description": "Email subject"},
"body": {"type": "string", "description": "Email body text"}
},
"required": ["to", "subject", "body"]
}
}
]
# Claude picks the right tool
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "Find me some books about Python programming"}
]
)
# Claude will call search_database with query="Python programming" and category="books"
for block in message.content:
if block.type == "tool_use":
print(f"Tool: {block.name}")
print(f"Input: {json.dumps(block.input, indent=2)}")
Tip: Good descriptions are critical. Claude picks tools based on the description, not the name. Write descriptions that clearly explain what the tool does, what it returns, and when to use it.
Forcing Tool Use
By default, Claude decides whether to use a tool. You can override this with tool_choice.
# Let Claude decide (default)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto"}, # default
messages=[{"role": "user", "content": "Hello"}]
)
# Force Claude to use ANY tool
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "any"},
messages=[{"role": "user", "content": "Hello"}]
)
# Force a SPECIFIC tool
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "get_weather"},
messages=[{"role": "user", "content": "Hello"}]
)
| tool_choice | Behavior |
|---|---|
auto | Claude decides (default) |
any | Claude must use at least one tool |
tool + name | Claude must use the specified tool |
Use any when you always want structured output through a tool. Use tool + name for programmatic tool calling where you need a specific function called.
The Tool Use Loop
In complex scenarios, Claude may need to call multiple tools in sequence. Build a loop that handles this.
Python
import anthropic
import json
client = anthropic.Anthropic()
# Tool registry — maps names to functions
tool_functions = {
"get_weather": lambda city: {"temp": 18, "conditions": "Cloudy"},
"search_database": lambda query, **kwargs: [{"name": "Python Crash Course", "price": 29.99}],
"send_email": lambda to, subject, body: {"status": "sent", "id": "msg_123"},
}
def run_agent(user_message: str, tools: list) -> str:
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=tools,
messages=messages
)
# If Claude is done, return the text
if response.stop_reason == "end_turn":
return next(
(block.text for block in response.content if block.type == "text"),
""
)
# Process tool calls
if response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
# Execute the function
func = tool_functions.get(block.name)
if func:
try:
result = func(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
except Exception as e:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps({"error": str(e)}),
"is_error": True
})
messages.append({"role": "user", "content": tool_results})
# Use it
result = run_agent("What is the weather in Berlin and find me Python books", tools)
print(result)
This loop continues until Claude stops calling tools and gives a final text response. In practice, most interactions need 1-2 tool calls.
Error Handling in Tool Results
When a tool fails, tell Claude about the error. Set is_error to True so Claude knows something went wrong.
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps({"error": "City not found. Please check the city name."}),
"is_error": True
})
Claude will then either:
- Try again with different input
- Ask the user for clarification
- Explain what went wrong
Tool Definitions and Cost
Tool definitions count as input tokens. Each tool adds to your cost on every API call.
| Tools | Approximate Tokens |
|---|---|
| 1 simple tool | ~200 tokens |
| 5 tools | ~800-1200 tokens |
| 10 tools | ~1500-2500 tokens |
Tips to reduce cost:
- Only include tools relevant to the current task
- Keep descriptions concise but clear
- Use prompt caching for tool definitions that do not change
Best Practices
1. Write Clear Descriptions
# Bad — vague description
{"name": "search", "description": "Search for things"}
# Good — specific description
{"name": "search_products", "description": "Search the product catalog by name, category, or price range. Returns up to 10 matching products with name, price, and availability."}
2. Use Enums for Fixed Values
"category": {
"type": "string",
"enum": ["electronics", "clothing", "books", "food"],
"description": "Product category"
}
Enums prevent Claude from sending invalid values.
3. Mark Required Fields
Only mark fields as required if the tool cannot work without them. Optional fields with defaults give Claude more flexibility.
4. Validate Inputs
Always validate tool inputs before executing. Claude usually sends correct data, but edge cases happen.
def get_weather(city: str) -> dict:
if not city or len(city) > 100:
return {"error": "Invalid city name"}
# ... actual implementation
Summary
| Concept | Details |
|---|---|
| Tool definition | Name + description + JSON Schema |
| Flow | Request → tool_use → execute → tool_result → response |
| tool_choice | auto (default), any (force tool), tool (force specific) |
| Multiple tools | Claude picks the right one based on descriptions |
| Error handling | Set is_error: True in tool_result |
| Cost | Tool definitions add ~200 tokens per tool |
Tool use is the bridge between Claude’s intelligence and your application’s capabilities. It turns Claude from a text generator into an agent that can take actions.
What’s Next?
In the next article, we will cover Vision — sending images to Claude for analysis, data extraction, and document processing.
Next: Vision — Analyzing Images and Documents