All guides
Claude

Parallel tool use

Checked 09/15/2026View original
On this page

By default, Claude may call multiple tools in a single response. This page covers how to run those calls, how to format the message history so parallelism keeps working, and how to disable parallel tool use when you need to. For the single-call flow, see Handle tool calls.

Execution semantics

When Claude calls tools, the response has a stop_reason of tool_use and can contain several tool_use blocks in a single assistant turn. How you run those calls is your decision. The API doesn't prescribe an execution order: you can run the calls concurrently (Promise.all, asyncio.gather), sequentially in the order they appear, or in any combination that suits your tools.

Choose the strategy based on what your tools do. Independent, read-only operations are usually safe to run in parallel for lower latency. Tools with side effects, shared state, or ordering requirements might be better run sequentially.

Whichever strategy you use, return one tool_result for each tool_use block, all together in the next user message. Match each result to its call with tool_use_id, and put every tool_result block before any text content in that message. See Handle tool calls for the full formatting rules. If you choose not to run a particular call (for example, because you ran the batch sequentially and an earlier call failed), still return a tool_result for it with is_error: true and a brief explanation.

{
  "type": "tool_result",
  "tool_use_id": "toolu_02",
  "is_error": true,
  "content": "Not executed: the preceding write_file call failed."
}

The computer use tool and the browser use tool are stricter. When Claude returns several of their member tool calls in one turn (a batch action), run them sequentially in the order they appear and stop at the first failure; each tool defines the exact text to return for the calls you skip.

Test parallel tool calls

The following script sends a request that should trigger parallel tool calls, verifies the response contains them, and formats the tool results so parallelism keeps working. Run it with ANTHROPIC_API_KEY set in your environment:

# This end-to-end test flow doesn't translate well to a one-off shell command.
# See the SDK tabs for the full flow.
client = 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"],
        },
    },
]

# Test conversation with parallel tool calls
messages = [
    {
        "role": "user",
        "content": "What's the weather in SF and NYC, and what time is it there?",
    }
]

# Make initial request
print("Requesting parallel tool calls...")
response = client.messages.create(
    model="claude-opus-5", max_tokens=1024, messages=messages, tools=tools
)

# Check for parallel tool calls
tool_uses = [block for block in response.content if block.type == "tool_use"]
print(f"\n✓ Claude made {len(tool_uses)} tool calls")

if len(tool_uses) > 1:
    print("✓ Parallel tool calls detected!")
    for tool in tool_uses:
        print(f"  - {tool.name}: {tool.input}")
else:
    print("✗ No parallel tool calls detected")

# Simulate tool execution and format results correctly
tool_results = []
for tool_use in tool_uses:
    if tool_use.name == "get_weather":
        if "San Francisco" in str(tool_use.input):
            result = "San Francisco: 68°F, partly cloudy"
        else:
            result = "New York: 45°F, clear skies"
    else:  # get_time
        if "Los_Angeles" in str(tool_use.input):
            result = "2:30 PM PST"
        else:
            result = "5:30 PM EST"

    tool_results.append(
        {"type": "tool_result", "tool_use_id": tool_use.id, "content": result}
    )

# Continue conversation with tool results
messages.extend(
    [
        {"role": "assistant", "content": response.content},
        {"role": "user", "content": tool_results},  # All results in one message!
    ]
)

# Get final response
print("\nGetting final response...")
final_response = client.messages.create(
    model="claude-opus-5", max_tokens=1024, messages=messages, tools=tools
)

final_text = next(
    block.text for block in final_response.content if block.type == "text"
)
print(f"\nClaude's response:\n{final_text}")

# Verify formatting
print("\n--- Verification ---")
print(f"✓ Tool results sent in single user message: {len(tool_results)} results")
print("✓ No text before tool results in content array")
print("✓ Conversation formatted correctly for future parallel tool use")
const client = new Anthropic();

// Define tools
const tools: Anthropic.Tool[] = [
  {
    name: "get_weather",
    description: "Get the current weather in a given location",
    input_schema: {
      type: "object" as const,
      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" as const,
      properties: {
        timezone: {
          type: "string",
          description: "The timezone, e.g. America/New_York"
        }
      },
      required: ["timezone"]
    }
  }
];

