10.2 Tool Execution, Multi-Tool Calls & Error Handling
Key Takeaways
- When Claude invokes tools, the Messages API returns stop_reason: 'tool_use' and the assistant response includes one or more tool_use content blocks containing a unique id, tool name, and pre-parsed input arguments.
- Multi-tool calling allows Claude to emit multiple parallel tool_use blocks in a single assistant turn; client applications should execute these calls concurrently (e.g., via asyncio.gather or Promise.all) to minimize end-to-end latency.
- Every tool_use block must be answered with a corresponding tool_result block in the subsequent user turn, strictly matching the tool_use_id and providing string or block-based content.
- Tool execution failures must be reported back to Claude using is_error: true with descriptive diagnostic feedback, enabling Claude to self-correct its parameters rather than throwing unhandled client-side exceptions that terminate the agent loop.
- Robust agent loops implement maximum iteration ceilings and cycle-detection guards to prevent infinite self-correction loops when a tool repeatedly returns errors.
Tool Execution, Multi-Tool Calls & Error Handling
Exam Blueprint Focus: Building production agents requires handling the complete execution lifecycle of tool calling. You must master response detection via
stop_reason: "tool_use", extract and safely parsetool_usecontent blocks, implement asynchronous multi-tool execution for parallel tool invocations, construct validtool_resultmessages adhering to the strict 1:1 pairing rule, and leverageis_error: trueto drive autonomous model self-correction.
Detecting and Parsing Tool Call Responses
When Claude determines that an external action or data lookup is required to satisfy a user prompt, it halts text generation and returns an assistant message containing structured tool invocation data. The client application must inspect two critical fields in the response:
stop_reason: When tools are invoked,stop_reasonis set to"tool_use"(in contrast to normal prose completions wherestop_reasonis"end_turn", or length truncation where it is"max_tokens").contentBlocks: The assistant messagecontentarray contains one or more content blocks. Claude may output atextblock containing its chain-of-thought explanation immediately followed by one or moretool_useblocks.
Anatomy of the tool_use Block
Each tool_use block possesses a strict anatomical structure:
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "I will look up the current weather conditions for Tokyo to answer your question."
},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835l192",
"name": "get_weather",
"input": {
"city": "Tokyo",
"units": "metric"
}
}
]
}
type(string): Always"tool_use".id(string): A unique, globally distinct identifier generated by Anthropic, prefixed withtoolu_. This ID is the primary key that links this specific request to its eventual execution result.name(string): Matches the tool name defined in the request'stoolsarray.input(object): A pre-parsed JSON object containing the parameters generated by Claude conforming to your declaredinput_schema.
Safe Parsing Best Practices
In official Anthropic SDKs (Python and TypeScript), the input payload is already deserialized into native dictionaries or objects. However, production applications should never blindly pass this dictionary directly into database queries or shell commands. Always validate parameters using runtime validation frameworks like Pydantic in Python or Zod in TypeScript:
from pydantic import BaseModel, Field, ValidationError
class WeatherInput(BaseModel):
city: str = Field(min_length=1, max_length=100)
units: str = Field(pattern="^(metric|imperial)$")
try:
# tool_block.input is a dict
validated_args = WeatherInput(**tool_block.input)
execute_weather_lookup(validated_args.city, validated_args.units)
except ValidationError as e:
# Handle schema validation mismatch
report_tool_error(tool_block.id, str(e))
Multi-Tool Calling: Parallel Tool Invocation
Modern Claude models (Claude Sonnet 5, Claude Opus 5) feature native multi-tool calling. When a user's request involves multiple independent data points, Claude does not issue sequential single-tool calls across multiple turns. Instead, Claude emits multiple tool_use content blocks within a single assistant message:
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01_tokyo_weather",
"name": "get_weather",
"input": {"city": "Tokyo"}
},
{
"type": "tool_use",
"id": "toolu_02_london_weather",
"name": "get_weather",
"input": {"city": "London"}
},
{
"type": "tool_use",
"id": "toolu_03_paris_weather",
"name": "get_weather",
"input": {"city": "Paris"}
}
]
}
Concurrency and Latency Optimization
Executing multi-tool calls serially introduces severe latency. If fetching weather for one city takes 400ms, running three calls sequentially incurs 1,200ms of client execution latency before Claude can even begin synthesizing the final answer. Client applications should dispatch parallel tool calls concurrently using asynchronous concurrency primitives such as asyncio.gather in Python or Promise.all in TypeScript:
import asyncio
async def dispatch_tool_call(tool_use_block):
tool_id = tool_use_block.id
name = tool_use_block.name
args = tool_use_block.input
try:
result_data = await registry.execute(name, args)
return {
"type": "tool_result",
"tool_use_id": tool_id,
"content": json.dumps(result_data)
}
except Exception as exc:
return {
"type": "tool_result",
"tool_use_id": tool_id,
"content": f"Error executing {name}: {str(exc)}",
"is_error": True
}
# Execute all tool calls emitted in the turn concurrently
results = await asyncio.gather(*[
dispatch_tool_call(block)
for block in response.content
if block.type == "tool_use"
])
Formatting tool_result Content Blocks
Once the client application executes the requested tools, it must format the execution outputs and submit them back to the Messages API in a new user turn. This message must follow strict structural rules:
role(string): Must be"user".content(array): An array oftool_resultcontent blocks.
Anatomy of a tool_result Block
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835l192",
"content": "{\"temperature_celsius\": 22.4, \"condition\": \"Partly Cloudy\", \"humidity\": 65}",
"is_error": false
}
]
}
type(string): Always"tool_result".tool_use_id(string): Must match the exactidfrom the correspondingtool_useblock.content(string or array): The output data returned by your function. Typically a JSON string or plaintext message. It can also be an array of nested blocks (e.g., returning base64 images via{"type": "image", ...}).is_error(boolean, optional): Set totrueif the tool encountered an operational error, business logic failure, or runtime exception.
The Strict 1:1 Invariant
The Anthropic Messages API enforces a strict 1:1 pairing invariant:
- Every
tool_useblock emitted by Claude MUST receive a correspondingtool_resultblock in the immediately following user message. - If Claude emits three
tool_useblocks, the subsequent user message must contain exactly threetool_resultblocks matching those three IDs. - If any
tool_use_idis missing, duplicated, or does not match an antecedenttool_useblock, the Messages API rejects the request with an HTTP 400invalid_request_error.
Preserving Message History Integrity
When appending tool results, you must maintain proper conversation turn alternation. The assistant message containing the tool_use blocks must be appended to the history first, followed immediately by the user message containing the tool_result blocks. Dropping or altering the assistant turn invalidates the conversation sequence:
[User Message: "What's the weather in Tokyo?"]
|
v
[Assistant Message: tool_use (id: 'toolu_123', name: 'get_weather')] <-- MUST BE PRESERVED
|
v
[User Message: tool_result (tool_use_id: 'toolu_123', content: '...')] <-- IMMEDIATE FOLLOW-UP
|
v
[Assistant Message: "The weather in Tokyo is currently..."]
Self-Correction Patterns via is_error: true
A critical distinction tested on the CCDV-F exam is the difference between client-side exceptions and model-assisted self-correction:
- Brittle Anti-Pattern (Unhandled Client Exception): The client catches an error from a database or API, raises an unhandled exception in the application runtime, and crashes the agent loop. The conversation aborts, frustrating the user.
- Resilient Architectural Pattern (Model Self-Correction): The client catches the error, marks the
tool_resultblock withis_error: true, and provides a detailed, actionable diagnostic message. When Claude receivesis_error: true, its internal reasoning kicks in: it evaluates what parameter was incorrect, adjusts its inputs, and re-invokes the tool with corrected parameters.
// Client catches a SQL syntax error and returns is_error: true
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_sql_query_4982",
"content": "Database Execution Error: Column 'user_full_name' does not exist in table 'customers'. Available columns are: ['customer_id', 'first_name', 'last_name', 'email', 'created_at'].",
"is_error": true
}
]
}
Upon receiving this error payload, Claude recognizes that user_full_name was invalid, inspects the list of available columns, and generates a corrected query: SELECT first_name, last_name FROM customers WHERE ....
Authoring Actionable Error Payloads
To maximize Claude's self-correction fidelity, follow these error reporting guidelines:
- Provide Specific Root Causes: Avoid generic messages like
"Error: 500 Internal Server Error". Instead, state:"Error 404: Resource not found. No invoice exists with ID 'INV-9021'." - Include Valid Ranges and Expected Formats: If a date parsing error occurs, return:
"Invalid date format: '10/24/2026'. Dates must follow ISO 8601 YYYY-MM-DD format (e.g., '2026-10-24')." - Do Not Hide Schema Constraints: If an integer parameter was out of bounds, return the valid range:
"Value 500 exceeds maximum allowable batch_size of 100."
Loop Ceilings & Cycle Prevention
Autonomous self-correction introduces the risk of infinite loops if a tool persistently fails. Production agent loops must implement strict safety guards:
- Maximum Retries: Cap consecutive tool error retries (typically
max_tool_retries = 3). - Parameter Hash Cycle Detection: Track previously attempted argument payloads; if Claude submits identical arguments that previously generated an error, abort the loop and inform the user.
When processing a response from the Anthropic Messages API, an application detects stop_reason: 'tool_use' and identifies two tool_use content blocks in response.content. What is the required structure and sequencing of the subsequent request to the Messages API?
A developer implements an autonomous database querying agent using Claude Sonnet 5. When Claude generates a SQL query that triggers a database syntax error ('PG::SyntaxError: syntax error at or near FROMM'), how should the application handle this exception to achieve automated self-correction?
An AI service invokes three independent API tools concurrently using asyncio.gather in response to a multi-tool call from Claude. Two tools execute successfully within 150ms, but the third tool encounters an HTTP 504 Gateway Timeout. What is the correct way to construct the follow-up message to Claude?