把现有 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),并在客户端工厂函数里按配置选择。这样切换上游不需要改业务代码。
验证返回结构是否不变
迁移后最值得做的是「结构验证」,而不是只看能否返回文本。建议按下面顺序检查:
- 断言响应形状:确认
choices[0].message.content、choices[0].finishreason、usage.prompttokens/completion_tokens等字段存在且类型一致。 - 对比同一 prompt:用迁移前后的两个 client 各跑一次相同的 prompt,比较字段路径而非文本内容(生成本身有随机性)。
- 检查流式响应:如果你用
stream=True,确认返回的每个 chunk 结构与官方一致,例如delta.content的增量拼接结果符合预期。 - 错误处理路径:传一个无效模型名或空 messages,确认抛出的异常类型与状态码处理逻辑仍然适用。
- 用量字段:如果你的计费、埋点或日志依赖
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,按业务场景选择。