Claude uses 'tool use' instead of 'function calling'. This guide shows how Claude tool_use maps to OpenAI function calling and how to migrate your existing code. Working examples for 2026.
Claude doesn't use the term "function calling" — the feature is called tool use. The behavior is identical to OpenAI function calling, with minor API surface differences.
| Concept | OpenAI (Python) | Claude (Python) |
|---|---|---|
| Define tools | functions=[{name, description, parameters}] | tools=[{name, description, input_schema}] |
| Auto tool choice | function_call="auto" | tool_choice="auto" (default) |
| Force a tool | function_call={"name": "fn"} | tool_choice={"type": "tool", "name": "fn"} |
| Disable tools | function_call="none" | tool_choice={"type": "none"} |
| Tool call in response | choice.message.function_call | block (type="tool_use") |
| Arguments | json.loads(function_call.arguments) | block.input (already a dict) |
| Send result back | role="function" | type="tool_result" |
import anthropic
import json
client = anthropic.Anthropic()
# Define tools (equivalent to OpenAI's 'functions')
tools = [
{
"name": "get_weather",
"description": "Get current weather for a city. Returns temperature in Celsius and conditions.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, e.g. 'London'"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit"}
},
"required": ["city"]
}
},
{
"name": "search_web",
"description": "Search the web for current information.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"max_results": {"type": "integer", "description": "Max results to return (default 5)"}
},
"required": ["query"]
}
}
]
# First call — Claude may call a tool
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "What's the weather like in Tokyo?"}]
)
print(f"stop_reason: {response.stop_reason}") # "tool_use" if Claude calls a tool
# Handle tool call
if response.stop_reason == "tool_use":
messages = [{"role": "user", "content": "What's the weather like in Tokyo?"}]
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
print(f"Claude called: {block.name}({block.input})")
# block.input is already a dict (no json.loads needed — unlike OpenAI)
# Execute your function here
result = {"temperature": 22, "conditions": "Partly cloudy", "city": block.input["city"]}
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
})
messages.append({"role": "user", "content": tool_results})
# Final response with tool result
final = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
print(final.content[0].text)
# OpenAI pattern (before)
# response = openai_client.chat.completions.create(
# model="gpt-4o",
# messages=messages,
# functions=[{"name": "get_weather", "description": "...", "parameters": schema}],
# function_call="auto"
# )
# fn_call = response.choices[0].message.function_call
# args = json.loads(fn_call.arguments) # ← must parse JSON string
# Claude equivalent (after)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[{"name": "get_weather", "description": "...", "input_schema": schema}],
# tool_choice defaults to "auto" — no change needed
messages=messages
)
for block in response.content:
if block.type == "tool_use":
args = block.input # ← already a dict, no json.loads needed
print(f"Tool: {block.name}, Args: {args}")
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=256,
tools=[{"name": "extract_address", "description": "Extract address components.", "input_schema": {
"type": "object",
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"state": {"type": "string"},
"zip": {"type": "string"}
},
"required": ["street", "city", "state", "zip"]
}}],
tool_choice={"type": "tool", "name": "extract_address"}, # force this tool always
messages=[{"role": "user", "content": "Ship to: 1 Infinite Loop, Cupertino, CA 95014"}]
)
for block in response.content:
if block.type == "tool_use":
print(block.input)
# {'street': '1 Infinite Loop', 'city': 'Cupertino', 'state': 'CA', 'zip': '95014'}
For structured output with Pydantic models, see Claude Structured Output Python. For multi-step agentic patterns, see Claude Agents Python. For cost estimates on tool-heavy workflows, use the Cost Calculator.