跳到主要内容

凌晨 3 点处理一个没有报 500 错误的 AI 功能报警

· 阅读需 13 分钟
Tian Pan
Software Engineer

传呼机在凌晨 3:02 响起。你眯起眼睛盯着手机,预料着那些常见故障:数据库故障转移、CDN 边缘节点失联,或者是某个八个月没人碰过的服务出现了 500 报错峰值。然而,警报显示的是:summarizer.eval-on-traffic.helpfulness rolling-1h: 4.21 → 4.05 (Δ -0.16)。没有 HTTP 错误。没有延迟峰值。没有服务宕机。系统在过去一小时内处理的每一个请求都返回了 200,并且响应体解析正常。然而,情况显然比午夜时分变糟了,而值班轮换要求你查明原因。

这种值班任务是标准的运维手册中从未提及的。出故障的东西并没有“坏掉”——它只是退化(regress)了。你多年来追踪的错误预算是以可用性和延迟来衡量的,而触发此次报警的故障模式在两者中都不可见。报警是真实的,客户受到的影响也是真实的,而你通常的诊断循环——检查部署日志、检查依赖图、查找错误的发布版本、执行回滚——在你意识到那个“错误的发布”可能只是昨天下午 4 点上线的一个 30 行系统提示词(system-prompt)的修改时,便碰了壁。在代码审查中,那次修改看起来完全无害。

当一半的故障模式都不会触发现有警报时,值班的形式就发生了变化。解决这个问题的团队会在 SRE 能力之外培养出第二种能力——评测驱动的事件响应(eval-driven incident response)。而那些没能做到的团队,则只能在两周后通过 Twitter 上的吐槽或客户流失报告才发现质量下降。从业者目前的报告显示,从隐性质量退化开始到第一个升级为投诉的用户反馈之间,存在 14 到 18 天的滞后。这不只是报警系统的问题,而是缺少了一类报警。

为什么在没有 500 报错的情况下触发了报警

传统的报警系统监控的是 AI 功能错误表面的错误半部分。一个由大语言模型(LLM)驱动的接口可以返回 HTTP 200 以及结构有效的响应,但其内容仍然可能是错误的、语气不当的、无根据的(ungrounded)、拒绝了不该拒绝的请求、答应了不该答应的要求、引用了不存在的来源,或者以极高的自信产生幻觉(hallucinating)。这些故障模式都不会出现在 p95 延迟或错误率中。它们体现在用户行为上——重试、编辑、放弃会话、点踩(thumbs-down),以及针对实际生产流量运行的评测信号中。

这第二类信号——流量评测(eval-on-traffic)——正是触发你传呼机的原因。系统会对实时请求的一个采样子集持续由裁判模型(judge model)或确定性检查器进行评分,滚动分数被视为一级指标,该分数的退化会像 CPU 飙升一样触发升级流程。这种机制现在已经足够成熟,大多数 LLM 观测平台都默认支持这种模式。而将该分数与 SLO(例如:“在 24 小时窗口内,99% 的总结响应在‘有用性’上的得分必须 ≥ 4.0”)挂钩,正是将基于感觉的“今天模型感觉变差了”转化为具有消耗率(burn rate)的报警条件的逻辑所在。

关键属性在于这一信号具有领先性。当质量下降时,点踩、重试率和会话放弃率最终都会发生变化,但那往往是几天后的事了,因为需要有足够多的用户遇到退化并费力表达不满。到那时,你的流失用户群已经形成了。而流量评测在几分钟内就会发生波动,因为裁判模型运行在用户刚刚看到的同一批流量上。

新的诊断循环

针对“服务宕机”的运维手册已有数十年的历史,且已形成条件反射。而针对“服务隐性退化”的运维手册则完全不同,大多数在生产环境中发布 AI 功能的值班团队仍处于摸索阶段。从一开始,诊断问题的方式就不同了。