// Make initial request
console.log("Requesting parallel tool calls...");
const response = await client.messages.create({
  model: "claude-opus-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!");
  for (const tool of toolUses) {
    if (tool.type === "tool_use") {
      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: Anthropic.ToolResultBlockParam[] = toolUses
  .filter((block): block is Anthropic.ToolUseBlock => block.type === "tool_use")
  .map((toolUse) => {
    const input = toolUse.input as Record<string, string>;
    let result: string;
    if (toolUse.name === "get_weather") {
      result = input.location?.includes("San Francisco")
        ? "San Francisco: 68F, partly cloudy"
        : "New York: 45F, clear skies";
    } else {
      result = input.timezone?.includes("Los_Angeles") ? "2:30 PM PST" : "5:30 PM EST";
    }

    return {
      type: "tool_result" as const,
      tool_use_id: toolUse.id,
      content: result
    };
  });

// Get final response with correct formatting
console.log("\nGetting final response...");
const finalResponse = await client.messages.create({
  model: "claude-opus-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 }
  ],
  tools: tools
});

for (const block of finalResponse.content) {
  if (block.type === "text") {
    console.log(`\nClaude's response:\n${block.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");
AnthropicClient client = new();

var tools = new List<ToolUnion>
{
    new ToolUnion(new Tool()
    {
        Name = "get_weather",
        Description = "Get the current weather in a given location",
        InputSchema = new InputSchema()
        {
            Properties = new Dictionary<string, JsonElement>
            {
                ["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The city and state, e.g. San Francisco, CA" }),
            },
            Required = ["location"],
        },
    }),
    new ToolUnion(new Tool()
    {
        Name = "get_time",
        Description = "Get the current time in a given timezone",
        InputSchema = new InputSchema()
        {
            Properties = new Dictionary<string, JsonElement>
            {
                ["timezone"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The timezone, e.g. America/New_York" }),
            },
            Required = ["timezone"],
        },
    }),
};

Console.WriteLine("Requesting parallel tool calls...");
var parameters = new MessageCreateParams
{
    Model = Model.ClaudeOpus5,
    MaxTokens = 1024,
    Messages = [new() { Role = Role.User, Content = "What's the weather in SF and NYC, and what time is it there?" }],
    Tools = tools
};

var response = await client.Messages.Create(parameters);

var toolUses = new List<ToolUseBlock>();
foreach (var block in response.Content)
{
    if (block.TryPickToolUse(out var toolUse))
    {
        toolUses.Add(toolUse);
    }
}
Console.WriteLine($"\n✓ Claude made {toolUses.Count} tool calls");

if (toolUses.Count > 1)
{
    Console.WriteLine("✓ Parallel tool calls detected!");
    foreach (var tool in toolUses)
    {
        Console.WriteLine($"  - {tool.Name}: {JsonSerializer.Serialize(tool.Input)}");
    }
}
else
{
    Console.WriteLine("✗ No parallel tool calls detected");
}

var toolResults = new List<ContentBlockParam>();
foreach (var toolUse in toolUses)
{
    string result;
    if (toolUse.Name == "get_weather")
    {
        result = JsonSerializer.Serialize(toolUse.Input).Contains("San Francisco")
            ? "San Francisco: 68°F, partly cloudy"
            : "New York: 45°F, clear skies";
    }
    else
    {
        result = JsonSerializer.Serialize(toolUse.Input).Contains("Los_Angeles")
            ? "2:30 PM PST"
            : "5:30 PM EST";
    }

    toolResults.Add(new ContentBlockParam(new ToolResultBlockParam()
    {
        ToolUseID = toolUse.ID,
        Content = result,
    }));
}

Console.WriteLine("\nGetting final response...");
var finalParameters = new MessageCreateParams
{
    Model = Model.ClaudeOpus5,
    MaxTokens = 1024,
    Messages = [
        new() { Role = Role.User, Content = "What's the weather in SF and NYC, and what time is it there?" },
        new() { Role = Role.Assistant, Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList() },
        new() { Role = Role.User, Content = new MessageParamContent(toolResults) }
    ],
    Tools = tools
};

var finalResponse = await client.Messages.Create(finalParameters);
var text = finalResponse.Content.Select(b => b.Value).OfType<TextBlock>().FirstOrDefault();
Console.WriteLine($"\nClaude's response:\n{text?.Text}");

Console.WriteLine("\n--- Verification ---");
Console.WriteLine($"✓ Tool results sent in single user message: {toolResults.Count} results");
Console.WriteLine("✓ No text before tool results in content array");
Console.WriteLine("✓ Conversation formatted correctly for future parallel tool use");
client := anthropic.NewClient()

tools := []anthropic.ToolUnionParam{
	{OfTool: &anthropic.ToolParam{
		Name:        "get_weather",
		Description: anthropic.String("Get the current weather in a given location"),
		InputSchema: anthropic.ToolInputSchemaParam{
			Properties: map[string]any{
				"location": map[string]any{
					"type":        "string",
					"description": "The city and state, e.g. San Francisco, CA",
				},
			},
			Required: []string{"location"},
		},
	}},
	{OfTool: &anthropic.ToolParam{
		Name:        "get_time",
		Description: anthropic.String("Get the current time in a given timezone"),
		InputSchema: anthropic.ToolInputSchemaParam{
			Properties: map[string]any{
				"timezone": map[string]any{
					"type":        "string",
					"description": "The timezone, e.g. America/New_York",
				},
			},
			Required: []string{"timezone"},
		},
	}},
}

fmt.Println("Requesting parallel tool calls...")
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
	Model:     anthropic.ModelClaudeOpus5,
	MaxTokens: 1024,
	Messages: []anthropic.MessageParam{
		anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in SF and NYC, and what time is it there?")),
	},
	Tools: tools,
})
if err != nil {
	log.Fatal(err)
}

// Find tool use blocks using type switch
type toolUseInfo struct {
	ID    string
	Name  string
	Input json.RawMessage
}
var toolUses []toolUseInfo
for _, block := range response.Content {
	switch variant := block.AsAny().(type) {
	case anthropic.ToolUseBlock:
		toolUses = append(toolUses, toolUseInfo{
			ID:    variant.ID,
			Name:  variant.Name,
			Input: variant.Input,
		})
	}
}

fmt.Printf("\n✓ Claude made %d tool calls\n", len(toolUses))

if len(toolUses) > 1 {
	fmt.Println("✓ Parallel tool calls detected!")
	for _, tool := range toolUses {
		fmt.Printf("  - %s: %s\n", tool.Name, string(tool.Input))
	}
} else {
	fmt.Println("✗ No parallel tool calls detected")
}

// Build tool results
var toolResults []anthropic.ContentBlockParamUnion
for _, toolUse := range toolUses {
	var result string
	inputStr := string(toolUse.Input)

	if toolUse.Name == "get_weather" {
		if strings.Contains(inputStr, "San Francisco") {
			result = "San Francisco: 68°F, partly cloudy"
		} else {
			result = "New York: 45°F, clear skies"
		}
	} else {
		if strings.Contains(inputStr, "Los_Angeles") {
			result = "2:30 PM PST"
		} else {
			result = "5:30 PM EST"
		}
	}

	toolResults = append(toolResults, anthropic.NewToolResultBlock(toolUse.ID, result, false))
}

fmt.Println("\nGetting final response...")
finalResponse, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
	Model:     anthropic.ModelClaudeOpus5,
	MaxTokens: 1024,
	Messages: []anthropic.MessageParam{
		anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in SF and NYC, and what time is it there?")),
		response.ToParam(),
		anthropic.NewUserMessage(toolResults...),
	},
	Tools: tools,
})
if err != nil {
	log.Fatal(err)
}

var finalText string
for _, block := range finalResponse.Content {
	if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
		finalText = textBlock.Text
		break
	}
}

fmt.Printf("\nClaude's response:\n%s\n", finalText)

fmt.Println("\n--- Verification ---")
fmt.Printf("✓ Tool results sent in single user message: %d results\n", len(toolResults))
fmt.Println("✓ No text before tool results in content array")
fmt.Println("✓ Conversation formatted correctly for future parallel tool use")
AnthropicClient client = AnthropicOkHttpClient.fromEnv();

Tool weatherTool = Tool.builder()
    .name("get_weather")
    .description("Get the current weather in a given location")
    .inputSchema(InputSchema.builder()
        .properties(JsonValue.from(Map.of(
            "location", Map.of(
                "type", "string",
                "description", "The city and state, e.g. San Francisco, CA"
            )
        )))
        .putAdditionalProperty("required", JsonValue.from(List.of("location")))
        .build())
    .build();

Tool timeTool = Tool.builder()
    .name("get_time")
    .description("Get the current time in a given timezone")
    .inputSchema(InputSchema.builder()
        .properties(JsonValue.from(Map.of(
            "timezone", Map.of(
                "type", "string",
                "description", "The timezone, e.g. America/New_York"
            )
        )))
        .putAdditionalProperty("required", JsonValue.from(List.of("timezone")))
        .build())
    .build();

MessageCreateParams params = MessageCreateParams.builder()
    .model(Model.CLAUDE_OPUS_5)
    .maxTokens(1024L)
    .addTool(weatherTool)
    .addTool(timeTool)
    .addUserMessage("What's the weather in SF and NYC, and what time is it there?")
    .build();

IO.println("Requesting parallel tool calls...");
Message response = client.messages().create(params);

List<ToolUseBlock> toolUses = new ArrayList<>();
for (ContentBlock block : response.content()) {
    if (block.toolUse().isPresent()) {
        toolUses.add(block.toolUse().get());
    }
}

IO.println("\n✓ Claude made " + toolUses.size() + " tool calls");

if (toolUses.size() > 1) {
    IO.println("✓ Parallel tool calls detected!");
    for (ToolUseBlock tool : toolUses) {
        IO.println("  - " + tool.name() + ": " + tool._input());
    }
} else {
    IO.println("✗ No parallel tool calls detected");
}

List<ContentBlockParam> toolResults = new ArrayList<>();
for (ToolUseBlock toolUse : toolUses) {
    String result;
    if (toolUse.name().equals("get_weather")) {
        String location = toolUse._input().toString();
        result = location.contains("San Francisco")
            ? "San Francisco: 68°F, partly cloudy"
            : "New York: 45°F, clear skies";
    } else {
        String timezone = toolUse._input().toString();
        result = timezone.contains("Los_Angeles")
            ? "2:30 PM PST"
            : "5:30 PM EST";
    }
    toolResults.add(ContentBlockParam.ofToolResult(
        ToolResultBlockParam.builder()
            .toolUseId(toolUse.id())
            .content(result)
            .build()
    ));
}

IO.println("\nGetting final response...");
MessageCreateParams finalParams = MessageCreateParams.builder()
    .model(Model.CLAUDE_OPUS_5)
    .maxTokens(1024L)
    .addTool(weatherTool)
    .addTool(timeTool)
    .addUserMessage("What's the weather in SF and NYC, and what time is it there?")
    .addMessage(response)
    .addUserMessageOfBlockParams(toolResults)
    .build();

Message finalResponse = client.messages().create(finalParams);
finalResponse.content().stream()
    .flatMap(block -> block.text().stream())
    .forEach(textBlock -> IO.println("\nClaude's response:\n" + textBlock.text()));

IO.println("\n--- Verification ---");
IO.println("✓ Tool results sent in single user message: " + toolResults.size() + " results");
IO.println("✓ No text before tool results in content array");
IO.println("✓ Conversation formatted correctly for future parallel tool use");
$client = new Client();

$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']
        ]
    ]
];

echo "Requesting parallel tool calls...\n";
$response = $client->messages->create(
    maxTokens: 1024,
    messages: [
        ['role' => 'user', 'content' => "What's the weather in SF and NYC, and what time is it there?"]
    ],
    model: 'claude-opus-5',
    tools: $tools,
);

$toolUses = array_filter($response->content, fn($block) => $block->type === 'tool_use');
echo "\n✓ Claude made " . count($toolUses) . " tool calls\n";

if (count($toolUses) > 1) {
    echo "✓ Parallel tool calls detected!\n";
    foreach ($toolUses as $tool) {
        echo "  - {$tool->name}: " . json_encode($tool->input) . "\n";
    }
} else {
    echo "✗ No parallel tool calls detected\n";
}

$toolResults = [];
foreach ($toolUses as $toolUse) {
    if ($toolUse->name === 'get_weather') {
        $result = str_contains(json_encode($toolUse->input), 'San Francisco')
            ? 'San Francisco: 68°F, partly cloudy'
            : 'New York: 45°F, clear skies';
    } else {
        $result = str_contains(json_encode($toolUse->input), 'Los_Angeles')
            ? '2:30 PM PST'
            : '5:30 PM EST';
    }

    $toolResults[] = [
        'type' => 'tool_result',
        'tool_use_id' => $toolUse->id,
        'content' => $result
    ];
}

echo "\nGetting final response...\n";
$finalResponse = $client->messages->create(
    maxTokens: 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]
    ],
    model: 'claude-opus-5',
    tools: $tools,
);

$textBlock = array_find($finalResponse->content, static fn ($block): bool => $block->type === 'text');
echo "\nClaude's response:\n{$textBlock->text}\n";

echo "\n--- Verification ---\n";
echo "✓ Tool results sent in single user message: " . count($toolResults) . " results\n";
echo "✓ No text before tool results in content array\n";
echo "✓ Conversation formatted correctly for future parallel tool use\n";
client = Anthropic::Client.new

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"]
    }
  }
]

puts "Requesting parallel tool calls..."
response = client.messages.create(
  model: "claude-opus-5",
  max_tokens: 1024,
  messages: [
    { role: "user", content: "What's the weather in SF and NYC, and what time is it there?" }
  ],
  tools: tools
)

tool_uses = response.content.select { |block| block.type == :tool_use }
puts "\n✓ Claude made #{tool_uses.length} tool calls"

if tool_uses.length > 1
  puts "✓ Parallel tool calls detected!"
  tool_uses.each do |tool|
    puts "  - #{tool.name}: #{tool.input}"
  end
else
  puts "✗ No parallel tool calls detected"
end

tool_results = tool_uses.map do |tool_use|
  result = if tool_use.name == "get_weather"
    location = tool_use.input[:location].to_s
    location.include?("San Francisco") ? "San Francisco: 68°F, partly cloudy" : "New York: 45°F, clear skies"
  else
    timezone = tool_use.input[:timezone].to_s
    timezone.include?("Los_Angeles") ? "2:30 PM PST" : "5:30 PM EST"
  end

  {
    type: "tool_result",
    tool_use_id: tool_use.id,
    content: result
  }
end

puts "\nGetting final response..."
final_response = client.messages.create(
  model: "claude-opus-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: tool_results }
  ],
  tools: tools
)

final_text = final_response.content.find { |block| block.type == :text }
puts "\nClaude's response:\n#{final_text.text}"

puts "\n--- Verification ---"
puts "✓ Tool results sent in single user message: #{tool_results.length} results"
puts "✓ No text before tool results in content array"
puts "✓ Conversation formatted correctly for future parallel tool use"

The summary lines at the end restate the two formatting rules that keep parallelism working: every tool result returns in a single user message, and no text content appears before the tool results in that message.

Maximizing parallel tool use

Claude 4 and later models make parallel tool calls by default when a request benefits from multiple tools. For all models, you can increase the likelihood of parallel tool calls with targeted prompting:

```text wrap
For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
```

For even stronger parallel tool use (recommended if the default isn't sufficient), use:

```text wrap
<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>
```
```text wrap
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 Fable 5.1 may issue fewer parallel tool calls than earlier models, most noticeably in long agent loops where the next reads are only implied (custom coding agents, bash and text editor harnesses, computer use). Standard function calling is unaffected. For the batching instruction to add and where to put it, see Batch independent tool calls in agent loops.

Disable parallel tool use

Parallel tool use is on by default. To turn it off, set disable_parallel_tool_use: true inside the tool_choice object. It is not a top-level request parameter. The effect depends on the tool_choice type.

At most one tool call

When tool_choice type is auto (the default), setting disable_parallel_tool_use: true means Claude calls at most one tool per response. Claude can still answer in plain text without calling any tool. The highlighted lines are the only change from a standard tool use request:

ant messages create <<'YAML'
model: claude-opus-5
max_tokens: 1024
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]
tool_choice:
  type: auto
  disable_parallel_tool_use: true
messages:
  - role: user
    content: What is the weather in San Francisco and New York?
YAML
client = Anthropic()

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    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"],
            },
        }
    ],
    tool_choice={"type": "auto", "disable_parallel_tool_use": True},
    messages=[
        {
            "role": "user",
            "content": "What is the weather in San Francisco and New York?",
        }
    ],
)
print(response.content)
const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-5",
  max_tokens: 1024,
  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"]
      }
    }
  ],
  tool_choice: { type: "auto", disable_parallel_tool_use: true },
  messages: [{ role: "user", content: "What is the weather in San Francisco and New York?" }]
});
console.log(response.content);
AnthropicClient client = new();

