BeClaude
Guide2026-04-27

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

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

Quick Answer

This guide walks you through building a tool-using agent with Claude, covering tool definition, execution, parallel tool calls, strict mode, and error handling with practical code examples.

Claude APITool UseAgent DevelopmentFunction CallingAI Integration

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

Claude's tool use capability is one of its most powerful features, allowing you to extend the model's functionality by giving it access to external APIs, databases, and custom functions. Whether you're building a customer support bot that can look up orders, a research assistant that can search the web, or an automation agent that can interact with your infrastructure, tool use is the key to unlocking Claude's full potential.

In this guide, you'll learn how to build a robust tool-using agent from scratch, covering everything from basic tool definitions to advanced patterns like parallel tool calls and strict mode.

Understanding Claude's Tool Use Architecture

Before diving into code, it's important to understand how tool use works under the hood:

  • You define tools – You provide Claude with a JSON schema describing each tool's name, description, and parameters.
  • Claude decides when to use them – Based on the conversation context, Claude may respond with a tool_use stop reason and the arguments to call.
  • You execute the tool – Your application runs the tool function with the provided arguments.
  • You return the result – You send the tool result back to Claude, which then continues the conversation.
This loop continues until Claude determines the task is complete and generates a final text response.

Prerequisites

  • An Anthropic API key (get one at console.anthropic.com)
  • Python 3.8+ or Node.js 16+
  • The Anthropic SDK installed (pip install anthropic or npm install @anthropic-ai/sdk)

Step 1: Defining Your Tools

Tools are defined using JSON Schema. Each tool needs:

  • name: A unique identifier (use snake_case)
  • description: Clear explanation of what the tool does
  • input_schema: Parameters the tool accepts
Here's an example of a weather lookup tool and a database query tool:

import anthropic

client = anthropic.Anthropic()

tools = [ { "name": "get_weather", "description": "Get the current weather for a city", "input_schema": { "type": "object", "properties": { "location": { "type": "string", "description": "City name, e.g., San Francisco, CA" }, "units": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit" } }, "required": ["location"] } }, { "name": "query_database", "description": "Query a SQL database for customer information", "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "SQL SELECT query to execute" } }, "required": ["query"] } } ]

Step 2: Implementing Tool Handlers

Next, you need to implement the actual functions that Claude will call. These are your "tool handlers":

def handle_get_weather(location: str, units: str = "celsius"):
    # In production, call a real weather API
    mock_data = {
        "San Francisco, CA": {"temp": 18, "condition": "Foggy"},
        "New York, NY": {"temp": 22, "condition": "Sunny"},
        "London, UK": {"temp": 12, "condition": "Cloudy"}
    }
    data = mock_data.get(location, {"temp": 20, "condition": "Unknown"})
    return f"Current weather in {location}: {data['temp']}°{units[0].upper()}, {data['condition']}"

def handle_query_database(query: str): # NEVER execute raw SQL in production without sanitization! # This is a mock example for demonstration if "SELECT" not in query.upper(): return "Error: Only SELECT queries are allowed" mock_results = [ {"id": 1, "name": "Alice", "email": "[email protected]"}, {"id": 2, "name": "Bob", "email": "[email protected]"} ] return str(mock_results)

tool_handlers = { "get_weather": handle_get_weather, "query_database": handle_query_database }

Step 3: The Core Agent Loop

Now let's build the main loop that processes Claude's responses and executes tools:

