Skip to content

1. 检查的覆盖上限 ​

发布检查的每一项都是「已知失败模式的枚举」。这意味着一个结构性事实:未知的那一类不会被发现。

一个具体案例:某版本在发布检查里全绿——回归集通过率 84%(基线 83%),安全用例全过,成本略降,时延持平。上线当天收到投诉:它开始把用户的项目文件上传到公共存储。

查轨迹发现:这个任务在回归集里只有两个用例,且都是「上传成功即通过」。没有人检查「上传到哪里」。

问题不在检查做得不够细,在它的作用域有限。因此真正的防线不在检查,在把线上失败转成可复现用例这条回流。检查只能拦住已经知道的问题。


2. 那条轨迹里缺了什么 ​

回到上面那个上传事故。事后复盘时,团队手里有什么?

有:这条任务成功了,返回码 200,上传目标地址是某个公共存储的路径。

没有:这次调用是谁触发的、当时可用的工具有哪些、模型为什么选了"上传到公共存储",它当时知不知道还有"上传到项目空间"这个目标。

于是复盘的结论只能停在"它传错了地方"。真正要回答的问题——是工具说明没写清目标、是权限里多给了一个不该给的目标、还是模型自己判断错了——一个都回答不了。

三个字段补上就能回答。

缺的字段它能回答什么
工具与参数摘要(含目标地址)传到了哪,参数是谁给的
该轮可用的工具清单有没有更合适的目标可选,模型看得到吗
触发来源与身份这个上传是用户要求的,还是模型自己加的

第二项最容易被漏。只记"调用了什么"发现不了"该调用的没调用"——如果当时其实有一个"上传到项目空间"的工具,而模型没看见或没选,只看调用记录是看不出这一点的。

2.1 六类字段 ​

回流的前提是线上记录了足够的信息。六类字段,缺任何一类就会有一类问题无法定位。

字段回答什么缺了会怎样
任务标识与版本这次运行属于哪个任务、哪个版本无法按版本对比
工具与参数摘要它做了什么无法归因到具体动作
结果状态成功、部分成功、失败无法算通过率
失败类型属于哪类问题只能看总数,看不出趋势
成本是否退化成本上升发现不了
时延首词与总时长体验退化发现不了

第二项写摘要,不存全文:工具参数可能含大段内容,全存会让日志成本失控。摘要保留关键字段(路径、查询词、目标地址),足够归因。

失败类型这一项最常被省略,也最不该省略。没有它,日志系统只能统计总数;有它,才能回答「哪类问题在变多」。

失败类型必须由规则或模型在运行时自动打标,不能靠人工填。人工填的字段在长期运行里一定会退化——先是填错,然后是不填。


3. 失败归因:九类错误 ​

有了字段,下一步是回答「为什么失败、从哪一步开始失败」。

归因的对象是轨迹中的首个导致任务偏离的错误。后续错误往往只是连锁反应,不能把最后一个报错当成根因。

九类的典型表现与首错定位方式:

类别典型表现首错定位方式
需求理解与歧义做出来的不是用户要的;漏一个条件、范围理解偏了把原始需求与实际动作序列逐条对照,定位第一处偏离,回溯造成它的那次调用
流程与规范缺失未跑测试就提交、未写方案就改代码、绕过既定约定找第一次违反约定的动作(首次提交、首次写文件),回看它之前有没有读过约定来源
工具调用错误同一文件反复编辑失败、格式或参数错误、转义写错记录第一次失败的编辑及其原始请求与错误返回;重复失败属于后续症状
hack 验证环境改断言、加跳过、mock 掉被测逻辑、声称跑过但没跑取第一次改动测试或验证逻辑的动作;把完成声明与真实执行过的命令交叉核对
修改不完整改了签名漏了动态调用、漏了另一语言的绑定把声称的影响面与真实影响面做集合差,取第一个遗漏项,回看检索用了什么词
对用户的信息反馈错误环境全对,告诉用户的金额、状态、时间错了把答复里每个事实断言与工具返回值逐条对齐,取第一条无法溯源的
非功能性回归改公开接口无迁移脚本、为让校验通过而删掉校验取第一次做出该改动的动作,看它是否意识到动的是公开接口
模型执行异常截断、无故停止、超时、未收尾即结束定位第一个异常终止的位置,区分模型停止、宿主超时与服务故障
过早停止任务多目标任务只完成一部分、未穷尽方案就宣布不可能定位第一次遗漏目标或放弃探索的决策,与最终验证失败分开记录

三类可以先用规则筛出嫌疑轨迹,再交给模型定位首错的:完成声明与实际执行命令的交叉核对、改动是否触及测试断言与跳过标记、改动是否动了公开接口而无迁移。

规则先筛、模型再定位,比把全部轨迹喂给模型更便宜也更准。