var parameters = new MessageCreateParams
{
    Model = Model.ClaudeOpus5,
    MaxTokens = 1024,
    Tools = [
        new ToolUnion(new Tool()
        {
            Name = "get_weather",
            Description = "Get the current weather in a given location",
            InputSchema = new InputSchema()
            {
                Properties = new Dictionary<string, JsonElement>
                {
                    ["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The city and state, e.g. San Francisco, CA" }),
                },
                Required = ["location"],
            },
        }),
    ],
    ToolChoice = new ToolChoiceAuto { DisableParallelToolUse = true },
    Messages = [new() { Role = Role.User, Content = "What is the weather in San Francisco and New York?" }]
};

var response = await client.Messages.Create(parameters);
Console.WriteLine(response);
client := anthropic.NewClient()

response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
	Model:     anthropic.ModelClaudeOpus5,
	MaxTokens: 1024,
	Tools: []anthropic.ToolUnionParam{
		{OfTool: &anthropic.ToolParam{
			Name:        "get_weather",
			Description: anthropic.String("Get the current weather in a given location"),
			InputSchema: anthropic.ToolInputSchemaParam{
				Properties: map[string]any{
					"location": map[string]any{
						"type":        "string",
						"description": "The city and state, e.g. San Francisco, CA",
					},
				},
				Required: []string{"location"},
			},
		}},
	},
	ToolChoice: anthropic.ToolChoiceUnionParam{
		OfAuto: &anthropic.ToolChoiceAutoParam{
			DisableParallelToolUse: anthropic.Bool(true),
		},
	},
	Messages: []anthropic.MessageParam{
		anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather in San Francisco and New York?")),
	},
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Content)
AnthropicClient client = AnthropicOkHttpClient.fromEnv();

