Skip to content
Back to Blog
Agentic AI6 min read

Claude API Tool Use: Building Custom AI Workflows

Complete guide to implementing tool use (function calling) with the Claude API. Covers tool definitions, execution patterns, multi-turn conversations, and production best practices.

What Is Tool Use in the Claude API?

Tool use -- also called function calling -- is the mechanism that allows Claude to interact with external systems. Instead of only generating text, Claude can request that your application execute a specific function with specific arguments. Your application runs the function, returns the result, and Claude incorporates that result into its reasoning.

This is the foundation of every agentic application. Without tool use, Claude is limited to its training data and the content you provide in the prompt. With tool use, Claude can query databases, call APIs, read files, send emails, and perform any operation you expose as a tool.

Defining Tools

Tools are defined as JSON schemas that describe the function name, description, and parameters. The quality of your tool definitions directly impacts how reliably Claude uses them.

from anthropic import Anthropic

client = Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Get current weather conditions for a specific location. Returns temperature, humidity, and conditions.",
        "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. Defaults to fahrenheit."
                }
            },
            "required": ["location"]
        }
    },
    {
        "name": "search_database",
        "description": "Search the product database by name, category, or price range. Returns matching products with details.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search query string"
                },
                "category": {
                    "type": "string",
                    "enum": ["electronics", "clothing", "books", "home"],
                    "description": "Product category filter"
                },
                "max_price": {
                    "type": "number",
                    "description": "Maximum price in USD"
                },
                "limit": {
                    "type": "integer",
                    "description": "Maximum number of results to return. Default 10."
                }
            },
            "required": ["query"]
        }
    }
]

Tool Description Best Practices

The tool description is arguably more important than the schema itself. Claude uses it to decide when and whether to use the tool.

  • Be specific about what the tool returns, not just what it does
  • Include edge cases: "Returns an empty array if no results match"
  • Specify units and formats: "Prices are in USD", "Dates are ISO 8601"
  • Note limitations: "Only searches products added in the last 30 days"

The Tool Use Conversation Loop

A complete tool use interaction involves multiple API calls:

import json

def run_tool_loop(user_message: str, tools: list, max_iterations: int = 10) -> str:
    messages = [{"role": "user", "content": user_message}]

    for _ in range(max_iterations):
        response = client.messages.create(
            model="claude-sonnet-4-5-20250514",
            max_tokens=4096,
            tools=tools,
            messages=messages,
        )

        # Check if Claude wants to use a tool
        if response.stop_reason == "tool_use":
            # Extract tool use blocks
            tool_results = []
            assistant_content = response.content

            for block in response.content:
                if block.type == "tool_use":
                    # Execute the tool
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": json.dumps(result),
                    })

            # Add assistant message and tool results
            messages.append({"role": "assistant", "content": assistant_content})
            messages.append({"role": "user", "content": tool_results})

        elif response.stop_reason == "end_turn":
            # Claude is done -- extract final text
            return "".join(
                block.text for block in response.content if block.type == "text"
            )

    return "Max iterations reached without final response."

def execute_tool(name: str, input_data: dict):
    """Route tool calls to actual implementations."""
    if name == "get_weather":
        return fetch_weather(input_data["location"], input_data.get("unit", "fahrenheit"))
    elif name == "search_database":
        return search_products(**input_data)
    else:
        return {"error": f"Unknown tool: {name}"}

Parallel Tool Use

Claude can request multiple tools in a single response. When it does, the tool use blocks appear as separate items in the content array. You should execute them all and return all results.

# Claude might return content like:
# [TextBlock("Let me check both..."), ToolUseBlock(get_weather, ...), ToolUseBlock(search_database, ...)]

# Execute all tool calls (potentially in parallel)
import asyncio

async def execute_tools_parallel(tool_blocks):
    tasks = [
        execute_tool_async(block.name, block.input)
        for block in tool_blocks
        if block.type == "tool_use"
    ]
    return await asyncio.gather(*tasks)

Forcing Tool Use

Sometimes you want Claude to always use a specific tool rather than answering from its own knowledge. Use the tool_choice parameter:

# Force Claude to use a specific tool
response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=4096,
    tools=tools,
    tool_choice={"type": "tool", "name": "search_database"},
    messages=[{"role": "user", "content": "Find me headphones under $100"}]
)

# Force Claude to use any tool (must use at least one)
response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=4096,
    tools=tools,
    tool_choice={"type": "any"},
    messages=[{"role": "user", "content": "What is the weather in Tokyo?"}]
)

# Let Claude decide (default behavior)
response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=4096,
    tools=tools,
    tool_choice={"type": "auto"},
    messages=[{"role": "user", "content": "Tell me about Claude"}]
)

Error Handling in Tool Results

When a tool execution fails, return the error as a tool result with is_error: true. Claude will see the error and can decide how to proceed -- often retrying with different parameters or informing the user.

def execute_tool_safe(name: str, input_data: dict) -> dict:
    try:
        result = execute_tool(name, input_data)
        return {
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": json.dumps(result),
        }
    except Exception as e:
        return {
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": f"Error executing {name}: {str(e)}",
            "is_error": True,
        }

Advanced Pattern: Tool Chains

Some workflows require Claude to call tools in a specific sequence. Instead of hardcoding the sequence, describe the workflow in the system prompt and let Claude orchestrate:

system_prompt = """You are a customer support agent with access to these tools:
- lookup_customer: Find a customer by email or phone
- get_order_history: Get recent orders for a customer ID
- create_ticket: Create a support ticket
- send_email: Send an email to a customer

Workflow for order issues:
1. First lookup the customer
2. Then check their order history
3. Create a ticket with the relevant details
4. Send confirmation email to the customer

Always follow this sequence. Do not skip steps."""

Token Cost Implications

Tool definitions consume input tokens on every API call in the conversation. A large tool schema adds significant cost over multi-turn conversations.

Number of Tools Avg Schema Size Tokens Per Call Cost at Sonnet Rate
5 tools 200 tokens each 1,000 $0.003
20 tools 200 tokens each 4,000 $0.012
50 tools 200 tokens each 10,000 $0.030

For applications with many tools, consider using prompt caching to cache the tool definitions. With caching, you pay full price on the first call and 90% less on subsequent calls.

Production Checklist

Before deploying tool use in production:

  • Validate all tool inputs before execution (do not trust Claude's output blindly)
  • Set timeouts on tool execution to prevent hung API calls
  • Log every tool call with input, output, and duration for debugging
  • Rate-limit tool execution to prevent runaway loops
  • Sanitize tool outputs before returning them (strip sensitive data)
  • Test with adversarial prompts to verify Claude does not misuse tools
  • Monitor token usage to catch unexpected cost spikes from tool-heavy conversations
Share this article
N

NYC News

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.