Intermediate

Claude API Tool Use

A quick reference for defining tools, running the agentic loop, and using the SDK tool runner with Claude.

TL;DR
  1. 01Define tools with a name, description, and JSON Schema input_schema.
  2. 02When stop_reason is tool_use, run the tool and send results back.
  3. 03Use the SDK's tool runner to handle the agentic loop automatically.

Define a Tool

  • input_schema

    Defines a tool's inputs as JSON Schema with a name and description.

    tools = [{
        "name": "get_weather",
        "description": "Returns current weather for a given city.",
        "input_schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"]
        }
    }]
  • tools parameter

    Passes the tools array to messages.create() alongside the prompt.

    message = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=1024,
        tools=tools,
        messages=[{"role": "user", "content": "Weather in Tokyo?"}]
    )
  • description field

    Tells Claude exactly when to call the tool, not just what it does.

    "description": "Call this when the user asks about current weather."
  • required array

    Marks which input fields Claude must always provide.

    "required": ["city"]

The Agentic Loop

  • tool_use stop_reason

    Signals Claude wants to call a tool instead of finishing the reply.

    if message.stop_reason == "tool_use":
        # extract tool calls, run them, send results back
  • extract tool calls

    Filters the response content for tool_use blocks to find what Claude called.

    tool_calls = [b for b in message.content if b.type == "tool_use"]
  • tool_result block

    Packages a tool's output with the matching tool_use_id to send back.

    tool_results.append({
        "type": "tool_result",
        "tool_use_id": call.id,
        "content": str(result)
    })
  • loop until end_turn

    Repeats the call-execute-respond cycle until Claude stops calling tools.

    while message.stop_reason == "tool_use":
        message = client.messages.create(...)

SDK Tool Runner

  • @beta_tool

    Turns a plain Python function into a tool with an auto-generated schema.

    from anthropic.lib.beta import beta_tool
    
    @beta_tool
    def get_weather(city: str) -> str:
        """Returns current weather for a given city."""
        return f"Sunny, 22C in {city}"
  • tool_runner()

    Runs the full call-execute-loop cycle automatically until Claude is done.

    runner = client.beta.messages.tool_runner(
        model="claude-opus-4-8",
        max_tokens=1024,
        tools=[get_weather],
        messages=[{"role": "user", "content": "Weather in Tokyo?"}]
    )
  • until_done()

    Blocks until the loop finishes and returns Claude's final response.

    final_message = runner.until_done()
    print(final_message.content[0].text)

Tool Choice Control

  • tool_choice: auto

    Lets Claude decide whether to call a tool; this is the default.

    tool_choice={"type": "auto"}
  • tool_choice: tool

    Forces Claude to call one specific named tool on this turn.

    tool_choice={"type": "tool", "name": "get_weather"}
  • tool_choice: none

    Disables tool calls mid-conversation once you have the data you need.

    tool_choice={"type": "none"}
  • disable_parallel_tool_use

    Forces exactly one tool call per turn when call order matters.

    tool_choice={"type": "auto", "disable_parallel_tool_use": True}
Tips
  1. 01Use tool_choice: {type: "tool"} to force a specific tool call instead of asking Claude for structured JSON.
  2. 02Write clear, prescriptive tool descriptions that say exactly when Claude should call them, not just what they do.
Warnings
  1. 01Always send the full assistant content block, including tool_use blocks, back in the next turn or you'll get a validation error.
  2. 02Return every tool_result for a turn in a single message — splitting them across messages trains Claude to stop making parallel calls.
In Practice
FAQ