主用模型 API 挂了怎么自动切换?降级链路设计要点

去年冬天有个客户的客服机器人凌晨两点开始胡言乱语——不是模型答错,是主用模型 API 返回 429 限流,代码里没做任何处理,把错误信息当成「回答」直接塞给了用户。第二天运营截图给我看,那半小时里机器人对着十几个用户回复 rate limit exceeded。
这件事之后,我们把降级链路当成和 prompt 同等重要的东西来设计。下面是我们实际在跑的四件事:链路分层、切换判定、状态回切、体验一致性。
降级链路:三层起,别只准备一个备胎
很多人理解的降级是「主模型挂了换备用模型」。两个供应商、两个 key,看着够了。真出事你会发现两个问题:备用和主用可能挂在同一家云、同一个区域,一起挂;备用模型自己也被限流时,你没有第三层可退。
我们现在默认三层,职责和延迟预算分开定:
| 层级 | 典型选择 | 触发后行为 | 单层超时 |
|---|---|---|---|
| 主用 | 能力较强的商用模型 | 正常业务全量走这里 | 8-10 秒 |
| 备用 | 另一家厂商的同档模型(不同云、不同区域、不同账号) | 承接主用失败流量,允许质量略降 | 6 秒 |
| 兜底 | 小参数模型 / 缓存命中 / 模板与规则回复 | 确保「有回复」,而不是抛错误 | 2 秒内必须返回 |
总预算卡死在 15 秒左右,也就是用户还能忍的上限。三层串起来如果让用户等半分钟,体验比直接报错还差。
另一个细节:备用层要和主用层共用同一套 prompt 模板和同一套输出解析,只是模型名不同。你要是为备用模型单独写一套 prompt,半年后没人维护得动。链路参数(供应商、模型名、超时、重试次数、权重)应该放在配置文件里而不是代码里,运维改配置不用发版——这块我们在模型 API 与成本管理里是统一收口的。
切换判定:哪些错该切,哪些错切了也没用
这是常出错的地方。默认「只要报错就切」看起来稳,实际会把真实 bug 藏起来,问题拖到几周后才爆。
| 错误类型 | 是否切换 | 原因 |
|---|---|---|
| 429 限流 | 切 | 等下去还是等,换一家立刻能用 |
| 5xx 服务端错误 | 切 | 上游问题,原地重试意义不大 |
| 连接超时、DNS/网络错误 | 切 | 大概率是链路问题 |
| 400 参数错误 | 不切 | 请求体写错了,换谁都是 400 |
| 401 / 403 鉴权失败 | 不切,直接告警 | key 过期或权限没了,切换只会掩盖事故 |
| 上下文超长 | 不切 | 换个大窗口模型可能悄悄通过,欠的债以后还 |
| 内容审核拒绝 | 不切 | 换模型绕过审核是合规风险 |
| 输出解析失败 | 有限次切 + 修复重试 | 可能是模型能力问题,也可能是 prompt 问题,要能区分 |
即使该切,也别在单次失败时就切。我们用滑动窗口:最近 60 秒内该供应商错误率超过 30%、且样本数不少于 5 次才熔断。单次失败先原地重试一次,带 200-500ms 抖动,重试还失败才进备用层。这样做的好处是偶发抖动不会打到备用模型上——平时备用从不预热,真出事时冷启动反而更慢。
状态回切:切过去容易,切回来要慢
主用一恢复就立刻全量切回去,是抖动的主要来源。上游刚恢复那几分钟往往还在限流或者响应很慢,你一股脑切回去,又挂,又切走,来回几次日志全是噪音,用户侧的表现是时快时慢。
我们的熔断器是三态:
- 关闭:正常走主用。
- 打开:全量走备用,同时按固定间隔(比如 30 秒)放一个探测请求给主用。
- 半开:探测连续成功 3 次,才放 10% 流量回主用;这 10% 的错误率仍低于阈值,再逐步放到全量。
一次完整回切走 5-10 分钟完全可以接受,业务侧感知不到,但能挡掉绝大多数「恢复即雪崩」。另外必须留手动开关:大促、重要客户演示之前,我们会手动锁定在某个供应商上,不让它自动漂移。这个开关要做成运营也能点,别只留在运维的脚本里。
体验一致性:降级不是换个模型就完事
这一节是我们被反复打脸的地方。
坑一:输出格式对不上
主用模型开了 JSON mode,输出很守规矩;备用模型不支持 JSON mode,返回的是带 markdown 代码块的字符串,下游解析器当场崩。或者备用模型把字段名从 answer 写成了 response。所以切换层必须做两件事:schema 校验,以及一次带错误信息的修复重试——把 schema 和校验报错一起塞回去让它重写。实在修不好就走兜底,别把坏数据放过去。
坑二:说话风格差一截
同一个 prompt,主用回答 80 字、分三点、语气克制;备用回答 400 字、带 emoji、结尾还反问你「还有什么可以帮您」。客服场景里用户一眼就看出来,品牌感直接垮。我们的做法是在系统提示里写死风格约束(字数上限、禁用 emoji、禁止反问、禁止用「首先其次」这类模板开头),并在切换层加一层轻量后处理:超长截断到句子边界、去掉 markdown 标题、把列表折叠成短句。不高级,但有效。
坑三:能力边界不同,任务要分级
备用模型在长上下文、多跳推理、函数调用参数准确性上普遍弱一档。所以我们在任务层面做了白名单:对准确性敏感的任务(金额计算、工单分类、结构化抽取)不允许降到兜底层,宁可返回「稍后再试」;对延迟敏感但容错高的任务(闲聊、文案润色)可以一路降到小模型。这个分级要写在业务代码里,别指望调度层去猜。
坑四:多轮对话换模型就失忆
会话中途换模型,备用模型看不到前面的上下文就会答非所问。会话历史必须由你自己的服务端维护,每一层模型都当无状态调用。这条顺带让私有化迁移也变简单了——上下文在你手上,换谁都能接。
兜底层怎么设计才不丢人
兜底不是「随便回一句」。我们对兜底的要求是:说清楚现在不能做什么、给出替代路径、别假装自己知道。
比如知识库问答,兜底可以直接返回检索到的原文片段,加一句「当前生成服务繁忙,以下是相关资料原文」。这比一个瞎编的答案强得多,也符合企业知识库的设计初衷——检索和生成拆开,生成挂了检索还在。检索层通常不依赖大模型(embedding 可本地缓存),这是它天然的稳定性优势。
缓存也是便宜好用的兜底层。高频问题的标准答案缓存 24 小时,客服场景命中率一般能到 20%-40%,这部分流量根本不进模型。
成本上别被三层吓到:备用层只在故障窗口内被调用,正常月份占总调用量的比例通常不到 3%。预算按主用模型单价估,再加 5%-10% 的降级余量就够,具体以实际方案与报价为准。
我们踩过的坑,列一下
- 备用 key 和主 key 在同一个账号下,主账号被限流时两个一起挂。账号、区域、云厂商都要分开。
- 备用层没设超时,请求一直挂着,连接池被占满,整个服务被拖死。每一层都强制超时,没有例外。
- 日志只记「降级成功」,没记降到哪一层、什么原因。复盘时什么都查不到。至少要落 request_id、层级、供应商、错误码、耗时。
- 备用模型直接复用主用的流式输出,但分块粒度不同,前端渲染出「半个字半个字蹦」的观感。要不要对备用层开流式,得单独想。
- 降级告警阈值设得太松,切了三天没人发现,成本涨了、质量掉了才知道。
落地顺序:别一口气上三层
如果你现在只有一层,按这个顺序做,每一步都能独立见效:
- 先做超时、重试、错误分类。这一步能解决大部分「假故障」。
- 接第二个供应商,只做 429 和 5xx 的切换,回切先手动。
- 补 schema 校验和风格后处理,解决一致性。
- 上熔断器半开状态和自动回切,再补兜底层。
这套东西我们在AI 工作流自动化里是当基础设施做的,因为它不只服务聊天——批量抽取、定时任务、智能体的工具调用都跑在同一个网关上,一处做好,处处受益。
如果你正被「模型偶尔抽风导致业务报错」困着,可以把现在的调用链路和错误日志发我们看看,一般能指出该在哪一层加保护。电话 400-873-2750,或者在线联系我们。