← Back to Blog

Claude Tool Use: Function Calling Guide

How to define tools, handle function calls, and build reliable tool-use workflows with the Claude API. Production patterns included.


Tool use is what turns Claude from a text generator into a software agent. Instead of just writing about what it would do, Claude calls your functions, gets real results, and reasons over them.

Most tutorials cover the basics and stop. This guide covers everything from defining your first tool to production patterns that handle errors, retries, and multi-step orchestration.

What Tool Use Actually Is

When you provide tool definitions to the Claude API, you're giving the model a menu of functions it can call. Claude decides when to call them, with what arguments, and how to use the results. You execute the actual function and feed the result back.

The flow looks like this:

  1. You send a message with tool definitions
  2. Claude responds with a tool_use content block (function name + arguments)
  3. You execute the function locally
  4. You send the result back as a tool_result message
  5. Claude incorporates the result and continues

Claude never executes code on your system. It generates structured JSON describing which function to call and with what parameters. You're always in the loop.

Defining Tools

Tools are defined using JSON Schema. Each tool needs a name, description, and an input schema describing its parameters:

import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Get current weather for a city. Use when the user asks about weather conditions, temperature, or forecast.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "City name, e.g. 'San Francisco' or 'London'"
                },
                "units": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature units"
                }
            },
            "required": ["city"]
        }
    }
]

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}]
)

Tool Description Quality Matters

The description field is not documentation for humans — it's instructions for Claude. A vague description produces unreliable tool selection. A precise one produces consistent behavior.

Bad: "description": "Gets weather"

Good: "description": "Get current weather conditions for a specific city. Returns temperature, humidity, and conditions. Use when the user asks about weather, temperature, or if they should bring an umbrella."

The description should answer three questions: what does this tool do, what does it return, and when should Claude use it. If you're getting inconsistent tool selection, improve descriptions before touching anything else.

Handling Tool Use Responses

When Claude wants to call a tool, the response includes a tool_use block instead of (or alongside) text. You need to detect this and handle it:

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}]
)

# Check if Claude wants to use a tool
if response.stop_reason == "tool_use":
    # Extract the tool use block
    tool_block = next(b for b in response.content if b.type == "tool_use")
    print(f"Tool: {tool_block.name}")
    print(f"Args: {tool_block.input}")  # {"city": "Tokyo"}
    print(f"ID:   {tool_block.id}")     # unique ID for this call
else:
    # Claude responded with text (no tool needed)
    print(response.content[0].text)

Sending Tool Results Back

After executing the function, send the result back to Claude as a tool_result message. Claude uses this to formulate its final response:

# Execute the actual function (your code)
weather_data = get_weather(city="Tokyo")  # your function

# Continue the conversation with the tool result
final = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "What's the weather in Tokyo?"},
        {"role": "assistant", "content": response.content},
        {
            "role": "user",
            "content": [{
                "type": "tool_result",
                "tool_use_id": tool_block.id,
                "content": json.dumps(weather_data)
            }]
        }
    ]
)

print(final.content[0].text)
# "The current weather in Tokyo is 22°C with partly cloudy skies..."

The tool_use_id connects the result to the specific tool call. This matters when Claude makes multiple tool calls in a single response.

The Complete Tool Use Loop

In production, you need a loop that handles multiple rounds of tool calls. Claude might call one tool, examine the result, and call another:

def run_tool_loop(user_message, tools, tool_handlers):
    """Run a complete tool-use conversation loop."""
    messages = [{"role": "user", "content": user_message}]
    
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=4096,
            tools=tools,
            messages=messages,
        )
        
        # If Claude is done (no more tool calls), return the text
        if response.stop_reason == "end_turn":
            return next(b.text for b in response.content if b.type == "text")
        
        # Process tool calls
        messages.append({"role": "assistant", "content": response.content})
        
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = tool_handlers[block.name]
                try:
                    result = handler(**block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": json.dumps(result),
                    })
                except Exception as e:
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": json.dumps({"error": str(e)}),
                        "is_error": True,
                    })
        
        messages.append({"role": "user", "content": tool_results})

Key details: when a tool fails, send the error back with is_error: true. Claude will often recover — retrying with corrected arguments or taking an alternative approach. Swallowing errors silently produces worse outcomes than reporting them.

Parallel Tool Calls

Claude can request multiple tool calls in a single response. This happens when the model determines that two or more functions can run independently:

