How to Use Claude API Function Calling: Complete Tool Use Guide for Building AI Agents

What Function Calling Enables

Without function calling, Claude can only respond with text. With function calling (tool use), Claude can interact with external systems: query databases, call APIs, search the web, send emails, execute code, and perform any action you expose as a tool.

This transforms Claude from a text generator into an agent — an AI that can take actions, gather information, and make decisions based on real-world data. A customer support bot that can look up order status, a research assistant that can search your internal knowledge base, a coding assistant that can run tests — all of these require function calling.

The interaction pattern is simple:

  1. You define tools (functions Claude can call)
  2. Claude decides when and which tool to call
  3. Your code executes the function
  4. You send the result back to Claude
  5. Claude uses the result to formulate its response

Step 1: Define Your Tools

Tool Definition Schema

Each tool is defined as a JSON object with a name, description, and input schema:

import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a specific location. Use this when the user asks about weather conditions.",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City name, e.g., 'San Francisco, CA' or 'Tokyo, Japan'"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit. Default: celsius"
                }
            },
            "required": ["location"]
        }
    }
]

Writing Good Tool Descriptions

The description tells Claude when and how to use the tool. Be specific:

# BAD: Too vague
"description": "Gets data"

# BAD: Too technical (Claude doesn't need implementation details)
"description": "Calls the internal REST API at /api/v2/weather with OAuth2 bearer token"

# GOOD: Clear purpose and usage guidance
"description": "Get the current weather for a specific location. Returns temperature, conditions, humidity, and wind speed. Use this when the user asks about current weather, not for forecasts."

Parameter Descriptions Matter

Claude uses parameter descriptions to determine what values to pass:

"properties": {
    "query": {
        "type": "string",
        "description": "The search query. Use natural language, not keywords. Example: 'latest quarterly revenue for Apple' not 'AAPL Q4 revenue'"
    },
    "max_results": {
        "type": "integer",
        "description": "Maximum number of results to return. Default: 5. Maximum: 20."
    },
    "date_range": {
        "type": "string",
        "enum": ["today", "this_week", "this_month", "this_year", "all_time"],
        "description": "Filter results by recency. Use 'today' for breaking news, 'this_year' for recent developments."
    }
}

Multiple Tools

Most applications define 3-10 tools:

tools = [
    {
        "name": "search_knowledge_base",
        "description": "Search the company knowledge base for product information, policies, and procedures.",
        "input_schema": { ... }
    },
    {
        "name": "get_order_status",
        "description": "Look up the current status of a customer order by order ID or email address.",
        "input_schema": { ... }
    },
    {
        "name": "create_support_ticket",
        "description": "Create a new support ticket when the issue cannot be resolved in this conversation.",
        "input_schema": { ... }
    },
    {
        "name": "send_email",
        "description": "Send an email to the customer. Use only when the customer explicitly requests email communication.",
        "input_schema": { ... }
    }
]

Step 2: Send a Request with Tools

Basic Request

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

Checking the Response Type

Claude’s response may contain text, a tool use request, or both:

# Check what Claude wants to do
for block in message.content:
    if block.type == "text":
        print(f"Claude says: {block.text}")
    elif block.type == "tool_use":
        print(f"Claude wants to call: {block.name}")
        print(f"With arguments: {block.input}")
        print(f"Tool use ID: {block.id}")

When Claude decides a tool is needed, the response’s stop_reason will be "tool_use" instead of "end_turn".

Step 3: Handle the Tool Use Response

Extract and Execute

import json

def handle_tool_call(tool_name, tool_input):
    """Execute the tool and return the result."""
    if tool_name == "get_weather":
        # Call your actual weather API
        result = fetch_weather(
            location=tool_input["location"],
            unit=tool_input.get("unit", "celsius")
        )
        return result
    elif tool_name == "get_order_status":
        result = lookup_order(tool_input["order_id"])
        return result
    elif tool_name == "search_knowledge_base":
        result = search_kb(tool_input["query"])
        return result
    else:
        return {"error": f"Unknown tool: {tool_name}"}

# Process Claude's response
for block in message.content:
    if block.type == "tool_use":
        tool_result = handle_tool_call(block.name, block.input)
        tool_use_id = block.id

Step 4: Return Tool Results

Send the Result Back to Claude

# Build the conversation with tool results
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    messages=[
        # Original user message
        {"role": "user", "content": "What's the weather like in Tokyo?"},
        # Claude's tool use request
        {"role": "assistant", "content": message.content},
        # Tool result
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,
                    "content": json.dumps(tool_result)
                }
            ]
        }
    ]
)

# Claude now responds with the final answer
print(response.content[0].text)
# "The current weather in Tokyo is 22°C with partly cloudy
#  skies. Humidity is at 65% with light winds from the east
#  at 12 km/h."

Tool Result Format

Tool results can be strings or structured data:

# Simple string result
{"type": "tool_result", "tool_use_id": id, "content": "Order #1234 shipped on March 25"}

