通过统一 OpenAI 兼容网关消除各 Provider SDK 差异,实现跨模型的统一 fallback 循环,代码从多 try/catch 栈简化为简单循环。
模型提供商会宕机。他们会限速、出现容量波动、某个模型被弃用,有时候请求就是会失败。如果这个模型调用恰好位于用户很在意的路径上,你就需要一套 fallback 机制:第一个模型报错,就换另一个,理想情况是来自不同提供商,这样一家厂商的倒霉日就不会变成你的。
问题在于,用常规方式构建这套 fallback 意味着你得自己扛下提供商之间的差异。每家都有自己的 SDK、自己的客户端初始化方式、自己的错误类,以及自己对「可重试失败」的定义。你的 fallback 逻辑最终会变成一堆provider-specific 的 try/catch 块,而且每个都必须保持正确。通过一个 OpenAI 兼容的网关,差异消失了:每个模型的请求格式相同、HTTP 错误相同,所以 fallback 就是对模型名的简单循环。我在 Neon Function 上实现了它,并针对一个真实故障的模型做了测试。仓库链接在最后。
使用 per-provider SDK 的跨提供商 fallback,意味着每家提供商都有不同的客户端初始化和不同的错误处理逻辑。这套方案脆弱不说,代码量还大。
通过一个网关,每个模型的请求和 HTTP 状态码都是一样的,所以 fallback 就是一个循环:当前模型报错就尝试下一个。
我做了测试:对 ["not-a-real-model", "claude-haiku-4-5"] 发起的请求,第一个失败了,回退到 Claude,返回了答案并附带它尝试过的记录。
同样的循环也是一个路由原语:先试便宜的模型,失败了再升级;或者按成本、延迟、能力来排序这个链条。
假设你想要「先试 GPT,失败了换 Claude」。用 provider SDK 的话,就有两个客户端、两种读取错误的方式、两套思维模型:
// 当每个 provider 有自己的 SDK 时,你最终会写出这样的形状。
try {
return await openai.chat.completions.create({ model: 'gpt-5-nano', messages });
} catch (err) {
if (isRetryable(err)) {
// 不同的 SDK、不同的客户端、不同的错误类型、不同的选项。
return await anthropic.messages.create({ model: 'claude-haiku-4-5', ... });
}
throw err;
}
加上第三家提供商,情况只会变更糟,而且不是线性变糟,是组合级变糟——因为每个新的 fallback 目标都是一个新 SDK,带着新的错误语义需要特殊处理。决定是否 fallback 的逻辑现在和与各家厂商通信的逻辑纠缠在一起了。

通过网关,每个模型都是同一个 POST 请求和同一个 HTTP 状态码,所以是否 fallback 的决策是统一的。把模型排好序,依次尝试,遇到第一个成功的就停下:
async function chatWithFallback(models: string[], prompt: string, maxTokens: number) {
const tried: { model: string; status: number }[] = [];
for (const model of models) {
const result = await callGateway(model, prompt, maxTokens); // 对每个模型都是相同的调用
tried.push({ model, status: result.status });
if (result.ok) return { model, content: result.content, usage: result.usage, tried };
}
throw new Error(`all models failed: ${JSON.stringify(tried)}`);
}
所有的 provider 共用同一个 callGateway,所以错误只能来自一个地方,处理逻辑也只需在一个地方写。加第四个或第五个 fallback 只需要往数组里加一个字符串。
我发了一个请求,第一个模型不存在,后面跟着一个真实模型。网关对坏模型返回了 400,循环继续,Claude 给出了回答。响应中包含了它尝试过的记录,所以 fallback 是可观测的。

tried 数组是关键部分。第一个模型返回了 400,循环推进,第二个返回了 200 并附上答案。在生产环境中,这条 tried 记录告诉你发生了 fallback,这样你就可以在备份模型被使用得太多时发出告警。
一旦「从一个有序列表里选模型」变成了一个循环,你就拥有了一个路由原语,而不仅仅是一个故障处理程序。同样的结构可以覆盖:
成本优先。 把最便宜的可用模型放在前面,只有失败时才升级到贵的。大多数请求永远不会打到那个昂贵的模型上。
延迟优先。 把响应最快的模型放在前面,用于交互式路径。
能力优先。 通过为每个请求选择顺序,把长上下文或工具调用请求路由到更大的模型,把其他请求路由到小模型。
这个链条是数据,所以路由策略可以放在配置文件里,或者按请求计算,无需触碰调用点。
Fallback 按设计会隐藏失败,所以要记录 tried 记录,并在备份模型被大量使用时告警;沉默的 fallback 就是沉默的故障。还有,回退到一个能力相当的模型,而不是弱很多的模型,否则你的用户在故障期间得到的是一个悄然变差的答案,而不是他们本来会注意到的报错。
Fallback 循环及其使用的单一 callGateway 在这里:
https://github.com/The-DevOps-Daily/neon-ai-gateway-demo
跨提供商 fallback 之所以背负了「繁琐」的名声,只是因为每家提供商带来了自己的 SDK 和错误模型。在前面放一个网关,这一切都消失了:统一的请求格式、统一的状态码,fallback 变成了对一个有序模型名列表的循环。这个列表本身也是一个路由旋钮——按成本、按延迟或按能力优先——所以你为故障时添加的韧性,同时也就是把每个请求送到正确模型的机制。