# User: "Compare the weather in Tokyo and London"
# Claude might respond with TWO tool_use blocks

tool_blocks = [b for b in response.content if b.type == "tool_use"]
# tool_blocks[0]: get_weather({"city": "Tokyo"})
# tool_blocks[1]: get_weather({"city": "London"})

# Execute both and send results back
results = []
for block in tool_blocks:
    result = tool_handlers[block.name](**block.input)
    results.append({
        "type": "tool_result",
        "tool_use_id": block.id,
        "content": json.dumps(result),
    })

For actual parallelism, use asyncio.gather or a thread pool to execute the functions concurrently. The API doesn't care about execution order — it just needs all results returned in the next message.

Tool Choice: Controlling When Tools Are Called

The tool_choice parameter controls Claude's tool-calling behavior:

# Let Claude decide (default)
response = client.messages.create(
    ...,
    tool_choice={"type": "auto"},
)

# Force a specific tool
response = client.messages.create(
    ...,
    tool_choice={"type": "tool", "name": "get_weather"},
)

# Force Claude to use ANY tool (must pick one)
response = client.messages.create(
    ...,
    tool_choice={"type": "any"},
)

auto is the default and right for most cases. Claude decides whether to call a tool based on the conversation.

tool (with a specific name) forces Claude to call that exact tool. Useful when you know which function is needed and want to skip the decision step.

any forces Claude to call at least one tool, but it gets to choose which one. Useful in agent loops where every turn must take an action.

TypeScript Examples

The TypeScript SDK follows the same patterns:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const tools: Anthropic.Tool[] = [
  {
    name: "search_database",
    description: "Search the product database by query string. Returns matching products with price and availability.",
    input_schema: {
      type: "object" as const,
      properties: {
        query: { type: "string", description: "Search query" },
        max_results: { type: "number", description: "Max results (default 10)" },
      },
      required: ["query"],
    },
  },
];

const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  tools,
  messages: [{ role: "user", content: "Find wireless headphones under $100" }],
});

for (const block of response.content) {
  if (block.type === "tool_use") {
    console.log(`Call: ${block.name}(${JSON.stringify(block.input)})`);
  }
}

Production Patterns

Pattern 1: Structured Output via Tool Use

One of the most powerful tool-use patterns doesn't involve calling external functions at all. Define a "tool" that's actually a structured output format, and force Claude to "call" it:

tools = [{
    "name": "extract_invoice",
    "description": "Extract structured data from an invoice document.",
    "input_schema": {
        "type": "object",
        "properties": {
            "invoice_number": {"type": "string"},
            "date": {"type": "string", "format": "date"},
            "total": {"type": "number"},
            "line_items": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "description": {"type": "string"},
                        "quantity": {"type": "integer"},
                        "unit_price": {"type": "number"}
                    }
                }
            }
        },
        "required": ["invoice_number", "date", "total", "line_items"]
    }
}]

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "extract_invoice"},
    messages=[{"role": "user", "content": f"Extract data from this invoice:\n{invoice_text}"}]
)

# The tool input IS the structured output
data = next(b.input for b in response.content if b.type == "tool_use")

This gives you JSON Schema-validated structured output without asking Claude to "output JSON" in the prompt. The schema constraints are enforced by the tool definition, producing more reliable results than prompt-based JSON extraction.

Pattern 2: Tool Use with Extended Thinking

Combine tool use with extended thinking for complex decision-making about which tools to call:

response = client.messages.create(
    model="claude-opus-4-6",
    max_tokens=8000,
    thinking={"type": "enabled", "budget_tokens": 5000},
    tools=tools,
    messages=[{
        "role": "user",
        "content": "Analyze our Q2 revenue trends and compare against industry benchmarks"
    }]
)

With thinking enabled, Claude plans its tool-use strategy before making the first call. This produces better sequencing — fewer wasted calls, more efficient multi-step workflows.

Pattern 3: Guardrailed Tool Use

For tools that modify state (database writes, API calls, file operations), add a confirmation step:

tools = [
    {
        "name": "delete_user",
        "description": "Delete a user account. DESTRUCTIVE: requires confirmation. Always call confirm_action first.",
        "input_schema": {
            "type": "object",
            "properties": {
                "user_id": {"type": "string"},
                "confirmed": {"type": "boolean", "description": "Must be true. Set by confirm_action."}
            },
            "required": ["user_id", "confirmed"]
        }
    },
    {
        "name": "confirm_action",
        "description": "Present a destructive action to the user for confirmation before executing.",
        "input_schema": {
            "type": "object",
            "properties": {
                "action": {"type": "string"},
                "details": {"type": "string"}
            },
            "required": ["action", "details"]
        }
    }
]

