Claude API 和 OpenAI API 有什么区别
Claude 的 messages 和 OpenAI 的 chat/completions 看着像,实际有 6 处硬性差异:system 位置、max_tokens 必填、content 块数组、工具调用结构、多模态格式、流式事件名。这篇逐项对照并给出同一需求的两套写法,附迁移检查表。
很多团队先用 OpenAI SDK 跑通了业务,后来想加 Claude 做二线模型或代码任务,第一反应是「把 model 改一下就行」。多数情况下能跑,但只要用到多轮工具调用、thinking、多模态或流式解析,就会撞上一连串结构差异。原因是两家协议长得像,但设计思路不同:OpenAI 的 chat/completions 是消息数组驱动,Anthropic 的 messages 是块(block)数组驱动。
这篇把差异逐项列出来,给同一需求的两套写法,最后给一张迁移检查表。
先说结论
- 只做纯文本单轮对话:两家几乎可以互换,改
model即可 - 一旦用到
system、max_tokens、工具调用、多模态、流式,就必须按协议分别写,不能混用字段 - Anthropic 协议里
max_tokens必填,system是顶层参数不在messages里,这两点最容易漏 - 响应体的
content在 Anthropic 是块数组(text / thinking / tool_use),不是字符串 - 想一个 Key 同时调两类模型,走兼容网关最省事:OpenAI 协议端点和 Claude 原生端点并存,凭证只维护一份
差异一:system 提示词的位置
OpenAI 把 system 当成一种消息角色塞进数组;Anthropic 把它提成顶层参数,messages 里只允许 user 和 assistant。
# OpenAI 协议
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "你是严谨的技术助手。"},
{"role": "user", "content": "解释一下幂等性"},
],
)
# Anthropic 协议
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system="你是严谨的技术助手。",
messages=[
{"role": "user", "content": "解释一下幂等性"},
],
)
如果把 {"role": "system"} 写进 Anthropic 的 messages,会直接报 400。反过来,把 Anthropic 的 system= 参数塞给 OpenAI SDK,SDK 会报参数校验错误。
差异二:max_tokens 必填
OpenAI 不传 max_tokens 时会用模型默认上限;Anthropic 要求显式声明,否则 400。
这一条看着是小事,实际影响成本控制——不传就是赌默认值,而 claude-opus-5-thinking 这类模型的默认输出上限很高,一个失控的长回复能吃掉不少额度。建议两边都养成显式设置的习惯。
差异三:响应的 content 是块数组
# OpenAI:字符串或 null
text = resp.choices[0].message.content
# Anthropic:块数组,要按 type 挑
text = ""
for block in resp.content:
if block.type == "text":
text += block.text
为什么 Anthropic 要设计成数组?因为它要同时承载三种东西:正常文本(text)、推理过程(thinking)、工具调用请求(tool_use)。一个回复里可能三者并存。开启了 thinking 的模型,resp.content[0] 很可能不是文本块——直接取 [0].text 会抛异常,这是迁移时最常见的崩溃点。
差异四:工具调用的往返结构
OpenAI 用 tool_calls 字段 + role: "tool" 消息回传结果;Anthropic 用 tool_use 块 + tool_result 块。
# OpenAI:工具定义是 function 包一层
tools = [{
"type": "function",
"function": {
"name": "get_order",
"description": "按订单号查询订单状态",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
},
}]
# Anthropic:input_schema 直接放定义,没有 function 包裹
tools = [{
"name": "get_order",
"description": "按订单号查询订单状态",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
}]
回传结果的方式也不同:
| 环节 | OpenAI 协议 | Anthropic 协议 |
|---|---|---|
| 工具定义字段 | tools[].function.parameters | tools[].input_schema |
| 模型发起调用 | message.tool_calls | content 里的 tool_use 块 |
| 结果回传 | role: "tool" 消息 | tool_result 内容块 |
| 结果关联方式 | tool_call_id | tool_use_id |
| 是否允许并行调用 | 支持,多个 tool_calls | 支持,多个 tool_use 块 |
如果业务已经在用 OpenAI 的 function calling 跑通了,迁到 Anthropic 时建议单独写一层适配器,把「工具定义」「调用解析」「结果回传」三个函数抽象出来,上层业务只认自己的内部结构。Function calling 的基础概念可以看 工具调用入门指南。
差异五:多模态输入的格式
OpenAI 用 image_url,Anthropic 用 source 里的 base64 或 URL:
# OpenAI
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
# Anthropic
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "..."}}
Anthropic 要求显式声明 media_type,OpenAI 从 data URI 里自动推断。少写这一个字段会 400。
差异六:流式事件名
OpenAI 流式返回 data: {...} 的 chunk,以 data: [DONE] 结束;Anthropic 返回带 event: 行的结构化事件流(message_start、content_block_delta、message_stop 等)。
好消息是官方 SDK 都封装好了:OpenAI 用 for await 迭代 chunk,Anthropic 用 client.messages.stream() 上下文管理器取 text_stream。手写 SSE 解析时才需要关心这些事件名,原理可以看 SSE 流式输出从 curl 到前端。
差异七:thinking 与 reasoning 参数
thinking 类模型两家的开关方式完全不同:
# Anthropic:thinking 是独立对象,带 token 预算
resp = client.messages.create(
model="claude-sonnet-5-thinking",
max_tokens=16000,
thinking={"type": "enabled", "budget_tokens": 8000},
messages=[{"role": "user", "content": "9.11 和 9.9 哪个大?"}],
)
OpenAI 系的推理模型一般不暴露显式预算参数,靠模型名区分思考深度(如带 -high / -low 后缀的变体)。思考 token 是计费的,预算越大账单越高,这一点在两家都一样。
一个 Key 同时调两类模型
如果两种模型都要用,最笨的办法是维护两套 Key、两套额度和两套充值;比较务实的做法是走统一网关:OpenAI 兼容端点 https://api.zaimod.com/v1 覆盖绝大多数模型,Claude 原生端点 https://api.zaimod.com/v1/messages 保留 thinking、原生工具等全特性,凭证是同一把 Key。
import anthropic
from openai import OpenAI
# 同一个 Key,两套客户端
openai_client = OpenAI(api_key="sk-xxx", base_url="https://api.zaimod.com/v1")
claude_client = anthropic.Anthropic(api_key="sk-xxx", base_url="https://api.zaimod.com")
单价按模型分别计价,default 分组倍率 ×1.0。claude-sonnet-5 站内挂单价为 $4 / $20 每百万 tokens(输入/输出),gpt-4o-mini 为 $0.156 / $0.624,都在 价格速查 可查。计费公式与分组倍率写在 计费规则文档 里:官转分组 ×1.5,官方直连渠道稳定性更高、价格也更高。
迁移检查表
从 OpenAI 协议迁到 Anthropic 协议时,按这个顺序过一遍,基本能一次跑通:
| 检查项 | 常见错误写法 | 正确写法 |
|---|---|---|
| system 位置 | messages 里放 role: system | 顶层 system 参数 |
| max_tokens | 省略不传 | 显式声明 |
| 响应取值 | resp.choices[0].message.content | 遍历 resp.content 找 text 块 |
| 工具定义 | function 包一层 | input_schema 直挂 |
| 工具结果回传 | role: tool 消息 | tool_result 内容块 |
| 图片 | image_url | image + source + media_type |
| 流式 | 手动解析 [DONE] | client.messages.stream |
反过来从 Anthropic 迁到 OpenAI 协议,把表左右两列对调即可,另外注意 OpenAI 的 content 可能是 null(只返回工具调用时),必须做空值处理。
常见问题
Q:只改 model 名能跑通吗?
纯文本单轮对话多数能跑通,但 max_tokens 和 system 这两项会立刻报错。如果你用的是 OpenAI SDK 调 Claude 模型(走兼容端点),那实际上用的是 OpenAI 协议,不是 Anthropic 协议,两者不要混为一谈。
Q:哪家的输出更便宜?
不能一概而论,同名系列的档位差异很大。站内挂单价 claude-sonnet-5 是 $4 / $20,gpt-5.6-luna 是 $1 / $8,gemini-2.5-flash 是 $0.3 / $2.5,deepseek-v4-flash 是 $3 / $9(每百万 tokens,输入/输出)。按任务质量需求选型比单纯比价更划算,参考 2026 大模型 API 价格对比。
Q:thinking 会导致输出为空吗?
不会,但会出现 content[0] 是 thinking 块的情况。如果你的代码写死取 [0].text,就会读到错误内容或直接报错。正确做法是遍历块数组按 type 过滤。
Q:两种协议的错误码一致吗?
HTTP 状态码语义一致(401 Key 无效、404 模型名不存在、429 限流、5xx 上游异常),但错误体的 JSON 结构不同。别写死解析某一家字段,统一按状态码分流最稳,详见 报错速查。