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. 归因记录的结构
归因结论要能被复核、被聚合、被转成用例,因此必须是结构化的。一段散文式的说明无法做这三件事。
一份可用的记录长这样:
{
"轨迹标识": "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. 反馈闭环
线上失败到新用例的路径要短,否则同类问题会反复出现。
一条可用的流程:
- 失败样本自动进候选池,带失败类型标签;
- 按类型聚类,同一类型只保留最有代表性的若干条;
- 人工确认后转成正式用例,进对应任务族;
- 用例先进训练子集,不进保留集。
第 4 步的理由:新用例来自线上,它反映的正是当前版本的问题。如果直接进保留集,保留集就被当前版本污染了——它会变得容易通过。
去重与聚类这一步不能省。线上失败样本的分布高度偏斜,同一类问题可能有几千条。全部加进用例集会让评测集被单一问题主导。
10. 线上异常的三类信号源
问题案例不只在评测里出现,多数来自线上。三类信号源的可靠程度与成本不同。
| 信号源 | 可靠度 | 成本 | 特点 |
|---|---|---|---|
| 用户明确纠正 | 高 | 零 | 「不要这么做」直接指出错误 |
| 用户点踩或负面反馈 | 中 | 零 | 缺原因,需回看轨迹 |
| 事后检查发现 | 高 | 中 | 状态检查、规则验证、模型评审 |
第一类最可靠也最省事,局限是覆盖率低——多数用户遇到问题直接离开,不会纠正。
第二类数量多但信息少。一条点踩只说明「这次不好」,具体哪里不好要回看轨迹。它的实际用途是缩小排查范围,不做归因依据。
第三类是唯一能发现「用户没察觉但确实出错」的通道。「做对了但说错了」就属于这一类——环境状态全对,用户也没投诉,但告知的信息是错的。
三类合起来的判据是:只有第三类能发现沉默的失败。前两类的共同前提是用户察觉到了问题。这就是状态检查与规则验证不能省的原因——它们不依赖用户的反应。
10.1 便宜的事后检查
不是所有事后检查都需要复杂规则。一个成本极低的检查是完成声明与执行记录的一致性。
做法:把回复里的完成类措辞(已处理、已发送、已更新)抽出来,与真实成功执行的工具调用对照。没有对应调用的,就是一次虚假声明。
成本是几次字符串匹配,但它能发现一类很隐蔽的失败:Agent 在中途遇到阻碍后没有停下,继续推进并声称完成。
前面那个 32 步的例子就属于这一类。它全程没有报错,最后自行宣告完成,而实际数据根本没写进去。
11. 失败模式
| 失败模式 | 症状 | 成因 | 修的位置 |
|---|---|---|---|
| 检查变成仪式 | 所有改动都能过,上线仍有问题 | 检查项与真实风险脱节,或结果没人看 | 定期用「已知有问题的版本」测检查能否拦住 |
| 观测成本失控 | 日志成本超过模型调用成本 | 记录了全量工具输出与完整对话 | 只记摘要,全文另存按需读 |
| 回滚不可用 | 出问题后无法快速回到上一版本 | 数据库结构变了、配置不兼容 | 把可回滚作为发布前提,每次发布前验证 |
| 门禁与灰度脱节 | 检查全绿,灰度立刻全量 | 灰度没有明确的观察指标与时长 | 灰度必须定义观察什么、看多久、什么情况下停 |
| 归因只记结论 | 有失败类型但没有首错位置 | 归因只做分类不做定位 | 记录要含步骤号、工具名、证据 |
| 用例只增不减 | 通过率随时间缓慢下降 | 废弃用例没有退役 | 定期查「从未通过过的用例」数量 |
第一条值得展开:如果一个检查项在过去半年里从未拦住任何改动,它要么多余,要么失效。两种都需要处理。
第三条最致命,因为它在正常情况下完全看不出来。一个具体的检查方式:每次发布后,在测试环境里立刻执行一次回滚,确认能成功。这个操作只花几分钟,但它是「回滚可用」的唯一证据。
第五条常被忽略:归因记录如果只有类别没有位置,事后无法复核,也无法转成前缀用例。记录里至少要含步骤号、工具名、证据引用与置信度。