InputSchema schema = InputSchema.builder()
    .properties(
        JsonValue.from(
            Map.of(
                "location", Map.of(
                    "type", "string",
                    "description", "The city and state, e.g. San Francisco, CA"
                )
            )
        )
    )
    .putAdditionalProperty("required", JsonValue.from(List.of("location")))
    .build();

MessageCreateParams params = MessageCreateParams.builder()
    .model(Model.CLAUDE_OPUS_5)
    .maxTokens(1024L)
    .addTool(
        Tool.builder()
            .name("get_weather")
            .description("Get the current weather in a given location")
            .inputSchema(schema)
            .build()
    )
    .toolChoice(ToolChoiceAuto.builder().disableParallelToolUse(true).build())
    .addUserMessage("What is the weather in San Francisco and New York?")
    .build();

Message response = client.messages().create(params);
IO.println(response.content());
$client = new Client();

$response = $client->messages->create(
    maxTokens: 1024,
    messages: [
        ['role' => 'user', 'content' => 'What is the weather in San Francisco and New York?']
    ],
    model: 'claude-opus-5',
    toolChoice: ['type' => 'auto', 'disableParallelToolUse' => true],
    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']
            ]
        ]
    ],
);

echo $response;
client = Anthropic::Client.new

response = client.messages.create(
  model: "claude-opus-5",
  max_tokens: 1024,
  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"]
      }
    }
  ],
  tool_choice: { type: "auto", disable_parallel_tool_use: true },
  messages: [
    { role: "user", content: "What is the weather in San Francisco and New York?" }
  ]
)
puts response.content

