多模型接入与降级链
大模型 API 就是一个外部依赖,跟你依赖的支付网关、短信服务没有本质区别。它会超时、会限流、会返回奇怪的东西,也会整个挂掉。
但很多团队对它的态度跟对其他外部依赖不一样——直接把 provider 的 SDK 写进业务代码,模型名硬编码,出错了就抛异常。然后第一次 provider 故障就发现:没有任何退路。
1. 抽象层要不要
1.1 先说结论
要,但别做成"通用抽象"。
业务代码里直接 new OpenAI() 的问题不是耦合,而是切换成本发生在最糟糕的时刻——provider 挂了、你正在改线上代码。改成"模型名 + 统一接口"的形式,切换就只是改配置。
1.2 OpenAI 兼容格式是事实标准
目前的主流做法是:自研或选一个网关,对外暴露 OpenAI 兼容的 chat completions 接口,对内路由到不同 provider。应用只改 baseURL 就能换后端。
开源侧有 LiteLLM(代理 + Python 库,100+ provider,支持 YAML 配 fallback 与熔断),托管侧有 OpenRouter、Portkey 等。这类网关顺带把用量统计、预算、缓存一起解决了——尤其是按团队分预算这件事,自己实现一遍不划算。
1.3 但兼容不等于等价
OpenAI 兼容只是请求和响应的形状兼容,不等于行为等价。切换模型之后,这些都会变:
- 输出的风格和长度倾向
- 对 System Prompt 的服从程度
- 结构化输出的严格程度
- 工具调用的参数填写质量
- 分词不同,所以 token 计数和价格不可直接比
所以"统一接口"只能统一调用方式,不能统一效果。每次换模型都要重新过一遍评测(见《评测集怎么变成门禁》),不能假设兼容就是可替换。
2. 错误要分类,不同错误走不同的路
把 provider 的错误当成一类处理,是降级设计最常见的错误。至少分四类:
| 错误 | 例子 | 该怎么做 |
|---|---|---|
| 瞬时故障 | 5xx、连接超时、读超时 | 同 provider 重试(带抖动),再换备选 |
| 限流 | 429、配额耗尽 | 退避重试;持续限流则切 provider |
| 请求本身的问题 | 上下文超窗、参数非法 | 换更大上下文的模型,或截断后重试 |
| 内容策略 | 拒绝生成 | 不要重试,也不要换 provider 硬冲 |
最后一类要特别小心:同一个请求被拒,换一家 provider 再发一次,本质是在找一家愿意生成的。这不是降级,是绕过。正确处理是给用户一个明确的反馈。
上下文超窗这一类有个专门的做法:配一条 context_window_fallbacks,超窗时自动切到上下文更大的模型,而不是报错。LiteLLM 这类网关支持把 fallback 按触发原因分开配。
3. 熔断比重试更重要
重试解决的是偶发错误,熔断解决的是"上游已经不行了,但每个请求还要等满超时"。
# 连续失败 3 次后,60 秒内不再把流量分给它
model_list:
- model_name: primary
litellm_params:
model: openai/gpt-5
api_key: os.environ/OPENAI_API_KEY
# 注意:allowed_fails 必须写在 model_info 下
# litellm_params 会被原样透传给上游,写在里面会被当成未知参数、不生效
model_info:
allowed_fails: 3
cooldown_time: 60
allowed_fails 写在 litellm_params 里是一个高频坑:不报错,但熔断永远不触发。要设全局默认,写在 router_settings 下。
熔断的作用有两个:一是快速失败,别让用户每次等 30 秒超时;二是给上游恢复的时间,别在它半死不活的时候继续打。
3.1 语义健康检查
标准熔断看 HTTP 状态码。但大模型有一种失败:返回 200,响应体是空的或结构不对。
技术上不是错误,功能上是坏的。如果不做检查,这个 provider 会一直留在轮换里,持续返回垃圾。
function isHealthy(resp: unknown): boolean {
if (!resp) return false
if (!resp.choices?.length) return false
const content = resp.choices[0]?.message?.content
// 空响应、或结构化输出模式下解析不出目标字段,都算失败
return typeof content === 'string' && content.trim().length > 0
}
判断标准自己定:有没有期望的字段、能不能解析成目标 schema、长度是否在合理区间。开销很小,但能拦住一整类"静默降级"。
4. 降级链怎么排
备选池里有谁,是选型阶段定的事(见《模型选型看哪几项》);这一节讲的是这些备选怎么排。
三层,按触发顺序:
第一层:同档换 provider。 能力相近的另一家。用户几乎无感,质量差异小。
第二层:降档换小模型。 更快更便宜,质量有可见下降。适用于"能答就行"的场景,比如客服首轮应答。
第三层:显式失败。 全部不可用时,返回明确的错误状态和说明。
第三层很重要:不要让系统用一段听起来合理、实际上没有依据的话去冒充答案。用户宁可看到"服务暂时不可用",也不想收到一句编出来的回复。这一点和《答案要能追溯到出处》里讲的是同一个原则——没有依据时,说没有依据。
如果确实需要兜底,可选的是:返回缓存的答案(并标注这是缓存的、生成于什么时间),或者转人工。
4.1 自托管作为最后一道
对可用性要求高的场景,可以在链尾挂一个自托管的开源模型(vLLM 或 Ollama)。它的价值不在于质量,而在于它挂不挂由你决定——当所有云厂商同时出问题时,它还在。
代价是你要运维它,而且它的质量通常低于商用前沿模型。把它当熔断的兜底,不要当主力。
4.2 对冲请求的取舍
同一个请求同时发给两家、取先返回的那个、取消另一个。它能明显压低 P99,但不能说"消除":两家延迟往往正相关(同一个上游抖动、同一段网络),长尾会一起长;而且取消不及时的话,token 花费会接近翻倍。
更常见的做法是延迟触发:先发一路,超过 P95 还没首字才发第二路。成本增量变成「触发率 × 一次调用」,通常远低于 2 倍,同样能压住长尾。代价是对冲会放大上游负载,要和限流器配合,别在自己的重试风暴里再叠一层对冲。
5. 降级带来的不一致
这是降级设计里最容易被忽略的部分:降级不只是换个模型,它会改变系统行为。
5.1 输出风格会变
同一个提示词在不同模型上的输出长度、语气、详略都不同。如果你的下游有解析逻辑(提取字段、截断显示、二次处理),这些逻辑可能对某个模型恰好能用、对另一个就崩。
缓解办法是把输出约束成 schema(见《结构化输出与自愈》),让下游依赖结构而不是依赖文风。这也是结构化输出最有价值的场景之一——不是为了好看,是为了跨模型稳定。
5.2 提示缓存会失效
如果用了提示缓存(见《Token 成本的三个杠杆》),切到另一个 provider 意味着缓存全部作废,第一次请求要付全额写入价。降级期间成本会突然升高,这是正常的,但要能看出来——不然监控上会显示"成本异常上涨",而实际原因是"降级率上升"。
5.3 限流额度是分开的
降级到备选 provider 之后,你用的是备选的额度。如果备选平时没流量、额度是冷的,突然全量打过去,可能触发它的冷启动限流。
所以备选 provider 最好保持一定的日常流量(哪怕只分 5%),既让额度保持温热,也能持续验证它确实可用——一条从没被真实流量验证过的降级链,等于没有降级链。
6. 要监控什么
降级的价值取决于你能不能知道它在发生。六个指标:
- 降级率 —— 走非主模型的请求占比。它涨了说明上游在恶化,而不是说明降级很稳
- 各 provider 的延迟 —— 按 p50/p95 分开,p95 先动
- 各 provider 的错误率 —— 按错误类型分(超时 / 429 / 5xx)
- 熔断状态变化 —— 打开和关闭都要告警,关闭才说明恢复了
- 降级期间的缓存命中率 —— 多少用户拿到的是陈旧答案
- 每次降级的成本增量 —— 提示缓存失效、模型单价不同都会体现
前四个是基础设施指标,后两个是业务影响指标。只监控前四个,会出现"系统一切正常,但用户拿到的都是昨天的答案"这种情况。
链路追踪怎么组织见《给大模型链路装仪表》,限流相关的计数维度见《限流的三个维度》。
7. 几个会犯的错
- 降级链从没被演练过。 上线时配好了,半年没触发,第一次触发发现备选的 key 过期了。要定期用故障注入验证:把主模型配成不可达,看链路是否真的走通。
- 重试和降级叠在一起。 主模型重试 3 次全失败才降级,用户等了 90 秒。应该边重试边计时,超过总的延迟预算就直接降级。
- 所有请求共用一条链。 离线批处理不该抢用户请求的降级额度。按业务分级配不同的链。
- 降级后不恢复。 主模型恢复了,但流量还在备选上——熔断没有半开探测,或者没有自动回切。要能自动回切,且回切要灰度。
- 把降级率当成功指标。 降级率是问题指标,不是能力指标。它升高是坏消息。
最后一条值得再强调:一套"能优雅降级"的系统,如果降级率长期在 5% 以上,说明主链路本身有问题。降级是止血,不是治疗。
降级链本身属于接入层的一部分。把凭证、路由、限流、预算、审计一起收进一个受控的控制面之后,降级才有地方登记"哪些模型互为等价替代",也才有地方给调用方返回"实际由谁服务"。完整设计见《统一接入层:AI 网关》。