想象你家正在看球赛直播,突然整栋楼停电了——电视黑了,路由器断了。如果家里恰好装了备用电源系统:主电网恢复前,自动切换开关检测到断电,瞬间把负载切到蓄电池;电池快耗尽时,又自动启动汽油发电机。整个过程你几乎无感,屏幕只是闪了一下,球赛继续播放。LLM 服务的降级策略 Fallback,就是这套“备用电源系统”。 大模型在线的日子里,主模型可能超时、限流、报 5xx 错误,甚至整个服务不可用。你不能让用户盯着空白页面干等,而是准备好一套完整的降级链:主模型挂了切备用模型,备用模型不行还有兜底缓存,再不行返回一个明确的话术。整个链条由健康探测、自动切换、日志记录、恢复回切几个部件协同工作,保证任何单点故障都不会让服务中断。这就是 LLM 在线服务高可用的最后一道防线。

岗位关联

对 AI 从业者来说,Fallback 降级是“可用性工程”的核心技能。掌握了它,你设计的 LLM 应用不再依赖单一模型的运气,而是像电力系统一样可靠,这是从原型走向生产的关键一跃。

目录
  1. 可视化演示
  2. 降级链概念
  3. 降级链设计
  4. 优先级排序
  5. 健康探测
  6. 自动切换
  7. 降级日志
  8. 恢复回切
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示Fallback降级的核心机制,通过入门/进阶/实战/优化四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Fallback降级:Fall Fallback降级链设计与优 健康探测与故障检测机制 核心机制可视化 Fallback降级整体架构与关键组件

降级链概念

停电时,家里的备用电源不是“烧掉就完了”,而是一个完整的预案:主电网断电 → 自动切换开关启动 → 蓄电池顶 10 分钟 → 发电机介入 → 电网恢复后再切回。每层都在前一层失效时顶上,形成一个环环相扣的链条。LLM 服务的 Fallback 降级链,就是把这个预案搬进了模型调用层:主模型是电网,备选模型是蓄电池,缓存答案是发电机。

降级链的工程化定义,是把“如果 A 不行就用 B,B 不行就用 C”这条思维变成可配置、可观测、可自动执行的路由逻辑。在代码层面,它表现为一张有序模型候选列表和一个调度器。调度器按顺序尝试列表中的模型,一旦某个模型返回成功,就直接把结果回给上游;如果全部失败,才抛出最终的降级异常或返回固定话术。

这条链的价值在于把所有单点故障的可能性圈在一个可控的“笼子”里。任何一个模型故障,都只是链上的一个环节断裂,而不是整条服务崩塌。

就像家里的备用电源层数越多,全屋断电的概率越低。每多加一个候选模型,就相当于多备了一路供电,服务的中断风险就指数级下降。这也是为什么 Fallback 被称为“最后一道防线”——它不追求单机完美,而追求系统整体不死。

Achain=1−∏i=1n(1−ai)\displaystyle A_{chain} = 1 - \prod_{i=1}^{n}(1 - a_i)
$A_{chain}$ 表示整条降级链的综合可用率,$a_i$ 表示第 $i$ 个模型的独立可用率。只要链条里有一个模型可用,整个服务就有响应。
  1. \\(a_i\\) 每个环节的可用率第 $i$ 个模型在观测周期内成功响应的概率,比如模型 A 可用率 0.95,意味着 100 次调用里大约 95 次成功。
    ai=success_counttotal_count\displaystyle a_i = \frac{\text{success\_count}}{\text{total\_count}}
  2. \\((1 - a_i)\\) 每个环节的失效率模型 $i$ 不可用的概率。可用率 0.95 对应的失效率就是 0.05。
    fail_ratei=1−ai\displaystyle \text{fail\_rate}_i = 1 - a_i
  3. \\(\prod\\) 所有环节同时失效的概率把每个模型的失效率相乘,得到“所有模型都不可用”的概率。因为链路是串联候选的关系,任何一个成功就能救场。
    P(all fail)=(1−a1)(1−a2)⋯(1−an)\displaystyle P(\text{all fail}) = (1 - a_1)(1 - a_2)\cdots(1 - a_n)
  4. \\(1 - \prod\\) 至少一个成功的概率用 1 减去全部失败的概率,就是整条链的可用率。模型越多,乘积越小,综合可用率越高。
    Achain=1−P(all fail)\displaystyle A_{chain} = 1 - P(\text{all fail})

