← All guides

Claude Tool Use: Complete Guide to Function Calling

How to implement tool use (function calling) with the Claude API — defining tools, handling tool_use blocks, executing functions, and building reliable.

Claude Tool Use: Complete Guide to Function Calling

Tool use (also called function calling) lets Claude request execution of external functions during a conversation — search engines, calculators, databases, APIs — then incorporate the results into its response. The pattern: define tools with JSON schema, send them to the API, handle tool_use blocks in the response, execute the function, return the result, and continue the conversation. This guide covers the complete implementation in Python and TypeScript, with patterns for reliable production use.


How tool use works (the request-execute-return loop)

Every tool use interaction follows the same cycle:

  1. You define tools — name, description, input schema
  2. Claude decides to use a tool — returns a tool_use block with the tool name and inputs
  3. You execute the tool — call your actual function with Claude's inputs
  4. You return the result — add a tool_result to the next message
  5. Claude continues — incorporates the result and either uses more tools or gives a final answer

This cycle repeats until Claude returns stop_reason: "end_turn" with no tool use.


Defining tools

Tools are defined as JSON schema objects. The schema must be precise — Claude uses it to generate valid inputs:

import anthropic

TOOLS = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a location. Returns temperature, conditions, and humidity.",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City name or 'City, Country' format (e.g., 'Seoul, Korea')"
                },
                "units": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature units. Default: celsius"
                }
            },
            "required": ["location"]
        }
    },
    {
        "name": "search_web",
        "description": "Search the web for current information. Use when you need real-time data not in your training.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "The search query"
                },
                "num_results": {
                    "type": "integer",
                    "description": "Number of results to return (1-10). Default: 5",
                    "minimum": 1,
                    "maximum": 10
                }
            },
            "required": ["query"]
        }
    }
]

Tool description guidelines:


Complete Python implementation

import anthropic
import json
from typing import Any

client = anthropic.Anthropic()

def get_weather(location: str, units: str = "celsius") -> dict:
    """Your actual weather API implementation."""
    # In production, call a real weather API here
    return {
        "location": location,
        "temperature": 18,
        "units": units,
        "conditions": "partly cloudy",
        "humidity": 65
    }

def search_web(query: str, num_results: int = 5) -> list[dict]:
    """Your actual search implementation."""
    # In production, call a search API here
    return [
        {"title": "Result 1", "url": "https://example.com/1", "snippet": "..."},
    ]

# Map tool names to actual functions
TOOL_FUNCTIONS = {
    "get_weather": get_weather,
    "search_web": search_web,
}

def execute_tool(tool_name: str, tool_input: dict) -> Any:
    """Execute a tool call and return the result as a string."""
    if tool_name not in TOOL_FUNCTIONS:
        return f"Error: unknown tool '{tool_name}'"
    
    try:
        result = TOOL_FUNCTIONS[tool_name](**tool_input)
        return json.dumps(result)
    except Exception as e:
        return f"Error executing {tool_name}: {str(e)}"

def run_with_tools(user_message: str) -> str:
    """
    Run a conversation with tool use.
    Loops until Claude returns a final text response.
    """
    messages = [{"role": "user", "content": user_message}]
    
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            tools=TOOLS,
            messages=messages,
        )
        
        # Final response — no more tool use
        if response.stop_reason == "end_turn":
            return next(
                (block.text for block in response.content if hasattr(block, "text")),
                ""
            )
        
        # Handle tool use
        if response.stop_reason == "tool_use":
            # Add Claude's response (with tool_use blocks) to messages
            messages.append({
                "role": "assistant",
                "content": response.content
            })
            
            # Execute all tool calls and collect results
            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result,
                    })
            
            # Add tool results to messages
            messages.append({
                "role": "user",
                "content": tool_results
            })
            # Continue the loop
            continue
        
        # Unexpected stop reason
        break
    
    return "Agent loop ended unexpectedly"

# Usage
answer = run_with_tools("What's the weather in Seoul right now? Is it good weather for a bike ride?")
print(answer)

TypeScript implementation

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

const client = new Anthropic();

const tools: Anthropic.Tool[] = [
  {
    name: "get_weather",
    description: "Get current weather for a location.",
    input_schema: {
      type: "object",
      properties: {
        location: { type: "string", description: "City name" },
        units: { type: "string", enum: ["celsius", "fahrenheit"] },
      },
      required: ["location"],
    },
  },
];

