返回探索
指南VibeFix 编辑部更新于 2026年10月24日

模型挂了怎么办:vibe 项目 AI 功能的降级与兜底策略

你的 vibe 项目的 AI 功能迟早会遇到服务商宕机、限流、超时和坏输出。上线前就把降级模式设计好:超时重试、降级阶梯、诚实缓存、排队通知与故障夜处置清单,本文一次讲清。

BalticServers 机房实拍:一排亮着蓝色指示灯的服务器机柜,提醒读者每个 AI 功能都依赖可能出故障的基础设施。

先划清边界:这篇指南不是什么

在开始之前,先说清楚这篇指南和站内已有的四篇指南的区别,免得你读到一半发现走错了篇。

第一,站内已有一篇讲限流的指南,它讲的是如何限制涌入你系统的请求、防止被刷爆——那是防别人搞垮你。这篇讲的是反方向:当你依赖的模型服务商自己挂了、限了你、慢了、返回了垃圾结果时,你怎么活下去。一个管入口,一个管出口依赖,问题域完全不同。

第二,站内已有一篇讲错误监控的指南,它讲的是如何发现故障、收到告警、定位崩溃——那是“看见问题”。这篇讲的是“问题已经发生了,用户还在线,你拿什么顶上去”:降级阶梯、缓存、排队、兜底文案。监控告诉你着火了,降级决定你着火时房子还能不能住人。

第三,站内已有一篇讲功能开关的指南,它讲的是用开关控制功能 rollout、灰度、紧急关停——那是发布策略。这篇讲的是运行时韧性:开关只是降级阶梯里最后一级的执行手段之一,真正的核心是“每一级降级输出什么、给谁看、什么时候自动升回来”,开关本身不回答这些问题。

第四,站内已有一篇讲上线前最后 1 小时的执行手册,它讲的是发布当天的检查清单与 Go / No-Go——那是上线前的执行。这篇讲的是上线之后、某个深夜服务商宕机时的运行时策略。时间轴不同:前者是发布日,后者是运行期的每一个夜晚。

所以这篇新增的东西是:一套面向 AI 功能的降级设计——超时与重试规则、模型降级阶梯、按功能类型分的降级决策表、缓存旧答案的诚实边界、异步排队替代方案、降级态文案、以及故障夜的关停开关与处置清单。这些内容在上述四篇里都没有成体系地出现过。

先接受一个现实:模型一定会挂

如果你在 vibe 项目里接了 AI 功能——聊天助手、摘要、分类、代码生成、Agent 循环——那么它出故障不是“会不会”,而是“什么时候、以什么方式”。我见过的真实故障形态只有四种,几乎可以覆盖全部:

  • 宕机(outage):服务商整段不可用,返回 5xx 或直接连不上。特点是全体用户同时受影响,你的代码再正确也没用。
  • 限流(rate limit):返回 429。你没做错什么,只是配额、用并发或突发流量撞了上限。特点是部分请求失败,而且越重试越糟——如果你的重试没有退避,你就是在帮服务商把自己打死。
  • 超时(timeout):请求发出去石沉大海。特点是结果未知:模型可能已经在跑、可能已经扣了费、可能已经写了库,你这边只知道“没等到回答”。
  • 坏输出(bad output):请求成功了,但结果是空的、截断的、格式错乱的、答非所问的,或者 JSON 解析失败。特点是 HTTP 状态码是 200,你的监控一片绿色,用户却看到了垃圾。

这四种故障需要不同的对策。把它们都当成“报错了,重试一次”处理,是最常见的设计失误。宕机时重试是浪费,限流时立即重试是火上浇油,超时时盲目重试可能造成重复扣费或重复写库,坏输出时重试同一模型大概率得到另一份垃圾。下面的每一节,都是针对某一种或几种故障形态的专门对策。

还有一个前提判断:你的 AI 功能是不是核心路径?如果 AI 只是锦上添花(比如“智能摘要”按钮),降级可以大胆;如果 AI 就是产品本身(比如 AI 写作工具),降级只能做到“保住用户已有的工作成果”,绝不能伪造一个假成果交差。这个判断决定了后面每一级阶梯的取舍,动手写代码之前先把它写下来,贴在需求文档第一行。