降级链设计

设计降级链就像为房子设计供电分层:电网负责日常供电,蓄电池负责中间过渡,发电机负责长时间兜底。每一层解决不同的问题,各司其职。LLM 服务同样如此,降级链不是随手把几个模型排个队,而是划分清晰的部署层级。

第一层是主模型层,通常是质量最好的大模型,比如 GPT-4 级别的服务;第二层是备用模型层,可能是稍差但依然可用的国产模型或开源模型;第三层是兜底层,可以是缓存的历史答案、静态模板,甚至是“系统繁忙”的提示话术。每层还要单独配置调用超时:主模型超时给 10 秒,备用模型给 5 秒,兜底层直接本地返回。

跳转规则定义了何时从上一层跳到下一层,常见触发条件有请求超时、返回错误码、限流拒绝。覆盖场景则要考虑清楚:只有超时才降级,还是 5xx 也降级?限流导致的降级,是直接走备用模型还是先重试一次?这些决策在链路设计阶段就要定下来,而不是故障发生后再临场思考。另外,链路每跳一次,都应当知道自己“跳了几步”,方便日志追踪。

设计链路时,可以用这个公式评估“多套一层备用是否划算”。比如备用模型很贵但主模型很稳定,加这一层可能纯属浪费;反之,主模型频繁抖动时,多一层备用就是性价比极高的保险。

E[C]=P1×C1+(1−P1)×P2×C2+(1−P1)(1−P2)×C3\displaystyle E[C] = P_1 \times C_1 + (1 - P_1) \times P_2 \times C_2 + (1 - P_1)(1 - P_2) \times C_3
$P_i$ 表示第 $i$ 层能成功处理请求的概率,$C_i$ 表示调用第 $i$ 层的成本。期望成本告诉我们:降级链不是越贵越好,而是要在可靠性和成本之间取平衡。
  1. 第一层正常成本主模型直接成功的概率 $P_1$ 乘以它的调用成本 $C_1$,这是最理想的情况,花最贵的钱买到最好的质量。
    P1×C1\displaystyle P_1 \times C_1
  2. 第二层降级成本主模型失败的概率 $(1 - P_1)$ 乘以备用模型成功概率 $P_2$,再乘备用模型成本 $C_2$。只有主模型失败时,才会产生这笔开销。
    (1−P1)×P2×C2\displaystyle (1 - P_1) \times P_2 \times C_2
  3. 第三层兜底成本前两层都失败时走兜底层,其概率是 $(1-P_1)(1-P_2)$,再乘上兜底成本 $C_3$。兜底层通常最便宜,甚至接近零成本。
    (1−P1)(1−P2)×C3\displaystyle (1 - P_1)(1 - P_2) \times C_3

优先级排序

电网恢复前,家里的自动切换是有优先级的:先用电池迅速顶上,再启动发电机,绝不会一上来就启动噪音巨大的发电机。LLM 降级链也一样,候选模型不是随机排列,而是按一套明确的规则排出先后顺序:质量优先、成本优先、速度优先,或者三者的动态加权。

质量优先意味着最好的模型排最前面,适用于对回答内容要求极高的场景,比如法律咨询、金融分析。成本优先把最便宜的模型排前面,适合超大规模但容错率高的场景,比如内容分类、关键词提取。速度优先则把低延迟模型排在前面,适合聊天机器人等对实时性敏感的业务。实际生产中很少只用单一维度,更常见的是加权得分。

动态权重可以理解为:在业务高峰时,成本权重调高,让便宜模型多承担流量;业务低谷时,质量权重调高,让主力模型发挥最佳效果。决策引擎实时计算每个模型的综合得分,得分最高的排在最前。这套逻辑与推荐系统中的“重排序”思路一脉相承:不是谁最好就永远用谁,而是当前状态下谁最合适就用谁。

这套打分机制决定了降级链的“性格”:一个追求质量的团队会把 $w_q$ 调得很大,让主力模型永远排第一;一个追求省钱的小团队可能会把 $w_c$ 调大,让便宜模型成为首选。就像家里的电力调度,取决于你是更怕贵、更怕吵,还是更怕断电。