# Structured result (JSON string)
{"type": "tool_result", "tool_use_id": id, "content": json.dumps({
    "order_id": "1234",
    "status": "shipped",
    "tracking": "1Z999AA10123456784",
    "estimated_delivery": "2026-03-29"
})}

# Error result
{"type": "tool_result", "tool_use_id": id, "content": "Error: Order not found", "is_error": True}

Step 5: Build Multi-Step Agents

The Agent Loop

Complex tasks require multiple tool calls. Build a loop:

def run_agent(user_message, tools, system_prompt=None):
    messages = [{"role": "user", "content": user_message}]

    while True:
        # Call Claude
        response = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=4096,
            system=system_prompt or "",
            tools=tools,
            messages=messages
        )

        # If Claude is done (no more tool calls), return
        if response.stop_reason == "end_turn":
            return response.content[0].text

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

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

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

# Usage
answer = run_agent(
    "Find the order for john@example.com and check if it's been delivered",
    tools=support_tools,
    system_prompt="You are a customer support agent for Acme Corp."
)

Multi-Step Example Flow

User: "Find John's latest order and if it hasn't arrived, create a support ticket."

Step 1: Claude calls get_order_by_email(email="john@example.com")
  Result: {order_id: "5678", status: "in_transit", expected: "2026-03-28"}

Step 2: Claude calls get_tracking_details(order_id="5678")
  Result: {carrier: "FedEx", last_update: "In transit - Chicago", delayed: true}

Step 3: Claude calls create_support_ticket(
  customer_email="john@example.com",
  subject="Delayed order #5678",
  description="Order expected 3/28, currently delayed in transit at Chicago"
)
  Result: {ticket_id: "T-9012", status: "created"}

Step 4: Claude responds to user:
"I found John's order #5678. It's currently in transit but delayed — last seen in
Chicago, originally expected March 28. I've created support ticket T-9012 to track
the delay. The support team will follow up with John directly."

Step 6: Production Error Handling

Parameter Validation

Validate tool inputs before execution:

def handle_tool_call(tool_name, tool_input):
    if tool_name == "get_order_status":
        order_id = tool_input.get("order_id")
        if not order_id:
            return {"error": "order_id is required"}
        if not order_id.isalnum():
            return {"error": "Invalid order_id format"}

        try:
            return lookup_order(order_id)
        except OrderNotFoundError:
            return {"error": f"Order {order_id} not found"}
        except DatabaseError as e:
            return {"error": "Unable to look up order. Please try again."}
        except Exception as e:
            logger.error(f"Unexpected error in get_order_status: {e}")
            return {"error": "An unexpected error occurred"}

Preventing Infinite Loops

def run_agent(user_message, tools, max_iterations=10):
    messages = [{"role": "user", "content": user_message}]

    for iteration in range(max_iterations):
        response = client.messages.create(...)

        if response.stop_reason == "end_turn":
            return response.content[0].text

        # Process tool calls...

    # If we hit max iterations, force a response
    return "I was unable to complete this task. Please try rephrasing your request."

Handling Parallel Tool Calls

Claude may request multiple tools simultaneously:

# Claude might return multiple tool_use blocks
tool_results = []
for block in response.content:
    if block.type == "tool_use":
        # Execute each tool (can be parallelized)
        result = handle_tool_call(block.name, block.input)
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": json.dumps(result)
        })

# Send all results back at once
messages.append({"role": "user", "content": tool_results})

Rate Limiting and Retries

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=1, max=10))
def call_claude_with_tools(messages, tools):
    return client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        tools=tools,
        messages=messages
    )

Design Patterns for Tool Use

Pattern 1: Retrieval-Augmented Generation (RAG)

tools = [{
    "name": "search_documents",
    "description": "Search the company document repository. Use this before answering any question about company policies, products, or procedures.",
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "Natural language search query"},
            "collection": {"type": "string", "enum": ["policies", "products", "procedures", "faq"]}
        },
        "required": ["query"]
    }
}]

system = """You are a company assistant. ALWAYS search the document
repository before answering questions about company information.
Never answer from general knowledge — only from search results."""

Pattern 2: Action Agent

tools = [
    {"name": "send_slack_message", ...},
    {"name": "create_jira_ticket", ...},
    {"name": "schedule_meeting", ...},
    {"name": "update_spreadsheet", ...}
]

system = """You are an executive assistant that can take actions.
Before taking any action, confirm with the user what you plan to do.
Never send messages or create tickets without explicit user approval."""

Pattern 3: Data Analysis Agent

tools = [
    {"name": "query_database", ...},
    {"name": "run_calculation", ...},
    {"name": "create_chart", ...}
]

system = """You are a data analyst. When asked a question about data:
1. First, query the database to get raw data
2. Then, perform calculations as needed
3. Finally, create a chart if the user would benefit from visualization
Explain your methodology alongside the results."""

Best Practices

Tool Naming