第一层防线:超时、重试与幂等

所有降级策略的地基是这一层。如果连超时和重试都没做对,后面的阶梯全是空中楼阁。

超时必须显式设置,而且要分层

不要依赖运行时的默认超时——很多 HTTP 客户端的默认超时是“无限”或几分钟,用户早关页面了,你的函数还在等。正确做法是分三层设超时:

  • 单次模型调用超时:按功能定。聊天类 20–30 秒,分类类 5–10 秒,后台摘要类可以放宽到 60 秒。超过这个时间,对用户来说已经没有意义了。
  • 端到端超时:用户从点击到看到结果的总预算,通常比单次调用超时略长,给重试和降级留一点余量,但绝不能无上限。
  • 流式输出的首字节超时:如果你用流式输出,第一个 token 迟迟不来和中途断流是两种故障。首字节超时(比如 10 秒)能让你早点触发降级,而不是让用户盯着空白光标转圈。

下面是一个 TypeScript 风格的超时包装器,它只解决一件事:让任何异步调用在规定时间内要么返回、要么明确失败,不留悬案。

async function withTimeout<T>(
  task: Promise<T>,
  ms: number,
  label = "ai-call",
): Promise<T> {
  let timer: ReturnType<typeof setTimeout> | undefined;
  const timeout = new Promise<never>((_, reject) => {
    timer = setTimeout(
      () => reject(new Error(`${label} timed out after ${ms}ms`)),
      ms,
    );
  });
  try {
    return await Promise.race([task, timeout]);
  } finally {
    clearTimeout(timer);
  }
}

// 用法:聊天调用 25 秒没回来就判超时,走降级分支
const reply = await withTimeout(callModel(prompt), 25_000, "chat");

注意一个细节:这个包装器只解决了“调用方不再等”,并没有取消服务商那边正在跑的任务。如果你用的是支持 AbortSignal 的客户端,务必把 signal 传进去;否则超时的请求可能仍在后台消耗配额。超时响应不等于任务取消,这一点在计费和写库时尤其重要。

重试只重试值得重试的错误,并且带退避

重试规则可以写成一张白名单,而不是“失败就重试”:

  • 值得重试:429(限流)、502 / 503 / 504(网关与服务商临时故障)、网络连接重置、流式输出中途断流(且你能从断点续接或整体重发)。
  • 绝不重试:400(请求本身有问题,重试一百次还是 400)、401 / 403(鉴权问题)、内容被安全策略拦截的 4xx、以及业务校验失败。
  • 最多重试一次且要小心:超时。因为结果未知,重试前必须确认操作是否幂等(见下文),否则可能重复扣费或重复写库。

重试次数建议封顶 3 次(含首次),退避用指数退避加随机抖动:第一次等约 1 秒,第二次约 2 秒,第三次约 4 秒,每次再加 0–50% 的随机抖动。抖动不是装饰:宕机恢复那一刻,所有客户端同时重试会形成第二波冲击,抖动就是用来打散这波冲击的。如果服务商在 429 响应里给了 Retry-After,那就优先服从它,而不是用自己的退避表硬顶。

const RETRYABLE = new Set([429, 502, 503, 504]);

function backoffMs(attempt: number): number {
  const base = Math.min(1000 * 2 ** attempt, 8000);
  return base + Math.floor(Math.random() * base * 0.5);
}

async function callWithRetry<T>(
  fn: () => Promise<T>,
  maxAttempts = 3,
): Promise<T> {
  let lastError: unknown;
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      lastError = err;
      const status = err?.status ?? err?.response?.status;
      const isLast = attempt === maxAttempts - 1;
      if (isLast || !RETRYABLE.has(status)) throw err;
      await new Promise((r) => setTimeout(r, backoffMs(attempt)));
    }
  }
  throw lastError;
}

还有一个容易被忽略的点:重试预算要全局控制,而不是每个请求各自重试 3 次。突发故障时,成千上万个请求各自重试 3 次,等于把流量放大 3 倍。简单有效的做法是:当 5xx / 429 比例超过阈值(比如 1 分钟内超过 20%),自动把全局重试次数降到 1 次甚至 0 次,故障恢复后再放开。这就是简易版的熔断,几十行代码就能做,不需要引入重型熔断库。

