Claude Tool Use: Complete Guide to Function Calling
Tool use (also called function calling) lets Claude request execution of external functions during a conversation — search engines, calculators, databases, APIs — then incorporate the results into its response. The pattern: define tools with JSON schema, send them to the API, handle tool_use blocks in the response, execute the function, return the result, and continue the conversation. This guide covers the complete implementation in Python and TypeScript, with patterns for reliable production use.
How tool use works (the request-execute-return loop)
Every tool use interaction follows the same cycle:
- You define tools — name, description, input schema
- Claude decides to use a tool — returns a
tool_useblock with the tool name and inputs - You execute the tool — call your actual function with Claude's inputs
- You return the result — add a
tool_resultto the next message - Claude continues — incorporates the result and either uses more tools or gives a final answer
This cycle repeats until Claude returns stop_reason: "end_turn" with no tool use.
Defining tools
Tools are defined as JSON schema objects. The schema must be precise — Claude uses it to generate valid inputs:
import anthropic
TOOLS = [
{
"name": "get_weather",
"description": "Get the current weather for a location. Returns temperature, conditions, and humidity.",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or 'City, Country' format (e.g., 'Seoul, Korea')"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units. Default: celsius"
}
},
"required": ["location"]
}
},
{
"name": "search_web",
"description": "Search the web for current information. Use when you need real-time data not in your training.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
},
"num_results": {
"type": "integer",
"description": "Number of results to return (1-10). Default: 5",
"minimum": 1,
"maximum": 10
}
},
"required": ["query"]
}
}
]
Tool description guidelines:
- Be specific about what the tool returns (not "gets weather", but "returns temperature, conditions, and humidity")
- Tell Claude when to use the tool ("use when you need real-time data")
- Describe the input format precisely ("'City, Country' format")
Complete Python implementation
import anthropic
import json
from typing import Any
client = anthropic.Anthropic()
def get_weather(location: str, units: str = "celsius") -> dict:
"""Your actual weather API implementation."""
# In production, call a real weather API here
return {
"location": location,
"temperature": 18,
"units": units,
"conditions": "partly cloudy",
"humidity": 65
}
def search_web(query: str, num_results: int = 5) -> list[dict]:
"""Your actual search implementation."""
# In production, call a search API here
return [
{"title": "Result 1", "url": "https://example.com/1", "snippet": "..."},
]
# Map tool names to actual functions
TOOL_FUNCTIONS = {
"get_weather": get_weather,
"search_web": search_web,
}
def execute_tool(tool_name: str, tool_input: dict) -> Any:
"""Execute a tool call and return the result as a string."""
if tool_name not in TOOL_FUNCTIONS:
return f"Error: unknown tool '{tool_name}'"
try:
result = TOOL_FUNCTIONS[tool_name](**tool_input)
return json.dumps(result)
except Exception as e:
return f"Error executing {tool_name}: {str(e)}"
def run_with_tools(user_message: str) -> str:
"""
Run a conversation with tool use.
Loops until Claude returns a final text response.
"""
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
tools=TOOLS,
messages=messages,
)
# Final response — no more tool use
if response.stop_reason == "end_turn":
return next(
(block.text for block in response.content if hasattr(block, "text")),
""
)
# Handle tool use
if response.stop_reason == "tool_use":
# Add Claude's response (with tool_use blocks) to messages
messages.append({
"role": "assistant",
"content": response.content
})
# Execute all tool calls and collect results
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
# Add tool results to messages
messages.append({
"role": "user",
"content": tool_results
})
# Continue the loop
continue
# Unexpected stop reason
break
return "Agent loop ended unexpectedly"
# Usage
answer = run_with_tools("What's the weather in Seoul right now? Is it good weather for a bike ride?")
print(answer)
TypeScript implementation
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description: "Get current weather for a location.",
input_schema: {
type: "object",
properties: {
location: { type: "string", description: "City name" },
units: { type: "string", enum: ["celsius", "fahrenheit"] },
},
required: ["location"],
},
},
];
async function executeTools(
toolUseBlocks: Anthropic.ToolUseBlock[]
): Promise<Anthropic.ToolResultBlockParam[]> {
return Promise.all(
toolUseBlocks.map(async (block) => {
let content: string;
try {
if (block.name === "get_weather") {
const input = block.input as { location: string; units?: string };
// Call your actual weather API
content = JSON.stringify({ temperature: 18, conditions: "sunny" });
} else {
content = `Unknown tool: ${block.name}`;
}
} catch (e) {
content = `Error: ${e}`;
}
return {
type: "tool_result" as const,
tool_use_id: block.id,
content,
};
})
);
}
async function runWithTools(userMessage: string): Promise<string> {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userMessage },
];
while (true) {
const response = await client.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 4096,
tools,
messages,
});
if (response.stop_reason === "end_turn") {
const textBlock = response.content.find((b) => b.type === "text");
return textBlock?.type === "text" ? textBlock.text : "";
}
if (response.stop_reason === "tool_use") {
messages.push({ role: "assistant", content: response.content });
const toolUseBlocks = response.content.filter(
(b): b is Anthropic.ToolUseBlock => b.type === "tool_use"
);
const toolResults = await executeTools(toolUseBlocks);
messages.push({ role: "user", content: toolResults });
continue;
}
break;
}
return "Unexpected end";
}
Controlling when tools are used
You can force or prevent tool use with the tool_choice parameter:
# Force Claude to use a specific tool (good for testing)
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=TOOLS,
tool_choice={"type": "tool", "name": "get_weather"},
messages=messages,
)
# Force Claude to use at least one tool
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=TOOLS,
tool_choice={"type": "any"},
messages=messages,
)
# Default: Claude decides whether to use tools
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=TOOLS,
tool_choice={"type": "auto"}, # default
messages=messages,
)
Handling tool errors gracefully
Return errors in tool_result rather than raising exceptions — Claude can reason about errors and adapt:
def execute_tool_safe(tool_name: str, tool_input: dict) -> str:
"""Execute a tool, returning errors as result content (not exceptions)."""
try:
result = TOOL_FUNCTIONS[tool_name](**tool_input)
return json.dumps({"success": True, "data": result})
except KeyError:
return json.dumps({"success": False, "error": f"Unknown tool: {tool_name}"})
except TypeError as e:
return json.dumps({"success": False, "error": f"Invalid parameters: {e}"})
except Exception as e:
# For network errors, Claude can retry or use fallback
return json.dumps({"success": False, "error": f"Tool failed: {e}"})
When Claude receives an error result, it will typically try a different approach, ask for clarification, or explain what went wrong — all better outcomes than a silent crash.
Setting a maximum tool-use iterations limit
Without a stop condition, a malfunctioning agent can loop indefinitely:
def run_with_tools_safe(user_message: str, max_iterations: int = 10) -> str:
messages = [{"role": "user", "content": user_message}]
iterations = 0
while iterations < max_iterations:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
tools=TOOLS,
messages=messages,
)
if response.stop_reason == "end_turn":
return next(
(block.text for block in response.content if hasattr(block, "text")),
""
)
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":
result = execute_tool_safe(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
messages.append({"role": "user", "content": tool_results})
iterations += 1
continue
break
return f"Agent reached maximum iterations ({max_iterations}). Last state: {messages[-1]}"
Frequently asked questions
How is tool use different from function calling in OpenAI? They're functionally equivalent. OpenAI calls it "function calling" and later renamed it "tools". The concepts are identical: you define a schema, the model returns structured call requests, you execute them, you return results. The API shapes differ in details but the pattern is the same.
Can Claude use multiple tools in parallel?
Claude may return multiple tool_use blocks in a single response when tools are independent. You should execute them in parallel when possible, then return all results together. The SDK doesn't force sequential execution.
What happens if I don't execute a tool Claude requested?
If you include a tool_use block from Claude's response in the conversation without a matching tool_result, the API returns an error. You must return a tool_result for every tool_use block before continuing.
How many tools can I define? No hard limit documented, but practical performance degrades with many tools. Claude needs to parse and reason about all tool schemas. 5–15 tools is the typical production range; above 20, consider grouping or routing.
Can tools have optional parameters?
Yes. Only list required parameters in the required array. Optional parameters are listed in properties without appearing in required. Claude will use them when helpful.
Related guides
- Claude Agent SDK: Build Your First Agent in 30 Minutes — building complete agents with tool use
- Claude API Error Handling: Production Patterns — robust error handling for tool failures
Take It Further
Claude Agent SDK Cookbook: 40 Production Patterns — Patterns 3–9 cover the complete Tool Use System: parallel tool execution, error recovery, dynamic tool registration, tool use with streaming, and a 10-tool agent with logging and cost tracking.
→ Get the Agent SDK Cookbook — $49
30-day money-back guarantee. Instant download.