BeClaude
Guide2026-04-25

How to Build a Tool-Using Agent with Claude: A Step-by-Step Guide

Learn how to build a tool-using agent with Claude API, from defining tools to handling tool calls, parallel execution, and best practices for production.

Quick Answer

This guide walks you through building a tool-using agent with Claude API, covering tool definition, tool call handling, parallel tool use, and production best practices with Python code examples.

Claude APITool UseAgentPythonFunction Calling

How to Build a Tool-Using Agent with Claude: A Step-by-Step Guide

Claude's tool use capability allows you to extend the model's functionality by giving it access to external tools, APIs, and functions. Instead of just generating text, Claude can decide when to call a tool, pass the right arguments, and then use the tool's output to continue the conversation. This opens up possibilities for building agents that can search the web, run code, manipulate files, or interact with any external service.

In this guide, you'll learn how to build a practical tool-using agent with Claude API, from defining tools to handling tool calls and optimizing for production.

Understanding Tool Use in Claude

Tool use (also called function calling) allows Claude to request the execution of a function you define. The flow works like this:

  • You define tools and include them in your API request
  • Claude analyzes the user's request and decides if a tool should be called
  • If yes, Claude returns a tool_use stop reason with the tool name and arguments
  • You execute the tool on your end and return the result
  • Claude uses the result to generate a final response
This pattern enables Claude to perform actions beyond text generation, making it a true agent.

Prerequisites

Before you start, make sure you have:

  • An Anthropic API key (get one from console.anthropic.com)
  • Python 3.8+ installed
  • The Anthropic Python SDK installed: pip install anthropic

Step 1: Define Your Tools

Tools are defined as JSON objects in the tools parameter of the Messages API. Each tool has a name, description, and an input schema (JSON Schema format).

Here's an example of defining a weather lookup tool:

import anthropic

client = anthropic.Anthropic()

weather_tool = { "name": "get_weather", "description": "Get the current weather for a given location", "input_schema": { "type": "object", "properties": { "location": { "type": "string", "description": "City name, e.g., San Francisco, CA" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit" } }, "required": ["location"] } }

Best practices for tool definitions:
  • Use clear, descriptive names (snake_case recommended)
  • Write detailed descriptions so Claude understands when to use the tool
  • Define precise input schemas with proper types and constraints
  • Mark required fields explicitly

Step 2: Send a Request with Tools

Include your tool definitions in the API request. Claude will automatically decide whether to use a tool based on the user's message.

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=[weather_tool],
    messages=[
        {"role": "user", "content": "What's the weather like in Tokyo?"}
    ]
)

Step 3: Handle Tool Calls

When Claude decides to use a tool, the response will have a stop_reason of "tool_use" and the content will include a tool_use block. You need to check for this and execute the corresponding function.

def handle_tool_call(tool_name, tool_input):
    """Execute the tool and return the result."""
    if tool_name == "get_weather":
        location = tool_input["location"]
        unit = tool_input.get("unit", "celsius")
        # In production, call a real weather API here
        return f"The weather in {location} is 22°{unit[0].upper()}, partly cloudy."
    else:
        return f"Unknown tool: {tool_name}"

Check if Claude wants to use a tool

if response.stop_reason == "tool_use": for content in response.content: if content.type == "tool_use": tool_name = content.name tool_input = content.input tool_result = handle_tool_call(tool_name, tool_input) print(f"Tool called: {tool_name}({tool_input})") print(f"Result: {tool_result}")

Step 4: Return Tool Results to Claude

After executing the tool, you need to send the result back to Claude so it can continue the conversation. This is done by appending a tool_result block to the messages.

# Continue the conversation with the tool result
messages = [
    {"role": "user", "content": "What's the weather like in Tokyo?"},
    {"role": "assistant", "content": response.content},  # Contains tool_use block
    {
        "role": "user",
        "content": [
            {
                "type": "tool_result",
                "tool_use_id": content.id,
                "content": tool_result
            }
        ]
    }
]

Get Claude's final response

final_response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, tools=[weather_tool], messages=messages )

print(final_response.content[0].text)

Output: "The weather in Tokyo is currently 22°C with partly cloudy skies. A pleasant day!"

Step 5: Parallel Tool Use

Claude can call multiple tools in a single response. This is useful when the user's request requires multiple independent actions.

# Example: User asks for weather in multiple cities
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=[weather_tool],
    messages=[
        {"role": "user", "content": "Compare the weather in Tokyo, London, and New York"}
    ]
)

if response.stop_reason == "tool_use": for content in response.content: if content.type == "tool_use": print(f"Calling {content.name} with {content.input}") # Execute each tool call independently

When returning results for parallel calls, include all tool_result blocks in a single message:

results = []
for content in response.content:
    if content.type == "tool_use":
        result = handle_tool_call(content.name, content.input)
        results.append({
            "type": "tool_result",
            "tool_use_id": content.id,
            "content": result
        })

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

Advanced: Building a Complete Agent Loop

For production use, you'll want a loop that handles multiple tool calls until Claude provides a final answer. Here's a complete agent pattern:

def run_agent(user_message, tools, max_turns=5):
    """Run an agent loop with tool use."""
    messages = [{"role": "user", "content": user_message}]
    
    for turn in range(max_turns):
        response = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            tools=tools,
            messages=messages
        )
        
        # Check if Claude is done
        if response.stop_reason != "tool_use":
            return response.content[0].text
        
        # Process tool calls
        tool_results = []
        for content in response.content:
            if content.type == "tool_use":
                result = handle_tool_call(content.name, content.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": content.id,
                    "content": result
                })
        
        # Add assistant response and tool results to messages
        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": tool_results})
    
    return "Agent reached maximum turns without final answer."

