Claude’s conversational ability is impressive. But conversation alone has a fundamental limitation: Claude can only work with information that is already in its training data or that you provide directly in the conversation. It cannot look something up in your database. It cannot make an API call to get current data. It cannot perform a calculation that requires external logic. It cannot take an action in another system.
Tool Use (also called Function Calling) removes this limitation entirely.
Tool Use lets you define external functions — calculators, database queries, API calls, file operations, anything — and give Claude access to them. When Claude encounters a task that one of these tools can help with, it calls the tool, receives the result, and incorporates that result into its response. The human-Claude conversation becomes Claude-plus-tools — a significantly more capable system.
This is the architectural foundation of AI agents: systems where Claude does not just generate text but takes actions in the world, retrieves real information, and integrates with other software.
This guide covers the complete Tool Use architecture, how to define and implement tools, the tool call lifecycle, and five working examples you can adapt for real use cases.
🔗 This is Post #12 in the Claude Unlocked series. Tool Use is closely related to Computer Use (Post #11) — Computer Use is a specialized set of tools for GUI interaction. Claude for Developers: Advanced Techniques (Post #15) covers production patterns for tool-enabled systems. Start with The Claude API for Non-Developers (Post #9) if the API setup is new to you.
What Is Tool Use and Why Does It Matter?
The Core Problem Tool Use Solves
Without Tool Use, Claude is limited to:
- Its training data (with a knowledge cutoff)
- What you tell it in the conversation
- Reasoning and text generation
With Tool Use, Claude can:
- Query your database for current data
- Call external APIs for real-time information
- Execute calculations with specialized logic
- Read and write files
- Search the web for current information
- Send notifications, create records, or trigger workflows
The difference between “an AI that can write about your data” and “an AI that can work with your data” is Tool Use.
The Mental Model: Claude as Orchestrator
When you give Claude tools, it becomes an orchestrator — it reasons about what tools to use, in what order, with what inputs, to accomplish your goal. This is different from scripted automation where you pre-define every step.
Without Tool Use: You tell Claude “Summarize our sales data.” Claude can only summarize data you paste into the conversation.
With Tool Use: You tell Claude “Summarize our sales data.” Claude calls get_sales_data(), receives the current data, analyzes it, and produces a summary — automatically, from live data.
The Tool Use Architecture
How a Tool Call Works
The Tool Use lifecycle happens in four stages:
1. YOU DEFINE tools (name, description, parameters)
2. USER sends a message to Claude
3. CLAUDE decides to use a tool and returns a tool_call
4. YOU execute the tool and return the result
5. CLAUDE incorporates the result and responds
This is critical to understand: Claude does not execute tools directly. It requests tool calls, you (or your application) execute them, and you return the results. Claude receives the result and continues reasoning.
This design is intentional — it keeps humans in control of what tools do and prevents Claude from taking actions you have not authorized.
The Four-Step API Flow
# Step 1: Define your tools and send the user message
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[your_tool_definitions],
messages=[{"role": "user", "content": user_message}]
)
# Step 2: Check if Claude wants to use a tool
if response.stop_reason == "tool_use":
# Step 3: Execute the tool
tool_result = execute_your_tool(response)
# Step 4: Return the result and get Claude's final response
final_response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[your_tool_definitions],
messages=[
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{"role": "user", "content": [
{
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": str(tool_result)
}
]}
]
)
Defining Tools: The JSON Schema
Tools are defined using JSON Schema — a standard format for describing data structures. Each tool definition has three parts:
{
"name": "tool_name", # Unique identifier
"description": "...", # What this tool does (Claude reads this)
"input_schema": { # What inputs the tool accepts
"type": "object",
"properties": {
"parameter_name": {
"type": "string", # or "number", "boolean", "array", "object"
"description": "What this parameter is for"
}
},
"required": ["parameter_name"] # Which params are required
}
}
The Description Is Critical
The description field is what Claude reads to decide when and how to use a tool. A good description:
- Clearly states what the tool does
- Indicates when to use it (and implicitly when not to)
- Notes any important limitations
Weak description:
"description": "Gets customer data"
Strong description:
"description": "Retrieves customer information from the CRM database "
"by customer ID. Use this when the user asks about a "
"specific customer's details, account status, or history. "
"Requires a valid customer_id (integer). Returns name, "
"email, account status, and last purchase date."
The difference: the strong description tells Claude exactly when to use this tool, what input it needs, and what to expect back.
Five Practical Tool Examples
Tool 1: Web Search
The most universally useful tool — giving Claude access to current information beyond its training data.
import anthropic
import requests
client = anthropic.Anthropic()
# Define the web search tool
tools = [
{
"name": "web_search",
"description": "Search the web for current information on any topic. "
"Use this when the user asks about recent events, "
"current prices, recent news, or any topic that "
"requires up-to-date information.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query to look up"
}
},
"required": ["query"]
}
}
]
def perform_web_search(query: str) -> str:
"""
Perform a web search using your preferred search API.
This example uses a hypothetical search API —
replace with Serper, SerpAPI, Brave Search API, etc.
"""
# Replace with your actual search API
response = requests.get(
"https://api.search-provider.com/search",
params={"q": query, "num": 5},
headers={"Authorization": "Bearer YOUR_SEARCH_API_KEY"}
)
results = response.json()
# Format results for Claude
formatted = []
for result in results.get("organic", [])[:5]:
formatted.append(f"Title: {result['title']}\n"
f"URL: {result['link']}\n"
f"Snippet: {result['snippet']}\n")
return "\n---\n".join(formatted)
def chat_with_search(user_message: str) -> str:
"""Send a message to Claude with web search capability"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
tools=tools,
messages=[{"role": "user", "content": user_message}]
)
# Handle tool use
while response.stop_reason == "tool_use":
tool_use = next(b for b in response.content
if b.type == "tool_use")
# Execute the search
search_results = perform_web_search(tool_use.input["query"])
# Return results to Claude
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
tools=tools,
messages=[
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{"role": "user", "content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": search_results
}
]}
]
)
return response.content[0].text
# Usage
answer = chat_with_search("What is the current price of gold per ounce?")
print(answer)
Tool 2: Database Query Tool
Give Claude read access to your database for answering questions about your data.
import sqlite3
import json
# Define the database query tool
database_tool = {
"name": "query_database",
"description": "Query the customer and orders database to retrieve "
"business information. Use for questions about customers, "
"orders, revenue, and product data. "
"IMPORTANT: Only SELECT queries are allowed.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "A SQL SELECT query to run against the database. "
"Available tables: customers (id, name, email, "
"created_at), orders (id, customer_id, amount, "
"status, created_at), products (id, name, price)"
}
},
"required": ["query"]
}
}
def execute_db_query(sql_query: str) -> str:
"""
Execute a read-only SQL query against the database.
Returns results as formatted JSON.
"""
# Safety check: only allow SELECT queries
if not sql_query.strip().upper().startswith("SELECT"):
return "ERROR: Only SELECT queries are permitted."
try:
conn = sqlite3.connect("your_database.db")
cursor = conn.cursor()
cursor.execute(sql_query)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
results = [dict(zip(columns, row)) for row in rows]
conn.close()
return json.dumps(results, indent=2, default=str)
except Exception as e:
return f"Query error: {str(e)}"
Safety note: This implementation includes a SELECT-only check. In production, also use a read-only database user, validate queries before execution, and consider query complexity limits.
Tool 3: Calculator Tool
For calculations requiring precision beyond Claude’s arithmetic reliability.
import math
import ast
import operator
# Define the calculator tool
calculator_tool = {
"name": "calculator",
"description": "Performs precise mathematical calculations. "
"Use for arithmetic, percentages, compound interest, "
"unit conversions, and any calculation where precision "
"matters. Supports: +, -, *, /, **, sqrt, log, exp, "
"sin, cos, tan, and Python math functions.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "A mathematical expression to evaluate. "
"Examples: '2 ** 10', 'math.sqrt(144)', "
"'1000 * (1 + 0.05) ** 10'"
}
},
"required": ["expression"]
}
}
def safe_calculate(expression: str) -> str:
"""
Safely evaluate a mathematical expression.
"""
# Allowed operations only
allowed_names = {
"math": math,
"abs": abs,
"round": round,
"min": min,
"max": max,
}
try:
# Use ast to safely parse and evaluate
tree = ast.parse(expression, mode='eval')
result = eval(compile(tree, '', 'eval'),
{"__builtins__": {}},
allowed_names)
return f"Result: {result}"
except Exception as e:
return f"Calculation error: {str(e)}"
Tool 4: Email Sending Tool (With Confirmation)
A tool that drafts emails for human review before sending — a common pattern for agentic workflows.
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# Define the email tool with explicit draft behavior
email_tool = {
"name": "send_email",
"description": "Drafts and sends emails. ALWAYS show the draft to "
"the user for confirmation before calling this tool. "
"Use for composing and sending emails when the user "
"explicitly requests an email be sent.",
"input_schema": {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "Recipient email address"
},
"subject": {
"type": "string",
"description": "Email subject line"
},
"body": {
"type": "string",
"description": "Email body text"
},
"confirmed": {
"type": "boolean",
"description": "Whether the user has confirmed they want "
"this email sent. Only call this tool when "
"confirmed is true."
}
},
"required": ["to", "subject", "body", "confirmed"]
}
}
def send_email(to: str, subject: str, body: str, confirmed: bool) -> str:
if not confirmed:
return ("Email not sent — user confirmation required. "
"Show the draft to the user and ask them to confirm.")
# Your SMTP configuration
msg = MIMEMultipart()
msg['From'] = "your@email.com"
msg['To'] = to
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))
try:
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login("your@email.com", "your_app_password")
server.send_message(msg)
return f"Email successfully sent to {to}"
except Exception as e:
return f"Failed to send email: {str(e)}"
Tool 5: Multi-Tool System (Putting It All Together)
A complete example combining multiple tools into a useful assistant:
import anthropic
client = anthropic.Anthropic()
# Combine multiple tools
tools = [
{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"country_code": {
"type": "string",
"description": "2-letter country code"
}
},
"required": ["city"]
}
},
{
"name": "get_stock_price",
"description": "Get current stock price for a ticker symbol",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Stock ticker symbol (e.g., AAPL, GOOGL)"
}
},
"required": ["ticker"]
}
},
{
"name": "unit_converter",
"description": "Convert between units of measurement",
"input_schema": {
"type": "object",
"properties": {
"value": {"type": "number", "description": "Value to convert"},
"from_unit": {"type": "string", "description": "Source unit"},
"to_unit": {"type": "string", "description": "Target unit"}
},
"required": ["value", "from_unit", "to_unit"]
}
}
]
def handle_tool_call(tool_name: str, tool_input: dict) -> str:
"""Route tool calls to appropriate implementations"""
if tool_name == "get_weather":
return get_weather_data(tool_input["city"],
tool_input.get("country_code", ""))
elif tool_name == "get_stock_price":
return get_stock_data(tool_input["ticker"])
elif tool_name == "unit_converter":
return convert_units(tool_input["value"],
tool_input["from_unit"],
tool_input["to_unit"])
else:
return f"Unknown tool: {tool_name}"
def run_agent(user_message: str) -> str:
"""Run the multi-tool agent with automatic tool handling"""
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
tools=tools,
messages=messages
)
# No tool calls — return the final response
if response.stop_reason == "end_turn":
return response.content[0].text
# Handle all tool calls in this response
if response.stop_reason == "tool_use":
messages.append({
"role": "assistant",
"content": response.content
})
# Process each tool call
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": result
})
messages.append({
"role": "user",
"content": tool_results
})
else:
# Unexpected stop reason
break
return "Task completed"
# Usage examples
print(run_agent("What's the weather in Tokyo and the current Apple stock price?"))
print(run_agent("Convert 5.7 miles to kilometers"))
Tool Design Best Practices
Principle 1: One Tool, One Responsibility
Tools should do one thing well. A search_and_summarize_web tool that both searches and summarizes is harder for Claude to use appropriately than a search_web tool (returns results) and a separate summarize_text tool (processes text). Single-responsibility tools are more reusable and easier to debug.
Principle 2: Descriptions Over Names
Tool names matter less than tool descriptions. Claude reads descriptions to decide when to use tools. Write descriptions as if you are training a new employee on what each tool is for — explain the when, not just the what.
Principle 3: Validate Inputs Before Executing
Never blindly execute tool inputs from Claude. Validate that inputs match expected formats, check for injection attempts, verify permissions, and enforce operation limits before execution:
def safe_db_query(query: str) -> str:
# Validate input
if len(query) > 1000:
return "Error: Query too long"
if not query.strip().upper().startswith("SELECT"):
return "Error: Only SELECT queries allowed"
if ";" in query[:-1]: # Prevent multiple statements
return "Error: Multiple statements not allowed"
# Then execute
return execute_query(query)
Principle 4: Return Structured, Informative Results
Tool results should give Claude exactly what it needs to proceed. Too little context and Claude cannot incorporate the result. Too much irrelevant data and you waste tokens.
Poor result:
{"data": [{"id": 1, "val": 234.5}, {"id": 2, "val": 198.3}]}
Good result:
Sales data for Q1 2026:
- January: $234,500 (8% above target)
- February: $198,300 (4% below target)
- Total: $432,800 (2% above Q1 target of $425,000)
Principle 5: Handle Errors Gracefully
Tool errors should return human-readable messages, not raw Python exceptions. Claude incorporates error messages into its responses — a readable error produces a helpful response:
def get_customer(customer_id: int) -> str:
try:
customer = db.get_customer(customer_id)
if not customer:
return f"No customer found with ID {customer_id}"
return json.dumps(customer)
except ValueError:
return f"Invalid customer ID: {customer_id}"
except ConnectionError:
return "Database connection failed. Please try again."
except Exception as e:
return f"Unexpected error retrieving customer: {str(e)}"
When Claude Decides to Use a Tool (and When It Doesn’t)
Understanding Claude’s tool selection behavior helps you write better tools and prompts.
Claude uses a tool when:
- The user’s request requires information or capability the tool provides
- The tool description clearly matches the need
- Claude judges that the tool would improve the response
Claude may not use a tool when:
- It believes it already has the information from training
- The tool description is vague about its purpose
- The request seems answerable without the tool
- Multiple tools could satisfy the need and Claude is uncertain which to use
Forcing Tool Use When Needed
If you need Claude to use a specific tool, be explicit in the system prompt:
system = """You are a customer service assistant with access to our CRM.
ALWAYS use the get_customer tool when a user asks about any specific
customer — do not rely on conversation history or assumptions.
Fresh data from the tool is required for every customer-related response."""
Parallel Tool Calls
When a task requires multiple tools that do not depend on each other, Claude can request multiple tool calls simultaneously — significantly improving efficiency.
# Claude can return multiple tool_use blocks in one response
for block in response.content:
if block.type == "tool_use":
# Claude might call get_weather AND get_stock_price simultaneously
# Execute both, return both results
tool_results.append(execute_tool(block))
Design your tools to be independent when possible — parallel execution reduces round-trips and speeds up complex tasks.
Tool Use in Production: Key Considerations
Rate Limiting and Cost Management
Each tool call round-trip counts as separate API requests. For high-volume production use:
- Implement caching for tool results that are expensive or rate-limited
- Set reasonable max token limits for tool responses
- Monitor tool call frequency in your usage dashboard
Timeout Handling
External API calls can time out. Your tool implementations need timeout handling:
import requests
def search_web_with_timeout(query: str) -> str:
try:
response = requests.get(
"https://search-api.com/search",
params={"q": query},
timeout=5 # 5 second timeout
)
return response.json()["results"]
except requests.Timeout:
return "Search timed out. Try a more specific query."
except requests.RequestException as e:
return f"Search failed: {str(e)}"
Logging Tool Calls
Log every tool call and result for debugging and auditing:
import logging
def execute_tool_with_logging(tool_name: str, inputs: dict) -> str:
logging.info(f"Tool called: {tool_name}, inputs: {inputs}")
result = execute_tool(tool_name, inputs)
logging.info(f"Tool result: {result[:200]}...") # Log first 200 chars
return result
Conclusion
Tool Use is the capability that transforms Claude from an AI you query into an AI that works with your systems. The architecture is straightforward once you understand it — Claude requests tool calls, you execute them, you return the results. What makes it powerful is that Claude reasons about which tools to use, when, and with what inputs — adapting to the actual task rather than following a pre-scripted sequence.
The five examples in this guide — web search, database query, calculator, email (with confirmation), and multi-tool orchestration — represent the most common Tool Use patterns. Each is a working template that, with appropriate customization for your specific APIs and data sources, can be deployed in production.
The architectural principle worth internalizing: tools give Claude access to the real world. Your job is to define what access is appropriate, build the tools carefully, validate inputs before executing, and return results that give Claude what it needs to continue reasoning effectively.
Your next step: Start with the calculator tool — it requires no external APIs, demonstrates the full Tool Use lifecycle, and gives you a working foundation to add more sophisticated tools on top of.
📚 Continue the Series:
- ← Previous Claude Computer Use: Letting Claude Browse, Click, and Operate Your Computer
- Next → Claude for Students and Academics: Research, Essays, and Learning Without Shortcuts
- Advanced patterns Claude for Developers: Advanced Techniques
- Building workflows Building with Claude: Real Apps, Workflows, and Automations
- API foundation The Claude API for Non-Developers
Last updated: April 2026. Tool Use API parameters, available tool types, and parallel tool call behavior are updated by Anthropic. Verify current specifications at docs.anthropic.com/tool-use.
⚠️ Validate all tool inputs before execution. Never trust user-provided data in tool calls without sanitization. Implement appropriate access controls and permission checks in your tool implementations. Monitor tool usage in production for unexpected costs and behavior.