每个成熟的 AI 系统都会配备回退方案(fallback)。当主模型被限流时,路由到更廉价的模型。当服务商返回 5xx 错误时,提供缓存的答案。当置信度低于阈值时,回退到手写的启发式规则(heuristic)。架构图中有一个清晰的小分支,标注为“降级模式”(degraded mode),每个人都因此感到更安全。
令人不安的部分在于:那个分支是系统中几乎从未运行过的唯一代码。主路径每天执行数百万次,并经过大量流量的调试、性能分析和实战测试。回退路径几乎从不执行——直到某天,它在负载下、在事故期间、在三名工程师看着仪表盘变红时,突然为所有人同时执行。
一个你不练习的回退方案并不是冗余。它是一个不受监控的第二系统,其“首秀”在统计学上注定会发生在最糟糕的时刻。
只在灾难期间运行的代码,就像从未运行过的代码一样在腐烂
软件腐烂并不是因为它在变化,而是因为它在原地踏步,而周围的一切都在改变。你的主 Prompt 每周都会被编辑——有人收紧了系统指令,添加了 Few-shot 示例,更换了模型版本。你的输出 Schema 增加了一个字段。你的下游解析器学会了期待那个字段。
回退路径注视着这一切发生,却并未参与其中。它的 Prompt 是六个月前主 Prompt 样子的“化石”。它的输出 Schema 上次针对解析器进行验证还是在两个过时的重构版本之前。它路由到的廉价模型早就在供应商的发布说明中被弃用了,而你团队中没人读过那些说明。
这些问题都不会浮现,因为回退路径没有运行。CI 捕获不到,因为 CI 运行的是测试覆盖的路径,而没人会为他们假设永远不会触发的分支编写测试。代码审查捕捉不到,因为破坏回退路径的 Diff 是对“主”路径的修改——回退路径成了附带损害,无声且处于监控视野之外。
这与几十年来一直困扰灾难恢复(DR)的失败模式如出一辙。最常见的备份失败并非备份本身无声停止工作,而是备份的“恢复”过程从未运行过,导致团队在真正的事故中才第一次发现漏洞。调查一致发现,只有大约一半的组织每年测试其灾难恢复计划。俗话说,未经测试的备份只是一个假设,而不是安全网。你的回退路径正是那个假设——更糟糕的是,它比没有回退方案具有更坏的特性:它制造了虚假的安全感。架构图显示你已经得到了保护,因此在生产环境停止协作之前,没有人会去真正承载并测试这个假设的承受能力。
停机终将到来——问题在于回退方案是否准备就绪
这不是一个你可以推迟的虚构风险。在 2025 年,每家主要的 LLM 供应商都至少经历过一次重大中断。公开的事故追踪器记录了各大 API 的数百次停机。一家供应商的状态数据显示,一年内发生了近 300 起事故;另一家在短短 90 天内记录了超过 100 起事故,其中数十起是重大事故。一家供应商因路由节点达到内存限制、未通过就绪检查,导致级联成容量不足,最终无法为任何人提供服务,经历了长达数小时的停机。
如果你依赖模型 API,你的回退路径“一定”会被调用。唯一悬而未决的问题是,届时它是否能正常工作。
这里的“正常工作”比团队设想的门槛更高。回退路径的触发并不等同于回退成功。思考一下在降级路径启动的那一刻,实际上必须满足哪些条件:
- 廉价模型或备份模型仍然存在并接受请求。
- 回退 Prompt 生成的输出仍符合解析器预期的 Schema。
- 你提供的缓存响应没有陈旧到错误的程度。
- 断路器(circuit breaker)确实触发了,并且是在几秒钟内而非几分钟内针对正确的信号触发的。
- 回退路径的延迟和 Token 成本本身不会级联成第二次故障。
其中的每一项都是一个活生生的假设。每一项都会发生漂移。而你第一次发现哪些环节断裂的时候,正是事故发生的时刻——除非你在此之前有意识地练习这条路径。
陈旧的回退方案无声地失败,这比响亮地失败更糟糕
抛出异常的回退方案几乎是一个“好”结果。它动静很大。你的告警会捕捉到它,值班人员会收到呼叫,失败是清晰易读的。
危险的回退方案是在狭义的技术意义上成功、但在所有重要意义上都失败的方案。备份模型返回了 200 状态码和一个自信且格式良好的答案——但它是根据一个不再反映你产品当前行为的 Prompt 生成的。缓存的响应被瞬间提供——但它编码的是上个季度的价格、政策或个性化事实,在今天已是错误的。启发式回退运行顺畅——却悄无声息地将每一个模糊案例都路由到同一个默认值,因为它针对的分布已经发生了偏移。
运维健康和行为正确并不是一回事。一个系统可以在所有基础设施指标上显示绿色,同时却在一个已经过时数月的回退逻辑上进行推理。你为主路径构建的监控测量的是延迟、错误率和吞吐量。这些都无法捕捉到一个快速、无错但“错误”的回退。降级路径需要它自己的正确性信号,否则它的失败在设计上就是不可见的。
这之所以如此容易被忽视,有一个结构性原因。回退路径在设计上就是你在情况已经变糟时才会采取的路径。预期很低。“这是降级模式,效果当然不怎么样。”这种说法为破碎的回退方案提供了掩护。用户和值班人员都会将糟糕的输出归咎于停机事故,而不是那段本该救命的代码中长达六个月的无声腐烂。
让降级路径成为一等公民
修复方案不是增加更多的回退层。而是将你现有的回退方案视为真正的生产代码 —— 就像热路径(hot path)上的其他代码一样,对其进行演练、监控和测试。以下几项实践可以让这一理念落地。
通过定期演练保持其“热度”。 定期将一小部分刻意选择的真实流量路由到降级路径中 —— 哪怕只有不到 1% 也足够了。这样做有两个好处。首先,它让回退路径针对当前输入持续进行演练,这样配置偏移(drift)就会体现为指标的逐渐变化,而不是突然发生的事故。其次,它能为你提供降级模式对用户实际影响的真实数据,而不是停留在假设层面。如果你从未在真实流量下观察过熔断器(circuit breaker)开启,你就无法确定它是否有效。你拥有的只是一种信念。
在 CI 中捕获配置偏移。 回退提示词和主提示词共享契约 —— 包括输出 Schema、解析器依赖的字段集以及产品预期的语调。将这些契约编码为一项检查,当两者出现分歧时使构建失败。一旦有人修改了主路径而回退路径没有同步跟进,流水线就会报错。这能将原本可能潜伏六个月的“无声腐烂”转化为 Pull Request 上一个显眼的红色叉号。
在评测套件中为回退方案评分。 如果你对主路径运行评测(你也应该这样做),那么也请针对降级路径运行一部分评测。不要把它当作事后的补充,而要作为一等公民的测试套件。它回答的是最关键的问题:当回退真正触发时,输出结果是否仍然足以交付给用户?一个通过了主路径评测但拥有陈旧且未经评分的回退方案的系统,只能保证在不出任何差错的日子里正常运行。
在 Staging 环境中强制触发故障。 演练日(Game days)和混沌实验并不神秘。安排一次吧。在预发布环境中刻意屏蔽主供应商,确认回退机制是否启动、是否启动得足够快,以及下游输出是否合理。针对 AI 系统的最新实践不仅限于基础设施故障:注入“语义化”失败 —— 如陈旧的检索结果、降级的工具调用、截断的上下文 —— 并观察系统是优雅降级(degrades gracefully)还是直接崩溃。演练日的意义在于,将每一个破碎假设的发现时机,从真实的事故现场转移到某个周二下午,转移到全团队都在有目的地观察的时候。
为降级模式设置专属仪表盘。 回退方案需要独立于主路径的监控。追踪它触发的频率、其输出质量与主路径的对比,以及所提供响应的陈旧程度。回退调用率的上升是供应商出现问题的领先指标。回退质量评分的下降则是你的安全网已经腐朽的领先指标 —— 你希望在平常的日子里看到这一点,而不是从事故复盘中推断出来。
真正的教训:未经测试的韧性是负债,而非资产
设置回退机制的本能是正确的。单点故障是真实存在的,供应商确实会宕机,而优雅降级也确实是正确的模式。错误在于认为“设计”了回退方案就等同于“拥有”了一个回退方案。
一个从未经受演练的回退路径并不是你系统的备用精简版。它是一个独立的、无人维护、未受监控、测试不足的代码库。你仅仅凭着在图表上画了一个框,就向自己承诺它会在你面临的最糟糕的情况下表现正确。
挑选出你系统中最重要的回退方案,问一个直白的问题:它的代码路径上一次实际运行是什么时候?你又是如何知道它产出了什么的?如果诚实的回答是“在上次事故期间”或“我不确定”,那么你并没有安全网。你只有一个披着安全网外壳的假设。
韧性(Resilience)不是你设计出来的属性。它是你通过不断演练来维持其生命力的属性。从未在实战中运行过的降级路径不是保险 —— 它是你生产系统中最大的一块未经测试的代码,而且它已经在你的日程表上和你最糟糕的一天约好了见面。
会员专享
余下内容仅对会员开放。
会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
- —完整文章,包含未公开存档的部分
- —可落地的工作框架,附带权衡与决策依据
- —新文章抢先看,先于公开发布
随时取消 · 一次订阅,畅读全部