智能体干了蠢事,第一反应往往是"模型幻觉了",然后往系统提示词里加一行。本月发表的一篇论文认为,这个诊断通常是错的。

打开网易新闻 查看精彩图片

Tommy Tran 的《失败即特性》(Failure Is a Feature)刊于 ACM Queue(2026年10月15日),起点是一个朴素的观察:智能体是一个循环。模型提出动作,执行框架(harness)拿它去调用某个工具,结果再回到模型。这相当于一个协调者在向它并不掌控的服务发起远程调用,跑在会丢包的网络之上。它是一个分布式系统,中间夹着一个不确定性的部件。

分布式系统对付这类问题已经有40年的答案。我们却一直在伸手去改提示词。

故障到底出在哪一环

论文按故障进入循环的位置做了分类:

  • 工具边界:超时、429 限流、连接中断。
  • 载荷:调用"成功"了,但返回的是被截断的 JSON、一个 HTML 错误页,或者已经漂移的 schema。
  • 模型:选错工具、编造参数、没做校验就宣布成功。
  • 副作用:写入已经生效,确认回执丢了,这一步被重试。
  • 循环本身:两个工具来回打乒乓、整夜重试一个已经死掉的 API、上下文耗尽。

其中只有一类是模型问题。其余几类都有枯燥但已知的修法。一个被截断的响应写着"balance": 1而不是 1000,模型照样会一本正经地拿它推理,因为模型什么都能解析。整条流水线上没有任何一环说过"不"。

那组实验

论文附带了一个小型开源执行框架,方便你自己验证这些说法。在固定的故障配置下,结果是:

单看重试,效果很好,直到你注意到重复。对一个已经成功的写操作再重试一次,你就发出了两笔退款。其他模式多出来的那 6 个点,价值还不如它们省下的清理工作。

三个值得抄走的做法

  1. 幂等键由执行框架持有,不由模型填写。如果让模型自己填 idempotency_key 参数,重试的那一步可能带着不同的键回来,去重就悄悄失效了。键应当从执行框架掌控的东西推导出来:key = f"{task_id}:{step_id}:{tool}",这一步的每次重试都相同。如果模型重新规划、再次提出该动作,那会拿到新的 step id,这是对的——那是一个新决策,不是一次重发。
  2. 把 token 预算当成截止时间。给每个任务设定时间上限、token 上限和步数上限,并在每一步开始前检查。论文称步数上限是全文最便宜的保险。几行代码就能把"智能体对着死掉的 API 循环了一整夜"变成"任务在第 20 步失败,原因明确"。
  3. 把模型挡在重试循环之外。让执行框架用退避加抖动去重试瞬时错误,只把最终结果或一个带类型的失败展示给模型。否则每一次尝试都要花 token,还会用失败叙述把上下文窗口填满。对于已经死掉的依赖,熔断器应当返回模型能据此规划的东西,比如{"error": "circuit_open", "retry_after_s": 30}

论文自己承认的保留意见

论文坦承这是一个注入了故障的玩具级智能体。当故障是随机注入时,熔断器实际上降低了任务成功率,只有在真实依赖真的挂掉时才有回报。所以这不是一份可以照抄的配方,而是一种停止瞎猜的方法。

更重要的那一点,与论文对 tau-bench 这类基准的说法一致:单次运行的成功率会美化智能体。一个每次尝试"大多数时候能用"的智能体,当你需要它连续成功八次时,失败率会高得多。挡在演示和能长期挂着跑之间的是可靠性,而不是原始能力。

如果你在做智能体,建议是先读论文、跑一遍那个执行框架,再动手写下一条提示词补丁。