跳到主要内容

AI 事故复盘中的“责任消失”难题

· 阅读需 10 分钟
Tian Pan
Software Engineer

当确定性系统崩溃时,你会找到 bug。堆栈跟踪指向某一行代码。代码差异(diff)显示了更改。回顾起来,修复方案显而易见。但 AI 系统并非如此。

当一个由大语言模型(LLM)驱动的功能开始输出更差的结果时,你寻找的不是 bug。你面对的是一个发生偏移的概率分布,它存在于一系列组件构成的堆栈中,而每个组件都引入了各自的方差。是模型的问题吗?是供应商在某个周二进行的无声更新?是架构变更后未刷新的检索索引?是某人为了修复另一个问题而修改的系统提示词(system prompt)?还是三个冲刺(sprint)前就停止捕获回归的评估系统(eval)?

复盘会议变成了责任拍卖会。每个人都出价“模型变了”,因为这是一个无法证伪且无需成本的借口。

这就是“消失的责任”问题:在 AI 系统中,问责在各个层级间的消解速度比事故响应人员追踪它的速度还要快。修复的方法不是更好地分配责任,而是一种结构化的归因方法论,它会提出一个精确的问题——哪一层先坏掉的——并在排除其他所有可能性之前,拒绝接受“模型问题”作为答案。

为什么 AI 事故与众不同

传统软件事故具有工程师们认为理所当然的一个属性:确定性复现。你可以重放完全相同的请求,得到完全相同的失败,并以此定位原因。系统是一个函数,而函数是有逆运算的。

从这个意义上说,AI 系统不是函数。通过相同堆栈路由的相同输入,在不同的运行、供应商、区域和硬件中可能会产生不同的输出。上周正确的响应在今天可能是错误的,即使你的代码库没有任何变化——因为供应商实际提供的模型可能已被悄悄更新,或者你的检索索引已与其真相源脱节,又或者在库升级增加了聊天历史填充后,以前能容纳系统提示词的上下文窗口现在容纳不下了。

一项针对主要 LLM 供应商的近 800 起事故的研究发现,故障模式因供应商架构的不同而有根本差异——有些表现为级联的多服务故障,有些则将损害限制在单个组件内。它们的共同点是依赖自我报告的事故数据,这意味着归因的质量取决于诊断团队的水平。而大多数团队并没有进行严谨的诊断。

结果是复盘中出现了一种特定的失败模式:时间线塌缩为“输出质量下降,我们调整了提示词,情况好转了”。其机制从未被隔离。预防措施含糊不清。同样类型的事故在六个月后会以不同的外表再次发生。

可以追究责任的四个层级

在归因之前,你需要一张地图。AI 系统在四个截然不同的架构层级上发生故障,每一层的故障模式都大不相同,需要不同的诊断证据。

模型层是幻觉、指令漂移和校准偏移发生的地方。模型是一个黑盒,但其行为并非如此。这一层的变化产生的错误输出往往具有一致性——模型在许多不同的输入中都会可靠地犯同样的新错误,通常与模型版本相关。

检索与上下文层涵盖了检索内容、排序方式以及时效性方面的故障。一个自信地引用过时法规的合规助手并不是在产生幻觉——它是在准确地转述一份陈旧文档,而该文档在三周前最后一次刷新的向量索引搜索中排名第一。模型完成了它的工作,失败的是检索层。

提示词与策略层是团队经常低估漂移的地方。提示词是与特定模型版本签订的行为契约。当底层模型发生变化时,即使文字没变,契约也已失效。静默的提示词编辑——即那些因为存在于配置文件或数据库行中而未经过代码审查的编辑——是导致被归咎于模型的质量回归最常见的原因之一。

编排与工具层是多步骤故障复合的地方。返回陈旧架构的工具调用、在中途开始返回 429 错误(请求过多)的 API、将单次不稳定的结果转化为对同一陈旧数据进行五次执行的重试循环——这些故障向上传播并表现为错误的输出。当用户看到错误的响应时,原始的工具故障早已被掩埋在没人关注的调用堆栈中。

在所有这些层之下的是基础设施——来自 GPU 架构和供应商区域之间浮点运算差异的硬件级非确定性。这一层几乎从不是根本原因,但会放大上方各层的方差。

归因协议

责任消失问题有一个可行的解决方案:对一切进行版本化,对每一跳进行插桩,并在穷尽逐层检查清单之前,拒绝关闭事故。

第一步是版本锁定。 进入系统的每个请求都应携带与其接触的每个层级的版本标识符:模型版本、提示词版本、检索索引版本、工具架构(schema)版本和策略版本。这听起来像是额外开销,直到你陷入事故处理 45 分钟后,唯一重要的问题是“模型版本在昨天和今天之间是否发生了变化?”如果你不能在 30 秒内回答这个问题,说明你没有进行版本锁定。

