Tool use and function calling

Tool use is the primitive that turns Claude from a text-completion engine into an agent. The model declares a tool call via a tool_use content block; the caller executes the function (or, for server tools, Anthropic does); the result comes back as a tool_result block; Claude continues reasoning. Parallel tool use (multiple tool_use blocks in one assistant turn) is on by default for Claude 4 models. This note covers the request/response shape, every tool_choice mode, parallel execution, strict mode, fine-grained streaming, the canonical client-side tool loop, and the divide between client tools (you execute) and server tools (Anthropic executes).

See also

1. Client tools vs server tools

Per platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works:

TypeWhere it runsExamplesDetected via
Client toolYour applicationUser-defined tools, Anthropic-schema tools (bash_20250124, text_editor_*, computer_*, memory_20250818)stop_reason: "tool_use" + tool_use block in response
Server toolAnthropic’s infrastructureweb_search_*, web_fetch_*, code_execution_*, tool_search_tool_*server_tool_use + corresponding *_tool_result block in same response

Client tools require you to implement the loop: read tool_use block, run your code, send back a user message containing a tool_result block. Server tools are transparent — Anthropic runs them and includes the result in the same response.

2. Defining a custom tool

{
  "name": "get_weather",
  "description": "Get the current weather for a location.",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City and state, e.g. San Francisco, CA"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Temperature unit"
      }
    },
    "required": ["location"]
  },
  "type": "custom",
  "cache_control": {"type": "ephemeral"},
  "strict": true,
  "defer_loading": false,
  "allowed_callers": ["direct"]
}

Field reference

  • name — alphanumeric + underscore, ≤64 chars
  • descriptionstrongly recommended. Claude uses this to decide when to call. Treat it as a doc-comment.
  • input_schema — JSON Schema for arguments. Anthropic supports a wide subset (object, properties, required, type, enum, anyOf, oneOf, items, etc.). Always set additionalProperties: false when you want strict shapes.
  • type: "custom" — implicit for user-defined tools; explicit for clarity. Other valid values are the server-tool type strings (web_search_20260209, etc.) which use a different namespace.
  • cache_control — see prompt-caching
  • strict: true (per Strict tool use) — guarantees output conforms to schema exactly. Required for production-grade structured-input tools. Has minor overhead.
  • defer_loading — used with tool search; the tool isn’t loaded into Claude’s context until referenced via the tool_search_tool_* server tool.
  • allowed_callers — restricts what context can invoke the tool. ["direct"] (default) means only the main model can call; values like "code_execution_20250825" allow code inside the code-execution sandbox to call your tool too. Useful for hybrid agents.

3. The tool-use loop (client tools)

Step 1: Initial call

import anthropic
client = anthropic.Anthropic()
 
tools = [{
    "name": "get_weather",
    "description": "Get current weather for a location.",
    "input_schema": {
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"],
    },
}]
 
messages = [{"role": "user", "content": "What's the weather in Paris?"}]
 
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=messages,
)
print(response.stop_reason)  # "tool_use"

Step 2: Extract the tool call

tool_use = next(b for b in response.content if b.type == "tool_use")
print(tool_use.id, tool_use.name, tool_use.input)
# "toolu_01...", "get_weather", {"location": "Paris"}

Step 3: Execute, send result back

weather_result = my_weather_function(**tool_use.input)  # "18°C, partly cloudy"
 
messages.append({"role": "assistant", "content": response.content})
messages.append({
    "role": "user",
    "content": [{
        "type": "tool_result",
        "tool_use_id": tool_use.id,
        "content": weather_result,            # or list of content blocks
        "is_error": False,
    }],
})
 
continuation = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=messages,
)
print(continuation.content[0].text)  # "The weather in Paris is..."

Step 4: Repeat until stop_reason == "end_turn"

For a generic agent loop:

while response.stop_reason == "tool_use":
    tool_uses = [b for b in response.content if b.type == "tool_use"]
    tool_results = []
    for tu in tool_uses:                      # parallel execution = run all at once
        try:
            result = dispatch(tu.name, tu.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": tu.id,
                "content": result,
            })
        except Exception as e:
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": tu.id,
                "content": str(e),
                "is_error": True,
            })
    messages.append({"role": "assistant", "content": response.content})
    messages.append({"role": "user", "content": tool_results})
    response = client.messages.create(model=model, max_tokens=1024, tools=tools, messages=messages)

4. tool_choice modes

