硅碳相变 Token工厂
大模型 APIAPI 网关成本优化聚合平台接入实践

如何在不修改代码的情况下切换 LLM 提供商

硅碳相变 Token工厂团队·2026-09-03

大多数人启动项目时并不会考虑提供商的可移植性。他们从最容易注册的供应商那里拿到一个密钥,编写集成代码,然后发布。几个月后,他们读到了一篇基准测试,或者打开了一张账单,又或者撞上了速率限制,于是他们想尝试点别的。这时痛苦就出现了,因为他们已经把对第一个提供商的假设深深嵌入了 HTTP 层。

解决办法不是什么抽象库。而是一种通信格式。OpenAI 的聊天补全 API 已经悄然成为与 LLM 通信的默认协议,一旦你基于它编写代码,切换提供商基本上就只是改两个字符串的事。

所有人都抄袭的 API

OpenAI 定义了一个简单的契约:你向 /v1/chat/completions POST JSON,然后拿回一个 choices[0].message.content 字符串。请求长这样:

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "用一段话解释 DNS"}]
  }'

现在几乎所有主流模型都说这种方言。DeepSeek、Qwen、ERNIE、Doubao 以及其他模型都暴露了一个端点,接受相同的请求体并返回相同的结构。这意味着你的客户端代码并不关心另一端是谁。你可以只替换 model 字符串和端点,其他什么都不用改。

下面是把同一个调用指向一个聚合器,它在同一个基础 URL 下承载了其中好几个模型:

curl https://api.token8341.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKENWORKS_API_KEY" \
  -d '{
    "model": "deepseek-chat",
    "messages": [{"role": "user", "content": "用一段话解释 DNS"}]
  }'

一个客户端,多个模型

在 Python 中,你通常会实例化一次 OpenAI SDK,并在每次请求时传入模型名称。如果你的提供商支持兼容 API,你只需设置一次 base_url,其他一切保持不变:

from openai import OpenAI

client = OpenAI(
    base_url="https://api.token8341.com/v1",
    api_key="sk-your-tokenworks-key",
)

def ask(model: str, prompt: str) -> str:
    resp = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
    )
    return resp.choices[0].message.content

# 同一个函数,三个不同的模型家族。
print(ask("gpt-4o", "帮我总结这段日志。"))
print(ask("deepseek-chat", "帮我总结这段日志。"))
print(ask("qwen-max", "帮我总结这段日志。"))

函数不变。模型字符串变。这就是全部诀窍。流式传输也是同样的方式:

stream = client.chat.completions.create(
    model="deepseek-chat",
    messages=[{"role": "user", "content": "写一首关于数据库的俳句"}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content or ""
    print(delta, end="")

函数调用、JSON 模式和嵌入全都运行在同一个兼容接口之上,所以切换时你并不局限于纯聊天。

切换并不能带给你什么

我想把那些不会自动改变的部分说清楚。传输层是可移植的;模型不是。

不同的模型对提示词的响应方式不同。为 GPT-4o 调优的系统提示词在一个需要不同结构的推理模型上可能表现不佳。上下文窗口各不相同,有时差异很大:一个模型可能接受 128k token,而另一个只接受 32k。分词器也不同,所以同一份文档在每个模型上消耗的 token 数不一样。最大输出长度是另一个按模型而非按协议决定的参数。

所以“不修改代码就能切换”实际上是“不用重写 HTTP 客户端就能切换”。在把生产流量指向一个新模型之前,你仍然需要为自己做一次评估运行。好消息是,兼容 API 让这种评估跑起来很便宜,因为测试框架不过是对模型名称的一个循环。

一个实用的模式

我在业余项目中实际的做法是维护一张小的配置表,而不是改代码:

MODELS = {
    "fast": "deepseek-chat",
    "smart": "qwen-max",
    "frontier": "gpt-4o",
}

def run(task, prompt):
    return ask(MODELS[task], prompt)

想为 fast 通道尝试一个更便宜的模型?改一行。想在第一个提供商被限流时回退到第二个?捕获异常并用下一个模型字符串重试。这些都不触及请求的构造。

这种回退模式正是多模型端点体现价值的地方。用一个基础 URL 和一个密钥,你就可以跨提供商进行路由、重试和 A/B 测试,全部通过你第一天写的那两行客户端设置完成。

需要注意的响应怪癖

传输层是兼容的,但一些响应层面的差异仍然会漏出来。推理模型有时会在正常消息之外返回一个额外字段,比如 reasoning_content。读取 choices[0].message.content 的代码无论如何都能继续工作,但你可能想在调试视图中把那段推理文本展示出来。错误消息也各不相同:一个提供商返回有用的 insufficient_quota 字符串,另一个只返回一个没有响应体的裸 429。如果你在构建重试逻辑,把任何 429 或 5xx 都当作“退避并重试”来处理,而不是去解析供应商特有的文本。

这些都不是让你锁定在单一提供商的理由。它是让你保持错误处理通用、把模型选择当作变量的理由。

这就是 SiCore TokenWorks 的立场:在 https://api.token8341.com/v1 提供一个 OpenAI 兼容端点,当你在 GPT-4o、Claude、Gemini、DeepSeek、Qwen、ERNIE、Doubao、Spark 和 Pangu 之间切换时,模型字符串是唯一需要改变的东西。