Chapter 18

Complete Tool Use Guide: Single Tool, Parallel Calls, Tool Chain Design Patterns and Token Overhead Calculation

Chapter 18: Tool Use Architecture Overview: Definition, Invocation, and Result Injection

18.1 The Core Design Philosophy of Tool Use

Tool Use is the most important capability extension mechanism in the Claude API. It allows the model to recognize when external capabilities are needed, construct structured function call requests, wait for results to return, and then integrate those results into a final answer.

Compared to simple prompt embedding, the essential difference of Tool Use lies in a structured contract: the input and output formats of tools are strictly defined. Claude doesn't guess results from text — it calls real external systems under explicit Schema constraints.

Tool Use solves three categories of core problems:

  1. Real-time information: Claude's training data has a cutoff date; search tools provide access to current information
  2. Calculation precision: Complex math and data processing are handled by code execution tools, avoiding errors from direct LLM computation
  3. System integration: Database queries and API call tools connect Claude to existing enterprise systems

18.2 The Complete Tool Use Interaction Flow

The key to understanding Tool Use is mastering its multi-turn interaction pattern. A complete tool call involves the following steps:

User message → Claude analysis → tool_use block (call request)
    ↓
Developer executes tool → obtains result
    ↓
tool_result block (result injection) → Claude generates final answer

At the API level, this process is represented by the following message sequence:

# Round 1: Send user request with tool definitions
messages = [
    {"role": "user", "content": "Query the account balance for user ID 12345"}
]

response1 = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    tools=[...],
    messages=messages
)
# response1.stop_reason == "tool_use"
# response1.content contains a tool_use block

# Round 2: Inject tool result
messages.append({"role": "assistant", "content": response1.content})
messages.append({
    "role": "user",
    "content": [{
        "type": "tool_result",
        "tool_use_id": response1.content[0].id,
        "content": "Account balance: $2,341.87"
    }]
})

response2 = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    tools=[...],
    messages=messages
)
# response2.stop_reason == "end_turn"
# Contains the final text answer

18.3 Complete JSON Schema for Tool Definitions

Basic Tool Structure

Each tool is defined by the following fields:

{
  "name": "get_account_balance",
  "description": "Query the account balance for a specified user. Returns the current balance and last updated timestamp.",
  "input_schema": {
    "type": "object",
    "properties": {
      "user_id": {
        "type": "string",
        "description": "Unique user identifier as a numeric string"
      },
      "currency": {
        "type": "string",
        "enum": ["USD", "EUR", "GBP"],
        "description": "Currency unit for the returned balance, defaults to USD",
        "default": "USD"
      }
    },
    "required": ["user_id"]
  }
}

Multi-Tool Definition Example

Production systems typically need multiple tools defined:

import anthropic
import json

tools = [
    {
        "name": "search_database",
        "description": "Search records in the enterprise database. Supports fuzzy matching and field filtering.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search keywords"
                },
                "table": {
                    "type": "string",
                    "enum": ["users", "orders", "products", "invoices"],
                    "description": "The database table to search"
                },
                "limit": {
                    "type": "integer",
                    "description": "Maximum number of results to return",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 10
                },
                "filters": {
                    "type": "object",
                    "description": "Field filter conditions: keys are field names, values are filter values",
                    "additionalProperties": {"type": "string"}
                }
            },
            "required": ["query", "table"]
        }
    },
    {
        "name": "send_email",
        "description": "Send an email. Used to notify users or deliver reports.",
        "input_schema": {
            "type": "object",
            "properties": {
                "to": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "List of recipient email addresses"
                },
                "subject": {
                    "type": "string",
                    "description": "Email subject line"
                },
                "body": {
                    "type": "string",
                    "description": "Email body, supports HTML"
                },
                "cc": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "CC address list (optional)"
                }
            },
            "required": ["to", "subject", "body"]
        }
    },
    {
        "name": "execute_calculation",
        "description": "Perform precise mathematical calculations. Always use this tool for complex numerical calculations instead of computing directly.",
        "input_schema": {
            "type": "object",
            "properties": {
                "expression": {
                    "type": "string",
                    "description": "Mathematical expression using Python syntax. E.g., '2 ** 32' or 'sum([1,2,3,4,5])'"
                }
            },
            "required": ["expression"]
        }
    }
]

18.4 The Complete Structure of tool_use Blocks