Usage

result = run_agent("What's the weather in Tokyo and London?", [weather_tool]) print(result)

Best Practices for Production

1. Error Handling

Always wrap tool execution in try-except blocks and return meaningful error messages to Claude:
def safe_tool_call(tool_name, tool_input):
    try:
        return handle_tool_call(tool_name, tool_input)
    except Exception as e:
        return f"Error executing {tool_name}: {str(e)}"

2. Tool Result Size Limits

Keep tool results concise. Claude has context window limits, so large results can cause truncation. If you need to return large data, consider summarizing or paginating.

3. Use Strict Tool Mode

For critical applications, enable strict tool use to ensure Claude only calls tools you've defined:
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=[weather_tool],
    tool_choice={"type": "any"},  # Forces tool use
    messages=[...]
)

4. Combine with Prompt Caching

If you have large tool definitions, use prompt caching to reduce latency and costs:
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=[weather_tool],
    extra_headers={"anthropic-beta": "prompt-caching-2024-07-31"},
    messages=[...]
)

Real-World Example: Multi-Tool Agent

Here's a more complex agent that combines a weather tool and a calculator tool:

calculator_tool = {
    "name": "calculate",
    "description": "Perform mathematical calculations",
    "input_schema": {
        "type": "object",
        "properties": {
            "expression": {
                "type": "string",
                "description": "Mathematical expression to evaluate, e.g., '2 + 2'"
            }
        },
        "required": ["expression"]
    }
}

def handle_tool_call(tool_name, tool_input): if tool_name == "get_weather": # Weather logic return "22°C" elif tool_name == "calculate": import ast try: # Safe evaluation result = eval(ast.literal_eval(tool_input["expression"])) return str(result) except: return "Calculation error" return "Unknown tool"

Key Takeaways

  • Tool use enables agents: Claude can call external functions, making it possible to build agents that interact with the real world through APIs and services.
  • Define tools clearly: Use descriptive names, detailed descriptions, and precise input schemas to help Claude choose the right tool.
  • Handle the tool_use stop reason: Always check response.stop_reason and process tool_use content blocks to execute tools and return results.
  • Parallel tool calls are supported: Claude can call multiple tools in one response, which is great for independent operations like fetching data from multiple sources.
  • Build a loop for complex tasks: For multi-step agents, implement a turn-based loop that continues until Claude produces a final answer or reaches a maximum turn limit.