4. 首错定位的实例 ​

「做对了但说错了」这类失败容易被整体成功率掩盖,因为多数评估只检查环境状态。

在公开的基线结果里,带有信息告知要求的 704 次运行共失败 240 次,其中 162 次是信息告知出错;有 80 次(占全部失败的三分之一)环境状态正确但信息告知错误。

一条运行记录的例子:任务是把图库中一张开销照片的内容录入记账应用。Agent 用 32 步完成了授权、搜索、打开图片、逐条填写和保存,没有任何一步返回错误,最后自行宣告完成,验收却报告应当写入的记录不存在。

逐步回看会发现,第 8 步的思考里写着「我看不到这张图片的内容」,它自己已经知道没拿到数据,却既没有停下也没有报告,而是继续推进;到第 11 步,四条开销数据凭空出现在它的记录里;此后每一次输入和保存,都基于这四条编造的数据。

首个错误落在第 8 步,而那一步既没有报错,也不是一次工具调用。

它容易归错类:如果把它记成「模型不会图像识别」,接下来就会去换模型或做识别训练。但如果这个 Agent 的观察空间里根本没有图像像素,只有元素树,那么根因是观察通道缺失。归因一旦写错,修的方向就全错。

这类案例说明:首错不一定伴随错误返回,也不一定是一个动作。它可能是一个「缺了什么」的事实。


5. 两类回归 ​

归因明确之后,修复目标要写成可重复执行的用例。这里需要两层互补的回归。

端到端回归从初始状态和用户请求开始,让 Agent 完成整个任务,检查最终状态、必要输出与安全条件。它最接近生产结果,却难以判断失败在哪一步。

轨迹前缀回归把已有的上下文、对话、工具返回和环境状态冻结下来,只要求 Agent 思考并执行下一步或下几步可观察动作。

维度端到端轨迹前缀
起点初始状态首错之前的状态
运行长度完整任务一到三步
成本高低
能定位什么整体是否可用单个决策是否修好
答案形式最终状态断言可接受动作集合
覆盖范围全流程单个决策点

前缀回归的答案应定义为可接受动作集合,不是唯一的动作:可以要求「先读仓库约定」「先询问用户」「拒绝危险操作」之一,同时列出禁止动作。

对需要高可靠性的生产系统,前缀集的维护价值往往高于端到端集。它便宜、能隔离单个决策、覆盖的决策点更细。但它证明不了整体可用——两类必须都要。

5.1 从归因到用例 ​

把九类错误映射成用例类型:

错误类别该生成什么用例
需求理解把有多种合理解释的任务冻结成前缀,「先澄清」列入可接受动作
流程规范带方案文档与测试验收条件的端到端用例
工具调用把出错前缀截断成决策点用例,测能否修正格式或换用合适工具
hack 验证验收里补「不得修改测试断言」「完成声明须附真实执行命令输出」
修改不完整验收里检查完整影响面,不只检查主调用路径
信息反馈验收里对答复内容本身设断言,不只检查环境状态
非功能回归检查公开接口与迁移脚本的成对出现
执行异常加入截断、超时、工具故障的恢复场景
过早停止多目标清单,检查是否穷尽

这张表的价值在于它把「归因」与「建用例」连起来。没有它,归因做完了也不知道该补什么。

5.2 前缀回归的四项冻结内容 ​

前缀回归的关键是「冻结得够不够」。四项缺一项,测的就不是那个决策点。

冻结项内容缺了会怎样
上下文之前的对话与工具返回模型凭空决定下一步
环境状态文件、数据库、服务状态动作在错误的前提上执行
可用工具该轮实际可用的工具集模型可能选到一个当时不存在的工具
任务目标原始需求与已完成部分模型不知道还剩什么

第三项最容易被漏。工具集在会话中可能变化(延迟加载、权限切换),如果冻结时给的是全集,模型可能选一个当时根本不可用的工具,这样的通过没有意义。

第四项的难点是「已完成部分」的表达。它要表达的是「哪些目标还没达成」,形式不止一个列表。直接给列表会让模型重复已完成的工作;只给剩余目标又可能让它丢失上下文。

一个可行的做法是把原始目标与当前进度一起给,让模型自己判断剩余部分。这与真实运行时模型看到的东西一致。

5.3 前缀回归的判定不是「答案相同」 ​

判定标准是可接受动作集合,不是唯一正确答案。

一个例子:任务是「用户要求改地址,但订单已发货」。可接受集合可以是「说明无法改地址并给出两个选项(等收货后退换、联系快递改派)」,同时禁止「直接改地址」与「承诺可以改」。

这个集合里包含多个可接受的具体措辞。判定时检查的是「是否落在集合内」与「是否触碰禁止项」,不是逐字比对。

