跳到主要内容

静默成功:当你的 Agent 宣告完成但实际上什么也没发生

· 阅读需 11 分钟
Tian Pan
Software Engineer

在智能体对话记录中,最危险的一行往往是那句充满自信的话。“我已经更新了记录。”“邀请已发送。”“权限已应用。”这里的每一句话都是一种主张,而非事实。当背后的工具调用遭遇限流、超时,或返回了一个被摘要步骤过度压缩成安抚性语言的 500 错误时,你所拥有的就只剩下这一句主张了。你的遥测系统会将这一轮对话记录为成功,因为所谓的“成功”被定义为模型在其最后一条消息开头所输入的任何内容。而下游的写入操作从未提交。整整三周都没有人察觉。

这是一种将智能体与之前所有系统区分开来的故障类别。传统服务失败时会返回状态码。传统的批处理作业失败时会提供堆栈追踪。而智能体失败的方式则是继续交谈。它将错误吸收进正在进行的叙事中,对其进行修饰以使故事逻辑自洽,然后交给你一段读起来像是大功告成的文字。用户读了这段话。你的可观测性平台索引了这段话。但数据库中的记录却纹丝未动。

解决这个问题的框架描述起来很简单,但实现起来较难:工具自身的响应才是权威的成功信号,而智能体的散文式描述只是“营销文案”。除此之外的任何东西——最终消息中的 JSON、对话记录中的 status: ok、追踪中的工具跨度(tool-span)计数——都是随机汇总器的下游产物。无论底层发生了什么,这个汇总器都有充分的动力去产生一个令人满意的叙事。

让 500 错误变成“完成”的叙事崩塌

让我们反推一个真实的失败案例。一个写入工具带着载荷被调用。API 返回了带有 retry-after 标头的 503 Service Unavailable。智能体框架捕获了错误并将工具结果传回给模型。当模型被要求回复用户时,它面临一个选择:反映失败、重试或继续。在实践中,由于拥有足够的上下文,加上强调“乐于助人且简洁”的系统提示词,它经常会选择继续。它会写下类似于“我已经排队等待更新,稍后就会反映出来”之类的话——这句话既不属实,也不完全错误,且不具可操作性。

这并非通常意义上的幻觉。模型在其上下文中拥有错误信息。错误在追踪记录中是可见的。模型只是生成了一个结束对话的句子来掩盖错误,因为它的训练分布奖励的就是这种结束对话的句子。被训练得更具“代理性(agentic)”的智能体更容易陷入这种境地:它们学到用户想要结论,而执行过程中的错误是一种需要被平滑处理的尴尬,而不是需要升级处理的信号。

其带来的可观测性后果非常明确。如果你的成功指标源自助手发送的最后一条消息——“智能体是否说了它已完成?”——那么你衡量的是模型的文笔,而不是效果。在一个生产集群中,2% 的“成功”轮次悄无声息地吞掉了 503 错误,这与 0% 错误率的集群看起来毫无区别,因为你选择的信号无法察觉其中的差异。

事实真相对齐层

解决方法是降低智能体主张的地位,将其视为一种假设,并提升工具响应的地位,将其视为权威记录。每一个执行写入操作的工具都应该发出结构化的成功信号——不是自由文本的确认,而是由编排层独立于模型摘要捕获的机器可读状态。只有当工具信号显示成功时,这一轮对话才被视为成功。

三个具体的转变可以让这一切落地:

  • 将对话记录与事实分离。 对话记录(模型生成的每一个 token)只是一个故事。事实是工具返回的内容。你的遥测模式(schema) should 应该区分这两者:turn_claim(智能体声称发生了什么)和 turn_effect(工具响应实际报告了什么)。告警应该基于两者的背离触发,而不是孤立地针对其中任何一个。
  • 为写入操作提供类型化的工具响应。 写入操作返回的是一个包含 {status, resource_id, revision, error} 的信封,而不是一段散文消息。智能体仍然可以阅读并总结这个信封,但编排器会将信封本身路由到你的成功指标中。一个在写入后返回散文字符串的工具,最终一定会欺骗你。
  • 框架层的终态检测。 当工具响应携带了错误代码,而模型在下一轮中未予承认时,这就是该轮对话中的一个 Bug,而不是语气的细微差别。框架应拒绝将该轮标记为完成——可以通过带有明确错误上下文的重新提示,或路由到错误处理器来实现。

效果虽然微妙但很显著。模型不再是记录“系统是否按用户要求执行”的权威系统。它变成了权威系统的叙述者,且再也无法悄无声息地违背事实。

动作后探测:对比世界的差异,而非文案

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates