Title: Lecture 4 — Same Job, Different Contracts: OpenAI, Claude, and the Compatibility Layer Between Them
Course: AIINFRA 300: Agentic AI & the Model Context Protocol (MCP) > Section: Week 04 — OpenAI, Claude, and Multi-Provider API Integration

📖 Lecture — Same Job, Different Contracts: OpenAI, Claude, and the Compatibility Layer Between Them

By now you've called plenty of REST APIs, so a chat completion request should feel familiar: you POST some messages, you get a response back. But as soon as you try to point the same client code at two different model providers, you'll discover that "familiar" only goes so far. OpenAI's Chat Completions API and Anthropic's Messages API agree on the big idea — send a conversation, get a completion — but they disagree on almost every detail of the contract. Understanding exactly where they diverge is the difference between an agent that "mostly works" and one you can actually trust in production. System messages are the first fault line. OpenAI's API treats the system (or "developer") role as just another message that can appear anywhere in the messages array — first, middle, interleaved with turns, repeated multiple times. Anthropic's Messages API has no system role in the messages array at all; instead, there's a single top-level system parameter that represents one leading instruction block. This isn't a cosmetic difference. If your code was written against OpenAI's flexible model and assumes you can drop a system message wherever you like, moving to Claude natively requires you to restructure that logic. Tool-calling and sampling parameters are the second fault line. OpenAI exposes a strict parameter on function/tool definitions that (when true) guarantees the model's output conforms exactly to your JSON schema. Claude's native tool use has its own robust mechanism for schema-constrained output, but it is invoked and configured differently. Temperature ranges differ too: OpenAI accepts 0–2, while Claude's native range is 0–1. So how do you migrate incrementally? Anthropic ships an official OpenAI-SDK compatibility layer: you keep using the openai Python/JS SDK, just point base_url at Anthropic's endpoint and use a Claude model string. This is genuinely useful for quick comparisons — "will Claude do roughly the same thing as GPT for this prompt?" — but Anthropic is explicit that it's meant for testing and comparison, not production use. The reason is that the layer doesn't fail loudly when it can't honor a request; it silently drops or reinterprets fields. Here's what actually happens under the hood:

OpenAI field/behavior | What the compatibility layer does
strict (tool calling) | Ignored — schema adherence is not guaranteed
response_format | Ignored
presence_penalty / frequency_penalty | Ignored
seed | Ignored (no reproducibility guarantee)
logit_bias / logprobs | Ignored
reasoning_effort | Ignored
System messages in multiple positions | Hoisted and concatenated into a single leading system message
temperature (0–2 range) | Silently clamped to Claude's 0–1 range
Prompt caching, PDF processing, citations, full extended-thinking output | Not available at all through this layer

Notice the pattern: almost nothing throws an error. Your code runs, you get a 200 response, and everything looks fine — until you inspect the output closely and realize the model never saw your seed, your JSON schema wasn't strictly enforced, or your temperature of 1.4 was quietly capped at 1.0. This is exactly the kind of gap that unit tests checking only "did I get a response" will never catch. What should you actually do with this knowledge? Three good options, in order of engineering maturity:

1. Use the compatibility layer only for exploration — quick side-by-side prompt comparisons during development, never in a request path a user depends on.
2. Use the native Claude API (Messages API) directly when you need guaranteed schema conformance, prompt caching, PDF/document handling, citations, or full extended-thinking traces. This is more code to maintain per provider, but it's the only path with full feature access and predictable behavior.
3. Use an AI gateway (e.g., Cloudflare AI Gateway, Braintrust) as an intelligent proxy. Your app still speaks one consistent /chat/completions-shaped API, but the gateway itself — not a thin SDK shim — does the real translation into each provider's native format at runtime, along with logging, caching, and cost tracking. This decouples your application code from any single provider's SDK while keeping the translation logic in a piece of infrastructure designed for exactly that job, rather than a documented-as-temporary compatibility mode.

A related tool-design note: current Claude models (Sonnet 4.6, Opus 4.8) tend to complete agentic tool-calling tasks in meaningfully fewer steps than earlier-generation models for equivalent task intelligence. When you're comparing providers for an agent's tool loop, don't just compare single-call latency or cost — compare the number of round trips the agent needs to finish the task, since that compounds directly into both latency and API spend. Finally, a framework worth knowing about: Pydantic AI, built by the Pydantic team, gives you multi-provider agents out of the box. Install it with pip install pydantic-ai (or scoped extras like pydantic-ai[openai], pydantic-ai[anthropic], pydantic-ai[gemini]), then instantiate an agent with a simple provider-prefixed model string:

from pydantic_ai import Agent

agent = Agent('anthropic:claude-sonnet-4-6', system_prompt="You are a helpful assistant.")
result = agent.run_sync("Explain the OpenAI compatibility layer in one sentence.")
print(result.output)

Swapping to 'openai:gpt-5' or 'google-gla:gemini-2.5-flash' is a one-line change — no manual request-shape translation required, because Pydantic AI's internals handle that per provider. Correcting three common misconceptions before the lab:

- "Pointing the OpenAI SDK at Claude gives full parity." It doesn't. The compatibility layer silently ignores many fields — including the tool-calling strict parameter — so your JSON output is not guaranteed to match your schema even if it usually does. Silent failure is more dangerous than a loud error, because it can pass casual testing and break in production.
- "System messages work the same way across providers." OpenAI allows system/developer messages anywhere in the conversation; Anthropic supports exactly one initial system message. The compat layer papers over this by hoisting and concatenating every system message into one block — which can change meaning if your prompt logic depended on ordering. Temperature ranges also differ (0–2 vs. clamped to 0–1), so a prompt tuned at temperature=1.6 for OpenAI silently behaves like temperature=1.0 on Claude through the shim.
- "The OpenAI compatibility layer is production-ready for multi-provider apps." Anthropic states plainly that it exists to help you test and compare model capabilities — not as a long-term production integration path. Prompts tuned against OpenAI's quirks may need real retuning to get the best results from Claude, and that retuning work is exactly what the compatibility layer lets you defer, not what it does for you.