Exactly one tool call

When tool_choice type is any or tool, setting disable_parallel_tool_use: true means Claude calls exactly one tool. Claude Fable 5.1 and Claude Mythos 5.1 don't support these tool_choice types (see Forcing tool use). The following example uses any. The same field works with tool:

ant messages create <<'YAML'
model: claude-opus-5
max_tokens: 1024
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]
tool_choice:
  type: any
  disable_parallel_tool_use: true
messages:
  - role: user
    content: What is the weather in San Francisco and New York?
YAML
client = Anthropic()

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    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"],
            },
        }
    ],
    tool_choice={"type": "any", "disable_parallel_tool_use": True},
    messages=[
        {
            "role": "user",
            "content": "What is the weather in San Francisco and New York?",
        }
    ],
)
print(response.content)
const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-5",
  max_tokens: 1024,
  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"]
      }
    }
  ],
  tool_choice: { type: "any", disable_parallel_tool_use: true },
  messages: [{ role: "user", content: "What is the weather in San Francisco and New York?" }]
});
console.log(response.content);
AnthropicClient client = new();

var parameters = new MessageCreateParams
{
    Model = Model.ClaudeOpus5,
    MaxTokens = 1024,
    Tools = [
        new ToolUnion(new Tool()
        {
            Name = "get_weather",
            Description = "Get the current weather in a given location",
            InputSchema = new InputSchema()
            {
                Properties = new Dictionary<string, JsonElement>
                {
                    ["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The city and state, e.g. San Francisco, CA" }),
                },
                Required = ["location"],
            },
        }),
    ],
    ToolChoice = new ToolChoiceAny { DisableParallelToolUse = true },
    Messages = [new() { Role = Role.User, Content = "What is the weather in San Francisco and New York?" }]
};

