大多数智能体失败,并非因为幻觉

Dev.to AI 2026-10-22T15:32:28.601952

智能体干了蠢事,大家的第一反应通常是「模型幻觉了」,于是往系统提示词里再加一行。本月的一篇论文认为,这往往是误诊。

Tommy Tran 在 ACM Queue 上发表的《Failure Is a Feature》(2026 年 10 月 15 日)从一个朴素的观察出发。智能体就是一个循环:模型提出一个动作,某个执行框架(harness)拿它去调用工具,结果再回传给模型。这本质上是一个协调者在通过会丢包的网络,对并不由自己掌控的服务发起远程调用。它是一个分布式系统,中间夹着一个不确定性的部件。这类问题,分布式系统已经有四十年的现成答案。我们却总是一再伸手去改提示词。

智能体真正出问题的地方

论文按故障进入循环的位置对失败进行分类:

这里面只有一个是模型的问题。其余的都有无聊但已知的解决办法。

一个被截断的响应,把 "balance" 写成 1 而不是 1000,模型照样会兴高采烈地拿它去推理,因为模型什么都能解析。整条管道里没有任何一环说过“不”。

实验

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

配置 任务完成率 副作用
裸智能体 33.3% 未提及
仅加重试 83.3% 产生重复记录
全部七种模式 89.6% 无重复、无孤儿写入

单看重试,成绩好得不得了,直到你注意到那些重复。对一次已经成功的写操作再重试,就等于退了两笔款。其他模式多出来的 6 个百分点,相比它们省下的善后工作,其实没那么重要。

值得借鉴的三件事

  1. 幂等键由执行框架持有,而不是模型。 如果你让模型去填 idempotency_key 参数,重试的那一步可能带着一个不同的键回来,去重就会悄无声息地失效。

从框架能控制的东西里推导这个 key:

def run_side_effect(task_id, step_id, tool, args):
    key = f"{task_id}:{step_id}:{tool}"  # 这一步每次重试都相同
    return call_with_retries(tool, {**args, "idempotency_key": key})

如果模型重新规划,又提出同一个动作,那会拿到一个新的 step id,这没问题——那是一次新的决策,不是重发。

2. 把 token 预算当成截止时间。 给每个任务设时间上限、token 上限和步数上限,每进入新一步之前先检查一遍。论文里管步数上限叫“最便宜的保险”。几行代码,就能把“智能体对着一个挂掉的 API 跑了一整夜”变成“任务在第 20 步失败,原因明确”。

3. 别让模型待在重试循环里。 瞬时错误交给框架去重试,用退避加随机抖动,只把最终结果或一个带类型的失败信息给模型看。否则每次尝试都要烧 token,还把上下文窗口塞满失败过程的描述。遇到依赖彻底挂掉的情况,应该由熔断器返回一个模型能据以规划的东西,比如 {"error": "circuit_open", "retry_after_s": 30}。

几点诚实的提醒

论文自己也说得很清楚:这是一个注入了故障的玩具智能体。故障是随机发生的时候,熔断器反而降低了任务成功率,只有在真实依赖确实宕机时才有价值。所以这不是一份可以照抄的配方,而是一种不再靠猜的做法。

更重要的结论,和论文对 tau-bench 这类基准的评论是一致的:单次运行的成功率会美化智能体的表现。一个每次尝试“大多数时候能成”的智能体,当你需要它连续成功八次时,失败率会高得多。决定一个东西是停留在演示、还是能长期无人值守跑下去的,是可靠性,而不是原始能力。

如果你在做智能体,我的建议是:先读论文、跑一遍那套测试框架,再去写下一条提示词修补。链接在下面。

查看原文