def run_agent(user_message: str, max_tool_rounds: int = 10):
    messages = [{"role": "user", "content": user_message}]
    
    for round_num in range(max_tool_rounds):
        response = client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=1024,
            tools=tools,
            messages=messages
        )
        
        # Check if Claude wants to use a tool
        if response.stop_reason == "tool_use":
            for content_block in response.content:
                if content_block.type == "tool_use":
                    tool_name = content_block.name
                    tool_input = content_block.input
                    
                    print(f"🔧 Claude wants to use: {tool_name}")
                    print(f"   Arguments: {tool_input}")
                    
                    # Execute the tool
                    handler = tool_handlers.get(tool_name)
                    if handler:
                        try:
                            result = handler(**tool_input)
                            print(f"   Result: {result[:100]}...")
                        except Exception as e:
                            result = f"Error executing {tool_name}: {str(e)}"
                    else:
                        result = f"Error: Unknown tool '{tool_name}'"
                    
                    # Add the tool result to messages
                    messages.append({
                        "role": "assistant",
                        "content": response.content
                    })
                    messages.append({
                        "role": "user",
                        "content": [
                            {
                                "type": "tool_result",
                                "tool_use_id": content_block.id,
                                "content": result
                            }
                        ]
                    })
        else:
            # Claude is done using tools, return the final response
            return response.content[0].text
    
    return "Agent reached maximum tool call limit."

Step 4: Running Your Agent

Let's test it with a multi-step query:

result = run_agent(
    "What's the weather in San Francisco? Also, find me the email address for customer ID 1."
)
print(f"\n🤖 Final response:\n{result}")

You'll see Claude first call get_weather, then query_database, and finally synthesize the results into a coherent response.

Advanced Patterns

Parallel Tool Use

Claude can call multiple tools in a single response, which is great for independent tasks. The SDK handles this automatically – just process all tool_use blocks in the response:

# In the agent loop, collect all tool calls first
tool_calls = [
    block for block in response.content 
    if block.type == "tool_use"
]

Execute them all (potentially in parallel with asyncio)

results = [] for tool_call in tool_calls: handler = tool_handlers.get(tool_call.name) result = handler(**tool_call.input) results.append({ "tool_use_id": tool_call.id, "content": result })

Then add all results back to messages

Strict Tool Use

If you want to force Claude to use a specific tool, use the tool_choice parameter:

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "get_weather"},  # Force this tool
    messages=messages
)

Set tool_choice to {"type": "any"} to allow Claude to choose freely (default), or {"type": "auto"} to let Claude decide whether to use tools or not.

Tool Use with Prompt Caching

For production systems, enable prompt caching to reduce costs and latency:

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    tools=tools,
    extra_headers={
        "anthropic-beta": "prompt-caching-2024-07-31"
    },
    messages=[
        {
            "role": "user", 
            "content": [
                {
                    "type": "text",
                    "text": "You are a helpful assistant with tools.",
                    "cache_control": {"type": "ephemeral"}
                },
                {
                    "type": "text", 
                    "text": user_message
                }
            ]
        }
    ]
)

Best Practices

  • Write excellent tool descriptions – Claude relies on descriptions to decide when to use each tool. Be specific about when the tool should be called and what it returns.
  • Handle errors gracefully – Always wrap tool execution in try/except blocks and return meaningful error messages so Claude can recover.
  • Set reasonable limits – Always cap the number of tool call rounds to prevent infinite loops.
  • Validate tool inputs – Never trust Claude's arguments blindly. Validate them before executing sensitive operations.
  • Use streaming for UX – For chat applications, stream Claude's responses to show users what's happening in real-time.

Troubleshooting Common Issues

IssueSolution
Claude doesn't use toolsImprove tool descriptions; reduce the number of tools
Wrong tool calledMake tool names and descriptions more distinct
Infinite tool loopsSet max_tool_rounds; add validation to stop repetitive calls
Tool results ignoredEnsure you're returning results in the correct format

Key Takeaways

  • Tool use enables Claude to interact with external systems – Define tools with clear JSON schemas and descriptive names to help Claude choose correctly.
  • The agent loop is straightforward – Send messages, check for tool_use stop reason, execute tools, return results, and repeat until Claude responds with text.
  • Parallel tool calls are supported natively – Claude can call multiple independent tools in a single response, improving efficiency.
  • Always implement safety guards – Validate inputs, limit tool call rounds, and handle errors gracefully to build production-ready agents.
  • Prompt caching reduces costs – Enable caching for system prompts and tool definitions to optimize API usage in high-volume applications.