How to implement tool use¶
- URL: https://platform.claude.com/docs/en/agents-and-tools/tool-use/implement-tool-use.md
- Retrieved: 2026-01-08T05:11:25.079955+00:00
How to implement tool use¶
Choosing a model¶
We recommend using the latest Claude Sonnet (4.5) or Claude Opus (4.5) model for complex tools and ambiguous queries; they handle multiple tools better and seek clarification when needed.
Use Claude Haiku models for straightforward tools, but note they may infer missing parameters.
Specifying client tools¶
Client tools (both Anthropic-defined and user-defined) are specified in the tools top-level parameter of the API request. Each tool definition includes:
| Parameter | Description |
|---|---|
name |
The name of the tool. Must match the regex ^[a-zA-Z0-9_-]{1,64}$. |
description |
A detailed plaintext description of what the tool does, when it should be used, and how it behaves. |
input_schema |
A JSON Schema object defining the expected parameters for the tool. |
input_examples |
(Optional, beta) An array of example input objects to help Claude understand how to use the tool. See [Providing tool use examples]. |
This tool, named `get_weather`, expects an input object with a required `location` string and an optional `unit` string that must be either "celsius" or "fahrenheit".
</section>
### Tool use system prompt
When you call the Claude API with the `tools` parameter, we construct a special system prompt from the tool definitions, tool configuration, and any user-specified system prompt. The constructed prompt is designed to instruct the model to use the specified tool(s) and provide the necessary context for the tool to operate properly:
### Best practices for tool definitions
To get the best performance out of Claude when using tools, follow these guidelines:
- **Provide extremely detailed descriptions.** This is by far the most important factor in tool performance. Your descriptions should explain every detail about the tool, including:
- What the tool does
- When it should be used (and when it shouldn't)
- What each parameter means and how it affects the tool's behavior
- Any important caveats or limitations, such as what information the tool does not return if the tool name is unclear. The more context you can give Claude about your tools, the better it will be at deciding when and how to use them. Aim for at least 3-4 sentences per tool description, more if the tool is complex.
- **Prioritize descriptions, but consider using `input_examples` for complex tools.** Clear descriptions are most important, but for tools with complex inputs, nested objects, or format-sensitive parameters, you can use the `input_examples` field (beta) to provide schema-validated examples. See [Providing tool use examples] for details.
<section title="Example of a good tool description">
```json JSON
{
"name": "get_stock_price",
"description": "Retrieves the current stock price for a given ticker symbol. The ticker symbol must be a valid symbol for a publicly traded company on a major US stock exchange like NYSE or NASDAQ. The tool will return the latest trade price in USD. It should be used when the user asks about the current or most recent price of a specific stock. It will not provide any other information about the stock or company.",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "The stock ticker symbol, e.g. AAPL for Apple Inc."
}
},
"required": ["ticker"]
}
}
</section>
The good description clearly explains what the tool does, when to use it, what data it returns, and what the `ticker` parameter means. The poor description is too brief and leaves Claude with many open questions about the tool's behavior and usage.
## Providing tool use examples
You can provide concrete examples of valid tool inputs to help Claude understand how to use your tools more effectively. This is particularly useful for complex tools with nested objects, optional parameters, or format-sensitive inputs.
<Info>
Tool use examples is a beta feature. Include the appropriate [beta header](https://platform.claude.com/docs/en/api/beta-headers) for your provider:
| Provider | Beta header | Supported models |
|----------|-------------|------------------|
| Claude API,<br/>Microsoft Foundry | `advanced-tool-use-2025-11-20` | All models |
| Vertex AI,<br/>Amazon Bedrock | `tool-examples-2025-10-29` | Claude Opus 4.5 only |
</Info>
### Basic usage
Add an optional `input_examples` field to your tool definition with an array of example input objects. Each example must be valid according to the tool's `input_schema`:
<CodeGroup>
```python Python
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
betas=["advanced-tool-use-2025-11-20"],
tools=[
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The unit of temperature"
}
},
"required": ["location"]
},
"input_examples": [
{
"location": "San Francisco, CA",
"unit": "fahrenheit"
},
{
"location": "Tokyo, Japan",
"unit": "celsius"
},
{
"location": "New York, NY" # 'unit' is optional
}
]
}
],
messages=[
{"role": "user", "content": "What's the weather like in San Francisco?"}
]
)
</CodeGroup>
Examples are included in the prompt alongside your tool schema, showing Claude concrete patterns for well-formed tool calls. This helps Claude understand when to include optional parameters, what formats to use, and how to structure complex inputs.
### Requirements and limitations
- **Schema validation** - Each example must be valid according to the tool's `input_schema`. Invalid examples return a 400 error
- **Not supported for server-side tools** - Only user-defined tools can have input examples
- **Token cost** - Examples add to prompt tokens: ~20-50 tokens for simple examples, ~100-200 tokens for complex nested objects
## Tool runner (beta)
The tool runner provides an out-of-the-box solution for executing tools with Claude. Instead of manually handling tool calls, tool results, and conversation management, the tool runner automatically:
- Executes tools when Claude calls them
- Handles the request/response cycle
- Manages conversation state
- Provides type safety and validation
We recommend that you use the tool runner for most tool use implementations.
<Note>
The tool runner is currently in beta and available in the [Python](https://github.com/anthropics/anthropic-sdk-python/blob/main/tools.md), [TypeScript](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/helpers.md#tool-helpers), and [Ruby](https://github.com/anthropics/anthropic-sdk-ruby/blob/main/helpers.md#3-auto-looping-tool-runner-beta) SDKs.
</Note>
<Tip>
**Automatic context management with compaction**
The tool runner supports automatic [compaction](https://platform.claude.com/docs/en/build-with-claude/context-editing#client-side-compaction-sdk), which generates summaries when token usage exceeds a threshold. This allows long-running agentic tasks to continue beyond context window limits.
</Tip>
### Basic usage
Define tools using the SDK helpers, then use the tool runner to execute them.
<Tabs>
<Tab title="Python">
Use the `@beta_tool` decorator to define tools with type hints and docstrings.
<Note>
If you're using the async client, replace `@beta_tool` with `@beta_async_tool` and define the function with `async def`.
</Note>
```python
import anthropic
import json
from anthropic import beta_tool
# Initialize client
client = anthropic.Anthropic()
# Define tools using the decorator
@beta_tool
def get_weather(location: str, unit: str = "fahrenheit") -> str:
"""Get the current weather in a given location.
Args:
location: The city and state, e.g. San Francisco, CA
unit: Temperature unit, either 'celsius' or 'fahrenheit'
"""
# In a full implementation, you'd call a weather API here
return json.dumps({"temperature": "20°C", "condition": "Sunny"})
@beta_tool
def calculate_sum(a: int, b: int) -> str:
"""Add two numbers together.
Args:
a: First number
b: Second number
"""
return str(a + b)
# Use the tool runner
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[get_weather, calculate_sum],
messages=[
{"role": "user", "content": "What's the weather like in Paris? Also, what's 15 + 27?"}
]
)
for message in runner:
print(message.content[0].text)
{
"name": "calculate_sum",
"description": "Adds two integers together.",
"input_schema": {
"additionalProperties": false,
"properties": {
"left": {
"description": "The first integer to add.",
"title": "Left",
"type": "integer"
},
"right": {
"description": "The second integer to add.",
"title": "Right",
"type": "integer"
}
},
"required": ["left", "right"],
"type": "object"
}
}
import { Anthropic } from '@anthropic-ai/sdk';
import { betaZodTool } from '@anthropic-ai/sdk/helpers/beta/zod';
import { z } from 'zod';
const anthropic = new Anthropic();
const getWeatherTool = betaZodTool({
name: 'get_weather',
description: 'Get the current weather in a given location',
inputSchema: z.object({
location: z.string().describe('The city and state, e.g. San Francisco, CA'),
unit: z.enum(['celsius', 'fahrenheit']).default('fahrenheit')
.describe('Temperature unit')
}),
run: async (input) => {
// In a full implementation, you'd call a weather API here
return JSON.stringify({temperature: '20°C', condition: 'Sunny'});
}
});
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
tools: [getWeatherTool],
messages: [{ role: 'user', content: "What's the weather like in Paris?" }]
});
for await (const message of runner) {
console.log(message.content[0].text);
}
import { Anthropic } from '@anthropic-ai/sdk';
import { betaTool } from '@anthropic-ai/sdk/helpers/beta/json-schema';
const anthropic = new Anthropic();
const calculateSumTool = betaTool({
name: 'calculate_sum',
description: 'Add two numbers together',
inputSchema: {
type: 'object',
properties: {
a: { type: 'number', description: 'First number' },
b: { type: 'number', description: 'Second number' }
},
required: ['a', 'b']
},
run: async (input) => {
return String(input.a + input.b);
}
});
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
tools: [calculateSumTool],
messages: [{ role: 'user', content: "What's 15 + 27?" }]
});
for await (const message of runner) {
console.log(message.content[0].text);
}
require "anthropic"
# Initialize client
client = Anthropic::Client.new
# Define input schema
class GetWeatherInput < Anthropic::BaseModel
required :location, String, doc: "The city and state, e.g. San Francisco, CA"
optional :unit, Anthropic::InputSchema::EnumOf["celsius", "fahrenheit"],
doc: "Temperature unit"
end
# Define tool
class GetWeather < Anthropic::BaseTool
doc "Get the current weather in a given location"
input_schema GetWeatherInput
def call(input)
# In a full implementation, you'd call a weather API here
JSON.generate({temperature: "20°C", condition: "Sunny"})
end
end
class CalculateSumInput < Anthropic::BaseModel
required :a, Integer, doc: "First number"
required :b, Integer, doc: "Second number"
end
class CalculateSum < Anthropic::BaseTool
doc "Add two numbers together"
input_schema CalculateSumInput
def call(input)
(input.a + input.b).to_s
end
end
# Use the tool runner
runner = client.beta.messages.tool_runner(
model: "claude-sonnet-4-5",
max_tokens: 1024,
tools: [GetWeather.new, CalculateSum.new],
messages: [
{role: "user", content: "What's the weather like in Paris? Also, what's 15 + 27?"}
]
)
runner.each_message do |message|
message.content.each do |block|
puts block.text if block.respond_to?(:text)
end
end
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[get_weather, calculate_sum],
messages=[
{"role": "user", "content": "What's the weather like in Paris? Also, what's 15 + 27?"}
]
)
final_message = runner.until_done()
print(final_message.content[0].text)
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
tools: [getWeatherTool],
messages: [{ role: 'user', content: "What's the weather like in Paris?" }]
});
const finalMessage = await runner;
console.log(finalMessage.content[0].text);
runner = client.beta.messages.tool_runner(
model: "claude-sonnet-4-5",
max_tokens: 1024,
tools: [GetWeather.new, CalculateSum.new],
messages: [
{role: "user", content: "What's the weather like in Paris? Also, what's 15 + 27?"}
]
)
all_messages = runner.run_until_finished
all_messages.each { |msg| puts msg.content }
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[get_weather],
messages=[{"role": "user", "content": "What's the weather in San Francisco?"}]
)
for message in runner:
# Optional: inspect the tool response (automatically appended by the runner)
tool_response = runner.generate_tool_call_response()
if tool_response:
print(f"Tool result: {tool_response}")
# Customize the next request
runner.set_messages_params(lambda params: {
**params,
"max_tokens": 2048 # Increase tokens for next request
})
# Or add additional messages
runner.append_messages(
{"role": "user", "content": "Please be concise in your response."}
)
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
tools: [getWeatherTool],
messages: [{ role: 'user', content: "What's the weather in San Francisco?" }]
});
for await (const message of runner) {
// Optional: inspect the tool result message (automatically appended by the runner)
const toolResultMessage = await runner.generateToolResponse();
if (toolResultMessage) {
console.log('Tool result:', toolResultMessage);
}
// Customize the next request
runner.setMessagesParams(params => ({
...params,
max_tokens: 2048 // Increase tokens for next request
}));
// Or add additional messages
runner.pushMessages(
{ role: 'user', content: 'Please be concise in your response.' }
);
}
runner = client.beta.messages.tool_runner(
model: "claude-sonnet-4-5",
max_tokens: 1024,
tools: [GetWeather.new],
messages: [{role: "user", content: "What's the weather in San Francisco?"}]
)
# Manual step-by-step control
message = runner.next_message
puts message.content
# Inject follow-up messages
runner.feed_messages([
{role: "user", content: "Also check Boston"}
])
# Access current parameters
puts runner.params
# View info-level logs including tool errors
export ANTHROPIC_LOG=info
# View debug-level logs for more verbose output
export ANTHROPIC_LOG=debug
import json
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[my_tool],
messages=[{"role": "user", "content": "Run the tool"}]
)
for message in runner:
tool_response = runner.generate_tool_call_response()
if tool_response:
# Check if any tool result has an error
for block in tool_response.content:
if block.is_error:
# Option 1: Raise an exception to stop the loop
raise RuntimeError(f"Tool failed: {json.dumps(block.content)}")
# Option 2: Log and continue (let Claude handle it)
# logger.error(f"Tool error: {json.dumps(block.content)}")
# Process the message normally
print(message.content)
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
tools: [myTool],
messages: [{ role: 'user', content: 'Run the tool' }]
});
for await (const message of runner) {
const toolResultMessage = await runner.generateToolResponse();
if (toolResultMessage) {
// Check if any tool result has an error
for (const block of toolResultMessage.content) {
if (block.type === 'tool_result' && block.is_error) {
// Option 1: Throw to stop the loop
throw new Error(`Tool failed: ${JSON.stringify(block.content)}`);
// Option 2: Log and continue (let Claude handle it)
// console.error(`Tool error: ${JSON.stringify(block.content)}`);
}
}
}
// Process the message normally
console.log(message.content);
}
runner = client.beta.messages.tool_runner(
model: "claude-sonnet-4-5",
max_tokens: 1024,
tools: [MyTool.new],
messages: [{role: "user", content: "Run the tool"}]
)
runner.each_message do |message|
# Get the tool response to check for errors
# Note: The runner automatically handles tool execution and appends results
# This is just for error checking/logging purposes
tool_results = runner.params[:messages].last
if tool_results && tool_results[:role] == "user"
tool_results[:content].each do |block|
if block[:type] == "tool_result" && block[:is_error]
# Option 1: Raise an exception to stop the loop
raise "Tool failed: #{block[:content]}"
# Option 2: Log and continue (let Claude handle it)
# logger.error("Tool error: #{block[:content]}")
end
end
end
puts message.content
end
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[search_documents],
messages=[{"role": "user", "content": "Search for information about the climate of San Francisco"}]
)
for message in runner:
tool_response = runner.generate_tool_call_response()
if tool_response:
# Modify the tool result to add cache control
for block in tool_response.content:
if block.type == "tool_result":
# Add cache_control to cache this tool result
block.cache_control = {"type": "ephemeral"}
# Append the modified response (this prevents auto-append of original)
runner.append_messages(message, tool_response)
print(message.content)
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
tools: [searchDocuments],
messages: [{ role: 'user', content: 'Search for information about the climate of San Francisco' }]
});
for await (const message of runner) {
const toolResultMessage = await runner.generateToolResponse();
if (toolResultMessage) {
// Modify the tool result to add cache control
for (const block of toolResultMessage.content) {
if (block.type === 'tool_result') {
// Add cache_control to cache this tool result
block.cache_control = { type: 'ephemeral' };
}
}
// Push the modified message (this prevents auto-append of original)
runner.pushMessages(message, toolResultMessage);
}
console.log(message.content);
}
runner = client.beta.messages.tool_runner(
model: "claude-sonnet-4-5",
max_tokens: 1024,
tools: [SearchDocuments.new],
messages: [{role: "user", content: "Search for information about the climate of San Francisco"}]
)
loop do
message = runner.next_message
break unless message
# Access the most recent tool results from the messages array
# The runner automatically adds tool results, but we can modify them
tool_results_message = runner.params[:messages].last
if tool_results_message && tool_results_message[:role] == "user"
tool_results_message[:content].each do |block|
if block[:type] == "tool_result"
# Modify the tool result to add cache control
block[:cache_control] = {type: "ephemeral"}
end
end
end
puts message.content
break if message.stop_reason != "tool_use"
end
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[calculate_sum],
messages=[{"role": "user", "content": "What is 15 + 27?"}],
stream=True
)
# When streaming, the runner returns BetaMessageStream
for message_stream in runner:
for event in message_stream:
print('event:', event)
print('message:', message_stream.get_final_message())
print(runner.until_done())
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1000,
messages: [{ role: 'user', content: 'What is the weather in San Francisco?' }],
tools: [getWeatherTool],
stream: true,
});
// When streaming, the runner returns BetaMessageStream
for await (const messageStream of runner) {
for await (const event of messageStream) {
console.log('event:', event);
}
console.log('message:', await messageStream.finalMessage());
}
console.log(await runner);
runner = client.beta.messages.tool_runner(
model: "claude-sonnet-4-5",
max_tokens: 1024,
tools: [CalculateSum.new],
messages: [{role: "user", content: "What is 15 + 27?"}]
)
runner.each_streaming do |event|
case event
when Anthropic::Streaming::TextEvent
print event.text
when Anthropic::Streaming::ToolUseEvent
puts "\nTool called: #{event.tool_name}"
end
end
tool_choice = {"type": "tool", "name": "get_weather"}
This natural response style helps users understand what Claude is doing and creates a more conversational interaction. You can guide the style and content of these responses through your system prompts and by providing `<examples>` in your prompts.
It's important to note that Claude may use various phrasings and approaches when explaining its actions. Your code should treat these responses like any other assistant-generated text, and not rely on specific formatting conventions.
### Parallel tool use
By default, Claude may use multiple tools to answer a user query. You can disable this behavior by:
- Setting `disable_parallel_tool_use=true` when tool_choice type is `auto`, which ensures that Claude uses **at most one** tool
- Setting `disable_parallel_tool_use=true` when tool_choice type is `any` or `tool`, which ensures that Claude uses **exactly one** tool
<section title="Complete parallel tool use example">
<Note>
**Simpler with Tool runner**: The example below shows manual parallel tool handling. For most use cases, [tool runner] automatically handle parallel tool execution with much less code.
</Note>
Here's a complete example showing how to properly format parallel tool calls in the message history:
<CodeGroup>
```python Python
import anthropic
client = anthropic.Anthropic()
# Define tools
tools = [
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
}
},
{
"name": "get_time",
"description": "Get the current time in a given timezone",
"input_schema": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "The timezone, e.g. America/New_York"
}
},
"required": ["timezone"]
}
}
]
# Initial request
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=[
{
"role": "user",
"content": "What's the weather in SF and NYC, and what time is it there?"
}
]
)
# Claude's response with parallel tool calls
print("Claude wants to use tools:", response.stop_reason == "tool_use")
print("Number of tool calls:", len([c for c in response.content if c.type == "tool_use"]))
# Build the conversation with tool results
messages = [
{
"role": "user",
"content": "What's the weather in SF and NYC, and what time is it there?"
},
{
"role": "assistant",
"content": response.content # Contains multiple tool_use blocks
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01", # Must match the ID from tool_use
"content": "San Francisco: 68°F, partly cloudy"
},
{
"type": "tool_result",
"tool_use_id": "toolu_02",
"content": "New York: 45°F, clear skies"
},
{
"type": "tool_result",
"tool_use_id": "toolu_03",
"content": "San Francisco time: 2:30 PM PST"
},
{
"type": "tool_result",
"tool_use_id": "toolu_04",
"content": "New York time: 5:30 PM EST"
}
]
}
]
# Get final response
final_response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tools,
messages=messages
)
print(final_response.content[0].text)
</CodeGroup>
The assistant message with parallel tool calls would look like this:
```json
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll check the weather and time for both San Francisco and New York City."
},
{
"type": "tool_use",
"id": "toolu_01",
"name": "get_weather",
"input": {"location": "San Francisco, CA"}
},
{
"type": "tool_use",
"id": "toolu_02",
"name": "get_weather",
"input": {"location": "New York, NY"}
},
{
"type": "tool_use",
"id": "toolu_03",
"name": "get_time",
"input": {"timezone": "America/Los_Angeles"}
},
{
"type": "tool_use",
"id": "toolu_04",
"name": "get_time",
"input": {"timezone": "America/New_York"}
}
]
}
```typescript TypeScript
#!/usr/bin/env node
// Test script to verify parallel tool calls with the Claude API
import { Anthropic } from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
// Define tools
const tools = [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
}
},
required: ["location"]
}
},
{
name: "get_time",
description: "Get the current time in a given timezone",
input_schema: {
type: "object",
properties: {
timezone: {
type: "string",
description: "The timezone, e.g. America/New_York"
}
},
required: ["timezone"]
}
}
];
async function testParallelTools() {
// Make initial request
console.log("Requesting parallel tool calls...");
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{
role: "user",
content: "What's the weather in SF and NYC, and what time is it there?"
}],
tools: tools
});
// Check for parallel tool calls
const toolUses = response.content.filter(block => block.type === "tool_use");
console.log(`\n✓ Claude made ${toolUses.length} tool calls`);
if (toolUses.length > 1) {
console.log("✓ Parallel tool calls detected!");
toolUses.forEach(tool => {
console.log(` - ${tool.name}: ${JSON.stringify(tool.input)}`);
});
} else {
console.log("✗ No parallel tool calls detected");
}
// Simulate tool execution and format results correctly
const toolResults = toolUses.map(toolUse => {
let result;
if (toolUse.name === "get_weather") {
result = toolUse.input.location.includes("San Francisco")
? "San Francisco: 68°F, partly cloudy"
: "New York: 45°F, clear skies";
} else {
result = toolUse.input.timezone.includes("Los_Angeles")
? "2:30 PM PST"
: "5:30 PM EST";
}
return {
type: "tool_result",
tool_use_id: toolUse.id,
content: result
};
});
// Get final response with correct formatting
console.log("\nGetting final response...");
const finalResponse = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [
{ role: "user", content: "What's the weather in SF and NYC, and what time is it there?" },
{ role: "assistant", content: response.content },
{ role: "user", content: toolResults } // All results in one message!
],
tools: tools
});
console.log(`\nClaude's response:\n${finalResponse.content[0].text}`);
// Verify formatting
console.log("\n--- Verification ---");
console.log(`✓ Tool results sent in single user message: ${toolResults.length} results`);
console.log("✓ No text before tool results in content array");
console.log("✓ Conversation formatted correctly for future parallel tool use");
}
testParallelTools().catch(console.error);
Maximizing parallel tool use¶
While Claude 4 models have excellent parallel tool use capabilities by default, you can increase the likelihood of parallel tool execution across all models with targeted prompting:
For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
<use_parallel_tool_calls>
For maximum efficiency, whenever you perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially. Prioritize calling tools in parallel whenever possible. For example, when reading 3 files, run 3 tool calls in parallel to read all 3 files into context at the same time. When running multiple read-only commands like `ls` or `list_dir`, always run all of the commands in parallel. Err on the side of maximizing parallel tool calls rather than running too many tools sequentially.
</use_parallel_tool_calls>
# Instead of:
"What's the weather in Paris? Also check London."
# Use:
"Check the weather in Paris and London simultaneously."
# Or be explicit:
"Please use parallel tool calls to get the weather for Paris, London, and Tokyo at the same time."
Claude Sonnet 3.7 may be less likely to make make parallel tool calls in a response, even when you have not set disable_parallel_tool_use. We recommend upgrading to Claude 4 models, which have built-in token-efficient tool use and improved parallel tool calling.
If you're still using Claude Sonnet 3.7, you can enable the token-efficient-tools-2025-02-19 beta header, which helps encourage Claude to use parallel tools. You can also introduce a "batch tool" that can act as a meta-tool to wrap invocations to other tools simultaneously.
See this example in our cookbook for how to use this workaround.
Handling tool use and tool result content blocks¶
Claude's response differs based on whether it uses a client or server tool.
Handling results from client tools¶
The response will have a stop_reason of tool_use and one or more tool_use content blocks that include:
id: A unique identifier for this particular tool use block. This will be used to match up the tool results later.name: The name of the tool being used.input: An object containing the input being passed to the tool, conforming to the tool'sinput_schema.
</section>
When you receive a tool use response for a client tool, you should:
1. Extract the `name`, `id`, and `input` from the `tool_use` block.
2. Run the actual tool in your codebase corresponding to that tool name, passing in the tool `input`.
3. Continue the conversation by sending a new message with the `role` of `user`, and a `content` block containing the `tool_result` type and the following information:
- `tool_use_id`: The `id` of the tool use request this is a result for.
- `content`: The result of the tool, as a string (e.g. `"content": "15 degrees"`), a list of nested content blocks (e.g. `"content": [{"type": "text", "text": "15 degrees"}]`), or a list of document blocks (e.g. `"content": ["type": "document", "source": {"type": "text", "media_type": "text/plain", "data": "15 degrees"}]`). These content blocks can use the `text`, `image`, or `document` types.
- `is_error` (optional): Set to `true` if the tool execution resulted in an error.
<Note>
**Important formatting requirements**:
- Tool result blocks must immediately follow their corresponding tool use blocks in the message history. You cannot include any messages between the assistant's tool use message and the user's tool result message.
- In the user message containing tool results, the tool_result blocks must come FIRST in the content array. Any text must come AFTER all tool results.
For example, this will cause a 400 error:
```json
{"role": "user", "content": [
{"type": "text", "text": "Here are the results:"}, // ❌ Text before tool_result
{"type": "tool_result", "tool_use_id": "toolu_01", ...}
]}
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_01", ...},
{"type": "text", "text": "What should I do next?"} // ✅ Text after tool_result
]}
</section>
<section title="Example of tool result with images">
```json JSON
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": [
{"type": "text", "text": "15 degrees"},
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "/9j/4AAQSkZJRg...",
}
}
]
}
]
}
</section>
<section title="Example of tool result with documents">
```json JSON
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": [
{"type": "text", "text": "The weather is"},
{
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "15 degrees"
}
}
]
}
]
}
```typescript TypeScript
// Check if response was truncated during tool use
if (response.stop_reason === "max_tokens") {
// Check if the last content block is an incomplete tool_use
const lastBlock = response.content[response.content.length - 1];
if (lastBlock.type === "tool_use") {
// Send the request with higher max_tokens
response = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 4096, // Increased limit
messages: messages,
tools: tools
});
}
}
```typescript TypeScript
import { Anthropic } from '@anthropic-ai/sdk';
const anthropic = new Anthropic();
// Initial request with web search
const response = await anthropic.messages.create({
model: "claude-3-7-sonnet-latest",
max_tokens: 1024,
messages: [
{
role: "user",
content: "Search for comprehensive information about quantum computing breakthroughs in 2025"
}
],
tools: [{
type: "web_search_20250305",
name: "web_search",
max_uses: 10
}]
});
// Check if the response has pause_turn stop reason
if (response.stop_reason === "pause_turn") {
// Continue the conversation with the paused content
const messages = [
{ role: "user", content: "Search for comprehensive information about quantum computing breakthroughs in 2025" },
{ role: "assistant", content: response.content }
];
// Send the continuation request
const continuation = await anthropic.messages.create({
model: "claude-3-7-sonnet-latest",
max_tokens: 1024,
messages: messages,
tools: [{
type: "web_search_20250305",
name: "web_search",
max_uses: 10
}]
});
console.log(continuation);
} else {
console.log(response);
}
Claude will then incorporate this error into its response to the user, e.g. "I'm sorry, I was unable to retrieve the current weather because the weather service API is not available. Please try again later."
</section>
<section title="Invalid tool name">
If Claude's attempted use of a tool is invalid (e.g. missing required parameters), it usually means that the there wasn't enough information for Claude to use the tool correctly. Your best bet during development is to try the request again with more-detailed `description` values in your tool definitions.
However, you can also continue the conversation forward with a `tool_result` that indicates the error, and Claude will try to use the tool again with the missing information filled in:
```json JSON
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Missing required 'location' parameter",
"is_error": true
}
]
}
// ❌ This reduces parallel tool use
[
{"role": "assistant", "content": [tool_use_1, tool_use_2]},
{"role": "user", "content": [tool_result_1]},
{"role": "user", "content": [tool_result_2]} // Separate message
]
// ✅ This maintains parallel tool use
[
{"role": "assistant", "content": [tool_use_1, tool_use_2]},
{"role": "user", "content": [tool_result_1, tool_result_2]} // Single message
]
<use_parallel_tool_calls>
For maximum efficiency, whenever you perform multiple independent operations,
invoke all relevant tools simultaneously rather than sequentially.
Prioritize calling tools in parallel whenever possible.
</use_parallel_tool_calls>
# Calculate average tools per tool-calling message
tool_call_messages = [msg for msg in messages if any(
block.type == "tool_use" for block in msg.content
)]
total_tool_calls = sum(
len([b for b in msg.content if b.type == "tool_use"])
for msg in tool_call_messages
)
avg_tools_per_message = total_tool_calls / len(tool_call_messages)
print(f"Average tools per message: {avg_tools_per_message}")
# Should be > 1.0 if parallel calls are working