Si=wq×Qi−wc×Ci−wl×Li\displaystyle S_i = w_q \times Q_i - w_c \times C_i - w_l \times L_i
$S_i$ 是模型 $i$ 的综合评分,$Q_i$ 是质量分,$C_i$ 是成本,$L_i$ 是延迟,$w_q$、$w_c$、$w_l$ 是三者权重。评分越高,排名越靠前。
  1. 输出质量分 \\(Q_i\\)通过离线评测或在线反馈给每个模型打质量分,范围 0 到 1。GPT-4 可能是 0.95,小模型可能只有 0.7。
    Qi=quality_score\displaystyle Q_i = \text{quality\_score}
  2. 调用成本 \\(C_i\\)单位成本,比如每千 token 的价格。成本是“负向因素”,所以公式里用减号,越贵越扣分。
    Ci=price_per_1k_tokens\displaystyle C_i = \text{price\_per\_1k\_tokens}
  3. 响应延迟 \\(L_i\\)平均首 token 时间或 P99 延迟,同样越低越好,公式里做减法。
    Li=p99_latency_seconds\displaystyle L_i = \text{p99\_latency\_seconds}
  4. 加权求和质量、成本、延迟分别乘各自的权重后相加,得到综合评分。业务通过调节三个权重,就能灵活控制降级链的气质。
    Si=wqQi−wcCi−wlLi\displaystyle S_i = w_q Q_i - w_c C_i - w_l L_i

健康探测

备用电源不会等全屋漆黑才反应过来,自动切换开关会持续监测电网电压,一旦电压跌到阈值以下,哪怕只有几毫秒,也会立刻切断主线路。LLM 降级也要有这种“持续监测”的传感器,也就是健康探测机制。

健康探测分两种常见形态:主动探测与被动探测。主动探测是定时向模型发送一个轻量级的 ping 请求,确认模型能正常响应;被动探测则是在真实请求中统计失败率、延迟和错误码。生产环境往往两者结合:主动探测保证“没流量时也能发现故障”,被动探测则真实反映请求通路的实际状态。

判断标准不能只看单次失败,而是要看连续失败次数或一段时间内的失败率。比如连续 3 次超时才标记为“可疑”,失败率超过 20% 才标记为“不健康”。标记后的故障节点会进入全局的“健康注册表”,所有降级决策路由都会参考这个表。探测频率也需要权衡:太频繁会增加成本,太稀疏又无法及时发现问题,典型做法是每 5 到 10 秒一次心跳。

健康探测如同电压监测器,时刻告诉我们“哪一路电已经不稳了”。它是一切降级决策的数据底座:没有准确的健康状态,切换就是盲人摸象,可能把一个好端端的模型臆断为故障,让全链路陷入无谓的震荡。

fail_rate=fn,degrade if fail_rate≥θ\displaystyle \text{fail\_rate} = \frac{f}{n}, \quad \text{degrade if}\ \text{fail\_rate} \geq \theta
$f$ 是统计窗口内的失败请求数,$n$ 是总请求数,$\theta$ 是触发降级的失败率阈值。当失败率超过阈值时,该模型进入故障名单。
  1. 统计失败数 \\(f\\)在滑动窗口(比如最近 1 分钟)内,统计超时、5xx、限流等各类失败的总次数。
    f=failures in window\displaystyle f = \text{failures in window}
  2. 窗口总请求数 \\(n\\)同一窗口内进入该模型的全部请求数,包括成功和失败。
    n=total requests in window\displaystyle n = \text{total requests in window}
  3. 失败率 \\(f/n\\)失败请求占全部请求的比例,反映当前模型的健康程度。失败率越高,模型越不可靠。
    fail_rate=fn\displaystyle \text{fail\_rate} = \frac{f}{n}
  4. 与阈值 \\(\theta\\) 比较当失败率大于等于阈值 $\theta$(比如 0.2),就判定为不健康,触发降级切换。阈值设得越低,系统越敏感,但也越容易误判。
    fail_rate≥θ⇒degrade\displaystyle \text{fail\_rate} \geq \theta \Rightarrow \text{degrade}

自动切换

当电压监测确认主电网异常,自动切换开关不会打电话问你该不该切,而是毫秒级完成动作。LLM 服务的自动切换也一样,判定条件一旦满足,立刻生效,不容犹豫。

