老张在城区开了家“智能小馆”,招牌菜是林大厨的手艺。林大厨工钱贵,还常请假,但老张从不担心——因为他心里装着一张救场底牌:林大厨来不了,就打电话给李二厨;李二厨高峰期也忙不过来,就让王帮厨先顶着;实在不行,从冰箱里端出昨天做好的预制菜,先让客人吃上。这套“主厨→二厨→帮厨→预制菜”的接替梯队,正是 LLM 应用里 Fallback 与降级链设计的日常版。 回到我们的世界:一个 LLM 应用,主模型 API 就是那个“林大厨”。它可能网络延迟(在路上堵车)、服务宕机(请了病假)、触发限流(订单涌进厨房忙不过来)。如果应用把全部身家押在单一模型上,一次故障就是全线停业。所以要设计多级容错机制:Fallback 策略决定“下一个找谁”,降级链决定“完整梯队怎么排”,还得管好超时、控住成本、保住用户体验。这篇文章,就带你把老张的厨房管理心法,翻译成 AI 应用架构里的工程方案。
学完本节,你将掌握 LLM 应用的 Fallback 与降级链设计方法,能让服务在模型故障时依然稳定可用——这是 AI 应用架构师解决“单点依赖”问题的核心能力。
可视化演示
动态演示Fallback与降级的核心机制,通过开始/执行/监控/完成四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Fallback与降级:一Fal 一、Fallback降级机制的 二、多模型分级降级链的设计与实 核心机制可视化 Fallback与降级整体架构与关键组件
容错设计总览
老张的餐厅最怕什么?最怕所有客人点菜时,只有林大厨一个人在灶台前忙活。他一旦生病请假,整家店就得挂上“暂停营业”的牌子。现实里,LLM 应用如果只依赖一家模型 API,就犯了同样的“单点依赖”错误——网络抖动、服务宕机、限流封禁、内容审查拦截,任何一个故障都能让用户看到悲伤的报错页面。
容错(Fault Tolerance)就是系统在部分组件失败时,依然能继续服务的能力。它要回答三个问题:故障从哪来(故障类型)、能做到多稳(可用性目标)、靠什么兜住(多级容错机制)。常见故障类型包括网络错误、限流(HTTP 429)、服务端 5xx、超时,以及模型输出不合格(空回复、格式错误、被安全策略拦截)。
衡量容错效果,最核心的指标是服务可用性。它由“平均无故障时间”和“平均修复时间”两个量共同决定:
99% 可用性听起来不错,但一个月会有 7 个多小时不可用。多级容错设计的价值,就是让每一级故障都被下一级接住:主模型层失败,降级链层接管;降级链全挂,缓存兜底层顶上。就像老张有了二厨、帮厨、预制菜三层救场,林大厨就算歇一个月,顾客也感觉不到店门关过。
- MTBF 无故障时长Mean Time Between Failures,指两次故障之间系统正常工作的平均时长。林大厨连续 99 天正常出菜,就是 MTBF=99 天。
- MTTR 修复时长Mean Time To Repair,指从故障发生到系统恢复的平均时长。林大厨感冒休息 1 天,就是 MTTR=1 天。
- 比值与百分号把“正常工作的时间”除以“正常工作加修复的总时间”,再乘 100%,得到用户能享受到服务的比例。
Fallback策略
老张救场的第一步,是“换人”的动作:电话里听到林大厨说“今天来不了”,立刻拨给李二厨,绝不干等。这个“当前厨师不行,立刻换下一个”的决策,就是 Fallback——模型调用失败后的即时切换机制。
Fallback 不是简单地写一个 try-except 换个 model 参数。它要解决四个问题:什么情况算失败(触发条件)、换谁(备选模型)、换了以后怎么知道行不行(结果校验)、在哪个环节兜底。典型触发条件有:连接超时、读取超时、429 限流、5xx 服务端错误、返回空内容或 JSON 解析失败。切换后,备用模型的输出同样要过一遍校验,否则可能返回一堆格式错误的内容。
Fallback 带来的收益可以用概率的视角看。假设每个模型单独调用有各自的成功率,那么多级 Fallback 后“最终能成功”的概率是:
所以林大厨成功率 90%、李二厨成功率 85% 时,两人接力后整体成功率 = 1 − 0.1×0.15 = 98.5%。Fallback 的本质,就是用“多一次尝试”换“更高的确定性”。
- 单模型成功率 p_i第 i 个模型的单次调用成功率,比如林大厨 10 次有 9 次能出菜,p=0.9。
- 单模型失败率每个模型都有失败的可能,失败率是成功率的补集。1−0.9=0.1。
- 连乘求全失败把所有模型的失败率连乘,得到“所有模型都失败”的概率。两个厨师都失手:0.1×0.15=0.015。
- 补集转成功用 1 减去“全失败概率”,得到至少有一个模型成功的概率。
降级链设计
老张后厨墙上贴着一张救场流程图:林大厨 → 李二厨 → 王帮厨 → 冰箱预制菜。这不是临时抓瞎,而是事先排好的固定梯队。对应到架构里,这串“优先级从高到低、按顺序接替”的模型列表,就是降级链(Degradation Chain)。
设计降级链有四个要点。第一,模型分级排序:按“质量优先、兼顾成本”的原则,把最聪明的模型放最前面,把又慢又贵的排后面。第二,链路自动编排:链路必须配置驱动而非硬编码,改一条链不用重新发版。第三,逐级降级:每次只往下退一级,而不是一步跳到底,避免把廉价模型累垮。第四,动态恢复:主模型恢复后要能自动切回,通常借助熔断器的半开状态试探。
降级链不是免费的——每一级尝试都消耗时间。整体期望延迟,等于每一级的延迟乘上“请求能走到这一级的概率”再累加:
这就是为什么降级链不能无限加长:每多加一级,链尾那几级几乎不会被调用,却让公式里多了一个可能拖慢整体延迟的项。老张的梯队只排了三层加一个冰箱,刚刚好。
- 单级延迟 t_i第 i 级模型从发起到返回的平均耗时,比如 GPT-4 平均 3 秒。
- 到达概率连乘请求能到达第 i 级,意味着前面所有级都失败了。前面各级失败率连乘,就是到达概率。
- 加权求和每级延迟×到达概率后累加,就是端到端的期望耗时。链越短、前面失败率越低,期望延迟越小。
超时与重试
老张给每通救场电话定了规矩:响 10 声没人接就挂断,换下一个厨师。他不会让顾客饿着肚子等一个永远不接的电话,更不会疯狂重拨把电话线打爆。这个“等多久挂电话”的边界,就是超时(Timeout);“挂了以后要不要再打、隔多久打”,就是重试(Retry)。
超时设计要分层:连接超时解决“网络都通不了”的问题,读取超时解决“服务端处理太久”的问题,再设一个总请求预算,比如“整体最多等 10 秒”,超过预算立即触发降级。重试则要配合指数退避:第一次失败等 0.1 秒,第二次等 0.2 秒,第三次 0.4 秒……每次都翻倍,并加上随机抖动,防止大量请求在同一瞬间重试形成“重试风暴”。
指数退避的等待时间通常写作:
当同一模型连续失败达到设定次数(比如 5 次),熔断器打开——接下来一段时间直接跳过它,不再白白等待和花钱。这就像老张发现林大厨这周状态不对,索性暂停派单给他,让二厨直接顶上,等确认他状态恢复(半开状态试探成功)再重新启用。
- 基础等待与倍率t_base 是第一次重试的基准时间,2^(n−1) 让等待随重试次数指数翻倍,两次尝试之间给服务喘息机会。
- 随机抖动rand(0, j) 在计算出的等待上叠加 0 到 j 之间的随机量,让同一时刻的重试散开。
- 封顶保护min 把等待时间限制在 t_max 以内,避免指数增长失控,让用户等得失去耐心。
成本控制
林大厨一道菜收费 1000,李二厨 100,王帮厨 10。要是连倒杯白开水都劳烦林大厨,月底账单能把老张吓哭。模型世界也一样:GPT-4 级别的模型和开源本地模型,单次调用成本能差两个数量级。
成本控制的第一个关键是请求分流:简单任务(翻译一句话、提取关键词)交给便宜模型,复杂任务(写长文、深度推理)才动用贵模型。第二个关键是记住“失败的调用也要付钱”——每次调用 API 都按 token 计费,哪怕返回超时重试,钱已扣掉。所以 Fallback 要克制,不能无脑重试十次。第三个关键是设单请求成本预算:一次用户请求的总花费超过阈值后,直接进入兜底模式,不再继续烧钱尝试。
把成本模型写出来,就是最朴素的一句话:
老张的生意经:招牌菜请林大厨,家常菜找王帮厨,救场时后厨不超过三通电话。对应到系统里,就是“简单请求走便宜模型,复杂请求走贵模型,Fallback 不超过两层,超过预算直接兜底”。
- 调用次数 n_i第 i 个模型被调用的次数,注意失败的调用和重试都要计入 n_i。
- 单次成本 c_i每个模型单次调用的价格,GPT-4 可能是本地模型的几十倍。
- 逐项累加各层“次数×单价”加总,就是整条链的总成本。想让总成本降下来,要么减次数(少重试),要么换低价模型(降级链)。
体验保障
有一次,顾客点了林大厨的招牌红烧肉,端上来的却是王帮厨的简化版。老张没有假装无事发生,而是主动说:“不好意思,今天主厨请假,这道菜由帮厨制作,口味略减,送您一道甜品。”顾客有预期、有补偿,反而觉得这家店实在。
模型降级也一样——用户是能感知到回答变短、变浅、变模板化的。体验保障的核心原则是“降级要透明,反馈要渐进”。具体手段有:降级透明提示(“当前使用轻量模式回复”),让用户明白为什么答案简单;渐进式反馈(流式输出、进度提示),避免用户面对一片空白;缓存兜底(复用相似的历史答案),保证核心信息不丢失。
衡量体验有没有守住,一个直白的指标是降级率——有多大比例的请求没有走主模型:
老张盯两个数:菜有没有端上来(成功率),端上来的是不是原版(降级率)。两个数都健康,顾客才留得住。降级不是失败,是“在资源受限时优先保证用户有钱可付、饭可吃”的取舍。
- 降级请求数N_degraded 是所有被降级链非首级模型处理的请求数量,顾客拿到“简化版菜”的次数。
- 总请求数N_total 是系统承受的全部请求数量,所有顾客下单的总次数。
- 比率与阈值两者相除得到降级率。降级率长期高于阈值(比如 5%),说明主模型稳定性出了问题,需要排查,而不是继续压榨用户耐心。
监控与运维
老张在厨房挂了一块大白板,记录每桌菜是哪个厨师做的、用了多久、有没有翻车。每周他还搞一次“林大厨失踪演练”——故意让林大厨休息一天,看李二厨和王帮厨能不能扛住客流。这块白板和这次演练,就是降级链的监控与运维。
监控要盯四类核心指标:成功率(请求最终成功的比例)、延迟分位数(P50/P95/P99,看大多数用户感受)、Fallback 触发次数(哪个模型最常掉链子)、降级率与单请求成本(体验和钱包双重警报)。配合 Trace ID,一次请求从主模型一路降级到缓存的完整路径都能串起来追溯。故障演练则像消防演习:定期模拟主模型宕机、限流、网络分区,验证降级链真的能接住。
SLO(服务等级目标)和错误预算给监控划了红线。假设我们承诺 99.9% 的成功率,那么一个月的错误预算就是:
错误预算快用完时,即使主模型“看起来恢复了”,也要谨慎——优先保稳定性,而不是追求回到完美链路。白板上的数据让老张知道“预制菜救了多少次场”“哪个厨子最容易掉链子”,招人、备菜、排班就都有了依据。
- SLO 服务目标Service Level Objective,对外承诺的服务水平,比如 99.9% 请求成功。
- 错误率余量1 − SLO 是允许失败的比例,99.9% 对应 0.1% 的失败空间。
- 月度预算分钟数43200 是一个月(30 天)的总分钟数,乘上允许失败比例,得到“每月最多可以失败多少分钟”。99.9% 对应约 43.2 分钟。
AI 应用场景
智能客服的降级链救场
阶段:推理服务调用 / 应用架构
解决的问题:传统客服系统只接一个模型 API,高峰期要么排队超时,要么直接报错,用户体感极差。引入 GPT-4 → GPT-3.5 → 本地开源模型 → FAQ 缓存的降级链后,任一环节故障都秒级切换,客服永远在线。
具体案例:某电商大促期间,GPT-4 触发限流,系统自动把 60% 的咨询转给 GPT-3.5,30% 转给本地模型,常见问题直接命中缓存库,大促全天客服可用率 99.98%。
批量内容生成的成本分流
阶段:成本优化 / 推理调度
解决的问题:全量调用 GPT-4 批量生成商品文案,月成本高得离谱。按任务复杂度分流:商品标题等短文本走本地小模型,营销长文走 GPT-4,本地模型失败时降级到规则模板,成本下降约 70%。
具体案例:一家电商 SaaS 平台每天生成 20 万条商品描述。调度器按 token 预估长度和任务类型路由,80% 请求落在本地模型,只有 20% 复杂请求打到 GPT-4,且配置了单日预算熔断。
多租户 SaaS 的熔断隔离
阶段:稳定性保障 / 容错治理
解决的问题:一个租户的突发流量把共享模型池打爆,导致所有租户集体超时。为每个租户设置配额和熔断器,某租户连续失败达到阈值就打开熔断,请求降级到共享备用池,避免“一颗老鼠屎坏一锅汤”。
具体案例:某企业级 AI 写作平台,某客户凌晨的批处理任务触发 429 限流,熔断器 5 秒内打开,该客户请求自动转移到备用模型队列,其他客户无感知,平台整体可用性保持在 99.95%。
Python 演示
import randomimport time class ModelClient: """模拟一个带成本与失败率的模型客户端""" def __init__(self, name, fail_rate, cost): self.name = name self.fail_rate = fail_rate self.cost = cost def chat(self, prompt): if random.random() < self.fail_rate: raise RuntimeError("{} 调用失败".format(self.name)) return "[{}] 回答: {}".format(self.name, prompt) def backoff_wait(attempt): """指数退避加抖动, 防止重试风暴""" return min(1.0, 0.05 * (2 ** attempt) + random.random() * 0.05) def call_with_fallback(prompt, chain, retries=2): """沿降级链依次尝试, 记录成本, 返回首个成功结果或兜底文案""" total_cost = 0 for model in chain: for attempt in range(retries): total_cost += model.cost try: return model.chat(prompt), total_cost except RuntimeError: time.sleep(backoff_wait(attempt)) print(">>> {} 重试耗尽, 降级到下一个模型".format(model.name)) print(">>> 全部模型失败, 启用缓存兜底") return "[兜底] 服务繁忙, 请稍后再试", total_cost def main(): chain = [ ModelClient("gpt-4", fail_rate=0.6, cost=10), ModelClient("gpt-3.5", fail_rate=0.4, cost=1), ModelClient("local-7b", fail_rate=0.2, cost=0), ] for prompt in ["总结今日新闻", "生成产品文案", "翻译用户评论"]: print("请求:", prompt) answer, total_cost = call_with_fallback(prompt, chain) print("返回:", answer) print("本次成本: {} 元".format(total_cost)) print("---") if __name__ == "__main__": main()
def call_with_fallback(prompt, chain, retries=2):降级链的主入口,chain 是有序模型列表,retries 是每级重试次数。for model in chain:按优先级遍历降级链,主模型先试,失败依次下探。total_cost += model.cost无论成功还是失败,每次调用都计入成本,还原“失败的 token 也收费”。raise RuntimeError("{} 调用失败".format(self.name))模拟模型调用异常,真实场景对应 API 超时、限流、5xx 等。time.sleep(backoff_wait(attempt))重试前按指数退避等待,避免瞬间重试压垮服务。return min(1.0, 0.05 * (2 ** attempt) + random.random() * 0.05)等待时间封顶 1 秒并加随机抖动,防止重试风暴。print(">>> {} 重试耗尽, 降级到下一个模型".format(model.name))输出降级事件,真实系统会写日志并上报监控指标。return "[兜底] 服务繁忙, 请稍后再试", total_cost全链失败后的最终兜底,保证请求永远有响应。
常见误区
误区我以为 Fallback 就是在代码里写个 try-except,捕获异常后换一个 model 参数重新调 API,几行代码就搞定,没什么好设计的。
正解真实的 Fallback 要考虑触发条件(超时/限流/内容审查/响应不合格都要识别)、备选模型策略(不是随机换,而是按质量和成本排好的固定序列)、结果校验(备用模型的输出同样要验证格式和质量),还要配合退避和熔断避免雪崩。徒手写 Fallback 能用,但经不起真实流量的考验。
为何常见:try-except 是编程入门知识,教程里“换个模型再试一次”的示例太简单,让人误以为 Fallback 只是个语法题。
误区为了保险,我在降级链里塞了五六个模型,GPT-4 挂了就依次试 GPT-3.5、Claude、Gemini、文心、通义,总觉得多备一层就多一分安全。
正解每加一层,请求多了“失败后才到达下一层”的等待成本,端到端延迟可能从 2 秒拖到 30 秒,反而把用户等跑。而且每一层失败调用都在计费。合理设计是 3-4 层:云端大模型 → 云端小模型 → 本地/自托管模型 → 缓存兜底,并设置总超时预算。
为何常见:人本能的“多准备几手总没错”思维,忽略了每一级尝试都有延迟和成本代价,忘了“可用性”还要乘上“体验”。
误区模型一慢我就想立刻换下一个,于是把超时设成 500ms,觉得这样失败能在一秒内完成降级,系统响应很快。
正解LLM 生成回答本来就慢,长文本动辄几十秒。500ms 超时会把大量“正常但稍慢”的请求误判为失败,引发无意义的降级,用户反而收到更多简化回答。正确做法是分层设置:连接超时 2-3 秒,读取超时 30-60 秒,再叠加总请求预算兜底。
为何常见:传统 API 调用的经验是“快失败”,但 LLM 的生成模式天然慢,“快失败”经验在此不适用。
误区只要降级链能返回一个结果,用户就不会察觉任何差别,处理降级时不需要额外的提示或补偿。
正解降级模型的输出质量明显更低——回答更短、更泛、甚至答非所问。用户能感知到“这答案变敷衍了”。所以要做透明提示(“当前轻量模式”)、渐进式反馈、缓存相似答案保证核心信息,把质量落差明明白白交代清楚。
为何常见:开发者盯着接口状态码,认为 200 就是成功;用户盯着答案能否解决自己的问题,两种视角存在天然偏差。
面试题
Q1. 主模型调用超时后,降级链的下一步正确动作是什么?
- A.无限重试主模型直到成功返回
- B.切换到降级链中的下一个模型← 正确答案
- C.立即抛出异常终止本次请求
- D.丢弃请求不向用户返回任何内容
答案:B。超时是典型的降级触发条件,正确做法是沿降级链切到下一个模型继续尝试。无限重试会拖垮延迟,直接抛异常和丢弃请求违背容错目标,都不能让服务继续可用。
Q2. 以下哪个模型顺序符合典型的降级链设计?
- A.本地模型再到 GPT-4 再到缓存兜底
- B.缓存兜底再到本地模型再到 GPT-4
- C.GPT-4 再到 GPT-3.5 再到本地模型← 正确答案
- D.GPT-3.5 再到缓存兜底再到 GPT-4
答案:C。降级链按“质量优先、成本兜底”排序:最强最贵的模型排最前,依次降级到较弱的模型,最后才是缓存。A 和 B 把本地模型和缓存放在 GPT-4 前面,D 把 GPT-4 放在链尾,都不合理。
Q3. 关于降级链长度的设计,以下哪种做法最合理?
- A.链条越长就越稳定可靠
- B.控制在几层并设置总超时预算← 正确答案
- C.只需要一层缓存兜底就够了
- D.链长由主模型的价格来决定
答案:B。每增加一级都会带来额外的等待和成本,链太长会让端到端延迟失控。实践上 3-4 层(云端大模型→云端小模型→本地模型→缓存)再加总超时预算比较合理,链长与主模型价格无关。
Q4. 指数退避重试策略的主要目的是什么?
- A.提高每一次请求的成功率
- B.避免重试风暴压垮下游服务← 正确答案
- C.降低每次模型调用的成本
- D.缩短整体响应耗时时间
答案:B。指数退避让重试间隔逐次翻倍并加抖动,把同一时刻的大量重试打散,避免下游服务被重试请求压垮。它不直接提高单次成功率,也不会降低成本或延迟,反而是用时间换稳定性。
Q5. 熔断器打开之后,系统处于什么状态?
- A.正常放行所有进入的请求
- B.立即失败不再调用该模型← 正确答案
- C.放大超时等待的时间窗口
- D.切换到更大的并发请求量
答案:B。熔断器打开后,系统不再把请求发给已失效的模型,而是快速失败并走降级链,避免继续等待和烧钱。经过冷却时间会进入半开状态试探,恢复成功后关闭熔断器。
Q6. 某服务 SLO 为 99.9%,按 30 天计算,月度错误预算约为多少?
- A.大约四十三点二分钟← 正确答案
- B.大约四小时三十二分钟
- C.大约四百三十二秒钟
- D.大约四点三二分钟
答案:A。30 天共 43200 分钟,错误预算 = (1 − 0.999) × 43200 = 43.2 分钟。也就是说,每月累计不可用时间超过 43.2 分钟就违背了 SLO。C 的 432 秒等于 7.2 分钟,数量级不对。
Q7. 主模型成功率 90%,备用模型成功率 80%,Fallback 后的整体成功率最接近?
- A.大约百分之九十左右
- B.大约百分之九十八左右← 正确答案
- C.大约百分之七十二左右
- D.大约百分之八十五左右
答案:B。整体成功率 = 1 − (1−0.9)×(1−0.8) = 1 − 0.02 = 0.98,即约 98%。C 的 72% 是把两个成功率直接相乘,那是“两个都成功”的概率,而不是“至少一个成功”。
Q8. 以下哪些情况应当触发模型降级?
- A.API 返回 429 限流错误← 正确答案
- B.API 请求超时无响应← 正确答案
- C.模型输出含敏感词被过滤← 正确答案
- D.用户主动要求更换模型
答案:ABC。限流、超时、输出被安全策略拦截都属于模型调用失败或结果不可用,应触发降级。用户主动要求更换模型是产品需求,不是故障,应由路由层处理而非容错层处理。
Q9. 降级链设计中,合理的终极兜底方案有哪些?
- A.返回缓存的历史答案内容← 正确答案
- B.返回固定话术提示服务繁忙← 正确答案
- C.用本地小模型生成简化回答← 正确答案
- D.直接返回五百错误状态码
答案:ABC。兜底的目的是“保证用户有响应”:缓存历史答案能提供高价值内容,固定话术至少交代了现状,本地小模型能继续生成。直接返回 500 是放弃兜底,与容错设计的目标相悖。
Q10. 关于降级链中的熔断器机制,以下说法正确的有?
- A.连续失败达到阈值后熔断器打开← 正确答案
- B.熔断打开期间不再调用该模型← 正确答案
- C.冷却时间后进入半开状态试探← 正确答案
- D.熔断器一旦打开就永远不会恢复
答案:ABC。熔断器三态:关闭(正常调用)→ 打开(连续失败超阈值,跳过模型)→ 半开(冷却后放少量试探请求)。试探成功则恢复关闭,失败则重新打开。D 说“永不恢复”会让模型永远被弃用,是错误的理解。