Claude is smart, but it cannot check the weather, query your database, or call your APIs. Tool use changes that. You define functions, and Claude decides when to call them.

This is Article 8 in the Claude AI — From Zero to Power User series. You should have completed Article 7: Messages API before this article.

By the end of this article, you will know how to define tools, handle the tool use flow, force tool execution, and build a working multi-tool assistant.


What is Tool Use?

Tool use (also called function calling) lets you define functions that Claude can call. Here is how it works:

  1. You send a message with tool definitions
  2. Claude decides which tool to call (if any) and provides the arguments
  3. You execute the function with those arguments
  4. You send the result back to Claude
  5. Claude uses the result to write its final response

Claude never executes your code. It only decides what to call and with what arguments. You run the function and return the result.


Defining Tools

A tool definition has three parts: name, description, and input schema. The input schema uses JSON Schema format.

Python

import anthropic
import json

client = anthropic.Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a specific city. Returns temperature in Celsius, conditions, and humidity.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "The city name, e.g. 'London' or 'New York'"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit. Defaults to celsius."
                }
            },
            "required": ["city"]
        }
    }
]

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

print(message.stop_reason)  # "tool_use"
print(message.content)

TypeScript

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

const client = new Anthropic();

const tools: Anthropic.Tool[] = [
  {
    name: "get_weather",
    description:
      "Get the current weather for a specific city. Returns temperature in Celsius, conditions, and humidity.",
    input_schema: {
      type: "object" as const,
      properties: {
        city: {
          type: "string",
          description: "The city name, e.g. 'London' or 'New York'",
        },
        unit: {
          type: "string",
          enum: ["celsius", "fahrenheit"],
          description: "Temperature unit. Defaults to celsius.",
        },
      },
      required: ["city"],
    },
  },
];

const message = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  tools,
  messages: [{ role: "user", content: "What is the weather in Berlin?" }],
});

console.log(message.stop_reason); // "tool_use"
console.log(message.content);

When Claude decides to use a tool, the response looks like this:

{
  "stop_reason": "tool_use",
  "content": [
    {
      "type": "text",
      "text": "I'll check the weather in Berlin for you."
    },
    {
      "type": "tool_use",
      "id": "toolu_01ABC123",
      "name": "get_weather",
      "input": {
        "city": "Berlin",
        "unit": "celsius"
      }
    }
  ]
}

Notice two things:

  1. stop_reason is "tool_use" instead of "end_turn"
  2. content has both a text block and a tool_use block

The Complete Tool Use Flow

The full flow requires three API calls:

  1. User message with tools → Claude responds with tool_use
  2. Tool result → Claude responds with final answer (or more tool calls)

Python — Complete Example

import anthropic
import json

client = anthropic.Anthropic()

# Step 1: Define tools
tools = [
    {
        "name": "get_weather",
        "description": "Get current weather for a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"}
            },
            "required": ["city"]
        }
    }
]

# Step 2: Your actual function
def get_weather(city: str) -> dict:
    # In a real app, call a weather API here
    weather_data = {
        "Berlin": {"temp": 18, "conditions": "Partly cloudy", "humidity": 65},
        "London": {"temp": 14, "conditions": "Rainy", "humidity": 80},
        "Tokyo": {"temp": 25, "conditions": "Sunny", "humidity": 55},
    }
    return weather_data.get(city, {"temp": 20, "conditions": "Unknown", "humidity": 50})

# Step 3: Send initial message
messages = [{"role": "user", "content": "What is the weather like in Berlin today?"}]

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=messages
)

# Step 4: Handle tool use
if response.stop_reason == "tool_use":
    # Find the tool_use block
    tool_block = next(block for block in response.content if block.type == "tool_use")

    # Execute the function
    result = get_weather(tool_block.input["city"])

    # Step 5: Send the result back
    messages.append({"role": "assistant", "content": response.content})
    messages.append({
        "role": "user",
        "content": [
            {
                "type": "tool_result",
                "tool_use_id": tool_block.id,
                "content": json.dumps(result)
            }
        ]
    })

    # Step 6: Get final response
    final_response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        tools=tools,
        messages=messages
    )

    print(final_response.content[0].text)
    # "The weather in Berlin is 18°C and partly cloudy with 65% humidity."

TypeScript — Complete Example

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

const client = new Anthropic();

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

function getWeather(city: string): Record<string, unknown> {
  const weatherData: Record<string, Record<string, unknown>> = {
    Berlin: { temp: 18, conditions: "Partly cloudy", humidity: 65 },
    London: { temp: 14, conditions: "Rainy", humidity: 80 },
    Tokyo: { temp: 25, conditions: "Sunny", humidity: 55 },
  };
  return weatherData[city] ?? { temp: 20, conditions: "Unknown", humidity: 50 };
}

const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "What is the weather like in Berlin today?" },
];

const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  tools,
  messages,
});