第一个问题不再只是对照部署图询问“过去 24 小时内变更了什么”。问题依旧,但覆盖范围扩大了两倍:

  • 是否上线了模型迁移? 模型厂商的版本升级是最常见的隐性退化诱因——claude-X.Y → X.Z 看起来只是版本号的变动,但其行为表现却像是逻辑上的差异。正是由于这个原因,许多团队会锁定模型 ID,并将迁移操作置于评测套件的门控之下。
  • 是否合并了提示词变更? 从任何实质性的定义来看,系统提示词和少样本示例(few-shot examples)都是代码,但它们通常通过与服务代码不同的路径发布,有时发布者甚至不在值班序列中。值班人员必须知道去检查提示词仓库,而不仅仅是服务仓库。
  • 工具、检索器(retriever)或知识源是否发生了变化? 索引重建、向量模型(embedding model)更换以及分块(chunking)策略调整,在下游都会表现为质量退化,而在上游看起来却只是基础设施工作。
  • 裁判模型本身是否发生了漂移? 这是最棘手的一种。裁判提示词也是提示词;裁判模型也可能迁移;裁判模型的校准集可能会过时。有时“退化”最后被证明是裁判模型在略有不同的标准上重新设定了基准。每月针对裁判模型运行校准集的团队能发现这一点;而不这样做的团队每个季度都要追逐两三次“幽灵退化”。
  • 输入分布是否发生了偏移? 营销活动、合作伙伴集成或局部地区的推行,都可能在不改变任何代码的情况下改变请求的形式。模型并没有变差,它只是在处理更多它原本就处于弱势的分布部分。

这个循环的产出是一个假设,而不是一个修复方案。修复通常包括以下三点之一:回滚提示词或模型版本锁定、缩小推送范围,或者接受退化并针对评测套件提交一个 Issue——这个评测套件本应在功能上线前就捕捉到这种退化。对于习惯了“回滚并进行无责复盘(blameless-postmortem)”的工程师来说,第三种做法可能感觉不够痛快,但它确实是正确的——对于软性退化,大多数“修复”其实是评测指标的改进,而非代码补丁。

设计告警,确保你真的能收到传呼

最难的设计问题不是告警本身,而是阈值。Eval 分数的噪声比 CPU 指标大得多。一个简单的“当 helpfulness 下降 5% 时告警”会在一个流量较小的周二晚上,因为样本量噪声而在半夜把你吵醒。而一个简单的“当 helpfulness 低于 4.0 持续一小时则告警”则会完全忽略渐进式的漂移,因为滚动平均值可能永远停留在 4.05。能经受住实际值班考验的模式具有以下几个特征:

  • 分数按功能、用户画像和请求形态进行分桶。 单一的“整体质量”数字会抹平太多的细节;“长文本、多语言、重检索”切片的性能退化会被占多数的“短文本、英语、无工具”请求所掩盖。将评估按这些维度进行切片的团队,能在全局指标变动前数小时发现退化。
  • 燃尽率告警(Burn-rate alerts)优于阈值告警。 SRE 风格的多窗口、多燃尽率告警(如 1 小时快燃尽,6 小时或 24 小时慢燃尽)比固定阈值更适合流量实时评估。持续一小时的 4 分下降与持续一天的 0.5 分下降是不同的事件,它们需要不同的操作手册(Runbook)。
  • 关键评估是确定性的;昂贵的评估是抽样的。 任何属于硬性政策底线的内容——拒绝不安全内容、必须包含引用、Schema 合规性——都应该针对每个请求运行,因为遗漏的代价很高,而运行正则表达式的成本几乎为零。对于每次调用都需要真金白银的裁判模型(Judge-model)评估,则应按预算允许的比例进行抽样,通常在稳定状态下为 1–10%,在疑似故障期间为 100%。
  • 裁判模型(Judge)拥有自己的监控。 裁判模型的平均分、拒绝率以及针对预留集的校准漂移都是需要追踪和告警的指标。如果裁判模型变得古怪,它产生的告警也会变得古怪,你需要在根据告警采取行动之前了解这一点。
  • 用户行为信号是佐证,而不是主导。 点踩率(Thumbs-down rates)、重试率和用户修改的编辑距离都应该出现在仪表盘中,但它们是用来确认流量评估中的退化是否对真实世界产生影响的,而不是主要触发源。如果将它们作为主要触发源,就会重蹈导致整个评估体系建立的那种 14–18 天滞后的覆辙。

值班轮班(On-call rotation)需要学习的内容

