The cousin-language analogy
Spanish and Portuguese feel almost identical to outsiders, but conjugations and false friends trip you up daily if you assume sameness. The OpenAI and Anthropic SDKs are like that. The shape is similar — chat-completion-style APIs with tool use, streaming, structured outputs. The names, defaults, and finer details diverge in ways that bite when you assume.
At a glance
| Concept | OpenAI | Anthropic |
|---|---|---|
| Endpoint name | chat.completions / responses |
messages |
| Top-level message field | messages: [...] |
messages: [...] (similar) |
| System prompt | role: "system" message |
top-level system parameter |
| Tool definitions | tools: [{type: "function", function: {...}}] |
tools: [{name, description, input_schema}] |
| Tool call in response | tool_calls: [...] on a message |
content: [{type: "tool_use", ...}] |
| Tool result back | role: "tool" message |
role: "user" with content: [{type: "tool_result", ...}] |
| Streaming format | SSE deltas | SSE events with explicit event: types |
| Token counts | usage.prompt_tokens / usage.completion_tokens |
usage.input_tokens / usage.output_tokens |
| Vision | image_url content part |
image content part with source |
| Cache control | (Limited) | cache_control markers per content block |
The headline: same problem space, different field names and default behaviours.
Behavioural differences worth knowing
- System prompt. OpenAI accepts a
systemrole message; Anthropic uses a top-levelsystemparameter. Cleaner separation in Anthropic — the system is not part of the conversation. - Multi-modal content. Both support text + image; the JSON shapes differ. Don't copy-paste image-handling code between the two without re-reading docs.
- Tool use response shape. OpenAI puts tool calls on a message object; Anthropic puts them as content blocks alongside text. When porting, you re-thread how you accumulate the response.
- Streaming events. OpenAI streams homogenous
deltachunks; Anthropic streams typed events (message_start,content_block_delta,message_stop, etc.). Anthropic's is more explicit, slightly more code to handle. - Stop reasons. Both have them; the strings differ.
length,stop,tool_calls(OpenAI) vsend_turn,max_tokens,tool_use,stop_sequence(Anthropic). - Retries. OpenAI's SDK has built-in exponential backoff with jitter on 429/503; Anthropic's does too in current versions. Always verify your version's defaults.
- Prompt caching. Anthropic ships
cache_controlmarkers as a first-class feature with significant cost wins for stable prefixes. - Token counting endpoints. Both expose ways to count tokens client-side without a full call. Useful for budget pre-checks.
Patterns that port cleanly
- Tool definitions. Map your internal tool registry to each SDK's tool shape with a thin adapter; the rest of the code stays identical.
- Streaming consumers. Abstract the event types behind your own typed events; provider differences disappear above that layer.
- Retries / backoff. Same logic, different exception types.
- Token bookkeeping. Normalise both providers' usage objects into a single internal
{ input, output, cached }shape.
What to abstract, what not to
Abstract — request/response shape, stream events, retries, error types, token usage. These are the boundary you stabilise.
Don't over-abstract — features that are genuinely different. Anthropic's prompt caching, OpenAI's structured outputs ergonomics, vision specifics. Forcing them into a least-common-denominator API leaves capability on the table.
Switching providers in production
If you build a thin internal SDK that exposes only what you use, switching providers (or running both behind a router) is a few-day job. If you spread provider-specific code across your codebase, it's a multi-week migration. Plan for the abstraction early, while it's small and cheap.
In one line
The two SDKs are 90% the same and 10% different. The 10% will eat a day if you assume otherwise — read the migration guide before porting code.