# GOOD: verb_noun format, clear action
"get_order_status"
"search_knowledge_base"
"create_support_ticket"
"send_notification"

# BAD: ambiguous or generic
"process"
"handle_request"
"do_thing"
"helper"

Limiting Tool Scope

Give each tool a narrow, well-defined purpose:

# BAD: One tool that does everything
"name": "database_operation",
"description": "Run any database operation"

# GOOD: Separate tools with clear boundaries
"name": "get_user_profile" — read-only, single user
"name": "update_user_email" — write, specific field
"name": "list_user_orders" — read-only, related data

Using the System Prompt to Guide Tool Selection

system = """When helping with customer support:
- Use search_knowledge_base FIRST for product questions
- Use get_order_status when the customer mentions an order
- Use create_support_ticket ONLY when you cannot resolve the issue
- NEVER use send_email without explicit customer consent

Tool usage priority:
1. Try to answer from knowledge base
2. Look up specific account data if needed
3. Escalate to ticket only as last resort"""

Frequently Asked Questions

How many tools can I define?

Claude supports up to 128 tools per request. Practically, 5-15 tools is the sweet spot. More tools increase the chance of Claude selecting the wrong one. If you need more than 15, consider grouping related functions into broader tools or using a two-stage approach (first select the category, then the specific tool).

Does Claude always use the right tool?

Claude is highly accurate at tool selection when descriptions are clear and specific. Ambiguous descriptions or overlapping tool purposes cause errors. Write descriptions that answer: “When should Claude use this tool, and when should it NOT?”

How do I handle tool calls that take a long time?

For synchronous APIs: increase your timeout. For async operations (sending email, creating tickets): return immediately with a confirmation (“Ticket created, ID: T-123”) rather than waiting for the operation to complete fully.

Can Claude call tools without user permission?

Claude will call tools whenever it determines they are needed to answer the user’s question. To require user confirmation before executing sensitive tools, implement a confirmation step in your application logic — not in the tool definition.

What happens if a tool returns an error?

Send the error back as a tool_result with is_error: true. Claude will acknowledge the error and either try an alternative approach, ask the user for different information, or explain that it could not complete the request.

Can I use tool use with streaming?

Yes. Tool use events appear in the stream as content_block_start events with type: "tool_use". You can begin executing the tool as soon as you receive the complete input field, without waiting for the full response to finish streaming.

Explore More Tools

Grok Best Practices for Academic Research and Literature Discovery: Leveraging X/Twitter for Scholarly Intelligence Best Practices Grok Best Practices for Content Strategy: Identify Trending Topics Before They Peak and Create Content That Captures Demand Best Practices Grok Case Study: How a DTC Beauty Brand Used Real-Time Social Listening to Save Their Product Launch Case Study Grok Case Study: How a Pharma Company Tracked Patient Sentiment During a Drug Launch and Caught a Safety Signal 48 Hours Before the FDA Case Study Grok Case Study: How a Disaster Relief Nonprofit Used Real-Time X/Twitter Monitoring to Coordinate Emergency Response 3x Faster Case Study Grok Case Study: How a Political Campaign Used X/Twitter Sentiment Analysis to Reshape Messaging and Win a Swing District Case Study How to Use Grok for Competitive Intelligence: Track Product Launches, Pricing Changes, and Market Positioning in Real Time How-To Grok vs Perplexity vs ChatGPT Search for Real-Time Information: Which AI Search Tool Is Most Accurate in 2026? Comparison How to Use Grok for Crisis Communication Monitoring: Detect, Assess, and Respond to PR Emergencies in Real Time How-To How to Use Grok for Product Improvement: Extract Customer Feedback Signals from X/Twitter That Your Support Team Misses How-To How to Use Grok for Conference Live Monitoring: Extract Event Insights and Identify Networking Opportunities in Real Time How-To How to Use Grok for Influencer Marketing: Discover, Vet, and Track Influencer Partnerships Using Real X/Twitter Data How-To How to Use Grok for Job Market Analysis: Track Industry Hiring Trends, Layoff Signals, and Salary Discussions on X/Twitter How-To How to Use Grok for Investor Relations: Track Earnings Sentiment, Analyst Reactions, and Shareholder Concerns in Real Time How-To How to Use Grok for Recruitment and Talent Intelligence: Identifying Hiring Signals from X/Twitter Data How-To How to Use Grok for Startup Fundraising Intelligence: Track Investor Sentiment, VC Activity, and Funding Trends on X/Twitter How-To How to Use Grok for Regulatory Compliance Monitoring: Real-Time Policy Tracking Across Industries How-To NotebookLM Best Practices for Financial Analysts: Due Diligence, Investment Research & Risk Factor Analysis Across SEC Filings Best Practices NotebookLM Best Practices for Teachers: Build Curriculum-Aligned Lesson Plans, Study Guides, and Assessment Materials from Your Own Resources Best Practices NotebookLM Case Study: How an Insurance Company Built a Claims Processing Training System That Cut Errors by 35% Case Study