触发条件通常包含三个方面:健康探测的结果(失败率超过阈值)、请求级错误(单次请求超时或返回 5xx)、限流信号(模型返回 429 等限流码)。切换动作本质上是一个“路由重定向”:将原本发往主模型的流量,改为发往降级链上第一个健康节点。切换时需要做到快速失败——主模型超时不能无限等待,通常设置一个短超时,超时立即转投下一层。

熔断保护是自动切换的重要安全阀。如果备用模型也在持续失败,继续发流量只会雪上加霜,此时“熔断器”打开,物理上切断对该模型的调用,让系统快速失败并进入兜底层。熔断不是永久性的,过一段时间会允许少量试探流量通过,如果成功则逐步恢复。整个切换过程中,关键指标是切换耗时:每多一秒切换时间,用户就多一秒等待,系统就多一分雪崩风险。

自动切换就是那个反应迅速的“ATS 开关”,它让故障从“发现”到“恢复”只隔着一个路由跳转的间隙。而对用户来说,最直观的感受就是——请求的返回也许慢了一点,但服务从未中断。

D=min⁡{i∣healthy(i)=1},choose model D\displaystyle D = \min\{ i \mid \text{healthy}(i) = 1 \}, \quad \text{choose model } D
降级链按顺序排列模型 1 到 n,$D$ 是第一个健康模型的序号。自动切换就是找到这个 $D$,把流量全部导向它。
  1. 候选集合遍历从主模型开始逐个检查健康状态,$i$ 从 1 递增到 n。
    i=1,2,3,…,n\displaystyle i = 1, 2, 3, \dots, n
  2. 健康判断 \\text{healthy}(i)第 $i$ 个模型的健康标记。健康为 1,故障为 0。这个标记由健康探测模块写入注册表。
    healthy(i)∈{0,1}\displaystyle \text{healthy}(i) \in \{0, 1\}
  3. 取最小值 \\min$\min$ 运算保证我们返回的是“第一个”健康模型,而不是任意一个。第一个意味着优先级最高,因为链是按优先级排序的。
    min⁡{i∣healthy(i)=1}\displaystyle \min\{ i \mid \text{healthy}(i) = 1 \}
  4. 结果 \\(D\\) 即为目标模型$D$ 是降级链上第一个健康节点的位置,自动切换就是动态计算出这个位置,再把请求路由到对应模型。
    route→D\displaystyle \text{route} \to D

降级日志

家里的电力系统装有一个监控面板,记录每次断电时间、切换了哪路电源、发电机运行了多久。这些数据不仅是维修依据,更是下次优化供电方案的基础。降级日志对 LLM 服务起着同样的作用。

每次降级事件至少要记录四类内容:时间戳(什么时候发生切换)、切换层级(从第几层降到了第几层)、原因(超时、5xx 还是限流)、影响指标(切换前后延迟变化)。这些日志会被汇总成指标:降级率 = 降级请求数 / 总请求数;各层使用率 = 某层成功处理请求数 / 总请求数。指标反映全局健康状况,日志则提供每个事件的细节证据。

追踪链路是为了回答“这个请求到底经历了什么”。通过 request_id 关联,可以在日志中完整还原一个请求从主模型尝试到兜底成功的完整路径。归因分析则更进一步:为什么这个模型失败了?是模型服务本身挂了,还是网络波动,还是配额耗尽?不同原因对应完全不同的修复策略。没有日志的降级链,就像一个没有黑匣子的航班——虽然安全降落了,但永远不知道刚才发生了什么。

降级率就像一个“供电可靠率”仪表盘:长期接近 0 说明主模型非常稳定;突然升高则提示主模型出了问题,或者链路配置有误。日志和指标合力,把每一次降级从“黑盒子”变成“透明盒子”,让工程师既能看到森林,也能看清每一棵树。