幂等键:超时重试的安全带

幂等键(idempotency key)解决的是超时场景最棘手的问题:你不知道第一次请求到底执行了没有。做法是:客户端为每一次“用户意图”生成一个唯一键(比如 UUID),随请求发给服务端;服务端用这个键做去重——同一个键第二次到达时,直接返回第一次的结果,不再重复执行、不再重复扣费、不再重复写库。

  • 键的粒度是“一次用户操作”,不是“一次 HTTP 请求”。用户点一次“生成报告”,整个流程共享一个键;重试、刷新、重复点击都带同一个键。
  • 键要服务端持久化,至少保留 24 小时,只放内存会在重启后失效,等于没做。
  • 非幂等操作必须配幂等键,幂等操作(纯查询、只读)不需要。判断标准是:重复执行会不会产生副作用——会,就加键;不会,就不用。
  • 键冲突时返回第一次的结果,而不是报错。返回 409 让用户“换个键再试”是把内部机制泄漏给了用户,正确做法是把第一次的结果原样还回去。

一个最小可用的服务端伪代码思路是:收到请求先查键——命中则直接返回缓存的结果;未命中则执行,执行完成后把结果和键一起存下来。注意“执行中”的状态也要处理:如果第一次请求还在跑,第二次带着同一个键进来,应该等待第一次完成(或返回“处理中”),而不是并行再跑一份。很多重复扣费事故的根因不是没做幂等,而是只做了“完成态去重”,没做“进行态去重”。

顺带说一句:幂等键不是重试的替代品,而是重试的前提。没有幂等键的超时重试,本质上是在赌“第一次没执行”;有幂等键的超时重试,才是工程上站得住脚的做法。

第二层防线:模型降级阶梯

当重试也救不回来时,就该降级了。降级阶梯的意思是:预先排好一串备选方案,按顺序逐级尝试,每一级都比上一级“便宜、简单、确定性更强”,直到某一级别成功,或者走到最后一级——诚实地告诉用户 AI 暂时不可用。

AI 功能降级阶梯示意图:从主模型逐级降到便宜模型、缓存答案、模板答案,最后是诚实的不可用状态

标准阶梯是五级:

  1. 主模型(primary):正常情况下用的模型,质量最好、成本最高。
  2. 便宜模型(cheaper model):同一服务商的小模型,或另一家服务商的平价模型。质量略降,但仍然是“真生成”。切换的前提是:你已经验证过它在你的任务上能用——不是所有任务都能降模型,见下面的决策表。
  3. 缓存答案(cached answer):以前对相同或高度相似输入生成过的好答案。适用于重复性高的问题(常见问题、固定模板的摘要、历史查询)。
  4. 静态 / 模板答案(static / template answer):预先写好的、不依赖模型的内容:操作指南、检查清单、表单、规则说明。质量最低,但 100% 可靠。
  5. 诚实的不可用状态(honest unavailable state):明确告诉用户 AI 功能暂时不可用,并给出替代路径(稍后重试、排队处理、人工入口)。这是底线,不是失败——伪造答案才是失败。

阶梯的实现关键是“逐级切换的条件要写死在代码里,而不是靠人临场决定”。每一级的触发条件建议写成:主模型连续失败 N 次、或 429 / 5xx 比例超过阈值、或单次延迟超过预算,就自动降一级;恢复条件同样写死:主模型连续成功 M 次、或健康检查通过,就自动升回来。手动升降级只留给极端情况,并且要记录是谁、在什么时候、为什么切的。

type FallbackResult =
  | { kind: "generated"; text: string; model: string }
  | { kind: "cached"; text: string; cachedAt: string }
  | { kind: "template"; text: string; templateId: string }
  | { kind: "unavailable"; reason: string };

async function generateWithFallback(prompt: string): Promise<FallbackResult> {
  // 第 1 级:主模型(含重试)
  try {
    const text = await callWithRetry(() => callPrimary(prompt));
    return { kind: "generated", text, model: "primary" };
  } catch { /* 掉到下一级 */ }

  // 第 2 级:便宜模型
  try {
    const text = await withTimeout(callCheaper(prompt), 15_000, "cheaper");
    return { kind: "generated", text, model: "cheaper" };
  } catch { /* 掉到下一级 */ }

  // 第 3 级:缓存答案
  const cached = await lookupCache(prompt);
  if (cached) return { kind: "cached", text: cached.text, cachedAt: cached.at };

  // 第 4 级:模板答案
  const template = matchTemplate(prompt);
  if (template) return { kind: "template", text: template.text, templateId: template.id };

  // 第 5 级:诚实不可用
  return { kind: "unavailable", reason: "AI provider unreachable" };
}

