Tool use / function calling

Advanced AI Workflows & the Claude API

Chapter 3  ·  Tool Use / Function Calling

Tool use — also called function calling — is the mechanism that lets Claude interact with the outside world. Instead of just generating text, Claude can decide to call a function you've defined: search a database, fetch a URL, run a calculation, send a notification. Your code executes the function and feeds the result back to Claude, which then continues its response. This turns Claude from a text generator into an agent that can take actions.

The Tool Use Loop

Tool use is a multi-turn exchange, not a single request. Understanding the loop is the foundation for everything else in this chapter:

You
Send request with tool definitions — your message plus a list of tools Claude is allowed to call. Claude sees the tool names, descriptions, and parameter schemas.
Claude
Responds with a tool_use block — Claude decides it needs a tool, returns the tool name and the arguments it wants to pass. stop_reason is "tool_use". No visible text response yet — Claude is waiting for the result.
Your code
Execute the function — you run the actual function with the arguments Claude provided and capture the result. Claude has no direct access to your systems — it only receives what you send back.
You
Send tool result back — append the assistant turn (Claude's tool_use block) and a new user turn containing the tool result to the messages list, then resend. The conversation history now includes: your question, Claude's tool call, your tool result.
Claude
Responds with the final answer — Claude reads the tool result and generates its final response to the user. stop_reason is now "end_turn". Claude may call more tools before reaching this step.

Defining a Tool

Each tool is a Python dict (or Pydantic model) that tells Claude what the function does and what parameters it takes. The description is the most important field — Claude uses it to decide when to call the tool.

name
required
Snake_case identifier. Must be unique across all tools in a request. Claude uses this exact string when it calls the tool. Example: "get_weather", "search_products", "send_email"
description
required
Plain-English description of what the tool does, when to use it, and what it returns. This is what Claude reads to decide whether to call the tool. More detail = better decisions. Bad: "Gets weather." Good: "Returns current weather for a city including temperature, conditions, and wind speed. Use when the user asks about current weather for a specific location."
input_schema
required
JSON Schema describing the parameters Claude should pass. Defines property names, types, descriptions, and which are required. Claude follows this schema when constructing the arguments. Use "required": [...] to mark mandatory parameters. Add a "description" to each property.

A Complete Tool Use Example

tool_use_example.pypython
import anthropic, json

client = anthropic.Anthropic()

# 1. Define the tool
tools = [{
    "name": "get_weather",
    "description": "Returns current weather for a city. Use when the user "
                 "asks about weather in a specific location.",
    "input_schema": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "City name, e.g. 'London'"},
            "units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
        },
        "required": ["city"]
    }
}]

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

# 3. Handle tool call
if response.stop_reason == "tool_use":
    tool_block = response.content[0]
    args = tool_block.input # {"city": "Tokyo"}

    # 4. Execute your actual function
    result = get_weather(args["city"], args.get("units", "celsius"))

    # 5. Append assistant turn + tool result, resend
    messages.append({"role": "assistant", "content": response.content})
    messages.append({
        "role": "user",
        "content": [{
            "type": "tool_result",
            "tool_use_id": tool_block.id, # must match
            "content": json.dumps(result)
        }]
    })

    # 6. Get final response
    final = client.messages.create(
        model="claude-sonnet-4-6", max_tokens=1024,
        tools=tools, messages=messages
    )
    print(final.content[0].text)

Real-World Tool Examples

search_database
Query a product database. Use when the user asks about specific products, prices, or availability.
query: string (required)
category: string (optional)
max_results: integer (optional, default 5)
create_calendar_event
Creates an event in the user's calendar. Use when the user explicitly asks to schedule or book something.
title: string (required)
start_datetime: string ISO8601 (required)
duration_minutes: integer (required)
description: string (optional)
send_notification
Sends a push notification to the user's device. Only use when the user has explicitly requested a reminder.
message: string (required)
channel: enum ["push","email","sms"] (required)
scheduled_at: string ISO8601 (optional)
run_calculation
Evaluates a mathematical expression precisely. Use for any arithmetic where floating-point accuracy matters.
expression: string (required)
precision: integer decimal places (optional, default 2)

Handling Multiple Tool Calls

Claude may call multiple tools in a single response, or call tools sequentially across multiple turns. A robust tool loop handles both:

tool_loop.py — handles multiple sequential callspython
def run_tool_loop(initial_messages, tools, max_turns=10):
    messages = initial_messages.copy()

    for _ in range(max_turns):
        response = client.messages.create(
            model="claude-sonnet-4-6", max_tokens=4096,
            tools=tools, messages=messages
        )

        if response.stop_reason == "end_turn":
            return response # done

        if response.stop_reason != "tool_use":
            break # unexpected stop reason

        # Append assistant turn
        messages.append({"role": "assistant", "content": response.content})

        # Execute all tool calls in this response
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = dispatch_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": json.dumps(result)
                })

        messages.append({"role": "user", "content": tool_results})

    raise RuntimeError("Tool loop exceeded max_turns")
Always cap the loop
Without a max_turns limit, a tool loop can run indefinitely if Claude keeps deciding to call tools. Set a reasonable ceiling — 5–10 turns covers almost all real use cases. Log and alert when the limit is hit so you can investigate whether the loop is behaving as expected.

Tool Design Principles

  • Write the description for Claude, not for humans. The description is Claude's only guide for when to call the tool. Include: what the tool does, what it returns, and when to use it vs not use it. If two tools are similar, explicitly distinguish them in the descriptions.
  • Describe each parameter. Every property in the input schema should have a "description" field. Claude uses these to understand what values are expected — missing descriptions lead to wrong arguments.
  • Use enums for constrained choices. If a parameter only accepts certain values, list them as an enum. Claude will pick from the list reliably rather than inventing a value that your code doesn't handle.
  • Keep tools focused. One tool should do one thing. A manage_user tool with ten modes is harder for Claude to invoke correctly than three separate tools: get_user, update_user, delete_user.
  • Validate inputs before executing. Claude follows the schema but can still pass unexpected values. Validate and sanitise all tool arguments before running them — especially for tools that write data or call external services.
  • Return rich results. Claude uses the tool result to formulate its answer. A result like {"temperature": 18, "condition": "partly cloudy", "humidity": 72, "wind_kph": 15} lets Claude give a better answer than "18°C".
  • Handle errors gracefully in the result. If a tool fails, return an error description in the result rather than raising an exception and crashing the loop. Claude can then tell the user what went wrong and potentially try a different approach.
Next — Chapter 4: Streaming Responses
Handling incremental output for real-time UIs — how streaming works at the API level, processing the event stream in Python, building a UI that shows Claude's response as it's generated, and when streaming isn't the right choice.