How to give AI models tools to call — and how the same concept differs across providers.
Tool use (also called function calling) is a mechanism where the model, instead of answering a question directly, outputs a structured request to call a function that you've defined. Your code executes the function, returns the result, and the model uses that result to produce a final response.
This is powerful because it gives the model access to real-world capabilities: looking up current data, querying databases, calling external APIs, performing calculations with guaranteed precision, executing code. The model doesn't do these things itself — it decides when to request them and synthesizes the results.
The loop: send tools definition → model returns tool_call → you execute → send result back → model responds.
Both Anthropic and OpenAI use JSON Schema to define tool parameters. The schema tells the model what arguments your function accepts, their types, and which are required.
python
# A well-defined tool
get_order_tool = {
"name": "get_order_status",
"description": "Look up the current status of a customer order by order ID. Returns status, estimated delivery date, and tracking number if available.",
"input_schema": { # Anthropic uses "input_schema"; OpenAI uses "parameters"
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order ID, format: ORD-XXXXXX"
},
"include_history": {
"type": "boolean",
"description": "Whether to include full status history",
"default": False
}
},
"required": ["order_id"]
}
}
Writing good descriptions matters more than you'd think. The model uses the description fields to decide when to call each tool and how to populate the arguments. Be specific about what the function does, when to use it, and what format the parameters expect.
```python import anthropic import json
client = anthropic.Anthropic()
# Define your tools tools = [ { "name": "search_products", "description": "Search the product catalog. Returns a list of matching products with name, price, and ID.", "input_schema": { "type": "object", "properties": { "query": {"type": "string"}, "max_price": {"type": "number", "description": "Maximum price in USD"}, "category": {"type": "string", "enum": ["electronics", "clothing", "books", "home"]} }, "required": ["query"] } } ]
# Your actual implementations def search_products(query: str, max_price: float = None, category: str = None) -> list: # Real database/API call return [{"id": "P001", "name": "Wireless Mouse", "price": 29.99}]
messages = [{"role": "user", "content": "Find me a wireless mouse under $50"}]
# Agentic loop — model may call multiple tools in sequence while True: response = client.messages.create( model="claude-opus-4-5", max_tokens=1024, tools=tools, messages=messages )
if response.stop_reason == "end_turn": # Model is done — extract final text response print(response.content[0].text) break
if response.stop_reason == "tool_use": # Append the model's response (including tool_use blocks) messages.append({"role": "assistant", "content": response.content})
# Process each tool call tool_results = [] for block in response.content: if block.type == "tool_use": # Execute the function func = {"search_products": search_products}[block.name] result = func(**block.input)
tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result) })
# Return all results in one message messages.append({"role": "user", "content": tool_results}) ```
```python from openai import OpenAI import json
client = OpenAI()
tools = [ { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a city.", "parameters": { "type": "object", "properties": { "city": {"type": "string"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} }, "required": ["city"] } } } ]
def get_weather(city: str, unit: str = "celsius") -> dict: return {"city": city, "temp": 22, "condition": "sunny", "unit": unit}
messages = [{"role": "user", "content": "What's the weather in Berlin and Paris?"}]
while True: response = client.chat.completions.create( model="gpt-4o", tools=tools, messages=messages )
choice = response.choices[0]
if choice.finish_reason == "stop": print(choice.message.content) break
if choice.finish_reason == "tool_calls": messages.append(choice.message) # Append assistant message with tool_calls
for tool_call in choice.message.tool_calls: args = json.loads(tool_call.function.arguments) result = get_weather(**args)
messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result) }) ```
GPT-4o and Claude can request multiple tools in a single response. In the OpenAI example above, asking about weather in Berlin AND Paris might produce two tool_calls in one response. Always iterate over message.tool_calls, not just index 0.
Similarly with Anthropic, the response content may contain multiple tool_use blocks. The code above handles this correctly — iterate over response.content and process each block.
```python # Web search {"name": "web_search", "description": "Search the internet for current information"}
# Calculator (guaranteed precision) {"name": "calculate", "description": "Evaluate a mathematical expression"}
# Database query {"name": "query_users", "description": "Look up user records by email or ID"}
# Send email (with your confirmation step before executing) {"name": "send_email", "description": "Draft and send an email to a recipient"}
# Read/write files {"name": "read_file", "description": "Read the contents of a file by path"} {"name": "write_file", "description": "Write content to a file"} ```
Tools add complexity. Don't reach for them when prompting works.
Use tools when: - You need real-time or private data not in the model's training - You need computational precision (math, code execution) - You need to trigger real side effects (send emails, update databases) - The model needs to take actions in a system
Don't use tools when: - The model can answer from its training data - You just need structured output (use JSON mode or schema instead) - The function would just retrieve static data you could put in the prompt
If your function throws an exception, return an error message rather than propagating the exception:
```python try: result = my_function(**tool_input) except Exception as e: result = {"error": str(e), "success": False}
# Always return something — the model needs a result to continue tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result), "is_error": True # Anthropic supports this flag }) ```
The model will gracefully handle an error result and typically tell the user it was unable to complete the action, rather than hallucinating a result.
Have a follow-up question about this topic?
Ask AI