跳到主要内容

AI 功能观察期:为什么两周的灰度发布会错过真正关键的问题

· 阅读需 14 分钟
Tian Pan
Software Engineer

为期两周的金丝雀发布(canary)是那种听起来足够自律,以至于让人可以跳过更难问题的实践之一。工程团队从微服务中引入了它——逐步放量 1% 几天,观察错误率,放量到 100%,宣布完成——并将它嫁接到 AI 功能上,却没问过 AI 特有的失效模式是否会在两周内显现。它们不会。扼杀该功能的账单在第六周才寄到。暴露出长尾意图的客户群体在第五周才开始使用。上线当天评分提升 3% 的评估偏移(eval drift)在第四周开始产生真金白银的损失,因为新 prompt 产生的更冗长的输出一直在累积 Token 开销,而由于仪表盘只盯着崩溃,没人注意到这一点。

一个围绕 p95 延迟和 HTTP 500 错误构建的金丝雀发布会告诉你 LLM 运行正常。它不会告诉你该功能是否有效。AI 功能失效的形式是部署仪式从未设计去捕捉的——用户行为的缓慢变化、缓存的逐渐侵蚀、检索质量的崩溃、拒绝率的攀升、以及走向错误的成本轨迹——而且几乎所有这些都需要两周以上的时间才能显现。按微服务时钟发版的团队,其发版节奏与失效发生的节奏并不匹配。

第二周后才出现的失效模式

大多数毁灭 AI 功能的失效模式都是缓慢的。它们不是崩溃,而是趋势。监控请求级指标的金丝雀发布是为快速、剧烈的故障而设计的。AI 的失效则是侧面袭来的。

第一个缓慢的失效是长尾意图。你上线时所针对的评估集是根据你已知的意图构建的。几周后,一个客户群体开始使用,将该功能推向了你从未测试过的领域——比如,一个在薪酬问题上得分 99% 的 HR 聊天机器人,开始被大量询问关于新公布的股权计划的问题,并开始对归属期(vesting cliffs)产生幻觉。该功能在发布时并没有损坏。只是发布时的评估集不能代表该功能现在所处的世界。对于每天查询量超过 10,000 次的高流量系统,第一个显著的偏移信号往往出现在第二到四周。对于中等流量系统,则需要六到八周。为期两周的金丝雀发布捕获不到任何中等流量的信号,也只能捕获到高流量系统的最早信号。

第二个缓慢的失效是缓存命中率侵蚀。Prompt 缓存(Prompt caching)让上线初期的经济效益看起来非常棒——第一张发票金额低于预算,财务很满意,功能看起来有利可图。然后,有人在 prompt 中间添加了一个有用的细节来修复质量问题,缓存命中率在一周内下降了 40%。每个请求的成本在没人注意的情况下攀升,因为它仍然小于总支出趋势,而趋势本身看起来像是使用量的增长。三个月后,财务询问为什么 LLM 支出趋势不对劲,团队才发现第三周的一次 prompt 修改是罪魁祸首。这个信号一直显示在某个没人关注的指标上:将缓存命中率作为一等公民的量化指标,而不是一个汇总成本。

第三个缓慢的失效是 Token 开销累加。一个在评估中得分 +3% 的 prompt 更改,平均增加了 200 个 Token 的输出。在上线当天,成本增量是隐形的——流量小,群体小。到第四周,流量翻了三倍,每个请求增加的成本已累积成每月五位数的差额。最糟糕的是:评估集没有衡量输出长度。团队优化了一个不具备帕累托感知(pareto-aware)的质量指标,而成本退化在质量提升的掩盖下隐藏了一个月。现在一些团队会将输出长度分布与延迟和拒绝率一起作为默认的金丝雀信号进行跟踪,但大多数团队仍然没有这样做。

第四个缓慢的失效是群体与时间的交互。你的季节性流量模式不会压缩到两周内。运行季度末报告的 B2B 客户直到季度结束才会使用。在开学季使用的零售产品在 8 月下旬才会迎来高峰。任何窄于该功能相关最长季节性周期的金丝雀窗口,都是在进行一场团队通常没有意识到的赌博。

观察窗口 vs. 金丝雀窗口 —— 它们是两码事

正确的思维模型是将它们分开。金丝雀窗口(canary window)是当你防范急性失效时——模型返回乱码、网关损坏、功能超出了延迟预算。两周的时间足够了。观察窗口(soak window)是当你防范缓慢失效时——偏移、成本累加、群体时间效应、缓存侵蚀。这个窗口需要以月而不是周来衡量。