注意这段代码的一个设计选择:每一级失败后都继续往下走,而不是直接抛错。调用方拿到结果后,根据 kind 决定怎么展示:generated 正常展示,cached 和 template 必须带上标识(见第四节),unavailable 走降级态 UI(见第六节)。把“降级”做成返回值而不是异常,是整个设计里最重要的一行哲学:降级是正常流程的一个分支,不是错误处理的事后补丁。

决策表:不是所有功能都适合同一条阶梯

降级阶梯要按功能类型裁剪。下面这张表可以直接拿去对照你的功能清单打勾:

功能类型便宜模型缓存答案模板答案最后一级
聊天 / 问答适合:先降模型,质量可接受适合常见问题;个性化问题不适合适合:引导用户去文档、FAQ、人工入口诚实不可用 + 排队转人工
分类 / 打标签适合:小模型分类通常够用适合:同一输入重复出现时适合:默认分类 + 人工复核标记先存下来、标“待分类”,事后补跑
生成(文章、代码、摘要)谨慎:质量落差用户一眼能看出来只适合重复性模板生成适合:给大纲、给模板、给检查清单绝不伪造全文;保存草稿 + 排队重生成
Agent 循环(多步任务)谨慎:小模型容易在多步推理中跑偏不适合:步骤依赖上下文,缓存难命中适合:把已完成步骤的结果先交出来暂停循环、保存进度、明确告诉用户做到哪一步

表里最值得展开的是两行。第一行是生成类功能绝不能用模板冒充生成结果:用户要的是一篇针对他材料的摘要,你给他一篇通用模板还不标注,那就是伪造。正确做法是给他大纲和模板,并明说“完整生成失败,这是提纲,你可以先填着”。第二行是 Agent 循环的降级单位是“步骤”不是“整个任务”:循环跑了 5 步、第 6 步挂了,降级应该是“把前 5 步的结果先交出来,并保存进度等恢复后继续”,而不是“整个任务作废”或“假装 6 步都跑完了”。

还有一个跨类型原则:降级不能降安全性。便宜模型、缓存、模板都必须经过和主模型同样的安全检查(敏感信息过滤、注入防护、输出校验)。故障夜最容易出的二次事故,就是降级通道绕过了主通道的安全校验——主模型被安全策略拦住的内容,绝不能从模板通道原样漏出去。

第三层防线:缓存旧答案,诚实是边界

缓存是降级阶梯里性价比最高的一级,也是最容易越界的一级。它的价值在于:很多 AI 请求本来就是重复的——同一个问题被不同用户反复问,同一份材料被反复摘要,同一个分类被反复打。把这些“上次的好答案”存下来,故障时顶上去,用户体验几乎不受损。

但缓存有个致命诱惑:它让你很容易把“旧答案”当成“新答案”交出去。判断能不能 served stale(提供陈旧答案),看三条:

  • 答案是否随时间变质?“如何配置 X”这类稳定知识可以缓存几周;“今天的汇率 / 股价 / 政策”这类时效答案缓存几小时就是误导。给每类答案定一个 TTL(存活时间),过期就失效,不要用一个全局 TTL 管所有类型。
  • 答案是否依赖用户个人输入?通用问题可以缓存;包含用户私人数据、订单、代码库的个性化答案,缓存命中率低不说,串用户的风险也大。个性化答案要么不缓存,要么按用户隔离缓存,绝不能跨用户复用。
  • 用户能不能分辨新旧?这是最关键的一条:只要答案是缓存的,就必须标注时间和来源,比如“这是 3 天前生成的参考答案,当前 AI 服务暂时不可用”。标注不是免责声明的装饰,而是诚实边界本身——用户有权知道他看到的不是实时生成的结果。