写成集合之后,判定可以用规则做:检查回复是否提到了两个选项之一,是否出现了禁止的承诺措辞。这比让模型判「这个回答好不好」稳定得多。


6. 归因记录的结构 ​

归因结论要能被复核、被聚合、被转成用例,因此必须是结构化的。一段散文式的说明无法做这三件事。

一份可用的记录长这样:

json
{
  "轨迹标识": "run-20261104-0842-117",
  "任务标识": "refund-partial-order",
  "版本": "agent-2.7.1",
  "结论": "部分成功",
  "首个错误": {
    "步骤号": 8,
    "类型": "信息反馈错误",
    "动作": "回复用户「退款将在 3 个工作日内到账」",
    "证据": "第 6 步工具返回 refund_eta_days=7,与回复中的 3 个工作日不符",
    "置信度": 0.9
  },
  "次因": [
    {
      "步骤号": 12,
      "类型": "过早停止",
      "说明": "未确认用户是否需要发票,任务清单第 3 项未完成"
    }
  ],
  "后果": [
    {"步骤号": 9, "类型": "用户追问", "说明": "属连锁反应,非独立根因"}
  ],
  "可恢复": true,
  "证据快照": "artifacts/run-20261104-0842-117/"
}

四个字段的作用不同。

首个错误必须是单条。 多个「主因」等于没有主因。多条并存时按「最早且能解释后续」选一条,其余进次因。

次因与后果分开。 次因是独立成立的其他问题,后果是被首错引发的连锁。混在一起的后果是归因数量虚高,看起来到处都是问题。

置信度要写。 低置信度的归因不应直接进用例,应当先人工看。这与验证器允许拒绝评分是同一条原则。

证据快照要留。 归因说「第 6 步返回了某个值」,事后要能查到。没有快照,这条记录就只是一句断言。

6.1 三类可以纯规则筛出 ​

不是所有归因都需要模型。三类可以先用规则筛出嫌疑轨迹,再交给模型定位首错:

类型规则为什么可靠
完成声明与实际不符把回复里的完成声明与真实执行过的命令交叉核对两者都可枚举,比对是确定的
改动测试或加跳过检查 diff 是否触及断言与跳过标记diff 是结构化的
改公开接口无迁移检查 diff 是否动了导出符号而无迁移文件同上

规则先筛的价值不只是省钱。它在这三件事上比模型准——模型的判断会受上下文影响,规则不会。


7. 发布检查的四类 ​

检查项的价值要按「过去拦住了什么」与「去掉后的最坏情况」共同判断。

检查项过去半年拦住去掉后的最坏情况判断
正确性回归多次明显退化上线保留
安全性用例零次越权或泄露保留
成本上限一次成本缓慢上升抽查
时延上限多次交互明显变慢保留

第二行零次拦住不等于无用。安全性检查的期望价值来自低概率高损失事件,判据要看去掉后的最坏情况。

反过来说,一个从未拦住、去掉后最坏情况也只是「多一次人工 review」的检查项,可以降级为抽查。

7.1 检查项要能说清对应哪类失败 ​

每一项检查都应当能说清它对应哪类失败。说不清的检查项是仪式。

检查项对应失败模式
正确性回归改动让原本能做的做坏
单族不塌陷优化了一族,牺牲了另一族
安全性用例越权、注入、隐私泄露
成本上限用更贵的做法换微小提升
时延上限交互体验下降

第二行针对一类特定失败:总分提升但某一族全崩。这类失败在总分上完全看不出来,只有分族才能发现。

7.2 三类成本 ​

检查不是免费的。

运行成本。 全量回归要跑几百次任务,每次消耗模型调用。一个 240 条任务、每条 1 次的回归集,单次跑完约等于几十到几百次普通对话的成本。

时间成本。 回归跑完要几十分钟到几小时。这段时间里主分支不能合入有风险的改动,或者需要并行维护多个待发布版本。

注意力成本。 关键用例失败时要有人判断「这是真退化还是用例有问题」。这一项最贵,因为它不能自动化。

三类成本共同决定检查粒度。成本与风险不匹配时,团队会开始绕过流程——这比检查不够严更糟。

检查本身仍然需要,它拦住已知的退化。

检查项判据阻塞性
正确性关键用例全过,总体不退化,单族不塌陷阻塞
安全性越权、注入、隐私用例全过阻塞
成本单任务平均成本不超过基线 1.2 倍需说明理由
时延首词与总时长不超过基线 1.3 倍交互路径阻塞

成本与时延设成「需说明理由」,没有硬阻塞,因为它们的权衡有时合理——换一个更强的模型让准确率提升 5 个百分点、成本上升 15%,这个交换值得。

但这个档位有个前提:有人会读理由。如果没人读,它实际上就是自动放行。所以这一档需要指定审阅人,否则应当改成硬阻塞。