一个合理的架构:

  • 金丝雀 (Canary):1% 到 25%,持续 1-2 周,自动回滚与急性阈值绑定(延迟 p99、拒绝率、单次请求成本硬上限、结构化输出的解析错误率)。目标:捕捉显而易见的问题。
  • 观察 (Soak):25% 到 100%,然后在全量流量下进行 4-8 周的观察期,之后再宣布发布成功。目标:发现缓慢发生的问题。在此期间,之前的版本保持热备并具备回滚资格。
  • 退出 (Exit):一份观察退出清单,明确在团队被允许标记发布完成之前,必须观察到的特定群体和时间周期。

命名很重要,因为它改变了领导层对“完成”的理解。如果金丝雀结束就意味着发布“完成”,那么团队就被要求在系统尚未显现失效之前对其进行认证。明确重新命名金丝雀之后的时期——观察期、监测期、发布后期——能让团队获得许可,在失效模式需要的时间内保持回滚路径畅通,并维持高频的评估。

Soak Window 关注的指标与 Canary 的不同之处

Canary 仪表盘擅长处理“现在时”——即当下什么坏了。而 Soak 仪表盘必须擅长处理“过去未完成时”——即什么正在变差,以及恶化的速度如何。这两者的指标集是截然不同的。

你应该追踪单次任务成本(Cost-per-task),而不仅仅是总成本。总成本随流量增长,无法说明任何问题。按意图类别或功能面拆分的单次任务成本,才能捕捉到提示词冗余(Chattier-prompt)回归和重试风暴(Retry-storm)回归。在 Soak Window 期间,单次任务成本的 p50 和 p95 周曲线能向你展示趋势;而月度账单则做不到这一点。

将缓存命中率作为核心指标(Top-line metric)。它不是一个调试统计数据,而是一个拥有自身 SLO 并在低于阈值时触发 SEV(服务事件)的指标。命中率是提示词修改破坏前缀结构的先行指标,它能在成本飙升之前暴露回归问题。

审核员队列深度和人机协作(Human-in-the-loop)的人力成本。如果 AI 功能的部分价值主张是减少人工审核,那么审核员队列就是该功能成功指标的一部分。每周增加 8% 的人工升级漂移在延迟仪表盘上是不可见的,但对单位经济效益(Unit economics)却是毁灭性的。从 Soak 的第一天起就开始追踪它。

结构化输出的输出形态漂移(Output-shape drift)。JSON 解析错误率、Schema 符合率、字段存在率。这些都是无声的杀手。模型更新或上游供应商的量化调整可能会改变有效但不同的输出分布,这种改变不会触发严格解析限制,但会导致下游消费者行为异常。每周与冻结的参考分布进行对比可以捕捉到这一点;而单次请求的错误率则不行。

拒绝率悄然攀升(Refusal-rate creep)。拒绝行为往往会发生偏移。一个经过安全调优的模型向前滚动,拒绝率每周上升 0.5%,八周后,该功能就会拒绝相当一部分合法请求。周与周之间的增量处于噪声之下,但八周的轨迹却清晰可见。

如果涉及 RAG,还需关注检索质量。Top-k 的嵌入检索相关性是一个随着语料库增长和查询分布偏移而衰减的指标。发布当日的检索质量并非稳态下的检索质量,两者之间的差距就是 Soak Window 需要观察的内容。

评估集更新频率是 Soak 的一部分

一个在发布时冻结的静态评估集(Eval set),从 Soak 开始的那一刻起就在腐烂。真实用户的探索会形成评估集作者从未想过的模式,而这些新模式才是最重要的。如果 Soak 的通过率是根据发布时的评估集来衡量的,那么团队就是在根据一个不再反映现实世界的基准为自己打分。

原则是在 Soak 期间以每周一次的频率更新评估集。抽取生产流量样本,进行标注(通过人工或经过抽检的、校准良好的 LLM-as-judge),并将其加入评估集。然后每周针对最新的评估集重新设定通过率基准。静态评估集所隐藏的缓慢回归——因为静态评估集不包含新的意图类别——在评估集更新包含它时会立即显现。

如果流水线已经存在,在 Soak 期间每周更新评估集的成本是很低的。不这样做的代价是:发布指标看起来很稳定,但面向用户的质量却在漂移,团队只有在支持票据堆积如山时才会察觉。票据是评估集陈旧问题的滞后指标。

在整个 Soak 期间保持回滚路径畅通