if (response.stop_reason === "tool_use") {
  const toolBlock = response.content.find(
    (block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
  );

  if (toolBlock) {
    const result = getWeather((toolBlock.input as { city: string }).city);

    messages.push({ role: "assistant", content: response.content });
    messages.push({
      role: "user",
      content: [
        {
          type: "tool_result",
          tool_use_id: toolBlock.id,
          content: JSON.stringify(result),
        },
      ],
    });

    const finalResponse = await client.messages.create({
      model: "claude-sonnet-4-6",
      max_tokens: 1024,
      tools,
      messages,
    });

    if (finalResponse.content[0].type === "text") {
      console.log(finalResponse.content[0].text);
    }
  }
}

Multiple Tools

You can define many tools. Claude picks the right one based on the user’s request.

Python

import anthropic
import json

client = anthropic.Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Get current weather for a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"}
            },
            "required": ["city"]
        }
    },
    {
        "name": "search_database",
        "description": "Search the product database by name or category.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query"},
                "category": {
                    "type": "string",
                    "enum": ["electronics", "clothing", "books"],
                    "description": "Product category to filter by"
                },
                "max_results": {
                    "type": "integer",
                    "description": "Maximum results to return. Default: 5"
                }
            },
            "required": ["query"]
        }
    },
    {
        "name": "send_email",
        "description": "Send an email to a recipient.",
        "input_schema": {
            "type": "object",
            "properties": {
                "to": {"type": "string", "description": "Recipient email"},
                "subject": {"type": "string", "description": "Email subject"},
                "body": {"type": "string", "description": "Email body text"}
            },
            "required": ["to", "subject", "body"]
        }
    }
]

# Claude picks the right tool
message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "Find me some books about Python programming"}
    ]
)

# Claude will call search_database with query="Python programming" and category="books"
for block in message.content:
    if block.type == "tool_use":
        print(f"Tool: {block.name}")
        print(f"Input: {json.dumps(block.input, indent=2)}")

Tip: Good descriptions are critical. Claude picks tools based on the description, not the name. Write descriptions that clearly explain what the tool does, what it returns, and when to use it.


Forcing Tool Use

By default, Claude decides whether to use a tool. You can override this with tool_choice.

# Let Claude decide (default)
message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto"},  # default
    messages=[{"role": "user", "content": "Hello"}]
)

# Force Claude to use ANY tool
message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "any"},
    messages=[{"role": "user", "content": "Hello"}]
)

# Force a SPECIFIC tool
message = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "get_weather"},
    messages=[{"role": "user", "content": "Hello"}]
)
tool_choiceBehavior
autoClaude decides (default)
anyClaude must use at least one tool
tool + nameClaude must use the specified tool

Use any when you always want structured output through a tool. Use tool + name for programmatic tool calling where you need a specific function called.


The Tool Use Loop

In complex scenarios, Claude may need to call multiple tools in sequence. Build a loop that handles this.

Python

import anthropic
import json

client = anthropic.Anthropic()

# Tool registry — maps names to functions
tool_functions = {
    "get_weather": lambda city: {"temp": 18, "conditions": "Cloudy"},
    "search_database": lambda query, **kwargs: [{"name": "Python Crash Course", "price": 29.99}],
    "send_email": lambda to, subject, body: {"status": "sent", "id": "msg_123"},
}

def run_agent(user_message: str, tools: list) -> str:
    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, return the text
        if response.stop_reason == "end_turn":
            return next(
                (block.text for block in response.content if block.type == "text"),
                ""
            )

        # Process tool calls
        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":
                    # Execute the function
                    func = tool_functions.get(block.name)
                    if func:
                        try:
                            result = func(**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})

# Use it
result = run_agent("What is the weather in Berlin and find me Python books", tools)
print(result)

This loop continues until Claude stops calling tools and gives a final text response. In practice, most interactions need 1-2 tool calls.


Error Handling in Tool Results

When a tool fails, tell Claude about the error. Set is_error to True so Claude knows something went wrong.

tool_results.append({
    "type": "tool_result",
    "tool_use_id": block.id,
    "content": json.dumps({"error": "City not found. Please check the city name."}),
    "is_error": True
})

Claude will then either:

  • Try again with different input
  • Ask the user for clarification
  • Explain what went wrong

Tool Definitions and Cost

Tool definitions count as input tokens. Each tool adds to your cost on every API call.

ToolsApproximate Tokens
1 simple tool~200 tokens
5 tools~800-1200 tokens
10 tools~1500-2500 tokens

Tips to reduce cost:

  • Only include tools relevant to the current task
  • Keep descriptions concise but clear
  • Use prompt caching for tool definitions that do not change

Best Practices

1. Write Clear Descriptions

# Bad — vague description
{"name": "search", "description": "Search for things"}

# Good — specific description
{"name": "search_products", "description": "Search the product catalog by name, category, or price range. Returns up to 10 matching products with name, price, and availability."}

2. Use Enums for Fixed Values

"category": {
    "type": "string",
    "enum": ["electronics", "clothing", "books", "food"],
    "description": "Product category"
}

Enums prevent Claude from sending invalid values.

3. Mark Required Fields

Only mark fields as required if the tool cannot work without them. Optional fields with defaults give Claude more flexibility.

4. Validate Inputs

Always validate tool inputs before executing. Claude usually sends correct data, but edge cases happen.

def get_weather(city: str) -> dict:
    if not city or len(city) > 100:
        return {"error": "Invalid city name"}
    # ... actual implementation

Summary

ConceptDetails
Tool definitionName + description + JSON Schema
FlowRequest → tool_use → execute → tool_result → response
tool_choiceauto (default), any (force tool), tool (force specific)
Multiple toolsClaude picks the right one based on descriptions
Error handlingSet is_error: True in tool_result
CostTool definitions add ~200 tokens per tool

Tool use is the bridge between Claude’s intelligence and your application’s capabilities. It turns Claude from a text generator into an agent that can take actions.


What’s Next?

In the next article, we will cover Vision — sending images to Claude for analysis, data extraction, and document processing.

Next: Vision — Analyzing Images and Documents