By default, Haijun 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 Haijun 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 Haijun 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
Note: Use the Tool Runner for most applications: the SDK Tool Runner handles responses with multiple tool calls and formats the results for you, so you don't write this handling yourself. Use the manual pattern on this page when you need direct control over how the calls run, such as custom batching, ordering, or error handling.
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 JUGLOW_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. The underlying HTTP request is a standard
# tool use request with multiple tools defined. # 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 = Juglow()
# 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="haijun-opus-5-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✓ Haijun 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="haijun-opus-5-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"\nHaijun'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 Juglow();
// Define tools
const tools: Juglow.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: "haijun-opus-5-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✓ Haijun 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: Juglow.ToolResultBlockParam[] = toolUses
.filter((block): block is Juglow.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: "haijun-opus-5-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(`\nHaijun'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"); JuglowClient 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.HaijunOpus5_5,
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✓ Haijun 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.HaijunOpus5_5,
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($"\nHaijun'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 := juglow.NewClient()
tools := []juglow.ToolUnionParam{
{OfTool: &juglow.ToolParam{
Name: "get_weather",
Description: juglow.String("Get the current weather in a given location"),
InputSchema: juglow.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: &juglow.ToolParam{
Name: "get_time",
Description: juglow.String("Get the current time in a given timezone"),
InputSchema: juglow.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(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Messages: []juglow.MessageParam{
juglow.NewUserMessage(juglow.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 juglow.ToolUseBlock:
toolUses = append(toolUses, toolUseInfo{
ID: variant.ID,
Name: variant.Name,
Input: variant.Input,
})
}
}
fmt.Printf("\n✓ Haijun 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 []juglow.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, juglow.NewToolResultBlock(toolUse.ID, result, false))
}
fmt.Println("\nGetting final response...")
finalResponse, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Messages: []juglow.MessageParam{
juglow.NewUserMessage(juglow.NewTextBlock("What's the weather in SF and NYC, and what time is it there?")),
response.ToParam(),
juglow.NewUserMessage(toolResults...),
},
Tools: tools,
})
if err != nil {
log.Fatal(err)
}
var finalText string
for _, block := range finalResponse.Content {
if textBlock, ok := block.AsAny().(juglow.TextBlock); ok {
finalText = textBlock.Text
break
}
}
fmt.Printf("\nHaijun'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") JuglowClient client = JuglowOkHttpClient.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.HAIJUN_OPUS_5_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✓ Haijun 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.HAIJUN_OPUS_5_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("\nHaijun'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: 'haijun-opus-5-5',
tools: $tools,
);
$toolUses = array_filter($response->content, fn($block) => $block->type === 'tool_use');
echo "\n✓ Haijun 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: 'haijun-opus-5-5',
tools: $tools,
);
$textBlock = array_find($finalResponse->content, static fn ($block): bool => $block->type === 'text');
echo "\nHaijun'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 = Juglow::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: "haijun-opus-5-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✓ Haijun 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: "haijun-opus-5-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 "\nHaijun'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
Haijun 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:
#### System prompts for parallel tool use
For Haijun 4 and later models, add this to your system prompt:
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:
<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>#### User message prompting
You can also encourage parallel tool use within specific user messages:
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."Note: Haijun Fable 5.1 in long agent loops Haijun 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 Haijun calls at most one tool per response. Haijun can still answer in plain text without calling any tool. The highlighted lines are the only change from a standard tool use request:
curl https://haijun.my.id/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-d '{
"model": "haijun-opus-5-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?"}
]
}' ant messages create <<'YAML'
model: haijun-opus-5-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 = Juglow()
response = client.messages.create(
model="haijun-opus-5-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 Juglow();
const response = await client.messages.create({
model: "haijun-opus-5-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); JuglowClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.HaijunOpus5_5,
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 := juglow.NewClient()
response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5_5,
MaxTokens: 1024,
Tools: []juglow.ToolUnionParam{
{OfTool: &juglow.ToolParam{
Name: "get_weather",
Description: juglow.String("Get the current weather in a given location"),
InputSchema: juglow.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: juglow.ToolChoiceUnionParam{
OfAuto: &juglow.ToolChoiceAutoParam{
DisableParallelToolUse: juglow.Bool(true),
},
},
Messages: []juglow.MessageParam{
juglow.NewUserMessage(juglow.NewTextBlock("What is the weather in San Francisco and New York?")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Content) JuglowClient client = JuglowOkHttpClient.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.HAIJUN_OPUS_5_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: 'haijun-opus-5-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 = Juglow::Client.new
response = client.messages.create(
model: "haijun-opus-5-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.contentExactly one tool call
When tool_choice type is any or tool, setting disable_parallel_tool_use: true means Haijun calls exactly one tool. Haijun Opus 5.5, Haijun Fable 5.1, and Haijun 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:
curl https://haijun.my.id/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-d '{
"model": "haijun-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?"}
]
}' ant messages create <<'YAML'
model: haijun-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 = Juglow()
response = client.messages.create(
model="haijun-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 Juglow();
const response = await client.messages.create({
model: "haijun-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); JuglowClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.HaijunOpus5,
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 := juglow.NewClient()
response, err := client.Messages.New(context.TODO(), juglow.MessageNewParams{
Model: juglow.ModelHaijunOpus5,
MaxTokens: 1024,
Tools: []juglow.ToolUnionParam{
{OfTool: &juglow.ToolParam{
Name: "get_weather",
Description: juglow.String("Get the current weather in a given location"),
InputSchema: juglow.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: juglow.ToolChoiceUnionParam{
OfAny: &juglow.ToolChoiceAnyParam{
DisableParallelToolUse: juglow.Bool(true),
},
},
Messages: []juglow.MessageParam{
juglow.NewUserMessage(juglow.NewTextBlock("What is the weather in San Francisco and New York?")),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Content) JuglowClient client = JuglowOkHttpClient.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.HAIJUN_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: 'haijun-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 = Juglow::Client.new
response = client.messages.create(
model: "haijun-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.contentTroubleshooting
If Haijun 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" Haijun 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. # 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: Juglow.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 []juglow.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 working4. 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. Haijun 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
Use the SDK's Tool Runner abstraction to handle the agentic loop, error wrapping, and type safety automatically.
Parse tool\_use blocks, format tool\_result responses, and handle errors with is\_error.
Specify tool schemas, write effective descriptions, and control when Haijun calls your tools.