Skip to content

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