下面是“可接受”与“不诚实”的对照,写文案和做评审时可以直接用:

  • 可接受:FAQ 类答案标注“缓存于某日,仅供参考”;历史摘要标注生成时间;分类结果标注“由历史规则复核,待模型恢复后重跑”。
  • 不诚实:把缓存答案冒充实时生成;把昨天的价格、政策、比分当成今天的数据;把针对 A 用户的答案展示给 B 用户;把模板答案包装成“AI 为你生成”。

工程上还有三个实操要点。第一,缓存键要包含“影响答案的关键输入”,而不是整个 prompt 原文:去掉无关的空白、语气词,保留实体、参数、版本号,否则缓存命中率会被无意义的措辞差异拖垮。第二,缓存写入只写“验证过的好答案”:主模型返回后经过格式校验、质量抽查(至少是长度与结构合理)才入库;坏输出绝不能进缓存,否则一次故障会污染之后所有的降级。第三,缓存本身也要有降级:如果缓存服务也挂了,阶梯要能直接跳到模板级,而不是在缓存这一步卡住——降级链上的每一环都不能是单点故障。

第四层防线:异步排队,別让用户干等

不是所有 AI 任务都需要在用户眼前实时完成。生成报告、批量摘要、长 Agent 任务、视频处理这类“用户能等几分钟”的工作,最好的降级不是立刻给一个差答案,而是收下来、排队、做好了通知用户。这就是 queue-and-notify(排队并通知)模式。

它的适用判断很简单:问一句“用户现在必须马上看到结果吗?”如果答案是否定的,就不要在请求-响应循环里硬扛模型故障。流程应该是:

  1. 用户提交任务,服务端立刻返回一个任务 ID 和“已排队,预计 X 分钟内完成”的确认,页面不阻塞。
  2. 任务进队列,工作进程按顺序消费;模型故障时任务留在队列里重试(带退避),而不是直接失败。
  3. 任务完成(或最终失败)后,通过站内通知、邮件或原页面状态刷新告诉用户。
  4. 用户回来时能看到结果、看到进度、看到失败原因和重试入口。

这个模式顺带解决了三个实时模式解决不了的问题。第一,削峰:突发流量先排队,不会瞬间把配额打爆。第二,重试友好:队列里的任务可以等服务商恢复后再跑,用户不需要守着页面反复点重试。第三,可观测:队列长度、等待时间、失败率本身就是健康指标,队列突然变长就是故障的最早信号之一。

但排队也有它的坑,需要提前处理好。第一,队列不是无限大的:要设上限和 TTL,超限时明确拒绝新任务(并告诉用户“现在排队已满,请稍后再试”),而不是无声地堆积到系统崩溃。第二,任务状态必须可查询:用户要能随时看到“排队中 / 处理中 / 已完成 / 已失败”,状态页不能只有一个转圈。第三,失败要有终点:队列任务重试次数也要封顶(比如 5 次),最终失败后要通知用户、保留输入、提供一键重跑,而不是悄悄丢掉。第四,涉及付费或配额的操作,排队任务执行前要重新校验配额和权限——用户排队时有额度,不代表两小时后执行时还有。

一个简单的选型建议:vibe 项目早期不需要上重型消息队列。用数据库表做任务队列(状态字段 + 轮询 worker)就能跑到相当大的量;等到队列成为瓶颈、需要多 worker、延迟保证时,再迁移到专业队列。过早引入重型基础设施,是 vibe 项目最常见的过度设计之一。

降级态的 UX 文案:说什么,绝不伪造什么

技术降级做得再好,文案写错了也会前功尽弃。用户在故障时最怕的不是“慢”,而是“不知道发生了什么、不知道该怎么办、不知道你给的东西能不能信”。降级态文案要回答三个问题:发生了什么、我现在能得到什么、接下来我该做什么。

先给一套可以直接套用的文案模板:

  • 聊天降级到便宜模型:“当前使用备用模型回答,质量可能略有下降;主模型恢复后会自动切换回来。”
  • 命中缓存:“AI 服务暂时不可用,以下是 3 天前生成的参考答案,仅供参考;恢复后可重新生成最新版本。”
  • 模板兜底:“AI 生成暂时不可用,你可以先按下面的检查清单操作;也可以留下任务,恢复后我们会第一时间生成。”
  • 完全不可用:“AI 功能暂时不可用,我们正在处理;你的内容已保存,不会丢失,恢复后可继续。”
  • 排队确认:“任务已排队,预计 10 分钟内完成;完成后我们会通知你,你也可以随时回来查看进度。”