var response = await client.Messages.Create(parameters);
Console.WriteLine(response);
client := anthropic.NewClient()

response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
	Model:     anthropic.ModelClaudeOpus5,
	MaxTokens: 1024,
	Tools: []anthropic.ToolUnionParam{
		{OfTool: &anthropic.ToolParam{
			Name:        "get_weather",
			Description: anthropic.String("Get the current weather in a given location"),
			InputSchema: anthropic.ToolInputSchemaParam{
				Properties: map[string]any{
					"location": map[string]any{
						"type":        "string",
						"description": "The city and state, e.g. San Francisco, CA",
					},
				},
				Required: []string{"location"},
			},
		}},
	},
	ToolChoice: anthropic.ToolChoiceUnionParam{
		OfAny: &anthropic.ToolChoiceAnyParam{
			DisableParallelToolUse: anthropic.Bool(true),
		},
	},
	Messages: []anthropic.MessageParam{
		anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather in San Francisco and New York?")),
	},
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Content)
AnthropicClient client = AnthropicOkHttpClient.fromEnv();

InputSchema schema = InputSchema.builder()
    .properties(
        JsonValue.from(
            Map.of(
                "location", Map.of(
                    "type", "string",
                    "description", "The city and state, e.g. San Francisco, CA"
                )
            )
        )
    )
    .putAdditionalProperty("required", JsonValue.from(List.of("location")))
    .build();

