几乎每个人在使用编程智能体时都会陷入一种模式,而它正在悄悄消解使用智能体的初衷。智能体完成一次改动后,你会问一个显而易见的问题:"这样能行吗?"智能体回答"能"。你相信了它,因为它听起来很自信,而且大多数时候确实说对了。但偶尔,它会自信满满、彻头彻尾地说错——而你要到生产环境里才会发现。
问题不在于智能体撒谎。问题在于这个问题本身。
为什么"这样能行吗"是个错误的问题
当你问智能体自己的改动是否可行时,你是在让它用产出这个改动的那套推理去评判自己的产出。它是在用同一支笔批改自己的卷子。如果它在写代码时漏掉了一个边界情况,那么在判断代码时,它同样会漏掉这个边界情况,因为局面里没有任何新信息——还是同一个模型、同一个上下文、同一批盲点。
更糟的是,语言模型天生倾向于附和。把问题问成"这样处理空邮箱是没问题的,对吧?",你就不是在请求评估,而是抛出了一个结论,邀请智能体来确认它。它通常会照办。你得到的不是验证,而是对你自己期望的一种非常流畅的回声。
转而索要证据
修正的方法很小,却能改变一切:不再要求智能体断言改动可行,而是要求它展示出来。证据是一件你自己可以核查的产物,由系统产生,而不是由智能体对系统的看法产生。并非所有证据都同等有效——大致存在一个层级:
- 执行证据最有说服力:智能体实际运行了代码,并向你展示真实输出——通过的测试、返回了预期结果的命令。
- 前后对比证据几乎同样有力:可观察状态发生的具体变化。查询次数从 47 降到 2;原本失败的请求现在返回 200。
- 智能体自己写的测试说服力较弱,因为写代码时漏掉的那个盲点,写测试时同样可能漏掉——但一个你可以阅读并运行的测试,依然远胜于一句断言。
- 口头解释说服力最弱。有时这是你唯一能得到的东西,但"这是我的推理过程"并不是证据;它恰恰是证据本应去检验的东西。
无论哪种情况,做法都是一样的:把是非问题转化为对可观察结果的请求。
具体是什么样子
与其问"这次迁移能处理空邮箱吗?",不如让它插入一行空邮箱的数据,运行迁移,再把输出展示给你看。与其问"这修复了 N+1 查询问题吗?",不如让它加上查询日志,访问一次接口,把前后的查询次数展示给你看。与其问"限流器会重置吗?",不如让它触发限流、等待、再次尝试,并把请求成功的结果展示给你看。每一种情形里,你都用一个可以阅读的结果,替换了一个原本只能选择相信的断言。
这就是我们的智能体运行在沙箱中的原因
证据只有在智能体能够真正运行代码时才成立,而这是一项设计决策,不是事后补上的想法。每一次 TaskGoblin 运行都发生在一个已经克隆好仓库、配好工具链的沙箱中,因此智能体不是在推测代码会做什么——它可以运行测试、执行命令、观察真实结果。当它审查一个合并请求时,它给出的发现指向的是具体行为;当你回复 @taskgoblin fix 时,它要处理的改动是它可以实际验证的,而不仅仅是可以为之辩护的。
这也是为什么一套良好的开发环境如今回报比以往任何时候都更大。一个能够启动你的服务、加载种子数据、运行你的测试套件的智能体,能够产出真实的证据;一个只能阅读代码的智能体,只能给你一个看法。你的项目越是可运行,你能验证而非仅仅信任的智能体产出就越多。
一句话版本
不要问智能体它的工作是否正确。要求它展示出来——并且优先选择那些你自己也能核查的展示方式。运行代码并把输出交到你手里的智能体,做到了那件替代码打包票的智能体永远做不到的事:给你一样可以相信的东西,而不只是它自己的自信。