ModeJSONBehavior
Auto (default){"type": "auto"}Claude decides whether to use any tool.
Any{"type": "any"}Claude must use exactly one tool (no text-only response).
Specific tool{"type": "tool", "name": "get_weather"}Claude must use this exact tool.
None{"type": "none"}Disables tools. Equivalent to omitting tools.

All four modes accept "disable_parallel_tool_use": true to force a single tool call per turn (default is false — parallel allowed). See section 6.

Compatibility with extended thinking

Per extended thinking docs:

  • tool_choice: {"type": "auto"} and {"type": "none"} — supported with thinking
  • tool_choice: {"type": "any"} or {"type": "tool", ...}error: cannot force a tool while thinking is enabled

5. Tool-use system-prompt overhead

When tools is non-empty, the API automatically prepends a hidden system prompt that teaches the model the tool-use format. The overhead in input tokens:

Modeltool_choice: auto / nonetool_choice: any / tool
Claude 4 (Opus 4.7, 4.6, 4.5, 4.1, Sonnet 4.6, 4.5, 4, Haiku 4.5)346313
Haiku 3.5264340

This is additive to your own tool definitions and system prompt. Cacheable along with tools (place cache_control on the last stable tool).

6. Parallel tool use

By default on Claude 4 models, the assistant can emit multiple tool_use blocks in a single turn:

"content": [
  {"type": "text", "text": "I'll check both."},
  {"type": "tool_use", "id": "toolu_01", "name": "get_weather", "input": {"location": "Paris"}},
  {"type": "tool_use", "id": "toolu_02", "name": "get_weather", "input": {"location": "London"}}
]

Execute both in parallel, return both results in a single user message:

{
  "role": "user",
  "content": [
    {"type": "tool_result", "tool_use_id": "toolu_01", "content": "Paris: 18°C"},
    {"type": "tool_result", "tool_use_id": "toolu_02", "content": "London: 15°C"}
  ]
}

Disabling parallel

Pass disable_parallel_tool_use: true in tool_choice:

tool_choice={"type": "auto", "disable_parallel_tool_use": True}

When to disable: tools have side-effects whose order matters; you need single-step debugging; the LLM keeps issuing redundant parallel calls.

Parallel + thinking

Adaptive thinking on Opus 4.7 / 4.6 / Sonnet 4.6 supports interleaved thinking between tool calls automatically. For older Claude 4 models, add the interleaved-thinking-2025-05-14 beta header — Claude can then think between sequential tool results, making more sophisticated multi-step decisions.

7. Strict tool use

Per Strict tool use, set "strict": true on a tool definition. Behavior:

  • Output JSON guaranteed to conform to input_schema exactly
  • Recursive: applies to nested objects
  • Required: additionalProperties: false on every object
  • Limitation: subset of JSON Schema (no $ref, no oneOf with mixed types at the top level, no pattern)

Strict mode adds ~5-10 % to TTFT in exchange for eliminating an entire class of “missing required field” or “extra unknown field” errors. Use it for any tool that hits real infrastructure.

8. Fine-grained tool streaming

When streaming with tools, input_json_delta events arrive incrementally as Claude composes the tool’s input JSON:

event: content_block_start
data: {"type": "content_block_start", "index": 1,
       "content_block": {"type": "tool_use", "id": "toolu_01", "name": "get_weather", "input": {}}}

event: content_block_delta
data: {"type": "content_block_delta", "index": 1,
       "delta": {"type": "input_json_delta", "partial_json": "{\"location\":"}}

event: content_block_delta
data: {"type": "content_block_delta", "index": 1,
       "delta": {"type": "input_json_delta", "partial_json": "\"Paris\"}"}}

event: content_block_stop
data: {"type": "content_block_stop", "index": 1}

Accumulate partial_json strings, parse the full JSON at content_block_stop. The Python SDK’s stream.text_stream handles this transparently; for full input access, iterate raw events.

9. Tool result content

The tool_result.content field accepts:

  • A plain string: "content": "18°C, partly cloudy"
  • A list of content blocks: "content": [{"type": "text", "text": "..."}, {"type": "image", "source": {...}}]
  • An image (return screenshots from a bash or computer tool)

is_error: true signals failure. Claude often retries on error if there’s a sensible alternative input.

For very large tool outputs (database query dumps, log tails), truncate to a few thousand tokens with a “summary + tail” pattern; the model rarely benefits from more.

10. Error handling

