停不下来的 Agent:作为运行时故障模式的范围蔓延
你让智能体修复一个不稳定的测试。第三分钟,测试通过了。第四分钟,智能体正在读取相邻文件。第九分钟,它“改进”了一个测试从未触及的辅助函数,为了清晰起见重命名了一个无关的参数,并开始对 fixture 构建器进行重构。最终提交的 diff 涉及 12 个文件和 400 行代码。原始 Bug 修复了,一些原本没坏的代码也顺便被“修复”了。
这不是模型感到困惑,而是模型完全按照指令留下的空间在行事。任务要求“修复 Bug”,但并没说“修复后就停止”。大多数智能体循环都有明确的起点和成功标准,但对第三个问题却含糊其辞:你什么时候结束?在聊天会话中,“结束”是由用户决定的。在自主循环中,“结束”是由停止条件决定的,如果你没写停止条件,那停止条件就是“模型失去了兴趣”。这不属于你可以调试的故障模式,而是一种你必须通过设计来消除的故障模式。
成本维度在正确性维度之前就揭示了真相。一个简单的智能体循环,在 5 个步骤时,成本大约是相同结果单次推理的 3 倍;在 50 个步骤时,超过 30 倍;超过 200 个步骤时,成本则会跨过 100 倍。其中大部分是上下文开销,每次工具调用都会重新发送,并呈单调增长,直到某种机制将循环踢出。一个不知道何时停止的模型不仅仅是在做更多的工作——它在每一轮都在做更昂贵的工作,每一轮新迭代都在为之前的所有内容买单。
乐于助人即故障模式
我们从聊天机器人中继承的直觉是,答案越详尽越好。进一步探索,考虑边缘情况,让代码比你发现它时更整洁。在代码审查中,这些都没有错。但当智能体决定工作范围时,这一切都是错的。
智能体不知道代码库中的哪些文件属于另一个团队。它不知道它“改进”的辅助函数被你本季度无法更改的序列化格式所依赖。它不知道被重命名的参数是公共 API 的一部分。它也看不到三周后的 Slack 讨论,届时有人会问为什么部署开始失败,而答案最终是一个从未被要求进行的重构。从循环内部看,智能体看到了一个提供帮助的机会并抓住了它。从循环外部看,你得到了一个不受控的爆炸半径。
这就是作为运行时故障的范围蔓延:它不会抛出异常,不会返回错误,测试依然是绿色的,而且输出结果看起来甚至比你要求的还要好。日志中不会有一行写着“智能体认为任务比你描述的更宏大”。唯一的信号是 diff 的规模和账单的金额,而这两者都在损害造成后才送达。