The description tells Claude to confirm first. Combined with application-level validation (reject delete_user if confirmed is false), this creates a two-step safety pattern.

Common Mistakes

1. Too Many Tools

Providing 50 tools degrades performance. Claude spends tokens reasoning about which tool to use instead of using the right one. Keep tool sets focused — under 20 is ideal, under 10 is better. If you need more, use routing: a first call to classify the request, then a second call with only the relevant tool subset.

2. Vague Tool Descriptions

"Searches the database" tells Claude nothing about when to use it or what it returns. Every description should specify: what the tool does, what it returns, and when Claude should reach for it. Treat descriptions like prompts — because that's exactly what they are.

3. Not Sending Errors Back

When a tool call fails, some developers return nothing or a generic "error occurred." Send the actual error message back with is_error: true. Claude can often diagnose the problem and retry with corrected arguments. A 404 Not Found error tells Claude the resource doesn't exist. A silent failure tells Claude nothing.

4. Missing the Loop

Tool use is conversational. One request might need 3-5 rounds of tool calls before Claude has enough information to respond. If your implementation only handles a single tool call, complex queries will fail silently — Claude returns a tool request, you respond, and then Claude returns another tool request that nobody processes.

5. Ignoring tool_choice

If you know which tool should be called, use tool_choice to force it. This eliminates selection errors and reduces latency (Claude skips the decision step). For structured output patterns, tool_choice with a specific tool name is essential.

Tool Use on Claude Fable 5

Claude Fable 5 — released June 9, 2026 — changes one thing your tool loop must handle: refusals. Fable 5 ships with safety classifiers, and when one declines a request, the Messages API returns stop_reason: "refusal" as a successful HTTP 200 response — not an error. A tool-use loop that only branches on tool_use and end_turn will mishandle it. Add the case, and decide your retry strategy: a beta fallbacks parameter can retry the request on another Claude model server-side, or you can retry client-side via SDK middleware.

The good news: everything else in this guide works on Fable 5 unchanged, and it adds support for programmatic tool calling, the memory tool, and code execution at launch. For who gets access and what it costs, see our Fable 5 vs Mythos 5 access guide.

Tool Use and MCP

The Model Context Protocol (MCP) extends tool use into a standardized ecosystem. MCP servers expose tools that any compatible client can discover and use — turning tool definitions from hardcoded schemas into dynamic capabilities.

If you're building tools that multiple applications will use, consider packaging them as an MCP server. The protocol handles discovery, authentication, and schema negotiation so you don't have to. For a deep dive on building MCP servers, see our MCP server guide.

Cost Considerations

Tool definitions count as input tokens. Each tool adds roughly 100-300 tokens depending on schema complexity. With 10 tools, that's 1,000-3,000 extra input tokens per call.

Cost optimization strategies:

  • Route first, tool later. Use a cheap classification call to determine which tools are needed, then make the tool-enabled call with only the relevant subset.
  • Cache tool definitions. If using prompt caching, tool definitions in the system message are cached across calls.
  • Minimize schema verbosity. Use concise property names and descriptions. Every character counts as tokens.

For high-throughput pipelines, the token cost of tool definitions is a meaningful percentage of your bill. Monitor it.

The Bottom Line

Tool use is the bridge between Claude's reasoning and your systems. Define tools with precise descriptions, handle errors transparently, implement the full conversation loop, and use tool_choice when you know what you want. Start simple — one tool, one round trip — and add complexity only when the use case demands it.

For configuring the rest of your Claude API calls, see our custom instructions guide and temperature settings guide. Building full agent systems? Our Agent SDK guide covers end-to-end autonomous workflows. And if your team needs hands-on help implementing tool use in production, we consult.


Free: Claude custom instructions template pack

Eight copy-paste templates — developer, writer, analyst, CLAUDE.md starter, and more. Plus new guides in your inbox. No spam, unsubscribe anytime.

Or grab the templates directly — no email needed

Keep learning — for free

50+ AI courses. 590+ lessons. No paywall for starters.

Need help building this?

We build MCP servers, Claude workflows, and AI agents for teams. Strategy calls start at $150/hr.