从官方 API 或其他 OpenAI 兼容服务迁移:换地址和 Key,分批切流量,留好回滚
迁移到土土金 API,代码通常只改 base_url、api_key 和模型名;上线前要核对模型列表、流式输出、错误码和限流的差异,再按小流量、半量、全量分批切换,旧服务保留到稳定为止。本文给出迁移清单、按比例分流的代码和回滚办法。
迁移到土土金 API,代码通常只改三处:base_url 换成 https://www.tutujin.com/v1,api_key 换成在控制台创建的 Key,model 换成本站模型广场里的名字。真正要花时间的是上线前的核对和上线后的切换:先用模型列表接口确认模型都在,再核对流式输出、错误码和限流的差异,然后按小流量、半量、全量分批把请求切过来。原来的服务一直保留到全量稳定为止,分流比例放在配置里,出问题时调回 0 就是回滚。
迁移前先确认
| 要确认的 | 怎么确认 |
|---|---|
| 用到的接口本站支持 | 对照下面的接口表;用 Claude 或 Gemini 原生 SDK 的,也有对应的原生接口 |
| 模型都有,名字对得上 | 用新 Key 调用模型列表接口,见下文第 2 步 |
| 费用心里有数 | 在模型与价格查单价,估算方法见大模型 API 价格怎么算 |
| 分组选好了 | 分组决定线路和价格,见如何选分组 |
本站支持的接口:
| 接口 | 方法与路径 | 状态 | 模型数 |
|---|---|---|---|
| Chat Completions | POST/v1/chat/completions | 已实测 | 681 |
| Responses | POST/v1/responses | 原样转发 | 5 |
| Claude Messages | POST/v1/messages | 已实测 | 49 |
| Gemini generateContent | POST/v1beta/models/{model}:generateContent | 已实测 | 63 |
| 图像生成 | POST/v1/images/generations | 原样转发 | 54 |
| 图像编辑 | POST/v1/images/edits | 原样转发 | 31 |
| Embeddings 向量 | POST/v1/embeddings | 已实测 | 18 |
| Rerank 重排序 | POST/v1/rerank | 原样转发 | 6 |
| 语音合成 TTS | POST/v1/audio/speech | 原样转发 | 8 |
| 语音识别 STT | POST/v1/audio/transcriptions | 原样转发 | 1 |
| Midjourney 绘图 | POST/mj/submit/imagine | 原样转发 | 13 |
| Suno 音乐 | POST/suno/submit/music | 原样转发 | 2 |
「已实测」表示我们在本站完成过真实调用与扣费核对;「原样转发」表示请求按原格式转发给模型服务,参数以对应模型的官方说明为准。模型数为当前目录中支持该接口的模型数量。
第 1 步:创建 Key,改地址
在控制台「令牌」页创建一把迁移专用的 Key,设好额度上限和分组。然后按你原来用的方式改配置:
| 原来的调用方式 | 要改的地方 | 改成 |
|---|---|---|
| OpenAI SDK,或其他 OpenAI 兼容服务 | base_url / baseURL,或环境变量 OPENAI_BASE_URL | https://www.tutujin.com/v1 |
| Anthropic SDK | base_url / baseURL | https://www.tutujin.com |
| Google Gen AI SDK | http_options 里的 base_url | https://www.tutujin.com |
| 直接发 HTTP 请求 | 完整地址 | 例如 https://www.tutujin.com/v1/chat/completions |
api_key 一律换成本站的 Key。只改环境变量就能切过来的项目:
export OPENAI_BASE_URL="https://www.tutujin.com/v1"
export OPENAI_API_KEY="sk-你的Key"三种请求头的写法和各 SDK 的完整配置,见认证与 Base URL。
第 2 步:核对模型名
用新 Key 调用模型列表接口,返回的就是这把 Key 能调用的模型:
curl https://www.tutujin.com/v1/models \
-H "Authorization: Bearer $TUTUJIN_API_KEY"把代码里用到的每个模型名和返回结果逐个对上。同一个模型在不同平台的叫法可能不一样,以本站的名字为准;对不上的,在代码里做一张映射表。返回里没有某个模型,多半是 Key 的分组不提供它,在模型详情页查它有哪些分组,见模型列表接口。
第 3 步:核对行为差异
接口格式兼容,但有几处行为要按本站的情况处理:
| 方面 | 本站的情况 | 代码里要做的 |
|---|---|---|
| 余额不足 | 返回 403,错误码 insufficient_user_quota;OpenAI 官方返回的是 429 | 把 403 单独处理:提醒充值,不要放进重试队列 |
| 分组里没有这个模型 | 返回 503,错误码 model_not_found | 不要无限重试,换模型名或换分组 |
| 错误体 | error 对象里的 code 经常是空字符串,message 末尾带 request id | 按 HTTP 状态码判断,记录完整错误响应,排查时把 request id 发给客服 |
| 限流 | 429,额度随线路和负载变化,不公布固定数字 | 退避重试,设并发上限,见限流说明 |
| 流式输出 | Chat Completions 的流式输出已在本站实测;数据开始返回后出错,表现为流提前结束、收不到 data: [DONE] | 把没收到结束标记的流当作失败处理 |
| 参数支持 | 非 OpenAI 模型对 response_format、tools、reasoning_effort 等参数的支持各不相同 | 不支持的参数可能被忽略或返回 400,换模型后先用少量请求验证 |
| 用量字段 | 响应的 usage 返回输入、输出 tokens,命中缓存时有缓存 tokens | 在你自己的系统里按 usage 记账,再和控制台核对 |
完整的状态码对照见错误码与重试,计费字段的说明见计费与用量。
第 4 步:分批切换
不要一次全量切换。按比例分流,每一阶段都看过数据再进下一阶段:
| 阶段 | 发到本站的比例 | 看什么 | 进入下一阶段的条件 |
|---|---|---|---|
| 离线验证 | 不接线上流量 | 用一批真实的历史请求对比输出质量、延迟和花费 | 结果可以接受 |
| 小流量 | 5% 左右 | 错误率、延迟、单次请求的花费 | 稳定运行一段时间,没有新增的错误类型 |
| 半量 | 50% | 同上,再看高峰时段有没有 429 | 高峰期也稳定 |
| 全量 | 100% | 同上 | 稳定后再下线原来的服务 |
分流按用户或会话固定:同一个用户始终走同一边,对比效果、排查问题都更清楚。比例放在环境变量或配置中心里,调整和回滚都不用改代码:
import os
import zlib
from openai import OpenAI
tutujin = OpenAI(base_url="https://www.tutujin.com/v1", api_key=os.environ["TUTUJIN_API_KEY"])
legacy = OpenAI(base_url=os.environ["OLD_BASE_URL"], api_key=os.environ["OLD_API_KEY"])
# 原服务的模型名 → 本站的模型名,比如原平台的名字带厂商前缀
MODEL_MAP = {"openai/gpt-4.1-mini": "gpt-5.4-mini"}
def rollout() -> float:
# 0 = 全部走原服务,1 = 全部走本站;每次读取,改配置立即生效
return float(os.environ.get("TUTUJIN_ROLLOUT", "0"))
def chat(user_id: str, model: str, messages: list):
bucket = zlib.crc32(user_id.encode()) % 100 # 同一个用户永远落在同一个桶
if bucket < rollout() * 100:
return tutujin.chat.completions.create(model=MODEL_MAP.get(model, model), messages=messages)
return legacy.chat.completions.create(model=model, messages=messages)import OpenAI from 'openai'
import { crc32 } from 'node:zlib'
const tutujin = new OpenAI({ baseURL: 'https://www.tutujin.com/v1', apiKey: process.env.TUTUJIN_API_KEY })
const legacy = new OpenAI({ baseURL: process.env.OLD_BASE_URL, apiKey: process.env.OLD_API_KEY })
// 原服务的模型名 → 本站的模型名,比如原平台的名字带厂商前缀
const MODEL_MAP = { 'openai/gpt-4.1-mini': 'gpt-5.4-mini' }
// 0 = 全部走原服务,1 = 全部走本站;每次读取,改配置立即生效
const rollout = () => Number(process.env.TUTUJIN_ROLLOUT ?? '0')
export function chat(userId, model, messages) {
const bucket = crc32(userId) % 100 // 同一个用户永远落在同一个桶
if (bucket < rollout() * 100) {
return tutujin.chat.completions.create({ model: MODEL_MAP[model] ?? model, messages })
}
return legacy.chat.completions.create({ model, messages })
}Python 和 Node.js 两个版本对同一个用户算出的桶相同,两种语言的服务混用也能保持一致。每个阶段都在控制台「操作日志」里按令牌名称筛选这把迁移 Key,核对请求数、错误和花费,再和你自己系统里按 usage 记的账对一遍。
回滚
- 发现问题,先把分流比例调回 0,请求立刻全部回到原来的服务,不用改代码、不用重新部署。
- 全量之后,原来服务的账号和 Key 先别注销,保留到新服务稳定运行一段时间。
- 回滚之后,用操作日志里的时间、模型和错误信息定位原因;需要我们协助时,把错误里的 request id 发给客服微信
JsnonoChat。
切换完成后
- 给生产 Key 设额度上限和 IP 白名单,见 API Key 安全最佳实践。
- 关键业务给 Key 配多个分组、打开跨分组重试,见大模型 API 高可用。
- 每月按 Key 导出用量对账,见成本分摊与对账。
常见问题
迁移要改多少代码?
用 OpenAI SDK 的项目,一般只改 base_url、api_key 和模型名。另外要单独处理一个差异:余额不足时本站返回 403,不是 OpenAI 的 429,不能放进重试队列。
迁移期间能同时用原来的服务和土土金 API 吗?
能。分批切换时两边并行:按比例把一部分请求发到本站,其余照旧,确认稳定后再逐步提高比例。
模型名和原平台不一样怎么办?
以本站模型广场和模型列表接口返回的名字为准,在代码里维护一张模型名映射表,切换时按表替换。
本站的限流额度是多少?
不公布固定数字,额度随线路和负载变化。按退避重试和并发上限来设计调用;长期需要更高并发,加客服微信 JsnonoChat 说明模型、分组和请求规模。
出了问题怎么回滚?
把分流比例调回 0,请求就全部回到原来的服务。分流比例放在环境变量或配置中心里,回滚不用改代码、不用重新部署。