MessageCreateParams params = MessageCreateParams.builder()
    .model(Model.CLAUDE_OPUS_5)
    .maxTokens(1024L)
    .addTool(
        Tool.builder()
            .name("get_weather")
            .description("Get the current weather in a given location")
            .inputSchema(schema)
            .build()
    )
    .toolChoice(ToolChoiceAny.builder().disableParallelToolUse(true).build())
    .addUserMessage("What is the weather in San Francisco and New York?")
    .build();

Message response = client.messages().create(params);
IO.println(response.content());
$client = new Client();

$response = $client->messages->create(
    maxTokens: 1024,
    messages: [
        ['role' => 'user', 'content' => 'What is the weather in San Francisco and New York?']
    ],
    model: 'claude-opus-5',
    toolChoice: ['type' => 'any', 'disableParallelToolUse' => true],
    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']
            ]
        ]
    ],
);

echo $response;
client = Anthropic::Client.new

response = client.messages.create(
  model: "claude-opus-5",
  max_tokens: 1024,
  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"]
      }
    }
  ],
  tool_choice: { type: "any", disable_parallel_tool_use: true },
  messages: [
    { role: "user", content: "What is the weather in San Francisco and New York?" }
  ]
)
puts response.content

Troubleshooting

If Claude isn't making parallel tool calls when expected, check these common issues:

1. Incorrect tool result formatting

The most common issue is formatting tool results incorrectly in the conversation history. This "teaches" Claude to avoid parallel calls.

Specifically for parallel tool use:

  • Wrong: a separate user message for each tool result
  • Correct: all tool results together in a single user message
// Wrong: separate user messages reduce 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
]

// Correct: one user message with all results maintains parallel tool use
[
  {"role": "assistant", "content": [tool_use_1, tool_use_2]},
  {"role": "user", "content": [tool_result_1, tool_result_2]}  // Single message
]

See Handle tool calls for other formatting rules.

2. Weak prompting

Default prompting might not be sufficient. Use the stronger system prompt from Maximizing parallel tool use.

3. Measuring parallel tool usage

To verify parallel tool calls are working:

# Measuring parallel tool use is client-side analysis of responses you've already
# collected, so it doesn't translate to a one-off shell command. See the SDK tabs.
messages = []  # Message objects returned by client.messages.create across your run

tool_call_messages = [
    msg for msg in messages if any(block.type == "tool_use" for block in msg.content)
]
total_tool_calls = sum(
    len([block for block in msg.content if block.type == "tool_use"])
    for msg in tool_call_messages
)
avg_tools_per_message = (
    total_tool_calls / len(tool_call_messages) if tool_call_messages else 0.0
)
print(f"Average tools per message: {avg_tools_per_message}")
# Should be > 1.0 if parallel calls are working
const messages: Anthropic.Message[] = []; // Message objects returned by client.messages.create across your run