第二步是逐跳追踪。 为请求生命周期中的每个组件发出一个追踪跨度(trace span):检索查询和结果、提示词组装(实际进入上下文窗口的内容)、模型调用和原始响应、每次工具调用及其结果、输出解析。这些跨度应携带第一步中的版本标识符。没有这些,你只能靠猜测来诊断。有了它,你可以回放任何历史请求,并准确查看系统在每个层级看到的内容。

第三步是带覆盖的追踪回放。 一旦捕获到失败的请求,你应该能够通过受控替换来重新运行它:更换模型版本、更换提示词版本、更换检索快照。这会将事后分析的假设(“我认为模型变了”)转变为实验。如果在覆盖模型版本后失败消失,则说明模型层有问题。如果更换模型版本后问题依然存在,但在更换检索快照后消失,则说明检索层有问题。

第四步是归因检查清单。 在事后分析中指责任何层级之前,必须明确排除相反的假设。在事故发生前的 24 小时内,检索索引是否发生了变化?使用冻结的检索快照是否可以复现失败?在同一时间窗口内,是否有任何提示词或策略发生了变化?当提示词版本锁定到上周的版本时,失败是否消失?只有在这些问题有了答案之后,“模型行为改变”才能成为一个可采信的调查结果,而不是一个默认的借口。

你到底需要什么样的插桩

从未调试过非确定性生产事故的团队,往往会在应用层过度插桩,而在 AI 层插桩不足。他们拥有请求计数和错误率,但没有能够进行层级归因的带版本逐跳追踪。

最小可行 AI 可观测性栈如下:每次模型调用都要记录发送的确切提示词(不是模板,而是完整组装的字符串)、包含版本的模型标识符、任何后处理之前的原始补全结果以及延迟。每次检索调用都要记录查询、返回的前 k 个文档及其得分以及索引版本。每次工具调用都要记录输入、输出和架构版本。

这些数据并不一定要昂贵。对于高流量系统,进行 1–5% 的采样就足以诊断大多数事故,同时保持存储成本在可控范围内。关键要求是,当事故发生时,你可以提取失败请求的代表性样本,并为每个请求提供完整的追踪。

第二个要求是带有版本标签的基准。在你断言“质量下降”之前,你需要定义事故发生前质量是什么样的。那些只在出问题后才开始测量的团队是在搞考古,而不是工程。每周针对生产样本运行你的评估套件(eval suite),用上述所有版本标识符标记结果并存储它们。基准的存在让变化变得清晰可见。

AI 系统的无责事后分析

传统的无责事后分析(blameless post-mortem)通过将过错从人转向系统来发挥作用。其目标是找到导致人为错误的系统条件,而不是惩罚个人。AI 事后分析需要一个额外的转向:从模型转向系统状况所在的特定技术栈层级。

一份写得好的 AI 事故事后分析包括:

  • 事故发生时各层级的确切版本状态,并与 48 小时前的版本状态进行对比
  • 一个复现追踪:一个展示失败的捕获请求,以及哪个层级覆盖能解决问题的证据
  • 明确说明哪个层级是直接原因(proximate cause),哪些层级是促成因素
  • “为什么检测没能发现它”章节,用于识别缺失的覆盖范围——例如该查询类型没有评估、该指标没有告警、该组件没有版本跟踪

“为什么检测没能发现它”这个问题始终是事后分析中最有价值的部分。它揭示的是结构性缺陷,而不仅仅是偶然的失败。如果答案是“我们对这种查询分布没有覆盖”,那么预防措施就是增加覆盖。如果答案是“检索索引没有版本控制,所以我们无法判断它何时发生了变化”,那么预防措施就是增加版本控制。这些都是可执行的。而“模型发生了意外变化”则不是。

长期收益

实施分层归因的团队不仅能更出色地进行事后分析,还能构建更强大的系统。当你不得不回答“是哪一层导致了这个问题”时,你就会开始将每一层都设计为可归因的。提示词(Prompts)实现了版本控制;检索索引记录了变更日志;模型升级则针对行为快照进行金丝雀部署。归因行为催生了监测手段(instrumentation),从而明确了责任,并最终完成了闭环。

“责任消失”问题并非源于谁愿意担责的文化,而是一个结构性问题——即你的系统是否能够提供定位责任所需的证据。以目前的方式构建的大多数 AI 系统都无法做到这一点。事后分析以“模型变了”草草收场,并非因为你的团队懒惰,而是因为你的技术栈在设计上就无法推翻这一结论。

请在下一次事故发生之前,而非发生之时,就将证明能力内置到技术栈中。

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