Claude Advanced Tool Use: Patterns for Production Agents
Advanced Claude tool use means going beyond a single function call and building workflows that chain multiple tools, run calls in parallel, force specific tool selection, recover from errors gracefully, and stream partial results while tools are still executing. If you already know how to define a tool schema and handle one request-response cycle, this article covers the patterns that make tool use reliable at production scale: multi-tool orchestration, tool_choice control, parallel execution, and defensive error handling.
Most tutorials stop at "define a tool, Claude calls it, you return a result." That works for demos. Real agents need to handle Claude requesting three tools in one turn, a tool failing mid-chain, or a user query that requires five sequential steps where each step depends on the previous one's output. Below are the techniques that separate a fragile prototype from something you can put in front of paying users.
Parallel Tool Calls
Claude can request multiple tools in a single response when the tasks are independent. Instead of looping through tool calls one at a time, execute them concurrently and return all results together in the next turn.
const toolCalls = response.content.filter(block => block.type === "tool_use");
const results = await Promise.all(
toolCalls.map(async (call) => {
const output = await executeTool(call.name, call.input);
return {
type: "tool_result",
tool_use_id: call.id,
content: JSON.stringify(output),
};
})
);
This cuts latency significantly when a user asks something like "check the weather in three cities and summarize" — Claude issues three get_weather calls at once, and you don't want to wait on them sequentially.
Forcing or Restricting Tool Choice
By default, Claude decides whether to call a tool at all. In advanced workflows you often need tighter control:
tool_choice: {"type": "auto"}— Claude decides (default).tool_choice: {"type": "any"}— Claude must call one of the available tools.tool_choice: {"type": "tool", "name": "get_order_status"}— force a specific tool.tool_choice: {"type": "none"}— disable tool calls for this turn, useful when you want a plain text summary after tool results are already in context.
Forcing a specific tool is useful for structured extraction: if you want Claude to always return data in a fixed shape (say, parsing an invoice into JSON), define one tool and force it rather than hoping the model chooses to call it.
Chaining Tools Across Multiple Turns
Complex tasks often require sequential dependency: search for a customer, then fetch their orders, then check inventory for those items. Each step's output feeds the next tool call. The pattern is a loop, not a single request:
let messages = [{ role: "user", content: userQuery }];
while (true) {
const response = await callClaude(messages, tools);
messages.push({ role: "assistant", content: response.content });
const toolUses = response.content.filter(b => b.type === "tool_use");
if (toolUses.length === 0) break; // Claude produced a final answer
const toolResults = await Promise.all(
toolUses.map(async (call) => ({
type: "tool_result",
tool_use_id: call.id,
content: JSON.stringify(await executeTool(call.name, call.input)),
}))
);
messages.push({ role: "user", content: toolResults });
}
Cap this loop with a maximum iteration count. Without a limit, a malformed tool or ambiguous instructions can cause Claude to keep calling tools indefinitely, which burns tokens and money for no benefit.
Handling Tool Errors Without Breaking the Conversation
A tool call will eventually fail — an API times out, a database returns nothing, invalid input reaches your function. Don't drop the conversation or throw an unhandled exception. Return the error as a tool result with is_error: true so Claude can reason about it and try an alternative:
{
"type": "tool_result",
"tool_use_id": "toolu_01abc",
"content": "Order ID not found in database",
"is_error": true
}
Claude will often self-correct — retrying with different parameters, asking the user for clarification, or falling back to a different tool — instead of the whole exchange failing silently.
Streaming While Tools Are in Play
Advanced agents combine tool use with streaming so users see progress instead of a blank screen during multi-step workflows. Stream text as it's generated, but buffer tool_use blocks until they're complete (input JSON arrives incrementally and isn't safe to parse mid-stream). Watch for content_block_stop events to know when a tool call is fully formed before executing it.
Designing Tool Schemas for Complex Inputs
As workflows get more advanced, tool schemas grow beyond flat key-value inputs. Use nested objects and enums aggressively — the more constrained the schema, the fewer malformed calls you'll see:
{
"name": "create_ticket",
"input_schema": {
"type": "object",
"properties": {
"priority": { "type": "string", "enum": ["low", "medium", "high", "urgent"] },
"assignee": { "type": "object", "properties": {
"team": { "type": "string" },
"user_id": { "type": "string" }
}}
},
"required": ["priority"]
}
}
Tight schemas reduce the number of error-and-retry cycles your loop has to handle.
Where SubToAPI Fits
If you're building these patterns on top of a Claude subscription rather than a metered API key, SubToAPI turns that access into a standard HTTPS endpoint with application API keys (sub_live_...), so your parallel tool calls, streaming, and multi-turn loops work exactly like they would against any Messages-compatible API. Check the tool use docs and streaming guide for request formats, or start with the quickstart if you're wiring this up for the first time. Plans start at €9/month with a free trial at signup.
Questions
Can Claude call more than one tool in a single response? Yes. Claude can return multiple tool_use blocks in one turn when tasks are independent, and you should execute them concurrently rather than one at a time to reduce latency.
How do I force Claude to always use a specific tool? Set tool_choice to {"type": "tool", "name": "your_tool_name"}. This is useful for structured data extraction where you want a guaranteed output shape rather than optional tool use.
What happens if a tool call fails mid-workflow? Return the failure as a tool_result with is_error: true instead of breaking the conversation. Claude can read the error and retry, adjust parameters, or ask the user for more information.