Claude API Tool Use and Structured Outputs: Building Reliable AI Applications

Claude API Tool Use for AI Applications

Claude API tool use enables you to give Claude access to external functions — database queries, API calls, calculations, or any custom logic. Instead of asking Claude to generate answers from training data alone, you let it decide when to call your tools and use the results to provide accurate, grounded responses.

This guide covers practical patterns for implementing tool use with the Claude API, from basic function calling to complex multi-step workflows. Whether you are building a customer support bot, data analysis agent, or automation pipeline, these patterns will help you ship reliable AI features.

How Tool Use Works

The tool use flow has three steps: you define available tools, Claude decides which to call and with what parameters, and you execute the function and return results. Claude then uses those results to formulate its final response.

Claude API tool use architecture
Tool use flow: define tools, Claude calls them, return results
import anthropic

client = anthropic.Anthropic()

# Define tools Claude can use
tools = [
    {
        "name": "get_weather",
        "description": "Get current weather for a city. Use this when "
                       "the user asks about weather conditions.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "City name, e.g. 'San Francisco'"
                },
                "units": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature units"
                }
            },
            "required": ["city"]
        }
    },
    {
        "name": "search_products",
        "description": "Search product catalog by name, category, or "
                       "price range. Returns matching products.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string"},
                "category": {"type": "string"},
                "max_price": {"type": "number"},
                "in_stock": {"type": "boolean"}
            },
            "required": ["query"]
        }
    }
]

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

# Check if Claude wants to use a tool
for block in response.content:
    if block.type == "tool_use":
        print(f"Tool: {block.name}")
        print(f"Input: {block.input}")
        # Execute the tool and return results

Complete Tool Use Loop

def run_conversation(user_message: str, tools: list, tool_handlers: dict):
    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 responds with text only, we're done
        if response.stop_reason == "end_turn":
            return extract_text(response)

        # Process tool calls
        messages.append({"role": "assistant", "content": response.content})

        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = tool_handlers.get(block.name)
                if handler:
                    try:
                        result = handler(**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": f"Error: {str(e)}",
                            "is_error": True,
                        })

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

# Register tool handlers
handlers = {
    "get_weather": lambda city, units="celsius": fetch_weather(city, units),
    "search_products": lambda **kwargs: search_catalog(**kwargs),
}

answer = run_conversation("Find laptops under $1000", tools, handlers)

Structured Outputs with Tool Use

Furthermore, you can use tool definitions to force Claude to return data in a specific structure — even when you don’t actually call an external function:

# Use tool_choice to force structured output
structured_tool = {
    "name": "extract_invoice_data",
    "description": "Extract structured data from invoice text",
    "input_schema": {
        "type": "object",
        "properties": {
            "vendor_name": {"type": "string"},
            "invoice_number": {"type": "string"},
            "date": {"type": "string", "format": "date"},
            "line_items": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "description": {"type": "string"},
                        "quantity": {"type": "integer"},
                        "unit_price": {"type": "number"},
                        "total": {"type": "number"}
                    },
                    "required": ["description", "quantity", "total"]
                }
            },
            "subtotal": {"type": "number"},
            "tax": {"type": "number"},
            "total": {"type": "number"}
        },
        "required": ["vendor_name", "invoice_number", "total"]
    }
}

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=[structured_tool],
    tool_choice={"type": "tool", "name": "extract_invoice_data"},
    messages=[{
        "role": "user",
        "content": f"Extract data from this invoice:\n{invoice_text}"
    }]
)

# Claude is forced to return structured data matching the schema
invoice_data = response.content[0].input
AI application with structured outputs
Structured outputs ensure consistent, parseable responses from Claude

Production Patterns

Error Handling and Retries

from anthropic import RateLimitError, APIError
import time

def robust_tool_call(messages, tools, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = client.messages.create(
                model="claude-sonnet-4-6",
                max_tokens=4096,
                tools=tools,
                messages=messages,
            )
            return response
        except RateLimitError:
            wait = 2 ** attempt
            time.sleep(wait)
        except APIError as e:
            if attempt == max_retries - 1:
                raise
            time.sleep(1)
    raise Exception("Max retries exceeded")

When NOT to Use Tool Use

Tool use adds latency and cost (extra tokens for tool definitions). Consequently, skip it when Claude can answer from its training data, when you just need text generation (summaries, translations), or when a simple prompt template produces reliable enough output. As a result, reserve tool use for tasks that genuinely require external data or actions.

Claude API monitoring and observability
Monitoring tool use patterns and latency in production AI applications

Key Takeaways

Claude API tool use bridges the gap between language models and real-world systems. Well-defined tool schemas with clear descriptions produce the most reliable results. Start with 2-3 focused tools, measure accuracy, and expand gradually.

Related Reading

External Resources

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top