大多数轮班团队由 SRE 和后端工程师组成,他们通过处理系统可用性、延迟、容量和配置漂移等失效模式来磨练技能。这些人参与轮班没有错,但轮班所需的技能组合已经扩大了。忽视这一点的团队最终会形成两级值班制:SRE 处理 500 错误,AI 工程师处理软退化(Soft regressions),而实际上属于两者混合的告警则需要两倍的排查时间,因为它在两个互不理解对方思维模型的轮班组之间反复横跳。

一个能够端到端处理 AI 功能的轮班组拥有一套共享的知识体系,无论成员是从 SRE 还是 ML 领域进入的。他们知道 Prompt 存放在哪里以及如何对比差异(diff)。他们知道哪些模型 ID 是固定的,哪些是浮动的。他们知道如何查询过去 24 小时内按切片划分的流量评估分数存储。他们知道哪些评估是持续运行的,哪些是受抽样控制的。他们知道如何将 Prompt 变更作为一等公民(First-class)进行回滚,而不是作为需要在凌晨 3:02 找人审核的代码样式 PR。他们足够了解裁判模型的校准历史,足以将“裁判模型正在发生漂移”作为一个值得调查的假设。

交接文档(Handoff documentation)也必须随之扩展。一份只列举 500 错误和容量事件的每周值班摘要无法告诉下一班次他们需要知道的信息。真正有帮助的摘要应涵盖已上线的 Prompt 差异、进行中的模型迁移、当前标记为临界状态的评估切片、发生漂移的裁判模型校准运行,以及那些修复方案是评估改进而非代码补丁的待处理隐性退化事件。如果没有这些,每一班次在面对变化速度超过其吸收能力的系统时,都会感到无从下手。

复盘(Postmortem)也大不相同

一旦确认了告警并控制了退化,针对软退化事件的复盘会提出与 500 错误不同的一系列问题。“为什么会失败”很少是因为“这段代码写错了”;更多情况下是“没有评估案例捕捉到这种失效模式,我们的裁判模型对漂移不敏感,且 Prompt 变更评审员没有办法预测下游影响”。行动项通常倾向于评估优化、裁判模型校准运行、Prompt 评审工具以及发布策略的变更。代码更改反而是少数情况。

最健康的模式是将每一个软退化事件都作为新评估用例的自动来源。导致告警的精确请求形态、得分较低的精确模型输出以及发生漂移的分布切片,都会变成测试用例(Test fixtures)。下次提议变更 Prompt 时,针对其运行的评估套件将包含曾经在今晚把你吵醒的那种失效模式。这就是成熟的 ML 团队多年来针对离线数据运行的飞轮;现在的进步在于将其直接挂载到值班告警流水线上,使得生产环境的信号能反馈到准入信号中,而无需人工干预。

建立了这种闭环的团队,其值班轮班会随着时间的推移变得越来越安静,因为每一次事件都为下一次部署永久接种了针对该失效模式的疫苗。而没能建立这种机制的团队,最终会每隔六周就因为同样形态的退化而传呼同一位工程师,只是每次的 Prompt 差异略有不同,最终那位工程师会离职,而关于哪些 Prompt 针对哪些失效模式进行了微调的组织知识也会随之流失。

一种新的值班纪律,而非一种新工具

每当出现一种新的报警类别时,人们往往倾向于购买一款处理该报警的工具,并认为问题已经解决。流量评估(eval-on-traffic)平台确实存在且很有用,你也应该选择一个,但更艰巨的工作在于轮值纪律:运维手册(runbook)的更新、消耗率(burn-rate)阈值、裁判校准频率、提示词差异(prompt-diff)评审流程、复盘报告格式,以及明确将 AI 质量回退视为与可用性故障同等级别的一级事件。

当凌晨 3 点因为“帮助度”下降了 4 个点而触发报警时,这种区别就从哲学层面转变为操作层面了。做了功课的团队会响应报警,打开列有正确诊断循环的运维手册,并在 30 分钟内找到提示词差异、模型版本锁定问题或索引重建问题。没做准备的团队会响应报警,盯着它看,然后将其升级给三周前编写提示词的人,并在早上发现报警是正确的,回退是真实存在的,而根本原因就写在一段值班人员在凌晨 3 点根本无法解析的 PR 描述中。传呼机没变,改变的是值班本身。

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