文案有三条铁律。第一,说人话,不说黑话:不要写“上游 LLM provider 返回 503”,用户不关心也不应该关心你的架构;写“AI 服务暂时不可用”就够了。第二,给确定性,不给模糊承诺:“马上就好”是最糟糕的承诺,写不出来时间就写“恢复后通知你”,并真的做到通知。第三,绝不伪造状态:不要用假的打字动画假装 AI 在思考,不要用“AI 正在为你生成”掩盖其实已经失败的事实,不要把模板答案标成“AI 生成”。用户可以接受“暂时没有”,不能接受“被骗了才发现没有”。

还有几个 UI 细节值得做。降级态要用视觉上可区分但不刺眼的样式(比如浅色提示条),不要用红色报错吓用户——降级是“系统在正常应对”,不是“你做错了什么”。降级提示要放在答案旁边、用户看得到的地方,不要藏在页面角落的小字里。最重要的是:降级态必须提供一条出路——重试按钮、排队入口、人工入口、文档链接,至少给一条。没有出路的提示,等于把用户关在门外。

最后提醒一句:降级文案要提前写好、放进代码里,而不是故障夜现写。故障夜人的判断力是最差的,现写的文案最容易出现两种错误:过度承诺(“5 分钟就好”结果两小时没好)和推卸责任(“这是服务商的问题”)。提前写好的文案,经过冷静时的评审,才是用户在故障夜应该看到的东西。

故障夜的装备:关停开关、状态页与处置清单

前面五节是“事前设计”,这一节是“事中处置”。故障夜你需要的三件装备,最好在上线前就备齐。

Kill-switch:关停开关

Kill-switch(关停开关)是一个能立刻关闭某个 AI 功能、或强制全量降级到模板/排队的总开关。它的设计要点:

  • 开关要独立于 AI 功能本身。如果开关存在数据库里而数据库也挂了,开关就按不下去;用配置服务、环境变量或独立的开关服务,至少保证开关的可用性高于被它控制的功能。
  • 开关要分级:按功能开关(只关聊天)、按级别开关(强制降到模板级)、全局开关(全关 AI 功能)。故障夜你最不想做的事,就是“要么全开要么全关”的二选一。
  • 开关操作要留痕:谁、在什么时候、为什么切的,恢复时谁确认的。故障复盘时,开关记录是最关键的证据链之一。
  • 开关要演练:上线前至少手动拨一次开关,确认降级态真的出现、用户数据不受损、恢复后真的能升回来。没演练过的开关,等于没有开关。

注意 kill-switch 和功能开关指南里的开关不是一回事:那里的开关管的是“发布策略”,这里的开关管的是“故障时的生存策略”。同一个开关基础设施可以复用,但开关的语义、权限和演练方式要按故障场景单独设计。

状态页:让用户不用猜

状态页(status page)是故障夜减少客服压力最有效的工具,哪怕它只是一个静态页面。最低可用的状态页只需要三样东西:当前各功能的状态(正常 / 降级 / 不可用)、故障开始时间与简述、预计恢复信息或“下次更新时间”。“下次更新时间”比“预计恢复时间”更诚实、也更容易做到:你不知道什么时候能恢复,但你能承诺“30 分钟后更新进展”,并且真的做到。

状态页的更新责任要指定到人:故障夜谁负责更新、多久更新一次、通过什么渠道发布(状态页、站内公告、邮件)。没有负责人的状态页,故障开始一小时后就会停在“正在调查”不动,比没有状态页更伤信任。

故障夜处置清单(Runbook Checklist)