Rfb=NfbN\displaystyle R_{fb} = \frac{N_{fb}}{N}
$R_{fb}$ 是降级率,$N_{fb}$ 是发生了降级切换的请求数,$N$ 是总请求数。降级率是衡量系统稳定性的核心指标。
  1. 降级请求数 \\(N_{fb}\\)那些没有走主模型、而是降级到备用层或兜底层的请求总数。每个这样的请求都在日志中留有一条记录。
    Nfb=fallback_request_count\displaystyle N_{fb} = \text{fallback\_request\_count}
  2. 总请求数 \\(N\\)系统在统计周期内服务的全部请求数,无论是否降级。
    N=total_request_count\displaystyle N = \text{total\_request\_count}
  3. 两者相除降级率是降级请求占全部请求的比例。降级率为 5%,意味着 100 个请求里有 5 个走了备用路径。
    Rfb=NfbN\displaystyle R_{fb} = \frac{N_{fb}}{N}

恢复回切

主电网恢复后,自动切换开关往往不会立刻切回主线路,而是先稳定观察一段时间,确认电压不再波动,才最终切换回来。这是为了避免“刚切回来又断电”的反复横跳。LLM 降级策略的恢复回切同样讲究节奏。

回切条件是主模型在冷静期内持续保持健康。冷静期通常持续几分钟到几小时,期间健康探测持续上报“健康”状态。只有累计稳定时间超过阈值,比如 5 分钟连续健康,才会触发回切。回切动作也讲究顺序:先是小流量灰度回切,比如放 10% 的流量到主模型验证效果,观察无异常后再放大到 50%,最后全量回切。

全量切换要避开业务高峰,因为刚恢复的主模型通常比较脆弱,突然承接全量流量可能再次被打挂。恢复回切的目标不是“越快越好”,而是“稳定压倒一切”。一个健康的降级系统,切换出去要果断,切回来要谨慎——就像电网工人永远不会在雷雨天急着合闸送电。

回切策略决定了降级系统的“韧性”。懂得在恢复后稳步回归主模型的系统,既享受了备用模型的救命之恩,又不至于在恢复阶段再次裸奔。这是 LLM 服务高可用的最后一环,也是整个降级链完整闭环的关键所在。

