Function Calling Protocol Variations: Claude vs OpenAI¶
Overview¶
Claude and OpenAI have different function calling implementations. They're not 100% compatible.
Understanding the differences is critical for multi-provider agents.
Claude's Tool Use¶
How Claude Handles Tools¶
from anthropic import Anthropic
class ClaudeToolUse:
def __init__(self):
self.client = Anthropic()
def call_with_tools(self, messages: list):
"""Claude processes tool calls"""
response = self.client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
tools=[
{
"name": "search",
"description": "Search the web",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"}
}
}
}
],
messages=messages
)
# Claude returns tool_use block
for block in response.content:
if block.type == "tool_use":
# Execute tool
tool_result = self.execute_tool(block.name, block.input)
# Send back to Claude
messages.append({
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": block.id,
"content": tool_result
}
]
})
return response
OpenAI's Function Calling¶
How GPT-4 Handles Tools¶
from openai import OpenAI
class OpenAIFunctionCalling:
def __init__(self):
self.client = OpenAI()
def call_with_functions(self, messages: list):
"""OpenAI processes function calls"""
response = self.client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=[
{
"type": "function",
"function": {
"name": "search",
"description": "Search the web",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
}
}
}
}
]
)
# OpenAI returns tool_calls
for choice in response.choices:
for tool_call in choice.message.tool_calls:
# Execute tool
result = self.execute_function(
tool_call.function.name,
tool_call.function.arguments
)
# Send back to OpenAI
messages.append({
"role": "assistant",
"tool_calls": [tool_call]
})
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
return response
Key Differences¶
Comparison Table¶
| Aspect | Claude | OpenAI |
|---|---|---|
| Content Block | tool_use |
tool_calls |
| Schema Format | Simpler | Wrapped in "function" |
| Response Type | tool_use ID required |
tool_call_id |
| Message Role | user for tool result |
tool for tool result |
| Error Handling | error tool result |
Error in message |
| Streaming | Delta events | Different format |
Migration Path¶
Claude to OpenAI¶
class MigrationLayer:
"""Abstract differences between providers"""
def __init__(self, provider: str):
self.provider = provider
def call(self, messages: list):
"""Call appropriate implementation"""
if self.provider == "claude":
return self.claude_call(messages)
elif self.provider == "openai":
return self.openai_call(messages)
def process_tool_call(self, tool_response: dict):
"""Convert tool response to provider format"""
if self.provider == "claude":
return {
"type": "tool_result",
"tool_use_id": tool_response["id"],
"content": tool_response["result"]
}
elif self.provider == "openai":
return {
"tool_call_id": tool_response["id"],
"content": tool_response["result"]
}
Best Practices¶
Provider-Agnostic Code¶
class ProviderAgnosticAgent:
"""Work with any provider"""
def __init__(self, provider: str = "claude"):
self.provider = provider
self.setup_client()
def setup_client(self):
if self.provider == "claude":
from anthropic import Anthropic
self.client = Anthropic()
elif self.provider == "openai":
from openai import OpenAI
self.client = OpenAI()
def normalize_response(self, response):
"""Convert provider response to standard format"""
if self.provider == "claude":
return self.normalize_claude(response)
elif self.provider == "openai":
return self.normalize_openai(response)
3 Warnings ⚠️¶
Warning 1: Not Interchangeable¶
# ❌ WRONG
# Assume Claude and OpenAI code is same
claude_response = claude_client.create(...)
# Won't work with OpenAI API structure!
# ✅ RIGHT
# Use abstraction layer
result = provider_agent.call(...)
Warning 2: Schema Incompatibility¶
# ❌ WRONG
# Use OpenAI schema with Claude
openai_schema = {
"type": "function",
"function": {...}
}
claude_client.create(tools=[openai_schema])
# Breaks!
# ✅ RIGHT
# Convert schema format
claude_schema = convert_schema_openai_to_claude(openai_schema)
claude_client.create(tools=[claude_schema])
Warning 3: Streaming Differences¶
# ❌ WRONG
# Assume streaming works the same
for event in claude_stream:
print(event) # Works
for event in openai_stream:
print(event) # Different format!
# ✅ RIGHT
# Normalize streaming events
for event in self.provider_agent.stream_events():
process_event(event) # Works for both
Last Updated: August 9, 2026