调试与分诊智能体

10 分钟读完

U11
实战手册 · 编码与计算机操作智能体

调试智能体:交付物是一次复现,不是一个补丁。

一个读完调用栈就吐出 diff 的智能体,什么也没调试——它做的是模式匹配,而它会为一个自己从未观察过的 bug,递给你一份自信满满、测试齐备、彻头彻尾错误的修复。把"那个失败的测试"定为交付物,整个系统的形状就变了:评测标准变得客观,大部分令牌开销从生成挪到了观察,而所有人都在优化的那个补丁,反倒被证明是容易的那一半。

STEP 1

在"复现"这条线上把工作切开。

一份 bug 报告到来时,是对症状的描述;一个补丁则是对成因的改动。夹在两者中间的那一步承载了全部风险,而它恰恰是"上来就修"的智能体完全跳过的那一步。

  • 第一阶段产出复现,第二阶段产出修复。把它们当作两个独立阶段、各有各的退出条件来跑。第一阶段的退出条件是可被机器校验的——这在智能体工作里几乎绝无仅有:一个在当前提交上失败、并且描述了所报症状的测试。
  • 第一阶段失败是一个好结果,不是一次跑崩的运行。"我复现不出来,以下是我排除掉的东西和我还需要什么"是真有价值的,也正是一个称职工程师在第二小时会汇报的东西。一个无法返回这句话的智能体,只会转而编造一个成因——因为你没给它任何可接受的失败方式。
  • 能活下来的产物是复现。补丁会在评审里被重写;而那个失败的测试会进入测试套件,永久地挡住这次回归。请围绕测试来设计你的输出 schema,把 diff 当作附件而不是头条。
  • 这也正是 补丁生成与测试 是另一份手册的原因。那一份假定你已经知道该改什么。这一份讲的是你还不知道的那个阶段,而两者的成本画像几乎相反。

判断你造出来的是调试智能体还是打补丁智能体,最锋利的诊断是:面对一个它复现不出来的 bug,它会不会直说?如果每一次运行都以一个 diff 收尾,那你造的就是一个以"产出 diff"被打分的智能体,而它学会的正是你实际教给它的那一课。

STEP 2

给它三样编码智能体从不需要的输入。

写代码的智能体从源码出发。调试则从关于某一次具体执行的证据出发,而源码只是它三样输入之一。缺了另外两样中的任何一样,模型就只能靠猜。

  • 运行时状态。要的是值,不是变量名。调用栈只是一副骨架;智能体真正需要的是产生它的那个请求、失败栈帧上的实参、相关的那几行数据、当时生效的功能开关、真正被加载的那份配置。多数团队的 链路追踪 里早就有这些,只是从没把它接到智能体上。
  • 历史。它从什么时候开始失败的,那前后又改了什么。发布、迁移、依赖升级、配置修改、流量变化。一个从周二 14:03 开始出现的 bug,其嫌疑集合远小于一个没有时间特征的 bug;而这是最可靠地把搜索空间压塌的那一样输入。
  • 重跑的能力。没得商量,也正是调试智能体需要一个真实执行环境而非一个代码索引的原因。它必须能改一样东西然后观察结果——这意味着一份检出的代码库、装好的依赖,以及一条够得着有代表性数据的路径。复用 沙箱与执行 里那套隔离模型即可;要求是一样的,只是会话更长。

按此编预算。一次调试运行是观察密集型的:大量便宜的工具调用、很长的转录、相对很少的生成。照着写代码的经验来给这类智能体定规格的团队,会把上下文配少、把模型配大。相比换个更大的模型,更该选大上下文加上对既往尝试的激进摘要——瓶颈几乎总是"什么已经被排除了",而不是原始的推理能力。

STEP 3

让循环去收窄空间,而不是去提修复方案。

不加约束的话,模型调试的方式是生成一个最貌似合理的成因再去验证它——这套策略在常见 bug 上管用,而在那些你真正需要帮忙的 bug 上会退化成随机搜索。该强加进去的算法是二分。

  • 每一步都必须排除掉某样东西。要求智能体在每次动作之前先说明:这个结果将会杀死哪一个假设。一个其结果排除不了任何东西的动作就是一步浪费,而由这种步骤组成的运行,就是大家在 工具错误恢复 里都认得出的那种"乱扑腾"。
  • 维护一份显式的、写下来的嫌疑清单。带状态的假设:待查、已排除、已确认——放在工作状态里,而不是隐含在转录中。没有它,智能体会重新去测十五轮之前就已经排除掉的东西,而这正是长调试运行最主要的失败模式。
  • 对提交做二分,也对输入和配置做二分。git bisect 是最有名的那个,在你有一个"当时还好使"的版本时往往也是最快的路。同样的纪律适用于把输入不断缩小直到故障消失,也适用于一次只翻一个配置开关。
  • 去埋点,别去推断。加一行日志然后重跑,胜过再写一段"那个值大概是什么"的推理。智能体不太用这一招,因为打印感觉像是没给出答案;请把它做成一等工具,并奖励它被用上。
  • 按"排除数"而非"轮数"来封顶循环。当嫌疑清单不再变短时就停。轮数上限会砍掉有产出的运行,又让没产出的运行花光全部预算;而排除速率能直接把这两者区分开。