团队最常犯的错误是过早地注销旧版本。Canary 结束了,新版本全量上线,于是有人提交了一个清理任务,删除了旧的提示词模板、旧的检索索引和旧的端点。到了第三周,回滚选项消失了。到了第六周,当成本或质量问题真正显现时,如果不重新构建,就没有什么可以回滚的了。

微服务中的蓝绿部署模式可以完美迁移到这里,但有一个变化:在整个 Soak Window 期间(而不仅仅是 Canary 期间)保持前一个版本的就绪和验证状态。定期向其发送影子流量(Shadow traffic),以便你知道它在当前的语料库和当前的上游 API 下仍然有效。一个没有针对当前数据模式进行验证的回滚选项只是摆设;当你盯着新版本时,旧的提示词或检索器可能已经失效了。

保留规则必须是明确的,因为部署仪式感的引力会倾向于清理工作。“在 Soak 结束前不得废弃旧版本”需要成为一项书面政策,以抵御开发者在发布后自然产生的清理欲望。在 Soak 运行期间,请将旧版本视为生产基础设施的一部分。

击败 Soak Window 的组织压力

长期 Soak 的技术理由显而易见,但组织层面的推动却很困难。领导层想要宣布发布,市场部想要公关稿,团队想要释放认知负荷并开始下一项工作。两周时间可以很自然地塞进一个 Sprint,但八周则不行。

组织压力体现在语言上。“我们完成了吗?”这类问题会在两周时被提出。而“我们还在 Soak 期间吗?”在第六周时则无人问津,因为没有人为长达八周的任务准备话术。解决方法是将 Soak 作为项目计划中的一等公民:命名、追踪,并设定其自身的退出标准和仪表盘。一个只存在于团队脑海中的 Soak,会在领导层停止询问的那一刻被宣告结束。

另一个组织层面的解决方法是将 Soak 指标视为发布叙事的一部分,而不是独立的发布后工作。发布公告应标明 Soak Window。发布回顾发生在 Soak 退出时,而不是全量上线时。团队的激励结构应奖励对 Soak 纪律的遵守,而不仅仅是全量上线的速度。否则,下一个模仿该方案的团队会只学 Canary 而跳过 Soak——因为 Canary 才是可见的部分。

一个值得借鉴的观察期退出检查清单

书面化的退出标准能将“观察期结束是因为日历到了”转变为“观察期结束是因为失效模式已有足够的时间浮现”。一个合理的 AI 功能观察期检查清单包括:

  • 该功能旨在服务的所有季节性客群已在全量流量下观察了至少一个完整的周期(视情况而定,如每周、每月、每季度)。
  • 过去四周的单次任务成本 (Cost-per-task) 趋势持平或下降;如果上升,则必须查明原因并预测趋势。
  • 缓存命中率已连续三周稳定在 ±10% 以内。
  • 评估通过率 (Eval pass-rate) 每周都会重新建立基准,且针对最新评估集的通过率等于或高于发布基准。
  • 审核队列深度和升级率 (escalation rate) 保持在发布前基准的 10% 以内,或者其偏差是预期的并已计入预算。
  • 在观察窗口期内,拒绝率 (Refusal rate) 的偏移未超过 0.5 个百分点。
  • 对于结构化输出功能:JSON 解析错误率和 Schema 一致性率在容差范围内。
  • 旧版本仍然保持预热状态,最近进行了影子验证 (shadow-validated),并拥有经过测试的回滚方案 (rollback runbook)。
  • 已创建旧版本的清理任务单,计划在观察期结束之后而非之前执行。

清单本身不是观察期——观察才是。这份清单的作用是防止来自领导层的压力将“日历翻到了两周”转化为“发布已完成”。

这对部署仪式意味着什么

直白地说:AI 功能的发布需要一个与其失效模式的时间尺度相匹配的部署仪式,而不是代码部署的时间尺度。代码部署可以在一个 Sprint 内宣告完成,因为代码故障浮现很快且表现为报错。而 AI 功能的故障浮现缓慢且表现为某种趋势。混淆这两种仪式的团队实际上是在错误的时钟频率下交付产品。

4 到 8 周的观察期并不是对开发速度的征税。它是“成功的发布”与“六周后因账单暴涨、新客群上线或评估漂移累积超过阈值而被迫回滚的发布”之间的区别。大多数团队都会以一起本可避免的事故为代价交一次这种“税”,然后才开始建立观察期制度。而那些先行建立观察期的团队,则可以直接跳过这一课。

References:Let's stay in touch and Follow me for more thoughts and updates