📌 内容摘要
- 从 OpenAI 迁移到 Claude 的代码改动集中在5个地方:SDK 导入、客户端初始化、消息格式、模型名称、响应解析。
- 本文提供完整的参数对照表,覆盖基础调用、流式输出、工具调用、视觉输入、批量处理所有场景。
- 附逐场景的 Python 和 Node.js 迁移代码对比,可直接参照修改。
- 文末提供一键迁移的适配层代码,让现有 OpenAI 代码以最小改动接入 Claude。
一、核心差异一览
从 OpenAI API 迁移到 Claude API,最需要关注的5个核心差异:
| 差异点 | OpenAI | Claude(Anthropic) |
|---|---|---|
| System 消息位置 | messages 数组里,role 为 "system" |
独立的 system 参数,不在 messages 里 |
| 响应文本位置 | response.choices[0].message.content |
response.content[0].text |
| 结束原因字段名 | finish_reason(在 choices[0] 里) |
stop_reason(在响应根级别) |
| max tokens 字段名 | max_tokens(可选,有默认值) |
max_tokens(必填,无默认值) |
| Token 用量字段 | usage.prompt_tokens / completion_tokens |
usage.input_tokens / output_tokens |
Claude API 的
max_tokens 是必填参数——不传会报错。OpenAI 有默认值,迁移时很容易漏掉这个参数。建议默认设为 1024,根据具体任务调整。
二、基础调用参数对照
Python 版本对比
# ══════════════ OpenAI ══════════════
from openai import OpenAI
client = OpenAI(api_key="sk-...")
response = client.chat.completions.create(
model="gpt-5.4",
messages=[
{"role": "system", "content": "你是一个助手"}, # system 在 messages 里
{"role": "user", "content": "你好"},
],
max_tokens=1024, # 可选
temperature=0.7,
top_p=1.0,
n=1, # 生成几个候选
stop=None,
stream=False,
presence_penalty=0,
frequency_penalty=0,
)
text = response.choices[0].message.content # 取文本
reason = response.choices[0].finish_reason # 结束原因
usage = response.usage.prompt_tokens # token 用量
# ══════════════ Claude ══════════════
import anthropic
client = anthropic.Anthropic(api_key="sk-ant-...")
response = client.messages.create(
model="claude-sonnet-4-6",
system="你是一个助手", # ← system 独立参数
messages=[
{"role": "user", "content": "你好"}, # messages 里只放 user/assistant
],
max_tokens=1024, # ← 必填!
temperature=0.7,
top_p=1.0,
# n 参数不支持,Claude 每次只返回一个候选
stop_sequences=None, # ← 字段名不同(OpenAI 是 stop)
stream=False,
# presence_penalty / frequency_penalty 不支持
)
text = response.content[0].text # ← 结构不同
reason = response.stop_reason # ← 字段名不同,位置不同
usage = response.usage.input_tokens # ← 字段名不同
Node.js / TypeScript 版本对比
// ══════════════ OpenAI ══════════════
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: "sk-..." });
const response = await openai.chat.completions.create({
model: "gpt-5.4",
messages: [
{ role: "system", content: "你是一个助手" },
{ role: "user", content: "你好" },
],
max_tokens: 1024,
temperature: 0.7,
});
const text = response.choices[0].message.content;
const reason = response.choices[0].finish_reason;
const tokens = response.usage?.prompt_tokens;
// ══════════════ Claude ══════════════
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({ apiKey: "sk-ant-..." });
const response = await anthropic.messages.create({
model: "claude-sonnet-4-6",
system: "你是一个助手", // ← 独立参数
messages: [
{ role: "user", content: "你好" },
],
max_tokens: 1024, // ← 必填
temperature: 0.7,
});
const text = response.content[0].type === "text"
? response.content[0].text : "";
const reason = response.stop_reason; // ← 不同位置
const tokens = response.usage.input_tokens; // ← 不同字段名
三、完整参数对照表
| 功能 | OpenAI 参数 | Claude 参数 | 备注 |
|---|---|---|---|
| 模型名称 | model: "gpt-5.4" |
model: "claude-sonnet-4-6" |
见模型名称对照表 |
| 系统提示 | messages[0].role: "system" |
system: "..."(独立参数) |
⚠️ 最常见迁移坑 |
| 最大输出 token | max_tokens(可选) |
max_tokens(必填) |
⚠️ Claude 必填 |
| 温度 | temperature: 0-2 |
temperature: 0-1 |
⚠️ 范围不同!Claude 上限是1 |
| 核采样 | top_p: 0-1 |
top_p: 0-1 |
相同 |
| K 采样 | 不支持 | top_k: 整数 |
Claude 独有 |
| 停止序列 | stop: string | string[] |
stop_sequences: string[] |
字段名不同,Claude 只接受数组 |
| 候选数量 | n: 整数 |
不支持 | Claude 每次只返回1个 |
| 频率惩罚 | frequency_penalty |
不支持 | Claude 不支持 |
| 存在惩罚 | presence_penalty |
不支持 | Claude 不支持 |
| 流式输出 | stream: true |
stream: true 或 .stream() 方法 |
事件格式不同 |
| 随机种子 | seed: 整数 |
不支持 | Claude 不支持确定性种子 |
| JSON 模式 | response_format: {type: "json_object"} |
通过 Prompt 指定格式 | Claude 无原生 JSON 模式,用 Prompt 控制 |
| 用户标识 | user: "user_id" |
metadata: {user_id: "..."} |
字段名和位置不同 |
| Prompt 缓存 | 自动缓存(prefix caching) | cache_control: {type: "ephemeral"} |
Claude 需手动标记缓存位置 |
四、模型名称对照
| 定位 | OpenAI 模型 | Claude 对应模型 | Claude model string |
|---|---|---|---|
| 旗舰推理 | gpt-5.4 / o3 | Claude Opus 4.6 | claude-opus-4-6 |
| 日常主力 | gpt-5.4 | Claude Sonnet 4.6 | claude-sonnet-4-6 |
| 轻量快速 | gpt-4o-mini | Claude Haiku 4.5 | claude-haiku-4-5-20251001 |
五、流式输出差异
# ══════════════ OpenAI 流式 ══════════════
stream = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "你好"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
print(delta.content, end="", flush=True)
if chunk.choices[0].finish_reason == "stop":
print() # 换行
# ══════════════ Claude 流式(推荐方式)══════════════
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "你好"}],
) as stream:
for text in stream.text_stream: # ← 直接迭代文本片段
print(text, end="", flush=True)
print()
final = stream.get_final_message() # ← 流结束后获取完整统计
print(f"总 token:{final.usage.input_tokens + final.usage.output_tokens}")
# ══════════════ Claude 流式(低层事件方式)══════════════
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "你好"}],
) as stream:
for event in stream:
if event.type == "content_block_delta":
if event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
elif event.type == "message_stop":
print()
六、工具调用(Tool Use)差异
# ══════════════ OpenAI 工具调用 ══════════════
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
}]
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "北京今天天气"}],
tools=tools,
tool_choice="auto",
)
# 提取工具调用
tool_call = response.choices[0].message.tool_calls[0]
func_name = tool_call.function.name # "get_weather"
func_args = json.loads(tool_call.function.arguments) # {"city": "北京"}
# 把工具结果传回
messages.append(response.choices[0].message) # 添加 assistant 消息
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps({"temp": 25, "weather": "晴"})
})
# ══════════════ Claude 工具调用 ══════════════
tools = [{
"name": "get_weather", # ← 不需要 type: "function" 包装
"description": "获取天气",
"input_schema": { # ← 字段名是 input_schema,不是 parameters
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "北京今天天气"}],
tools=tools,
tool_choice={"type": "auto"}, # ← 不同的格式
)
# 提取工具调用
for block in response.content:
if block.type == "tool_use": # ← 在 content 数组里
func_name = block.name # "get_weather"
func_args = block.input # {"city": "北京"}(已经是字典,不需要 json.loads)
tool_use_id = block.id
# 把工具结果传回
messages.append({"role": "assistant", "content": response.content}) # ← 传整个 content 数组
messages.append({
"role": "user",
"content": [{
"type": "tool_result", # ← 格式完全不同
"tool_use_id": tool_use_id,
"content": json.dumps({"temp": 25, "weather": "晴"})
}]
})
七、视觉输入(图片)差异
import base64
with open("image.png", "rb") as f:
image_data = base64.b64encode(f.read()).decode()
# ══════════════ OpenAI 图片输入 ══════════════
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{
"role": "user",
"content": [
{
"type": "image_url", # ← type 是 "image_url"
"image_url": {
"url": f"data:image/png;base64,{image_data}", # ← data URL 格式
"detail": "high"
}
},
{"type": "text", "text": "描述这张图片"}
]
}],
max_tokens=1024,
)
# ══════════════ Claude 图片输入 ══════════════
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{
"role": "user",
"content": [
{
"type": "image", # ← type 是 "image"(不是 image_url)
"source": { # ← 有 source 嵌套层
"type": "base64",
"media_type": "image/png",
"data": image_data, # ← 直接是 base64 字符串,不是 data URL
}
# Claude 没有 "detail" 参数
},
{"type": "text", "text": "描述这张图片"}
]
}],
)
# 图片 URL 方式(Claude 支持直接传 URL)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/image.jpg"
}
},
{"type": "text", "text": "描述这张图片"}
]
}],
)
八、响应结构对照
# ══════════════ OpenAI 响应结构 ══════════════
{
"id": "chatcmpl-xxx",
"object": "chat.completion",
"model": "gpt-5.4",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "回复文本"
},
"finish_reason": "stop" # 在 choices[0] 里
}],
"usage": {
"prompt_tokens": 10, # 输入 token
"completion_tokens": 20, # 输出 token
"total_tokens": 30
}
}
# 取值方式:
text = response.choices[0].message.content
reason = response.choices[0].finish_reason
input = response.usage.prompt_tokens
output = response.usage.completion_tokens
# ══════════════ Claude 响应结构 ══════════════
{
"id": "msg_xxx",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-6",
"content": [{
"type": "text",
"text": "回复文本" # 在 content 数组里
}],
"stop_reason": "end_turn", # 在根级别,不在 content 里
"stop_sequence": null,
"usage": {
"input_tokens": 10, # 字段名不同
"output_tokens": 20
}
}
# 取值方式:
text = response.content[0].text
reason = response.stop_reason # 根级别
input = response.usage.input_tokens
output = response.usage.output_tokens
九、适配层代码(最小改动迁移)
如果你有大量已有的 OpenAI 代码,可以用以下适配层让它以最小改动运行在 Claude 上:
"""
Claude-OpenAI 适配层
让使用 OpenAI 接口风格的代码以最小改动运行在 Claude 上
"""
import anthropic
import json
from dataclasses import dataclass
from typing import Optional
_client = anthropic.Anthropic()
# OpenAI 风格的响应结构
@dataclass
class Message:
role: str
content: str
@dataclass
class Choice:
index: int
message: Message
finish_reason: str
@dataclass
class Usage:
prompt_tokens: int
completion_tokens: int
total_tokens: int
@dataclass
class ChatCompletion:
id: str
model: str
choices: list
usage: Usage
# 模型名称映射
MODEL_MAP = {
"gpt-4": "claude-opus-4-6",
"gpt-4o": "claude-sonnet-4-6",
"gpt-4o-mini": "claude-haiku-4-5-20251001",
"gpt-5.4": "claude-sonnet-4-6",
"o3": "claude-opus-4-6",
}
def create_completion(
model: str,
messages: list[dict],
max_tokens: int = 1024,
temperature: float = 1.0,
top_p: float = 1.0,
stop=None,
stream: bool = False,
**kwargs # 忽略 Claude 不支持的参数(n, presence_penalty 等)
) -> ChatCompletion:
"""
OpenAI 风格的接口,内部调用 Claude API
用法:与 openai.chat.completions.create() 完全一致
"""
# 映射模型名称
claude_model = MODEL_MAP.get(model, model)
# 提取 system 消息
system = ""
user_messages = []
for msg in messages:
if msg["role"] == "system":
system = msg["content"]
else:
user_messages.append(msg)
# 处理 stop 参数格式
stop_sequences = None
if stop:
stop_sequences = [stop] if isinstance(stop, str) else stop
# 限制 temperature 到 Claude 支持的范围
temperature = min(temperature, 1.0)
# 调用 Claude API
response = _client.messages.create(
model=claude_model,
system=system,
messages=user_messages,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
stop_sequences=stop_sequences,
)
# 转换为 OpenAI 风格的响应
return ChatCompletion(
id=response.id,
model=model,
choices=[Choice(
index=0,
message=Message(
role="assistant",
content=response.content[0].text
if response.content else ""
),
finish_reason="stop"
if response.stop_reason == "end_turn"
else response.stop_reason or "stop"
)],
usage=Usage(
prompt_tokens=response.usage.input_tokens,
completion_tokens=response.usage.output_tokens,
total_tokens=response.usage.input_tokens + response.usage.output_tokens
)
)
# 模拟 OpenAI 客户端结构
class _Completions:
def create(self, **kwargs):
return create_completion(**kwargs)
class _Chat:
completions = _Completions()
class ClaudeAsOpenAI:
"""使用和 OpenAI 客户端完全一样的接口"""
chat = _Chat()
# ══════════════ 使用示例 ══════════════
# 原来的 OpenAI 代码:
# from openai import OpenAI
# client = OpenAI(api_key="sk-...")
# 只需要改这一行:
client = ClaudeAsOpenAI()
# 以下代码完全不需要改动:
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "你是一个助手"},
{"role": "user", "content": "你好"},
],
max_tokens=1024,
)
print(response.choices[0].message.content)
print(response.usage.prompt_tokens)
十、错误码对照
| 错误类型 | OpenAI | Claude |
|---|---|---|
| 认证失败 | AuthenticationError(401) |
anthropic.AuthenticationError(401) |
| 速率限制 | RateLimitError(429) |
anthropic.RateLimitError(429) |
| 请求参数错误 | BadRequestError(400) |
anthropic.BadRequestError(400) |
| 服务器过载 | InternalServerError(500/503) |
anthropic.APIStatusError(529 = 过载) |
| 网络连接 | APIConnectionError |
anthropic.APIConnectionError |
常见问题
Q:迁移时最容易漏掉哪个参数?
max_tokens——在 OpenAI 中这是可选的,不传会用默认值;在 Claude 中这是必填的,不传会报 ValidationError。建议在迁移检查清单里把这条列为第一项。
Q:OpenAI 的 response_format: {type: "json_object"} 在 Claude 怎么实现?
Claude 没有原生的 JSON 模式参数,需要通过 Prompt 控制。在 System Prompt 或用户消息里加上”只输出 JSON,不要任何其他内容”,并提供 JSON schema 描述。效果上和 OpenAI 的 JSON 模式相当,但需要加容错解析逻辑(详见本系列的格式控制文章)。
Q:Claude 的工具调用结果为什么放在 user 消息里?
这是 Claude 和 OpenAI 在 API 设计上最大的架构差异。OpenAI 用独立的 tool 角色来传工具结果,Claude 把工具结果放在 user 消息的 content 数组里(类型为 tool_result)。两种设计都合理,但迁移时需要重写工具结果回传的逻辑。
总结
迁移检查清单——改这5个地方就能完成80%的迁移工作:
- SDK 导入和客户端初始化(
openai→anthropic) - System 消息从 messages 数组移到独立的
system参数 - 添加必填的
max_tokens参数 - 响应文本取值路径(
choices[0].message.content→content[0].text) - Token 用量字段名(
prompt_tokens→input_tokens,completion_tokens→output_tokens)
工具调用和视觉输入的迁移改动更多,需要按照本文的代码示例逐项处理。如果改动量大,使用文中的适配层代码能大幅减少工作量。