STEP 4

分诊是另一个智能体,而且它先跑。

落进 bug 队列里的东西,多数并不需要调试。它们需要的是分派——而把这两份工作混进一个智能体里,产出的是一个以全价调试重复报告的东西。

  • 分诊回答四个问题然后就停。这是重复的吗?有多严重?属于哪个组件、归谁?信息够不够动手?它便宜、量大,应该是一个小而快的模型配上对历史工单的检索——正合 小模型与本地模型 里那个模式。
  • 去重是价值遥遥领先的产出。同一个崩溃报上来四十次是常态,把它们收拢起来既是最大的一笔成本节省,也是最大的一次信号增益——四十份报告本身就是第一份报告未曾携带的严重性信息。
  • 严重级别是一条政策,不是一次判断。把你团队实际在用的那份评级标准连同示例一起交给智能体。任其自行其是的话,模型会把一切都评成"中",那和什么都不评是一回事。
  • "信息不足"是一个真实、常见且有用的结论。一个向报告者提出一个精确问题的智能体——版本号、确切输入、在全新会话里是否复现——比一个瞎猜的智能体挽回更多 bug,而成本只有百分之一。
  • 只在一道显式关卡处才升级给调试智能体。原则上可复现、有归属、非重复、且高于某个严重性下限。其余的都等人,或者等更多报告。没有这道关卡,你的调试开销就由队列量而非 bug 量决定。
STEP 5

生产环境权限:什么都能读,什么都别跑。

调试会把智能体往生产环境拽,因为证据在那儿。这是本手册里最锋利的一个安全决定,它值得一条明确的界线,而不是一次逐渐的漂移。

  • 从生产读,只在沙箱里写。链路、日志、指标、schema、查询计划、失败载荷的一份脱敏样本——都可以,都是只读。挂调试器、跑一个会加锁的查询、重启一个服务、改一个开关:这个智能体不干。用凭据去执行这条界线,而不是用提示词;见 为智能体设计受限凭据
  • 那份失败载荷是系统里最敏感的对象。它按定义就是真实的客户数据,而智能体正想把它塞进提示词。请在边界处、在它进入上下文之前就脱敏,并记录哪些字段被脱敏了,好让智能体知道那里原本有东西。
  • bug 报告是不可信输入。调用栈、粘贴的日志、附件和报告者的描述,在任何有外部用户的产品上都可被攻击者控制。这与 安全运营智能体 面对的是同一种暴露面,规则也一样:报告里的内容是关于世界的证据,永远不是指令。
  • 把事故通路和 bug 通路分开。在故障期间,人们希望智能体去动手。那是另一种风险姿态、另一套审批,属于 智能体事故响应——而不是你把 bug 队列智能体的权限松一松就能走到的地方。
STEP 6

按复现来评测,而不是按合入率。

合入率是所有人第一个伸手去够的指标,而它度量评审者的成分不比度量智能体的少。"复现/修复"这一刀给了你更好的东西。

  • 复现率。在被受理的 bug 中,智能体产出了一个在出错提交上失败的测试的占比。客观、检查便宜,而且它的升降都有真实原因。
  • 伪复现率。最要紧、却没人跟踪的那一个:因与所报症状无关的原因而失败的测试。这是最耗信任的失败,因为它看起来和成功一模一样。请抽样人工核查。
  • 正确弃权率。在人同样复现不出来的 bug 上,智能体说了"复现不出来"的比例。如果它接近零,说明你的智能体没有诚实失败的途径,那么其他每一个数字都是虚高的。
  • 每次运行的排除数,以及每次复现的成本。这一对是效率指标。"每次复现的成本"才是可以拿去跟一名工程师的工时对比的数;"每个合入补丁的成本"会藏起那些颗粒无收的运行,让你自我感觉良好。
  • 用你自己已关闭的 bug 造评测集。理想语料你早就有了:那个 bug、修它的那次提交,以及随之而来的那个测试。检出父提交,你就得到了一道有标准答案的评分练习——对你自己的代码库而言,这远比公开基准更有预测力,构造方法写在 评测编码智能体 里。

先把复现阶段单独上线。一个能把一半来件变成失败测试、并诚实地对其余部分说"不"的智能体,立刻就有用;因为它唯一的产出就是一个测试,所以无人监督地跑也安全;而且在你放任何东西去提修复方案之前,它已经替你产出了所需的带标注数据。补丁从来不是贵的那部分——知道该看哪一行才是;而当你要把这份"知道"交给别人时,它长的就是一个失败测试的样子。

延伸:编码智能体架构 讲它所处的外壳,代码库导航与上下文 讲知道该看哪儿之后怎么把代码找出来,代码评审智能体 讲补丁另一头的那位评审者,以及 轨迹评测 讲怎么给搜索过程而非答案打分。