📌 内容摘要

  • 从 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%的迁移工作:

  1. SDK 导入和客户端初始化(openaianthropic
  2. System 消息从 messages 数组移到独立的 system 参数
  3. 添加必填的 max_tokens 参数
  4. 响应文本取值路径(choices[0].message.contentcontent[0].text
  5. Token 用量字段名(prompt_tokensinput_tokenscompletion_tokensoutput_tokens

工具调用和视觉输入的迁移改动更多,需要按照本文的代码示例逐项处理。如果改动量大,使用文中的适配层代码能大幅减少工作量。