过去一年,我在不同项目里接过多家大模型:有的使用 OpenAI 风格接口,有的使用自有 SDK,有的鉴权头不同,还有的流式事件格式完全不一样。
只接一个模型时,这些差异不算麻烦。但当项目需要同时使用 GPT、Claude、Gemini、DeepSeek、Qwen 等模型,或者想在 Codex、Claude Code 和业务服务之间复用同一套模型资源时,问题就会逐渐暴露:
- 每增加一家供应方,都要维护一套 SDK 和环境变量;
- 模型切换会侵入业务代码;
- 超时、重试、限流和日志散落在各个调用点;
- 多个控制台的余额、账单和调用记录难以统一;
- AI 编程工具与业务程序往往还使用不同的协议。
这篇文章不比较哪个模型"最强",而是讨论一个更工程化的问题:如何用统一 AI 网关降低应用与模型供应方之间的耦合,并让同一套 API 同时服务于代码、Codex 和 Claude Code。
一、为什么不建议让业务代码直接绑定模型厂商
最常见的接入方式,是在业务层直接调用厂商 SDK:
text
业务服务 -> 厂商 SDK -> 模型 API
它的优点是链路短,也能第一时间使用厂商的专属能力。但当模型数量增加后,业务层很容易按 OpenAI、Anthropic、Google 等供应方堆叠条件分支。真正麻烦的并不是分支本身,而是它们背后的差异:
| 关注点 | 可能存在的差异 |
|---|---|
| 鉴权 | Bearer Token、自定义请求头、项目凭据 |
| 请求结构 | messages、contents、system 字段的位置 |
| 流式响应 | SSE 数据块、事件名称、结束标记 |
| 工具调用 | 参数结构、调用 ID、并行工具调用能力 |
| 错误语义 | HTTP 状态码、错误码、是否适合重试 |
| 用量统计 | 输入、输出、缓存、推理 Token 的口径 |
如果这些差异散落在 Controller、定时任务、Agent 和脚本中,后续想换模型时,改动范围通常比预期大。
更稳妥的做法,是在业务和模型供应方之间增加一个稳定的抽象层:
text
业务服务 / AI 编程工具
|
v
统一 AI 网关
├─ API Key 鉴权
├─ 协议适配
├─ 模型路由
├─ 超时与有限重试
├─ 限流与配额
└─ 日志与用量统计
|
v
OpenAI / Anthropic / Google / 其他模型服务
客户端只需要关心三个配置:
- Base URL;
- API Key;
- Model ID。
模型供应方发生变化时,客户端接口尽量保持稳定,差异由网关内部消化。
二、统一接口不等于抹平所有协议
OpenAI 兼容接口已经成为很多 AI 工具事实上的通用接入方式。例如对话请求通常采用以下结构:
json
{
"model": "模型 ID",
"messages": [
{ "role": "system", "content": "你是一名编程助手" },
{ "role": "user", "content": "解释这段代码" }
]
}
只要网关提供兼容端点,大量 SDK 和客户端就可以通过修改 Base URL 完成迁移,业务代码不必重新实现一遍。
但"兼容"需要有边界。OpenAI、Anthropic 与 Google 的协议并非完全等价,至少有三类能力需要单独处理:
1. 流式事件
不同协议的事件名称、增量字段和结束方式不完全相同。网关不能只转发原始字节,还需要保证客户端能正确识别文本增量、工具调用和结束事件。
2. 工具调用
工具定义、工具选择和调用结果回传的结构可能不同。简单对话可以统一,复杂 Agent 场景则要验证目标模型与兼容层是否完整支持工具调用。
3. 厂商专属参数
推理强度、缓存、视觉输入、结构化输出等能力,可能只在特定协议或模型中存在。好的抽象不是强行取交集,而是让通用能力保持一致,同时允许高级调用显式选择原生协议。
因此,一个实用的网关通常会同时提供 OpenAI、Anthropic 或 Google 兼容入口,而不是把所有请求都压成一种格式。
三、用 Node.js 调用 OpenAI 兼容接口
下面给出一个通用的 OpenAI 兼容接口示例。代码只使用 Node.js 18+ 自带的 fetch,没有第三方依赖,并包含环境变量校验、30 秒超时、HTTP 错误和响应结构检查。接口地址、密钥和模型都从环境变量读取,因此可以用于官方接口、自建网关或其他兼容服务。
js
const apiKey = process.env.AI_GATEWAY_API_KEY;
const model = process.env.AI_GATEWAY_MODEL;
const baseUrl = (process.env.AI_GATEWAY_BASE_URL || '').replace(/\/$/, '');
if (!apiKey) {
console.error('请先设置 AI_GATEWAY_API_KEY');
process.exit(1);
}
if (!model) {
console.error('请先设置 AI_GATEWAY_MODEL');
process.exit(1);
}
if (!baseUrl) {
console.error('请先设置 AI_GATEWAY_BASE_URL,例如兼容服务的 /v1 地址');
process.exit(1);
}
async function createChatCompletion() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30_000);
try {
const response = await fetch(`${baseUrl}/chat/completions`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model,
messages: [
{
role: 'system',
content: '你是一名严谨的 JavaScript 助手。'
},
{
role: 'user',
content: '请用三句话解释什么是事件循环。'
}
],
temperature: 0.3
}),
signal: controller.signal
});
const responseText = await response.text();
if (!response.ok) {
throw new Error(`请求失败:HTTP ${response.status} ${responseText}`);
}
const data = JSON.parse(responseText);
const content = data.choices?.[0]?.message?.content;
if (!content) {
throw new Error(`响应中没有可读取的文本:${responseText}`);
}
console.log(content);
} catch (error) {
if (error.name === 'AbortError') {
console.error('请求超时:30 秒内未收到完整响应');
} else {
console.error(error instanceof Error ? error.message : String(error));
}
process.exitCode = 1;
} finally {
clearTimeout(timeoutId);
}
}
createChatCompletion();
保存为 chat.js 后,先设置密钥和模型:
bash
export AI_GATEWAY_BASE_URL="https://vsoui.com/v1"
export AI_GATEWAY_API_KEY="sk-你的密钥"
export AI_GATEWAY_MODEL="your-model-id"
node chat.js
把示例地址和模型 ID 替换成实际服务提供的配置。可用模型不应该长期写死在代码里;如果兼容服务实现了模型列表接口,可以通过环境变量查询:
bash
curl "$AI_GATEWAY_BASE_URL/models" \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY"
这里有两个容易忽略的细节:
- Base URL 已经包含
/v1,代码里只需追加/chat/completions; - 错误响应也可能是 JSON,但排查阶段保留原始响应文本往往更有用。
四、Codex 和 Claude Code 本质上也在配置网关
AI 编程工具的界面不同,底层配置思路却很接近:告诉工具使用哪个协议、请求地址是什么、密钥从哪里读取,以及默认使用哪个模型。
Codex:OpenAI Responses 协议
Codex 支持自定义模型供应方。一个典型的 ~/.codex/config.toml 配置如下:
toml
model_provider = "custom_gateway"
model = "your-model-id"
model_reasoning_effort = "high"
[model_providers.custom_gateway]
name = "custom_gateway"
base_url = "https://vsoui.com/v1"
env_key = "OPENAI_API_KEY"
wire_api = "responses"
密钥仍然通过环境变量注入:
bash
export OPENAI_API_KEY="sk-你的密钥"
codex
这里最值得注意的是 wire_api = "responses"。业务代码常用 /chat/completions,而新版 Agent 工具可能使用 Responses API。配置兼容网关时,不能只确认"兼容 OpenAI",还要确认它是否支持具体客户端使用的端点。
base_url 和 model 是配置模板,使用时替换为目标服务的实际地址和模型 ID。
Claude Code:Anthropic 协议
Claude Code 更适合走 Anthropic 兼容入口,核心配置可以通过环境变量表达:
bash
export ANTHROPIC_AUTH_TOKEN="sk-你的密钥"
export ANTHROPIC_BASE_URL="https://vsoui.com"
export ANTHROPIC_MODEL="your-model-id"
claude
这里使用的是 Anthropic 兼容协议,而不是上一节的 OpenAI 兼容协议。不同网关对路径前缀的约定可能不同,Base URL 和模型名称都应以目标服务的接口说明为准。
如果同时使用 Codex、Claude Code、OpenCode 等工具,可以借助配置管理工具切换供应方;不过无论界面如何封装,最终都要检查四件事:
- Base URL 是否与协议匹配;
- 密钥读取的环境变量名是否正确;
- 客户端使用 Chat Completions、Responses 还是 Anthropic Messages;
- 所选模型是否支持工具调用、视觉或推理等所需能力。
五、一个统一模型网关真正需要解决什么
把请求成功转发,只完成了最基础的一步。用于真实项目时,还需要重点处理下面几类问题。
1. 超时分层
连接超时、首 Token 超时和完整响应超时应该分别观察。推理模型可能首 Token 较慢,如果只有一个很短的总超时,会把正常的深度推理误判为故障。
2. 有限重试
可以考虑重试网络错误、429 和部分 5xx,但不要无条件重试所有请求:
400一般是参数错误,重试没有意义;- 流式响应已向用户输出部分内容后,自动重试可能产生重复文本;
- 带工具副作用的 Agent 请求,需要幂等键或业务去重。
推荐采用有限次数的指数退避,并为单次请求设置总时间预算。
3. 模型路由与降级
路由不只是"模型 A 失败就换模型 B"。两个模型的上下文长度、工具调用、视觉能力和输出风格可能不同。降级策略应该按能力标签配置,例如:
text
代码任务 -> 代码模型主路由 -> 同能力模型备用路由
视觉任务 -> 支持图片输入的模型集合
批处理 -> 低成本模型 + 更严格的并发限制
4. 日志脱敏
建议记录请求 ID、模型、耗时、状态码、Token 用量和错误类型,但不要默认保存完整 Prompt。API Key、Authorization 头、用户隐私和上传文件信息必须脱敏。
5. 密钥隔离
生产密钥不应写在前端代码、仓库或截图中。至少应按环境和应用拆分密钥,并支持轮换、吊销、额度限制和异常用量告警。
6. 成本可观测
仅看请求次数不够。不同模型的输入、输出、缓存和推理 Token 价格可能不同,网关需要统一记录用量口径,才能回答"哪个功能、哪个用户、哪个模型消耗最多"。
六、统一网关并不适合所有项目
统一网关能降低接入成本,但它不是所有系统的默认答案。以下场景更应该认真评估官方直连或企业级专线方案:
- 对数据驻留地区有明确要求;
- 处理医疗、金融、政务等敏感数据;
- 依赖某家厂商刚发布、尚未被兼容层支持的专属能力;
- 需要与厂商签署单独的 SLA、DPA 或审计协议;
- 调用规模足够大,值得直接谈企业合同和专属配额。
无论选择官方接口、自建网关还是第三方兼容服务,都建议先用非敏感数据进行验证,测试模型可用性、流式响应、工具调用、超时、错误码和用量记录,再决定是否进入正式环境。
总结
多模型时代,真正值得稳定下来的不是某个具体模型名,而是应用与模型之间的接口边界。
一个实用的统一 AI 网关,至少应该做到:
- 用稳定入口隔离上游变化;
- 明确处理不同协议的兼容边界;
- 统一超时、重试、限流、日志和用量;
- 让业务代码和 AI 编程工具复用同一套接入方式;
- 对安全、合规和厂商专属能力保留清晰边界。
当接入规模扩大后,还可以继续把模型能力标签、动态路由、请求幂等和成本归因拆成独立模块。这样即使上游模型持续变化,业务层仍然可以维持稳定、可测试的调用边界。
接入地址是:https://vsoui.com/