switch_back(t)={1,healthy(t)=1 for t≥Δt0,otherwise\displaystyle \text{switch\_back}(t) = \begin{cases} 1, & \text{healthy}(t) = 1 \ \text{for}\ t \geq \Delta t \\ 0, & \text{otherwise} \end{cases}
回切决策函数:主模型必须连续健康 $\Delta t$ 时间后,才允许切回。$\Delta t$ 是冷静期时长,通常设为几分钟。
  1. 健康状态函数 \\text{healthy}(t)时间 $t$ 时主模型的健康标记。只有这个值为 1,才具备回切的前提。
    healthy(t)∈{0,1}\displaystyle \text{healthy}(t) \in \{0, 1\}
  2. 连续健康时长要求健康状态持续保持。注意“连续”两个字,中间任何一次失败都会重置计时器。
    t≥Δt\displaystyle t \geq \Delta t
  3. 决策函数为 1 则回切只有同时满足“当前健康”和“持续健康了 $\Delta t$”两个条件,回切决策才为 1,否则继续保持降级状态。
    switch_back(t)=1⇒revert\displaystyle \text{switch\_back}(t) = 1 \Rightarrow \text{revert}

AI 应用场景

智能客服系统的多模型容灾

阶段:模型服务部署与运维

解决的问题:传统方案中智能客服只绑定一家大模型供应商,一旦供应商限流或故障,客服系统直接瘫痪。通过 Fallback 降级链,系统可以在主模型不可用时自动切换到备用模型,甚至降级到基于知识库检索的模板回答,保证用户随时能获得响应。

具体案例:某电商平台的智能客服在双十一大促期间接入降级链:主力模型是云厂商 A 的大模型,备用模型是云厂商 B 的同等模型,兜底层是本地检索式问答。大促当天云厂商 A 突发限流,系统在 3 秒内自动切换,用户完全无感,客服响应成功率保持在 99.5% 以上。

批量文档处理任务的降级调度

阶段:批处理与异步任务调度

解决的问题:批量信息抽取任务中,如果数据量大且只依赖单一模型,一旦模型超时,整个任务队列就会堵塞。Fallback 链可以让批量任务在高级模型失败时自动降级到便宜的开源模型,优先保证任务不中断,再把少量高难度样本重新送高级模型精处理。

具体案例:某金融科技公司在处理每日数万份合同摘要时,先尝试 GPT-4 级别模型,失败率超过 15% 后自动切到开源模型继续跑。通过日志中记录的降级率,团队发现晚上 8 点到 10 点是模型高峰,于是把精处理任务调整到低峰时段,整体成本降低了 30%。

多租户 SaaS 平台的租户级降级

阶段:多租户资源配额与隔离

解决的问题:不同付费等级的租户拥有不同的可用性预算:VIP 租户可以排队等主模型恢复,免费租户则直接降级到缓存结果。通过 Fallback 链结合租户等级策略,平台可以精细控制降级深度,既省成本又保证核心客户体验。

具体案例:一家提供 AI 写作助手的 SaaS 公司,为付费用户配置了“主模型 → 备用模型 → 本地小模型”三级降级,为免费用户配置了“主模型 → 缓存相似模板”两级降级。主模型故障期间,付费用户几乎无感知,免费用户功能降为“润色修正”但仍可用,客服工单量下降了 60%。

Python 演示

python96 行
"""FallbackChain: 一个完整的降级链实现包含模型客户端、健康探测、自动切换、日志记录、恢复回切"""import timeimport random  class ModelClient:    def __init__(self, name, fail_rate=0.0, latency=0.5):        self.name = name        self.fail_rate = fail_rate        self.latency = latency        self.healthy = True     def invoke(self, prompt):        time.sleep(self.latency)        if random.random() < self.fail_rate:            raise RuntimeError(f"{self.name} unavailable")        return f"[{self.name}] reply: {prompt[:10]}..."     def probe(self):        try:            time.sleep(self.latency * 0.2)            if random.random() < self.fail_rate:                self.healthy = False            else:                self.healthy = True        except Exception:            self.healthy = False  class FallbackChain:    def __init__(self, models, cooldown=2):        self.models = models        self.cooldown = cooldown        self.history = []        self.fallback_count = 0        self.last_fail_time = 0     def execute(self, prompt):        for index, model in enumerate(self.models):            if not model.healthy:                self._log(index, model.name, "skipped")                continue            try:                result = model.invoke(prompt)                if index > 0:                    self.fallback_count += 1                self._log(index, model.name, "ok")                return result            except RuntimeError as exc:                self._log(index, model.name, f"fail: {exc}")                model.healthy = False                self.last_fail_time = time.time()        raise RuntimeError("all fallback models failed")     def health_check(self):        for model in self.models:            model.probe()     def try_recovery(self):        if time.time() - self.last_fail_time >= self.cooldown:            self.models[0].healthy = True            self._log(0, self.models[0].name, "recovered")     def _log(self, index, model_name, status):        self.history.append({            "time": time.time(),            "level": index,            "model": model_name,            "status": status        })  if __name__ == "__main__":    random.seed(1)    chain = FallbackChain([        ModelClient("gpt4", fail_rate=0.4, latency=0.3),        ModelClient("llama3", fail_rate=0.2, latency=0.2),        ModelClient("cache", fail_rate=0.0, latency=0.01),    ], cooldown=3)     for i in range(8):        chain.health_check()        chain.try_recovery()        try:            print(chain.execute(f"request {i}"))        except RuntimeError as exc:            print("final error:", exc)        time.sleep(0.5)     print("fallback_count:", chain.fallback_count)    print("log entries:", len(chain.history))    for entry in chain.history[-5:]:        print(entry)
目的:演示一个真实的 Fallback 降级链——三个模型以不同失败率构成链路,健康探测定期刷新状态,请求自动绕过故障节点,故障恢复后经过冷静期做回切,全程记录日志。
  1. class ModelClient模拟一个 LLM 客户端,包含名称、失败率、延迟和健康状态四个核心属性
  2. def probe(self)主动健康探测,按失败率概率更新健康标记,相当于电力系统的电压监测器
  3. def execute(self, prompt)核心降级执行逻辑,逐个尝试链路中的健康模型,成功即返回
  4. if not model.healthy: ... self._log(...)不健康的模型直接跳过并记录日志,避免不必要的无效调用
  5. if index > 0: self.fallback_count += 1只要走了非主模型就计入降级次数,用于后续计算降级率
  6. def health_check(self)遍历所有模型做一次心跳探测,刷新全局健康状态
  7. def try_recovery(self)冷静期过后尝试恢复主模型,体现“稳定优先”的回切策略
  8. self.history.append({...})把每次调用的层级、模型、状态写入日志,供追溯分析
  9. ModelClient("cache", fail_rate=0.0, latency=0.01)兜底层配置成本极低的缓存模型,失败率为 0,保证链路尾部永远兜得住

常见误区

误区我以为 Fallback 降级就是调用主模型失败后,原地重试一次同样的请求,如果还失败就报错。以前写代码遇到超时,我只会 `retry + sleep`,觉得多试几次总能成功。

正解降级链的核心不是重试同一个模型,而是切换到另一个模型或另一种兜底方案。就像一个停电的家庭不会反复按同一个开关,而是换备用电源。重试解决的是“瞬时抖动”,降级解决的是“持续不可用”,两者不可混为一谈。

为何常见:初学时接触的网络编程都在讲重试,加上对“模型服务”和“网络请求”的认知混淆,很自然把 retry 当成了 fallback 的全部。

误区我觉得主模型既然恢复了,就应该立刻把流量全部切回去,越快越能体现系统“自我修复”能力,用户也能尽快享受高质量回答。

正解立刻回切很可能导致“刚切回来又故障”的反复横跳,让系统在两三个模型之间抖成筛子。正确做法是设置冷静期:持续健康 5 分钟以上才允许回切,并且回切时先小流量灰度验证。就像电网检修后不会立刻满载送电,而是先低负载试运行。

为何常见:直觉上“越快恢复正常越好”,但忽略了刚刚恢复的模型往往处于不稳定状态,需要时间确认其可靠性。

误区我认为降级链里塞的模型越多越好,十个八个备用模型排成一条长龙,总能有一个活着的。反正代码是 for 循环,多几个候选也没什么成本。

正解每个候选模型都有探测成本、调用成本和失败开销。链路过长会显著增加请求的最坏延迟:每个故障节点都要等超时后才跳到下一个。更合理的做法是保持 2 到 3 层精炼链路,搭配缓存兜底,必要时用失败率统计做分流。就像家庭备用电源,不会同时买十台发电机——照顾不过来,还占地方。

为何常见:误以为“越多越保险”是普遍直觉,但忽略了每增加一层带来的延迟放大效应和运维复杂度。

误区我原以为健康探测只需要检查 HTTP 状态码,看到 200 就健康,看到 500 就故障,简单直接。

正解错误码只是信号之一。模型服务可能出现“假阳性”:返回 200 但内容为空或质量极差,也可能是响应时间飙升到 30 秒,虽然没报错但用户体验已经崩溃。健康的评估要综合错误码、延迟分布、失败率趋势等多个维度,必要时还要验证返回内容是否合法。

为何常见:传统的 HTTP 健康检查习惯根深蒂固,而 LLM 服务是“结果敏感型”的,不能只看通道通不通,还要看回答好不好。

误区我觉得只要降级功能能跑通就够了,加日志只会拖慢性能,反正用户也没抱怨。先上线再说,挂了再排查。

正解没有日志的降级链是盲飞。一旦生产环境出现降级风暴,没有时间戳、没有层级、没有原因,排查问题就像在黑暗里找一根针。降级率、各层使用率这些指标必须持续监控,否则你不知道链路是否真的在按预期工作,甚至不知道它已经悄悄坏了。

为何常见:功能开发时优先级天然高于可观测性,而且日志的价值只有在故障发生时才能体现,容易在“一切正常”时被忽略。

面试题

Q1. 降级策略 Fallback 的核心目标是什么?

单选题 · easy

  • A.让主模型永不出错
  • B.某个模型故障时自动启用备用方案← 正确答案
  • C.降低所有模型的调用成本
  • D.提高单个模型的响应速度

答案:B。Fallback 的核心是在主模型故障时切换到备用方案,保证服务不中断。它不能防止主模型出错,也不直接优化成本和速度。就像备用电源的目的是断电时顶上,而不是让电网永不故障。

Q2. 以下哪项不是降级链常见的触发条件?

单选题 · easy

  • A.请求超时
  • B.返回 5xx 错误码
  • C.模型返回 429 限流码
  • D.用户主动切换语言← 正确答案

答案:D。触发条件针对的是“模型服务异常”,包括超时、5xx、限流等。用户切换语言属于业务行为,与模型健康无关,不应触发降级。就像家里停电才启用发电机,你不会因为想开灯就启动发电机。

Q3. 降级链设计中,常见的部署层级有哪些?

多选题 · mid

  • A.主模型层← 正确答案
  • B.备用模型层← 正确答案
  • C.缓存兜底层← 正确答案
  • D.日志分析层

答案:ABC。典型降级链从上到下是主模型、备用模型、缓存或静态模板兜底。日志分析层是配套的可观测性系统,不是降级链本身的候选执行层。三层结构对应家庭供电的“电网、电池、发电机”。

Q4. 优先级排序公式中,综合评分采用哪种计算方式?

单选题 · mid

  • A.质量分减去成本分
  • B.只比较延迟高低
  • C.质量、成本、延迟加权求和← 正确答案
  • D.所有模型随机排列

答案:C。综合评分 $S_i = w_q Q_i - w_c C_i - w_l L_i$ 是三个维度的加权结果,质量加分,成本和延迟减分。随机排列无法体现优先级,只比较单一维度无法平衡多方需求。

Q5. 关于健康探测机制的描述,正确的有哪些?

多选题 · mid

  • A.主动探测会定时发送轻量请求← 正确答案
  • B.被动探测统计真实请求失败率← 正确答案
  • C.连续失败超过阈值才标记故障← 正确答案
  • D.单次失败必须立即触发降级

答案:ABC。健康探测通常结合主动和被动两种形态,且用连续失败次数或失败率阈值来判定,避免误判瞬时抖动。单次失败不应立即降级,否则系统会过于敏感,就像电压闪断一下不需要立刻切发电机。

Q6. 熔断保护在降级链中的作用是什么?

单选题 · mid

  • A.提高模型回答质量
  • B.切断对持续故障模型的流量← 正确答案
  • C.降低模型调用成本
  • D.加快主模型训练速度

答案:B。熔断是在备用模型也持续失败时打开“保险丝”,物理切断流量让系统快速失败并走兜底层。它保护的是整个系统不被连锁故障拖垮,与模型质量、训练速度无关。

Q7. 降级率 $R_{fb} = N_{fb} / N$ 中,$N_{fb}$ 表示什么?

单选题 · hard

  • A.请求总数
  • B.主模型成功次数
  • C.发生降级切换的请求数← 正确答案
  • D.缓存命中的请求数

答案:C。$N_{fb}$ 是走了备用或兜底路径的请求数,$N$ 是总请求数。降级率反映了系统依赖备用路径的程度。缓存命中虽然也是一种“兜底”,但严格意义上它不被算作降级请求。

Q8. 恢复回切时需要注意哪些原则?

多选题 · hard

  • A.主模型健康后立刻全量回切
  • B.需要设置冷静期确认稳定← 正确答案
  • C.可以小流量灰度回切← 正确答案
  • D.高峰时段也应立即切换

答案:BC。回切必须先经过冷静期确认主模型稳定,再通过小流量灰度逐步放大,最后全量。立刻回切或高峰时段强行切换都可能让脆弱的主模型再次崩溃,违背“稳定优先”原则。

Q9. 降级日志中,以下哪项不是必须要记录的内容?

单选题 · easy

  • A.切换发生的时间戳
  • B.从哪一层切换到哪一层
  • C.降级发生的具体原因
  • D.用户的个人身份信息← 正确答案

答案:D。日志需要时间戳、层级和原因,以便追溯和归因。用户个人身份信息属于隐私敏感数据,不应进入降级日志,否则会带来合规风险。日志记录的是系统行为,不是用户隐私。

Q10. 降级链的综合可用率满足 $A_{chain} = 1 - \prod(1 - a_i)$,以下说法正确的是?

多选题 · hard

  • A.模型越多综合可用率越高← 正确答案
  • B.所有模型同时失败的概率才决定最终失效← 正确答案
  • C.单个模型可用率对结果影响不大
  • D.每个模型的失效率相乘得到全部失败概率← 正确答案

答案:ABD。综合可用率是所有模型至少一个可用的概率,因此每个模型的失效率乘积决定了“全挂”的概率。增加模型数量会降低这个乘积。单个模型的可用率当然重要,但链条只要有一个健康节点,服务就能存活。