Tool Use API Fundamentals
Defining tools, tool choice modes, and multi-tool orchestration.
Learning Objectives
- Define tools with names, descriptions, and JSON schemas
- Understand auto, any, and tool choice modes
- Handle multi-tool calls in sequence and parallel
Tool Use API Fundamentals
Tool use (also known as function calling) is the mechanism that transforms Claude from a text-generation model into an agent capable of interacting with external systems. When you provide Claude with tool definitions, the model can decide when and how to invoke those tools to accomplish a task. Understanding the Tool Use API is foundational knowledge for the CCA-F exam and for building any production agentic system.
How Tool Use Works: The Lifecycle
The tool use lifecycle is a structured conversation loop between your application and the Claude API. Every tool interaction follows the same four-step pattern:
- Step 1 - Define tools: You provide Claude with an array of tool definitions alongside the user message. Each definition includes a name, description, and a JSON Schema for the expected input parameters.
- Step 2 - Model decides to use a tool: Based on the conversation context and available tools, Claude may return a response with
stop_reason: “tool_use”and one or moretool_usecontent blocks specifying which tool(s) to call and with what arguments. - Step 3 - Execute and return results: Your application executes the requested tool call(s) and sends the results back to Claude as
tool_resultcontent blocks in a subsequent user message. - Step 4 - Model formulates response: Claude incorporates the tool results into its reasoning and either provides a final answer or requests additional tool calls.
Basic Tool Definition Structure
Every tool definition requires three fields: name, description, and input_schema. The input schema follows JSON Schema format and tells Claude what parameters the tool accepts.
import anthropic
client = anthropic.Anthropic()
# Define a tool for looking up weather data
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a specified city. Returns temperature, conditions, and humidity. Use this when the user asks about current weather conditions in a specific location.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. San Francisco, London, Tokyo"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit preference. Defaults to celsius if not specified."
}
},
"required": ["city"]
}
}
]
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "What is the weather like in San Francisco?"}
]
)
print(response.stop_reason) # "tool_use"
print(response.content) # Contains tool_use blockJSON Schema for Input Parameters
The input_schema uses JSON Schema draft 2020-12 format. Claude reads this schema to understand what arguments to provide. Key schema features include:
- type: The data type (string, integer, number, boolean, array, object)
- description: Human-readable explanation of the parameter
- enum: A fixed set of allowed values
- required: Array of field names that must be provided
- default: Default value when the parameter is omitted
- minimum / maximum: Numeric range constraints
- pattern: Regex pattern for string validation
Parsing Tool Use Responses
When Claude decides to use a tool, the response contains one or more content blocks. You need to iterate through these blocks to find tool_use blocks and extract the tool name, input arguments, and a unique id that you must reference when returning results.
# Parse the response to extract tool calls
for block in response.content:
if block.type == "tool_use":
tool_name = block.name # "get_weather"
tool_input = block.input # {"city": "San Francisco"}
tool_use_id = block.id # "toolu_abc123..."
# Execute your actual tool logic
if tool_name == "get_weather":
result = fetch_weather(tool_input["city"], tool_input.get("units", "celsius"))
# Send the result back to Claude
followup = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "What is the weather like in San Francisco?"},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": "Temperature: 62F, Conditions: Partly cloudy, Humidity: 72%"
}
]
}
]
)
print(followup.content[0].text) # Natural language responsetool_use_id field is critical. Every tool_result must reference the exact tool_use_id from the corresponding tool_use block. Mismatched IDs will cause API errors. The exam may test your understanding of this pairing mechanism.Tool Choice Modes
The tool_choice parameter gives you control over whether and how Claude uses tools. There are three modes, each suited to different use cases:
Auto Mode (Default)
Claude decides whether to use a tool based on context. This is the default behavior and is appropriate for most agentic applications where you want the model to exercise judgment about when tools are needed.
# Auto mode - Claude decides whether to use tools
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto"}, # This is the default
messages=[{"role": "user", "content": "What is the weather in Tokyo?"}]
)Any Mode
Forces Claude to use at least one tool, but lets it choose which one. Use this when you know a tool call is required but want the model to select the most appropriate tool.
# Any mode - Claude must use a tool, but chooses which one
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
tool_choice={"type": "any"},
messages=[{"role": "user", "content": "Look up the weather for me in London"}]
)Tool Mode (Specific Tool)
Forces Claude to use a specific named tool. This is useful when your application logic requires a particular tool to be called regardless of the model's assessment.
# Tool mode - Force a specific tool
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "get_weather"},
messages=[{"role": "user", "content": "Tell me about London"}]
)auto (model decides), any (must use a tool, model picks which), and tool (forced specific tool). The exam frequently tests when to use each mode. Remember that auto is the default when tool_choice is not specified. A common scenario: use any when you know the user's request requires a tool but you have multiple tools available; use tool when you need to force a particular function call (e.g., structured output extraction).Multi-Tool Orchestration
Claude can work with multiple tools simultaneously. When you define several tools, Claude can chain them together across multiple turns or even request multiple tool calls in a single response (parallel tool use).
Defining Multiple 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"]
}
},
{
"name": "get_stock_price",
"description": "Get the current stock price for a given ticker symbol.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {"type": "string", "description": "Stock ticker, e.g. AAPL, GOOGL"}
},
"required": ["ticker"]
}
},
{
"name": "search_database",
"description": "Search an internal database for records matching a query.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"limit": {"type": "integer", "description": "Max results to return", "default": 10}
},
"required": ["query"]
}
}
]Parallel Tool Use
Claude can request multiple tool calls in a single response when it determines that several independent operations are needed. Your response will contain multiple tool_use blocks, and you must return a tool_result for each one.
# Claude may return multiple tool_use blocks in one response
# For example: "What is the weather in Tokyo and the price of AAPL?"
# The response.content might contain:
# [TextBlock("Let me look that up..."),
# ToolUseBlock(name="get_weather", input={"city": "Tokyo"}, id="toolu_1"),
# ToolUseBlock(name="get_stock_price", input={"ticker": "AAPL"}, id="toolu_2")]
# You must return results for ALL tool calls:
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": str(result)
})
# Send all results back in a single message
followup = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{"role": "user", "content": tool_results}
]
)Disabling Parallel Tool Use
You can disable parallel tool use if your application requires sequential execution by setting tool_choice with "disable_parallel_tool_use": true. This is useful when tools have dependencies on each other or when you need to ensure ordering.
# Disable parallel tool use for sequential execution
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto", "disable_parallel_tool_use": True},
messages=[{"role": "user", "content": user_message}]
)The Agentic Tool Use Loop
In production agentic systems, tool use typically runs inside a loop that continues until Claude stops requesting tools. This is the fundamental pattern for building agents.
def run_agent(user_message, tools, max_iterations=10):
"""Run an agentic loop until Claude provides a final answer."""
messages = [{"role": "user", "content": user_message}]
for i in range(max_iterations):
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
tools=tools,
messages=messages
)
# If Claude is done using tools, return the final response
if response.stop_reason == "end_turn":
return response
# Otherwise, process tool calls and continue the loop
messages.append({"role": "assistant", "content": response.content})
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": str(result)
})
messages.append({"role": "user", "content": tool_results})
raise RuntimeError("Agent exceeded maximum iterations")stop_reason == “tool_use” and terminates when stop_reason == “end_turn”. Always include a maximum iteration guard to prevent runaway agents. The exam may present a code snippet and ask you to identify what's missing (the max iteration guard is a common answer).Handling Tool Errors
When a tool execution fails, you should return the error to Claude as a tool result with the is_error flag set to true. This allows Claude to understand what went wrong and potentially retry or take an alternative approach.
# Returning an error result
try:
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
})
except Exception as e:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"is_error": True,
"content": f"Error: {str(e)}. Please check the input and try again."
})When Claude receives a tool_result with is_error: True, it can reason about the failure and decide whether to retry with different parameters, try a different tool, or inform the user about the problem. This error-handling flow is critical for building resilient agents.
Streaming Tool Use
Tool use works with streaming responses. When streaming, you receive events that progressively build up the tool use blocks. The key events to watch for are content_block_start (which tells you a tool_use block is beginning), content_block_delta (partial JSON for the input), and content_block_stop.
# Streaming tool use
with client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "What is the weather in Paris?"}]
) as stream:
for event in stream:
if event.type == "content_block_start":
if event.content_block.type == "tool_use":
print(f"Tool call starting: {event.content_block.name}")
elif event.type == "content_block_stop":
pass # Block complete
# After stream completes, get the full response
final_response = stream.get_final_message()Tool Use with Images and Multi-Modal Input
Claude can use tools in combination with image inputs. For example, a user might upload an image and ask Claude to analyze it using a tool. The tool call follows the same pattern -- Claude extracts relevant information from the image and passes it as tool arguments.
# Tool use with image input
import base64
with open("chart.png", "rb") as f:
image_data = base64.standard_b64encode(f.read()).decode("utf-8")
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=[{
"name": "log_chart_data",
"description": "Log extracted data points from a chart image.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Chart title"},
"data_points": {
"type": "array",
"items": {"type": "object"},
"description": "Extracted data points"
}
},
"required": ["title", "data_points"]
}
}],
tool_choice={"type": "tool", "name": "log_chart_data"},
messages=[{
"role": "user",
"content": [
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": image_data}},
{"type": "text", "text": "Extract the data points from this chart."}
]
}]
)tool_result with the correct tool_use_id, handle errors with is_error: true, and include a max iteration guard in agentic loops.