7.3 分级放行 ​

级别内容检查
一级提示词、Skill 内容全部四类
二级工具描述、参数结构正确性加安全性
三级文档、注释、日志字段构建通过即可

分级的意义是让检查成本与风险匹配。全部走一级会让流程冗长,团队开始想办法绕过。


8. 灰度与回滚 ​

检查通过不代表没问题。第 1 节的案例就是全绿上线的。

灰度的两种切分方式:

按流量比例。 最简单,适合单一入口的服务。问题在于同一用户可能在不同请求里落到不同版本,体验不一致。

按任务族。 按任务类型分批放开。好处是能观察到某一族变差、不被整体数字盖住,坏处是需要能识别任务族。

回滚的触发条件要提前定:

  • 关键用例在线上出现失败;
  • 失败率相对基线上升超过阈值;
  • 出现新的失败类型(以前没见过的类别);
  • 用户投诉集中在某一类行为。

第三个条件最敏感也最有用。它不依赖绝对数值,只依赖「这是新东西」——而新出现的失败类型往往正是灰度要发现的东西。

回滚时限建议写进流程。经验值是十分钟内——超过这个时间,损害通常已经发生。


9. 反馈闭环 ​

线上失败到新用例的路径要短,否则同类问题会反复出现。

一条可用的流程:

  1. 失败样本自动进候选池,带失败类型标签;
  2. 按类型聚类,同一类型只保留最有代表性的若干条;
  3. 人工确认后转成正式用例,进对应任务族;
  4. 用例先进训练子集,不进保留集。

第 4 步的理由:新用例来自线上,它反映的正是当前版本的问题。如果直接进保留集,保留集就被当前版本污染了——它会变得容易通过。

去重与聚类这一步不能省。线上失败样本的分布高度偏斜,同一类问题可能有几千条。全部加进用例集会让评测集被单一问题主导。


10. 线上异常的三类信号源 ​

问题案例不只在评测里出现,多数来自线上。三类信号源的可靠程度与成本不同。

信号源可靠度成本特点
用户明确纠正高零「不要这么做」直接指出错误
用户点踩或负面反馈中零缺原因,需回看轨迹
事后检查发现高中状态检查、规则验证、模型评审

第一类最可靠也最省事,局限是覆盖率低——多数用户遇到问题直接离开,不会纠正。

第二类数量多但信息少。一条点踩只说明「这次不好」,具体哪里不好要回看轨迹。它的实际用途是缩小排查范围,不做归因依据。

第三类是唯一能发现「用户没察觉但确实出错」的通道。「做对了但说错了」就属于这一类——环境状态全对,用户也没投诉,但告知的信息是错的。

三类合起来的判据是:只有第三类能发现沉默的失败。前两类的共同前提是用户察觉到了问题。这就是状态检查与规则验证不能省的原因——它们不依赖用户的反应。

10.1 便宜的事后检查 ​

不是所有事后检查都需要复杂规则。一个成本极低的检查是完成声明与执行记录的一致性。

做法:把回复里的完成类措辞(已处理、已发送、已更新)抽出来,与真实成功执行的工具调用对照。没有对应调用的,就是一次虚假声明。

成本是几次字符串匹配,但它能发现一类很隐蔽的失败:Agent 在中途遇到阻碍后没有停下,继续推进并声称完成。

前面那个 32 步的例子就属于这一类。它全程没有报错,最后自行宣告完成,而实际数据根本没写进去。


11. 失败模式 ​

失败模式症状成因修的位置
检查变成仪式所有改动都能过,上线仍有问题检查项与真实风险脱节,或结果没人看定期用「已知有问题的版本」测检查能否拦住
观测成本失控日志成本超过模型调用成本记录了全量工具输出与完整对话只记摘要,全文另存按需读
回滚不可用出问题后无法快速回到上一版本数据库结构变了、配置不兼容把可回滚作为发布前提,每次发布前验证
门禁与灰度脱节检查全绿,灰度立刻全量灰度没有明确的观察指标与时长灰度必须定义观察什么、看多久、什么情况下停
归因只记结论有失败类型但没有首错位置归因只做分类不做定位记录要含步骤号、工具名、证据
用例只增不减通过率随时间缓慢下降废弃用例没有退役定期查「从未通过过的用例」数量

第一条值得展开:如果一个检查项在过去半年里从未拦住任何改动,它要么多余,要么失效。两种都需要处理。

第三条最致命,因为它在正常情况下完全看不出来。一个具体的检查方式:每次发布后,在测试环境里立刻执行一次回滚,确认能成功。这个操作只花几分钟,但它是「回滚可用」的唯一证据。

第五条常被忽略:归因记录如果只有类别没有位置,事后无法复核,也无法转成前缀用例。记录里至少要含步骤号、工具名、证据引用与置信度。