async function executeTools(
  toolUseBlocks: Anthropic.ToolUseBlock[]
): Promise<Anthropic.ToolResultBlockParam[]> {
  return Promise.all(
    toolUseBlocks.map(async (block) => {
      let content: string;
      try {
        if (block.name === "get_weather") {
          const input = block.input as { location: string; units?: string };
          // Call your actual weather API
          content = JSON.stringify({ temperature: 18, conditions: "sunny" });
        } else {
          content = `Unknown tool: ${block.name}`;
        }
      } catch (e) {
        content = `Error: ${e}`;
      }
      return {
        type: "tool_result" as const,
        tool_use_id: block.id,
        content,
      };
    })
  );
}

async function runWithTools(userMessage: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: userMessage },
  ];

  while (true) {
    const response = await client.messages.create({
      model: "claude-sonnet-4-5",
      max_tokens: 4096,
      tools,
      messages,
    });

    if (response.stop_reason === "end_turn") {
      const textBlock = response.content.find((b) => b.type === "text");
      return textBlock?.type === "text" ? textBlock.text : "";
    }

    if (response.stop_reason === "tool_use") {
      messages.push({ role: "assistant", content: response.content });
      const toolUseBlocks = response.content.filter(
        (b): b is Anthropic.ToolUseBlock => b.type === "tool_use"
      );
      const toolResults = await executeTools(toolUseBlocks);
      messages.push({ role: "user", content: toolResults });
      continue;
    }

    break;
  }

  return "Unexpected end";
}

Controlling when tools are used

You can force or prevent tool use with the tool_choice parameter:

# Force Claude to use a specific tool (good for testing)
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=TOOLS,
    tool_choice={"type": "tool", "name": "get_weather"},
    messages=messages,
)

# Force Claude to use at least one tool
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=TOOLS,
    tool_choice={"type": "any"},
    messages=messages,
)

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

Handling tool errors gracefully

Return errors in tool_result rather than raising exceptions — Claude can reason about errors and adapt:

def execute_tool_safe(tool_name: str, tool_input: dict) -> str:
    """Execute a tool, returning errors as result content (not exceptions)."""
    try:
        result = TOOL_FUNCTIONS[tool_name](**tool_input)
        return json.dumps({"success": True, "data": result})
    except KeyError:
        return json.dumps({"success": False, "error": f"Unknown tool: {tool_name}"})
    except TypeError as e:
        return json.dumps({"success": False, "error": f"Invalid parameters: {e}"})
    except Exception as e:
        # For network errors, Claude can retry or use fallback
        return json.dumps({"success": False, "error": f"Tool failed: {e}"})

When Claude receives an error result, it will typically try a different approach, ask for clarification, or explain what went wrong — all better outcomes than a silent crash.


Setting a maximum tool-use iterations limit

Without a stop condition, a malfunctioning agent can loop indefinitely:

def run_with_tools_safe(user_message: str, max_iterations: int = 10) -> str:
    messages = [{"role": "user", "content": user_message}]
    iterations = 0
    
    while iterations < max_iterations:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            tools=TOOLS,
            messages=messages,
        )
        
        if response.stop_reason == "end_turn":
            return next(
                (block.text for block in response.content if hasattr(block, "text")),
                ""
            )
        
        if response.stop_reason == "tool_use":
            messages.append({"role": "assistant", "content": response.content})
            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    result = execute_tool_safe(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result,
                    })
            messages.append({"role": "user", "content": tool_results})
            iterations += 1
            continue
        
        break
    
    return f"Agent reached maximum iterations ({max_iterations}). Last state: {messages[-1]}"

Frequently asked questions

How is tool use different from function calling in OpenAI? They're functionally equivalent. OpenAI calls it "function calling" and later renamed it "tools". The concepts are identical: you define a schema, the model returns structured call requests, you execute them, you return results. The API shapes differ in details but the pattern is the same.

Can Claude use multiple tools in parallel? Claude may return multiple tool_use blocks in a single response when tools are independent. You should execute them in parallel when possible, then return all results together. The SDK doesn't force sequential execution.

What happens if I don't execute a tool Claude requested? If you include a tool_use block from Claude's response in the conversation without a matching tool_result, the API returns an error. You must return a tool_result for every tool_use block before continuing.

How many tools can I define? No hard limit documented, but practical performance degrades with many tools. Claude needs to parse and reason about all tool schemas. 5–15 tools is the typical production range; above 20, consider grouping or routing.

Can tools have optional parameters? Yes. Only list required parameters in the required array. Optional parameters are listed in properties without appearing in required. Claude will use them when helpful.


Related guides


Take It Further

Claude Agent SDK Cookbook: 40 Production Patterns — Patterns 3–9 cover the complete Tool Use System: parallel tool execution, error recovery, dynamic tool registration, tool use with streaming, and a 10-tool agent with logging and cost tracking.

→ Get the Agent SDK Cookbook — $49

30-day money-back guarantee. Instant download.

AI Disclosure: Drafted with Claude Code; all tool use patterns from Anthropic SDK documentation as of April 2026.

Tools and references