Skip to content

Function Calling Protocol Variations

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