下面这份清单可以直接打印出来贴在值班手册里。故障发生时,按顺序打勾,不要凭记忆发挥:

  1. 确认范围:是单个功能还是全部 AI 功能?是单个服务商还是多个?看监控和队列长度,不要只看用户反馈。
  2. 评估降级是否已自动生效:降级阶梯有没有按设计走到应有的级别?如果自动降级没生效,手动拨 kill-switch 到对应级别。
  3. 更新状态页:写清楚受影响功能、开始时间、下次更新时间。不要写原因推测,只写已确认的事实。
  4. 控制重试与流量:确认全局重试预算已收紧;必要时暂停非核心 AI 任务(批量任务、后台任务),把配额留给核心功能。
  5. 保护用户数据:确认排队任务、草稿、进行中的 Agent 进度都没有丢失;确认没有伪造输出被写入用户数据。
  6. 通知与值守:通知相关人员;安排值守轮换,故障超过 1 小时必须有人接手,不要一个人硬扛一夜。
  7. 恢复验证:服务商恢复后,先在小流量上验证主模型输出质量,再逐步升回;不要一恢复就全量切回——刚恢复的服务最容易二次抖动。
  8. 事后复盘:24 小时内写复盘:故障时间线、降级是否生效、哪些环节靠人顶、哪些要改成自动。复盘的产出应该是代码或配置的改动,而不是一句“下次注意”。

清单里第 7 条值得单独强调:恢复过程也要设计。很多故障的二次伤害发生在恢复那一刻:全量切回主模型、积压任务瞬间涌出、用户集中重试,三重冲击叠加,刚恢复的服务又被打挂。正确做法是分阶段恢复:先放 10% 流量验证质量,再 50%,再全量;积压的队列任务按优先级慢慢放,而不是一次性全放出来。

上线前自查:十个问题

写完这篇指南,最有用的收尾是这份自查清单。你的 AI 功能上线前,对下面十个问题都应该有明确答案,答不上来的那一项,就是故障夜最先爆的那一项:

  1. 每个 AI 调用的超时是多少?流式输出的首字节超时是多少?
  2. 哪些错误码会重试、重试几次、退避策略是什么?哪些错误绝不重试?
  3. 哪些操作有幂等键?键的粒度是用户操作还是 HTTP 请求?服务端存多久?
  4. 降级阶梯有几级?每一级的触发条件和恢复条件是什么?是自动还是手动?
  5. 便宜模型在你的任务上验证过吗?质量落差用户能接受吗?
  6. 缓存答案的 TTL 是多少?个性化答案有没有隔离?缓存答案有没有标注时间?
  7. 哪些任务走了排队模式?队列上限、任务 TTL、最终失败通知都定了吗?
  8. 降级态文案写好了吗?放在代码里了吗?有没有给用户出路?
  9. Kill-switch 能按功能分级关吗?演练过吗?操作留痕了吗?
  10. 状态页谁负责更新?故障夜的值守轮换和恢复验证流程定了吗?

最后回到开头那句话:AI 功能的故障不是意外,是运行期的常态。降级设计不是“等有空再补”的优化项,而是 AI 功能上线标准的一部分。一个没有降级设计的 AI 功能,上线那天就是它最脆弱的一天;一个有降级设计的 AI 功能,故障夜用户看到的不是白屏和报错,而是一句诚实的话、一个可用的替代方案、和一个“恢复后通知你”的承诺。把这份承诺提前写进代码里,故障夜你才睡得着觉。

阅读 0评论 0

评论 (0)

ME
0/1000
评论加载中...
浏览项目广场发布你的项目

相关文章

两名开发者并排坐在双显示器前结对编程,一人操作键盘,另一人指着屏幕上的代码参与讨论
指南
别当外包用:vibe 开发的人机结对编程协议实战

vibe coding 最大的翻车不是 AI 写错代码,而是人悄无声息地失去对代码的掌控感。这篇指南把经典结对编程的驾驶员/导航员模型,重构成一人 + 一 AI 的可执行协作协议:开工前 15 分钟对齐三件套、按四种信号换手、信任分三阶段校准、每 20 分钟停车检查、收尾留下决策日志与下次开工包。附可直接套用的模板、话术与检查清单。

开发工作流项目创作AI 编程实践
开发者从背后注视屏幕上的代码与 AI 对话面板
指南
别被 AI 评审带偏:给 AI 代码评审结论做二次验证的方法

AI 评审最大的风险不是漏掉问题,而是说得太像回事,让你照单全收。这篇讲评审的评审:四步二次验证法、三类意见三种处理、反向质问评审的四句话,以及按风险分级的复核强度。

开发工作流测试与质量AI 编程实践