When Claude decides to call a tool, a tool_use type block appears in the response content array:

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "Search for all orders with status pending"}]
)

print(f"Stop reason: {response.stop_reason}")  # "tool_use"

for block in response.content:
    print(f"Block type: {block.type}")
    if block.type == "tool_use":
        print(f"Tool name: {block.name}")
        print(f"Tool use ID: {block.id}")
        print(f"Input: {json.dumps(block.input, indent=2)}")

A typical tool_use block in JSON:

{
  "type": "tool_use",
  "id": "toolu_01XFDUDYJgAf9n7mP7YT7V5H",
  "name": "search_database",
  "input": {
    "query": "pending",
    "table": "orders",
    "filters": {
      "status": "pending"
    },
    "limit": 50
  }
}

18.5 The Injection Format for tool_result Blocks

After executing a tool, inject the result into the message history in tool_result format:

Successful Result

tool_result_success = {
    "type": "tool_result",
    "tool_use_id": "toolu_01XFDUDYJgAf9n7mP7YT7V5H",
    "content": json.dumps({
        "results": [
            {"order_id": "ORD-001", "user_id": "U123", "amount": 299.00, "status": "pending"},
            {"order_id": "ORD-002", "user_id": "U456", "amount": 1599.00, "status": "pending"}
        ],
        "total_count": 2,
        "query_time_ms": 45
    })
}

Error Result

tool_result_error = {
    "type": "tool_result",
    "tool_use_id": "toolu_01XFDUDYJgAf9n7mP7YT7V5H",
    "content": "Query failed: database connection timed out (retried 3 times)",
    "is_error": True
}

Image Result

Some tools (like screenshot tools) can return image content:

import base64

with open("screenshot.png", "rb") as f:
    image_data = base64.standard_b64encode(f.read()).decode("utf-8")

tool_result_image = {
    "type": "tool_result",
    "tool_use_id": "toolu_01XFDUDYJgAf9n7mP7YT7V5H",
    "content": [
        {
            "type": "image",
            "source": {
                "type": "base64",
                "media_type": "image/png",
                "data": image_data
            }
        },
        {
            "type": "text",
            "text": "Screenshot captured, resolution 1920x1080"
        }
    ]
}

18.6 Complete End-to-End Tool Call Implementation

Tool Execution Engine

import anthropic
import json
from typing import Any, Dict, List, Callable

class ToolExecutor:
    """Tool execution engine"""
    
    def __init__(self):
        self.tools: Dict[str, Callable] = {}
        self.tool_definitions: List[dict] = []
    
    def register(self, tool_definition: dict, func: Callable):
        """Register a tool"""
        name = tool_definition["name"]
        self.tools[name] = func
        self.tool_definitions.append(tool_definition)
        return self
    
    def execute(self, tool_name: str, tool_input: dict) -> Any:
        """Execute a tool call"""
        if tool_name not in self.tools:
            raise ValueError(f"Unknown tool: {tool_name}")
        return self.tools[tool_name](**tool_input)
    
    def build_result_block(self, tool_use_id: str, result: Any, 
                           is_error: bool = False) -> dict:
        """Build a tool_result block"""
        if isinstance(result, str):
            content = result
        else:
            content = json.dumps(result, default=str)
        
        block = {
            "type": "tool_result",
            "tool_use_id": tool_use_id,
            "content": content
        }
        if is_error:
            block["is_error"] = True
        return block


class ToolUseAgent:
    """Agent with tool call support"""
    
    def __init__(self, executor: ToolExecutor, model: str = "claude-opus-4-5"):
        self.client = anthropic.Anthropic()
        self.executor = executor
        self.model = model
        self.max_iterations = 10
    
    def run(self, user_message: str, system: str = "") -> str:
        """Run the tool call loop"""
        messages = [{"role": "user", "content": user_message}]
        create_kwargs = {
            "model": self.model,
            "max_tokens": 4096,
            "tools": self.executor.tool_definitions,
            "messages": messages
        }
        if system:
            create_kwargs["system"] = system
        
        for iteration in range(self.max_iterations):
            response = self.client.messages.create(**create_kwargs)
            
            if response.stop_reason == "end_turn":
                return ' '.join(
                    b.text for b in response.content if b.type == "text"
                )
            
            if response.stop_reason == "tool_use":
                tool_results = []
                
                for block in response.content:
                    if block.type != "tool_use":
                        continue
                    
                    print(f"[Tool call] {block.name}({json.dumps(block.input)})")
                    
                    try:
                        result = self.executor.execute(block.name, block.input)
                        result_block = self.executor.build_result_block(block.id, result)
                        print(f"[Tool result] Success")
                    except Exception as e:
                        result_block = self.executor.build_result_block(
                            block.id, str(e), is_error=True
                        )
                        print(f"[Tool result] Error: {e}")
                    
                    tool_results.append(result_block)
                
                messages.append({"role": "assistant", "content": response.content})
                messages.append({"role": "user", "content": tool_results})
                create_kwargs["messages"] = messages
            else:
                break
        
        return "Maximum iterations reached; task may be incomplete."

