chat-completions-migration-from-openai · ZH · 2026-10-06

把现有 OpenAI SDK 项目迁移到聚合站 /chat/completions 接口

面向已使用 OpenAI 官方 SDK 的项目,说明如何以最小改动迁移到聚合站的 /chat/completions 接口:只调整 base_url、API key 与少量环境变量,并给出验证返回结构一致性的实践方法。

迁移的目标:把改动限制在配置层

如果你现在的代码已经在用 OpenAI 官方 SDK(Python 的 openai、Node 的 openai,或其他兼容实现),迁移到聚合站通常不需要重写业务逻辑。聚合站提供与 OpenAI 兼容的 /chat/completions 接口,因此核心工作是把「请求发往哪里」和「用什么凭证」这两件事改掉,而不是改调用方式。

典型改动只有三处:

  • base_url:从 OpenAI 官方地址改为聚合站的接口地址(通常形如 https://<host>/v1,以文档为准)。
  • API key:把官方 key 换成聚合站签发的 key。
  • 模型名:沿用你请求里已有的字符串,确认它在聚合站可用即可。

为什么只需要改 base_url 就能跑

OpenAI SDK 的设计把「协议」和「端点」分开了。SDK 内部负责:

  • 组装请求体(model、messages、temperature 等);
  • 附加 Authorization: Bearer <key> 头;
  • 解析 JSON 响应并映射成 SDK 的返回对象。

只要服务端遵循 OpenAI 的请求/响应约定,SDK 就不会感知到端点变化。聚合站保持 /chat/completions 的路径与字段语义,因此 chat.completions.create(...) 这类调用可以原样保留。

需要留意的是「兼容」通常指对话补全这一层的字段与行为兼容。如果你依赖 OpenAI 特有的实验特性、特定工具调用细节或私有端点,建议先在测试环境验证。

Python 项目的迁移步骤

以官方 openai SDK 为例,常见写法是:

from openai import OpenAI

client = OpenAI(
    base_url="https://<聚合站域名>/v1",
    api_key=os.environ["AGGREGATOR_API_KEY"],
)

resp = client.chat.completions.create(
    model="<模型名>",
    messages=[{"role": "user", "content": "hello"}],
)
print(resp.choices[0].message.content)

关键点:

  • base_url 要指向聚合站的 /v1(或以文档给出的实际路径为准),不要保留默认的官方地址。
  • 不要硬编码 key。用环境变量区分不同环境,避免把 key 提交进仓库。
  • 如果你的代码此前用 openai.apikey = ... 的旧式全局配置,建议改为显式构造 client,便于注入不同的 baseurl。

Node / TypeScript 项目的迁移步骤

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://<聚合站域名>/v1",
  apiKey: process.env.AGGREGATOR_API_KEY,
});

const resp = await client.chat.completions.create({
  model: "<模型名>",
  messages: [{ role: "user", content: "hello" }],
});

注意 Node SDK 的字段名是 baseURL(大写 URL),与 Python 的 base_url 不同,这是最容易写错的地方。

环境变量与多环境管理

推荐把「端点」和「密钥」都放进环境变量,代码里只读配置:

  • OPENAIBASEURL 或自定义的 LLMBASEURL:指向聚合站。
  • OPENAIAPIKEY 或自定义的 LLMAPIKEY:使用聚合站签发的 key。

如果你的部署同时存在直连官方和走聚合站两种场景,可以用前缀区分(例如 AGG 与 OPENAI),并在客户端工厂函数里按配置选择。这样切换上游不需要改业务代码。

验证返回结构是否不变

迁移后最值得做的是「结构验证」,而不是只看能否返回文本。建议按下面顺序检查:

  1. 断言响应形状:确认 choices[0].message.content、choices[0].finishreason、usage.prompttokens / completion_tokens 等字段存在且类型一致。
  2. 对比同一 prompt:用迁移前后的两个 client 各跑一次相同的 prompt,比较字段路径而非文本内容(生成本身有随机性)。
  3. 检查流式响应:如果你用 stream=True,确认返回的每个 chunk 结构与官方一致,例如 delta.content 的增量拼接结果符合预期。
  4. 错误处理路径:传一个无效模型名或空 messages,确认抛出的异常类型与状态码处理逻辑仍然适用。
  5. 用量字段:如果你的计费、埋点或日志依赖 usage,确认它在聚合站返回中同样存在。

把上面这些写成一组轻量集成测试,未来更换模型或上游时可以直接复用。

计费与账号侧的差异

协议兼容不等于账号体系相同。聚合站有自己的计费方式,理解它能帮你正确设置预算与告警:

  • 作为使用者,按官方价乘以 1.3 扣费;
  • 如果你贡献 key,按官方价乘以 1.1(优质 key 为 1.2)以 USDC 返还。

也就是说,通过聚合站调用与直连官方在单价上是有差异的,具体以站点说明为准。建议在代码里记录每次调用的 usage,方便对账。

迁移检查清单

  • [ ] base_url / baseURL 已指向聚合站。
  • [ ] API key 从环境变量读取,未硬编码。
  • [ ] 代码中使用的模型名在聚合站可用。
  • [ ] 非流式与流式两种调用都跑通。
  • [ ] 断言了响应字段路径,而不是只看文本。
  • [ ] 错误处理与重试逻辑在新端点上行为符合预期。
  • [ ] 记录了 usage,便于后续核对扣费。

常见问题

需要改 import 吗?
通常不需要。仍使用官方 SDK 包,只是把 client 的配置指向聚合站。

为什么请求路径是 /v1/chat/completions?
因为 SDK 会在 baseurl 后拼接 /chat/completions。所以 baseurl 里要包含 /v1,否则路径会拼错。

模型名写错会怎样?
通常返回错误响应。建议在启动时做一次探测调用,或维护一份允许的模型名常量,避免运行时才发现。

可以同时保留直连官方吗?
可以。构造两个 client,各自持有不同的 base_url 与 key,按业务场景选择。