Prompting Claude with Tool Use
Tool use — also called function calling — is one of Claude's most powerful capabilities for building real applications. Instead of generating text that describes an action, Claude can generate structured requests to call actual functions in your system. This bridges the gap between a language model that talks about doing things and one that actually does them.
Understanding how to define tools, guide Claude's tool selection, and handle results is essential for building reliable AI-powered applications.
How Tool Use Works
When you send a request to the Claude API with tools defined, Claude can choose to respond with a tool_use content block instead of (or in addition to) a regular text response. This block contains:
- The name of the tool Claude wants to call
- The input — a JSON object matching the tool's input schema
Your application then executes the tool, sends the result back to Claude as a tool_result message, and Claude uses that result to formulate its final response.
The flow looks like this:
1. You send: user message + tool definitions
2. Claude responds: tool_use (name + input)
3. You execute: run the actual function with Claude's input
4. You send: tool_result (the output of the function)
5. Claude responds: final text answer incorporating the tool result
This is not a single API call — it is a multi-turn conversation where your application acts as the intermediary between Claude and the actual functions.
Defining Tool Schemas
Tools are defined using JSON Schema to describe their name, description, and input parameters. The quality of your tool definitions directly impacts how well Claude uses them.
Here is a well-defined tool:
{
"name": "get_weather",
"description": "Get the current weather conditions for a specific city. Returns temperature in Fahrenheit, weather condition (sunny, cloudy, rainy, etc.), and humidity percentage. Use this when the user asks about current weather, temperature, or conditions in a specific location.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g., 'San Francisco' or 'London, UK'. Include country for ambiguous city names."
},
"units": {
"type": "string",
"enum": ["fahrenheit", "celsius"],
"description": "Temperature unit. Defaults to fahrenheit for US cities, celsius for all others."
}
},
"required": ["city"]
}
}
Compare that to a poorly defined tool:
{
"name": "weather",
"description": "Gets weather",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string"
}
},
"required": ["location"]
}
}
The first definition tells Claude exactly what the tool returns, when to use it, what format the input should be in, and what the edge cases are. The second forces Claude to guess.
Key principles for tool definitions:
-
Descriptions are prompts. The tool description is not just documentation — it is a prompt that guides Claude's behavior. Write it as if you are instructing Claude when and how to use the tool.
-
Parameter descriptions matter. Each parameter's description should include the expected format, valid values, and any defaults. Claude reads these to construct its input.
-
Use enums for constrained values. If a parameter only accepts certain values, use
enumrather than describing valid options in the description text. Enums are enforced structurally. -
Mark required parameters explicitly. The
requiredarray tells Claude which parameters it must always provide versus which it can omit.
Guiding Tool Selection with System Prompts
When you provide multiple tools, Claude decides which one to use based on the user's request and the tool descriptions. You can influence this decision through the system prompt.
Prioritizing tools:
You have access to two data sources:
- Use get_customer_from_crm for customer information (name, plan, account status)
- Use query_analytics_db for usage metrics and billing data
Always check the CRM first before querying analytics. If the user asks about
a customer's usage, you still need their customer ID from the CRM first.
Restricting tool use:
You have access to the following tools. Important rules:
- NEVER call delete_record unless the user has explicitly confirmed deletion
in this conversation turn (not a previous turn)
- Use search_records before update_record — always verify the record
exists before attempting to update it
- Do not call send_email without first showing the user a preview of the
email content and receiving confirmation
Workflow ordering:
When helping a user book a flight:
1. First use search_flights to find options
2. Present the options to the user
3. Only call book_flight after the user selects a specific flight
4. After booking, always call send_confirmation_email
Never skip steps. Never book without explicit user selection.
System prompt instructions about tool use are highly effective because Claude treats them as operator-level guidance. If your system prompt says "always search before updating," Claude will follow that pattern reliably.
Handling Tool Results
When your application sends a tool result back to Claude, how you format that result affects Claude's ability to use it.
Good tool results are:
- Structured (JSON or clearly formatted)
- Complete (include all fields Claude might need)
- Honest about errors (return error information, not just empty results)
// Good: structured, complete, includes metadata
{
"status": "success",
"customer": {
"id": "cust_12345",
"name": "Jane Smith",
"plan": "professional",
"status": "active",
"created_at": "2024-03-15"
}
}
// Good: clear error with actionable information
{
"status": "error",
"error_code": "NOT_FOUND",
"message": "No customer found with email jane@example.com",
"suggestion": "Try searching by customer ID instead"
}
// Bad: ambiguous empty result
null
// Bad: raw error string
"Something went wrong"
When a tool returns an error, Claude can often recover — it might try a different approach, ask the user for more information, or use a different tool. But only if the error message tells Claude what went wrong and what alternatives exist.
Chaining Tools
Many real tasks require multiple tool calls in sequence. Claude handles this naturally — after receiving one tool result, it can decide to call another tool before giving its final response.
For example, a customer support workflow might require:
lookup_customer— find the customer by emailget_order_history— retrieve their recent orderscheck_refund_eligibility— determine if the order qualifies for a refundprocess_refund— execute the refund if eligible
Claude will chain these calls, using the output of each as input to the next. Your application just needs to handle the loop: send the tool result back and check if Claude wants to call another tool or is ready to respond with text.
Here is how the conversation flow looks in code:
import anthropic
import json
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Refund order #4521 for jane@example.com"}]
while True:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=messages,
)
# Check if Claude wants to use a tool
if response.stop_reason == "tool_use":
# Extract the tool use block
tool_block = next(b for b in response.content if b.type == "tool_use")
# Execute the tool
result = execute_tool(tool_block.name, tool_block.input)
# Add Claude's response and the tool result to messages
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_block.id,
"content": json.dumps(result)
}]
})
else:
# Claude is done — print the final text response
print(response.content[0].text)
break
The key insight is that tool chaining is not a special feature — it is the natural result of a well-implemented tool-use loop. Claude decides when it has enough information to respond.
Error Handling Patterns
Robust tool-use applications need to handle several error categories:
Tool execution failures — the function itself fails (network error, invalid input, timeout).
{
"type": "tool_result",
"tool_use_id": "toolu_abc123",
"content": "Error: Database connection timed out after 5000ms. The database may be temporarily unavailable.",
"is_error": true
}
Setting is_error: true tells Claude explicitly that the tool call failed. Claude can then explain the error to the user or try an alternative approach.
Invalid tool input — Claude provides input that does not match what your function expects. This usually indicates a problem with your tool definition. If Claude consistently provides a date as "March 5" when your function expects "2025-03-05," update the parameter description to specify the format explicitly.
Rate limiting and retries — if a tool depends on an external API with rate limits, your application should handle retries before returning the result to Claude. Do not return a rate-limit error to Claude and expect it to wait and retry — Claude cannot control timing.
Timeout handling — set reasonable timeouts on tool execution and return a clear error if exceeded:
def execute_tool(name, input_data):
try:
result = tool_functions[name](**input_data)
return {"status": "success", "data": result}
except TimeoutError:
return {
"status": "error",
"message": f"Tool {name} timed out after 10 seconds",
"suggestion": "Try with a more specific query to reduce processing time"
}
except KeyError:
return {
"status": "error",
"message": f"Unknown tool: {name}"
}
except Exception as e:
return {
"status": "error",
"message": str(e)
}
Using tool_choice to Control Behavior
The tool_choice parameter lets you control whether and how Claude uses tools:
auto(default) — Claude decides whether to use a tool or respond with text. Best for general conversational use.any— Claude must use a tool (any of the available tools). Use this when you always want a structured tool call, never a text-only response.tool— Claude must use a specific named tool. Use this when you know exactly which tool should be called and want to skip Claude's selection logic.none— Claude cannot use any tools and must respond with text only. Useful for follow-up turns where you want Claude to summarize results rather than make more tool calls.
# Force Claude to use a specific tool
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "search_products"},
messages=messages,
)
# Force Claude to use any tool (but must use one)
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
tool_choice={"type": "any"},
messages=messages,
)
When to use each option:
- Use
{"type": "tool", "name": "..."}when you are building a pipeline where the next step is always a specific tool call — for example, an extraction pipeline where Claude must always callextract_entities. - Use
{"type": "any"}when you want Claude to choose which tool but must use one — for example, a routing layer where the user's message must always be classified via a tool. - Use
{"type": "none"}after a chain of tool calls when you want Claude to synthesize results into a final text response without making additional calls. - Stick with
"auto"for conversational applications where Claude should judge whether a tool is needed.
Parallel Tool Calls
Claude can request multiple tool calls in a single response when the calls are independent. For example, if a user asks "What is the weather in New York and London?", Claude may return two tool_use blocks — one for each city — in a single response.
Your application should:
- Extract all
tool_useblocks from the response - Execute them (ideally in parallel for performance)
- Return all
tool_resultblocks in a single message
# Handle multiple tool calls in one response
tool_blocks = [b for b in response.content if b.type == "tool_use"]
results = []
for block in tool_blocks:
result = execute_tool(block.name, block.input)
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": results})
Parallel tool calls are a significant performance optimization. If Claude needs data from three independent sources, sequential calls take 3x as long as parallel ones. Structure your tool definitions so independence is clear — when tools operate on different data domains, Claude is more likely to call them in parallel.
Designing Tools That Work Well Together
When building a set of tools for Claude, think about how they compose. A well-designed tool set is like a well-designed API: each tool does one thing, and they combine naturally for complex tasks.
Good tool set design:
search_products(query, category, max_results) -> list of products
get_product_details(product_id) -> full product info
check_inventory(product_id, warehouse) -> stock count
create_order(product_id, quantity, customer_id) -> order confirmation
Each tool has a clear, single responsibility. Claude can search, inspect, check availability, and order — and the output of each naturally feeds into the input of the next.
Poor tool set design:
do_product_stuff(action, product_id, query, category, quantity, customer_id, warehouse)
A single tool with many optional parameters that does different things based on the action parameter. Claude has to reason about which combination of parameters to use for each action, and the description becomes unwieldy.
Rule of thumb: if your tool description needs to say "when action is X, provide parameters A and B; when action is Y, provide parameters C and D," split it into separate tools.
Real-World Example: Building a Customer Support Agent
Here is a complete example showing tool definitions, system prompt, and expected behavior for a customer support use case:
tools = [
{
"name": "lookup_customer",
"description": "Look up a customer by email address. Returns customer ID, name, plan, and account status. Use this as the first step when helping any customer.",
"input_schema": {
"type": "object",
"properties": {
"email": {
"type": "string",
"description": "Customer's email address"
}
},
"required": ["email"]
}
},
{
"name": "get_recent_orders",
"description": "Get a customer's recent orders. Returns the last 10 orders with ID, date, total, and status. Requires customer_id from lookup_customer.",
"input_schema": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "Customer ID from lookup_customer result"
}
},
"required": ["customer_id"]
}
},
{
"name": "initiate_refund",
"description": "Initiate a refund for a specific order. Only call this after confirming with the user that they want a refund. Returns a refund confirmation with tracking number.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order ID to refund"
},
"reason": {
"type": "string",
"enum": ["defective", "wrong_item", "not_as_described", "changed_mind", "other"],
"description": "Reason for the refund"
}
},
"required": ["order_id", "reason"]
}
}
]
system_prompt = """You are a customer support agent for ShopCo.
When helping customers:
1. Always look up the customer first using their email
2. If they have an issue with an order, retrieve their recent orders
3. NEVER initiate a refund without explicit customer confirmation
4. If a refund is requested, ask for the reason before processing
Be empathetic and solution-oriented. If you cannot resolve an issue,
explain what you have done and suggest they contact support@shopco.com."""
Notice how the system prompt and tool descriptions work together. The tool description for initiate_refund says "only call this after confirming with the user," and the system prompt reinforces this with "NEVER initiate a refund without explicit customer confirmation." This redundancy is intentional — it makes the constraint robust.
Key Takeaways
- Tool use turns Claude from a text generator into an agent that can take real actions in your system
- Tool definitions are prompts: the description and parameter descriptions guide Claude's behavior as much as system prompt instructions do
- The tool-use loop (send message, receive tool_use, execute, return tool_result) is the core pattern — tool chaining emerges naturally from this loop
- Use structured error responses with
is_error: trueso Claude can recover gracefully from failures - Use
tool_choiceto force specific tools in pipeline architectures, but default toautofor conversational applications - Parallel tool calls improve performance when Claude needs independent data from multiple sources — handle them concurrently in your application
- Design tools with single responsibilities and let Claude chain them, rather than building complex multi-purpose tools
- System prompt instructions and tool descriptions should reinforce each other for critical constraints like confirmation requirements
Discussion
Sign in to join the discussion.