18.7 The tool_choice Parameter: Controlling Tool Selection Behavior

The tool_choice parameter precisely controls whether Claude uses tools and which tool it uses:

# Mode 1: auto (default) — Claude decides whether to use tools
response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto"},
    messages=messages
)

# Mode 2: any — must use at least one tool
response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "any"},
    messages=messages
)

# Mode 3: tool — force a specific tool
response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    tools=tools,
    tool_choice={
        "type": "tool",
        "name": "search_database"
    },
    messages=messages
)

# Mode 4: none — disallow tool use (text output only)
response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "none"},
    messages=messages
)

Usage Scenarios

tool_choice mode Suitable scenario
auto General purpose; let Claude judge whether tools are needed
any Ensure Claude queries fresh data rather than relying on memory
tool + name Data extraction tasks; force structured output
none All information collected; Claude needs only to summarize

18.8 Error Handling and Retry Strategies

Tool Execution Error Handling

class RobustToolExecutor(ToolExecutor):
    """Tool executor with retry and error handling"""
    
    def __init__(self, max_retries: int = 3, retry_delay: float = 1.0):
        super().__init__()
        self.max_retries = max_retries
        self.retry_delay = retry_delay
    
    def execute(self, tool_name: str, tool_input: dict) -> Any:
        import time
        
        last_error = None
        for attempt in range(self.max_retries):
            try:
                return super().execute(tool_name, tool_input)
            except Exception as e:
                last_error = e
                if attempt < self.max_retries - 1:
                    print(f"Tool {tool_name} failed (attempt {attempt+1}), "
                          f"retrying in {self.retry_delay}s...")
                    time.sleep(self.retry_delay)
        
        raise RuntimeError(
            f"Tool {tool_name} failed after {self.max_retries} attempts: {last_error}"
        )

Claude's Behavior When Handling Tool Errors

When is_error: True is set, Claude will:

  1. Understand that the tool call failed
  2. Potentially retry with different parameters
  3. Inform the user why the task cannot be completed
  4. Offer alternative suggestions where applicable

18.9 Token Accounting and Performance Optimization

Tokens Consumed by Tool Definitions

Tool definitions are sent as part of the system prompt and consume input tokens:

def estimate_tool_tokens(tools: list) -> int:
    """Roughly estimate tokens consumed by tool definitions"""
    tool_text = json.dumps(tools)
    return len(tool_text) // 4  # Rough estimate: ~4 chars/token for English

print(f"Tool definitions estimated to consume ~{estimate_tool_tokens(tools)} tokens")

Optimization Strategies

  1. Concise descriptions: Keep each tool description under 100 words, avoid redundancy
  2. On-demand tool loading: Dynamically select a subset of tools based on conversation context
  3. Structured output via tool: Using tool_choice: tool for data extraction is more reliable than asking Claude to freely output JSON
  4. Trim tool results: Avoid returning oversized JSON — return only the fields Claude needs
def filter_tool_result(raw_result: dict, needed_fields: list) -> dict:
    """Filter tool results to retain only necessary fields"""
    return {k: v for k, v in raw_result.items() if k in needed_fields}

Summary

The core of the Tool Use architecture is a strict contract: the tool's Schema defines the input format, the tool_use block is Claude's call request, the tool_result block is the developer's result injection, and the entire loop is driven by stop_reason.

With this foundational architecture in hand, the next chapter dives deep into how to design high-quality tool Schemas that enable Claude to make more precise tool selections in complex scenarios.

Rate this chapter
4.7  / 5  (18 ratings)

💬 Comments