const toolCallMessages = messages.filter((message) =>
  message.content.some((block) => block.type === "tool_use")
);
const totalToolCalls = toolCallMessages.reduce(
  (sum, message) => sum + message.content.filter((block) => block.type === "tool_use").length,
  0
);
const avgToolsPerMessage =
  toolCallMessages.length > 0 ? totalToolCalls / toolCallMessages.length : 0;
console.log(`Average tools per message: ${avgToolsPerMessage}`);
// Should be > 1.0 if parallel calls are working
List<Message> messages = []; // Message objects returned by client.Messages.Create across your run

var toolCallMessages = messages
    .Where(message => message.Content.Any(block => block.TryPickToolUse(out _)))
    .ToList();
var totalToolCalls = toolCallMessages
    .Sum(message => message.Content.Count(block => block.TryPickToolUse(out _)));
var avgToolsPerMessage = toolCallMessages.Count > 0 ? (double)totalToolCalls / toolCallMessages.Count : 0.0;
Console.WriteLine($"Average tools per message: {avgToolsPerMessage}");
// Should be > 1.0 if parallel calls are working
var messages []anthropic.Message // Message values returned by client.Messages.New across your run

toolCallMessageCount := 0
totalToolCalls := 0
for _, message := range messages {
	callsInMessage := 0
	for _, block := range message.Content {
		if block.Type == "tool_use" {
			callsInMessage++
		}
	}
	if callsInMessage > 0 {
		toolCallMessageCount++
		totalToolCalls += callsInMessage
	}
}

avgToolsPerMessage := 0.0
if toolCallMessageCount > 0 {
	avgToolsPerMessage = float64(totalToolCalls) / float64(toolCallMessageCount)
}
fmt.Println("Average tools per message:", avgToolsPerMessage)
// Should be > 1.0 if parallel calls are working
List<Message> messages = List.of(); // Message objects returned by client.messages().create() across your run

List<Message> toolCallMessages = messages.stream()
    .filter(message -> message.content().stream().anyMatch(ContentBlock::isToolUse))
    .toList();
long totalToolCalls = toolCallMessages.stream()
    .mapToLong(message -> message.content().stream().filter(ContentBlock::isToolUse).count())
    .sum();
double avgToolsPerMessage = toolCallMessages.isEmpty() ? 0.0 : (double) totalToolCalls / toolCallMessages.size();
IO.println("Average tools per message: " + avgToolsPerMessage);
// Should be > 1.0 if parallel calls are working
// $messages: Message objects returned by $client->messages->create() across your run
$messages = [];

$toolCallMessages = array_values(array_filter(
    $messages,
    fn ($message) => count(array_filter($message->content, fn ($block) => $block->type === 'tool_use')) > 0
));
$totalToolCalls = array_sum(array_map(
    fn ($message) => count(array_filter($message->content, fn ($block) => $block->type === 'tool_use')),
    $toolCallMessages
));
$avgToolsPerMessage = count($toolCallMessages) > 0 ? $totalToolCalls / count($toolCallMessages) : 0.0;
echo "Average tools per message: {$avgToolsPerMessage}\n";
// Should be > 1.0 if parallel calls are working
messages = [] # Message objects returned by client.messages.create across your run

tool_call_messages = messages.select { |message| message.content.any? { |block| block.type == :tool_use } }
total_tool_calls = tool_call_messages.sum { |message| message.content.count { |block| block.type == :tool_use } }
avg_tools_per_message = tool_call_messages.empty? ? 0.0 : total_tool_calls.to_f / tool_call_messages.size
puts "Average tools per message: #{avg_tools_per_message}"
# Should be > 1.0 if parallel calls are working

4. Calls in a batch appear to depend on each other

Execution order is your choice. If your tools have ordering dependencies, running the batch sequentially and stopping on the first failure is a valid strategy (and the required one for the computer use and browser use tools): return is_error: true for any call you didn't run. If you run in parallel and a call fails because its prerequisite hadn't completed, return is_error: true with the natural error message. Claude will reissue the call on the next turn. To reduce dependent calls appearing together, add this to your system prompt: "Only batch tool calls that are independent of each other."

Next steps