Best practices from production agent loops:

  1. Wrap every dispatch in try/except, returning is_error: true with the exception message. Claude usually self-corrects.
  2. Bound the agent loop: cap at 10-30 iterations. An unbounded loop on a malformed tool can burn tokens fast.
  3. Detect ping-pong: if the same (tool_name, input_hash) repeats more than 3 times, break out — Claude is stuck.
  4. Token-budget the loop: track cumulative usage across iterations; abort if exceeding a per-turn cap.
  5. For server tools: errors come back inline as ..._tool_result_error content with an error_code. See computer-use-and-code-execution for the per-tool error codes.

11. Best practices for tool definitions

Per Anthropic’s prompting docs:

  1. Detailed descriptions matter more than schemas. Tell Claude when to use the tool, not just what it does.
  2. One purpose per tool. Avoid Swiss-army tools — narrower tools give better routing.
  3. Examples in description — “For example, to get NYC weather, call with location: 'New York, NY'.”
  4. Set required aggressively — required fields surface as “Claude must ask for this if missing.”
  5. Match the model: Opus is far better at recognizing missing required fields and asking the user; Sonnet/Haiku may guess values.
  6. Avoid jargon: tool descriptions are part of the prompt — write them in natural language, not code-style identifiers.

12. Server tools (Anthropic-executed)

Full coverage in computer-use-and-code-execution. Quick list:

Server toolType stringWhat it does
Web Searchweb_search_20260209 (latest, with dynamic filtering) or web_search_20250305Search the web; results inline; $10 per 1,000 searches.
Web Fetchweb_fetch_20260309 (latest) or earlierFetch a specific URL; returns parsed content.
Code Executioncode_execution_20260120 (latest)Sandboxed Python + bash; persistent files within session.
Bash Code Executionbash_code_execution_*Returned inside code_execution_* blocks when bash is invoked.
Text Editor Code Executiontext_editor_code_execution_*Returned inside code_execution_* blocks when text editor is invoked.
Tool Search (BM25 / regex)tool_search_tool_bm25 / tool_search_tool_regexServer-side semantic search across deferred tools; enables 100+ tool agents without context bloat.

Server tools are invoked the same way: include them in tools[], Claude calls them, results appear inline. No client-side loop needed.

13. Best-practice examples

Strict tool with rich schema

tools = [{
    "name": "create_calendar_event",
    "description": (
        "Create a new calendar event. Use this when the user asks to schedule, "
        "book, or create an appointment, meeting, or reminder. Returns the event ID."
    ),
    "strict": True,
    "input_schema": {
        "type": "object",
        "additionalProperties": False,
        "properties": {
            "title": {"type": "string", "description": "Event title"},
            "start_iso": {"type": "string", "description": "ISO 8601 start time, e.g. 2026-05-25T14:00:00"},
            "duration_minutes": {"type": "integer", "minimum": 5, "maximum": 480},
            "attendees": {
                "type": "array",
                "items": {"type": "string", "format": "email"},
                "description": "List of attendee email addresses",
            },
        },
        "required": ["title", "start_iso", "duration_minutes"],
    },
}]

Multi-tool agent with caching

tools = [
    {"name": "search_docs", "description": "...", "input_schema": {...}},
    {"name": "edit_file", "description": "...", "input_schema": {...}},
    {
        "name": "run_tests",
        "description": "...",
        "input_schema": {...},
        "cache_control": {"type": "ephemeral"},  # caches all 3 tool defs above
    },
]

TypeScript: typed tool with discriminated narrowing

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
 
const tools: Anthropic.Tool[] = [{
  name: "get_weather",
  description: "Get current weather.",
  input_schema: {
    type: "object",
    properties: { location: { type: "string" } },
    required: ["location"],
  },
}];
 
let response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  tools,
  messages: [{ role: "user", content: "Weather in Tokyo?" }],
});
 
while (response.stop_reason === "tool_use") {
  const toolUses = response.content.filter(
    (b): b is Anthropic.ToolUseBlock => b.type === "tool_use"
  );
  const results = await Promise.all(
    toolUses.map(async (tu) => {
      const r = await runMyTool(tu.name, tu.input);
      return { type: "tool_result" as const, tool_use_id: tu.id, content: r };
    })
  );
  response = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 1024,
    tools,
    messages: [
      { role: "user", content: "Weather in Tokyo?" },
      { role: "assistant", content: response.content },
      { role: "user", content: results },
    ],
  });
}

14. Token-efficient tools (beta)

anthropic-beta: token-efficient-tools-2025-02-19 — reduces the tokens used for tool-use encoding (especially helpful with many parallel tool calls). Not yet GA; opt in if your agent loops show high overhead in usage.output_tokens proportional to the actual content.

15. Further reading