跳到主要内容

Abandon 原语:为什么你的智能体循环需要一个一等公民的方式来终止计划

· 阅读需 12 分钟
Tian Pan
Software Engineer

看看大多数智能体框架提供的循环原语:continuereturnretry 以及一个硬性终止运行的步数预算。注意缺失了什么。这里有一条表示“工作成功”的路径,一条表示“模型想继续”的路径,还有一条表示“我们耗尽了资金或耐心,于是强行终止循环”的路径。唯独没有一条头等路径来表达“我正在执行的计划是错误的,我想将其丢弃并开始一个全新的计划”。放弃原语(abandon primitive)——即一种让规划器显式、结构化地宣布当前轨迹无望的方式——是智能体循环语法中缺失的动词。它的缺失导致了一类通常被误诊为“模型需要更多推理”的失败。

一个在注定失败的分支上执行了三步的规划器,会不断优化同一个错误的计划,因为循环唯一的出口是成功、重试上一步或耗尽预算。这些路径都不包含“放弃该策略并尝试另一个”。因此,智能体执行了循环所允许的操作:它就地修改计划、再调用一个工具、再请求一次澄清,并在步数预算耗尽前不断收敛到一个非解决方案。当最终撞墙时,用户看到的是一条礼貌的失败消息,而不是问题的答案。这些浪费的步骤所带来的成本是真实的——生产数据表明,智能体系统 5-10% 的 Token 支出都花在了无法产生任何可用结果的重试上,而这一数字主要由长期的“注定失败分支”占据,而非孤立的工具错误。

循环原语借用自请求/响应模式

大多数智能体控制结构的形态源于一个没有这类问题的地方。传统的服务处理请求、计算响应并退出。失败是局部的:一个函数抛出异常,调用者捕获,重试是围绕单一操作的循环,而且工作单元足够小,以至于“放弃”和“失败”是一回事。这些原语被带入了智能体循环中,因为编写这些结构的工程师已经熟悉它们了。

智能体的工作形态则不同。其单元是一个分解为一系列工具调用和推理步骤的计划,而在循环的每一次迭代中被问到的问题不是“上一次操作成功了吗?”——而是“我所在的轨迹是否仍有可能达到目标?”这是不同的问题,需要不同的控制流答案。工具错误意味着上一次调用失败了;策略失败则意味着最后 7 次调用都成功了,但它们是错误的调用。

当唯一可用的原语只有 continue、return、retry 和预算耗尽时,规划器无处放置“策略已错”的觉察。因此,这种觉察要么根本不会发生——模型继续优化一个本该被丢弃的计划——要么发生了但被挤进现有的某种形态中,通常是重试,但这实际上不会改变任何事情,因为底层计划仍然是错误的。

该原语需要明确的事项

“添加放弃路径”不是那种通过在模型的工具列表中增加一个分支就能实现的功能。该原语是一份契约,而这份契约必须回答大多数团队从未明确决定的四个问题。

保留什么状态。 当规划器选择放弃时,什么会被延续?对话历史?智能体收集的中间观察结果?部分输出?一个“在系统 A 中查找客户订单然后在系统 B 中更新”的注定失败计划可能已经了解到客户的账户 ID 是错误的——这是下一个计划需要的信息。但它也可能已经在具有副作用的工具中写入了半条记录,而这是下一个计划绝不能视为已完成的信息。

回退什么。 具有副作用的工具调用需要一个方案。要么每个有副作用的工具都具有足够的幂等性,使得重新执行是安全的(这是一个几乎没有真实工具集能满足的强有力假设),要么控制结构必须跟踪哪些已提交、哪些需要补偿,或者放弃路径本身必须被定义为“保留所有副作用,仅丢弃规划器自身的推理状态”。其中的每一种都是不同的产品选择,而没有做出选择的团队最终会意外地交付第三种方案。

向用户报告什么。 “放弃”既是一个控制流原语,也是一个 UX 原语。“我尝试了方案 X,但没成功,现在切换到方案 Y”是一个建立信任的时刻,而几乎没有智能体能展现这一点。大多数智能体将失败表现得像是终结性的,或者隐藏放弃行为,将第二个计划的输出呈现得好像自始至终都是第一个计划一样。前者浪费了用户关于尝试过什么的反馈信息;后者则以一种不诚实的方式破坏了信任,一旦用户察觉,信任便会崩塌。

新计划是在相同上下文中开始还是在一个全新的上下文中开始。 这是四点中最难的一点。在相同上下文中继续可以保留智能体学到的东西,但风险在于模型会锚定在失败的策略上——最近的失败具有粘性,刚刚放弃了计划 A 的规划器会不断提出计划 A 的微小变体。从头开始强制进行真正的重构,但会丢失促使放弃的诊断信息。正确的答案通常是结构化的交接(handoff)——在新的上下文中总结尝试过的内容以及失败的原因——但大多数控制结构也没有为此提供原语。

能够捕捉其缺失的评估

标准的智能体评估(evals)无法捕捉到缺失的放弃原语(abandon primitive),因为这些评估是围绕那些显而易见的首选计划即正确计划的任务构建的。模型提出一个序列,测试框架(harness)执行它,得出答案,然后评估进行打分。由于循环从未需要过放弃路径,因此该路径是否有效从未得到锻炼。

能够捕捉到这一缺口的评估是经过刻意设计的。选取一个任务,并对其进行工程化处理,使听起来最自然的第一个计划是错误的。比如,用户想要最近的账单,但“最近的”实际上是指“最近已付款的账单”,而智能体必须发现列表顶部的未付款账单是无关紧要的。或者用户想要处理退款,但实际的阻碍在更上游——订单从未扣款,因此没有什么可退款的,任何以“查找扣款记录”开始的计划在发出工具调用之前就注定失败了。

根据智能体是否达到了正确的最终状态来打分,你会发现两类模型之间存在差距:一类模型会收敛于一个注定失败的计划,并消耗预算来不断优化它;另一类模型则会转向(pivot)。在不同的测试框架中,转向通过不同的路径实现——有时模型会发出“让我重新考虑一下”的消息,而框架足够聪明,将其视为一种信号;有时规划器变得非常困惑,以至于意外重启。但转向是通过意外而非设计实现的,这恰恰说明了问题。原语可以将一种突发行为(emergent behavior)转变为你可以依赖的契约。

这种评估中的成本框架极具启发性。将智能体表现最差的 10% 轨迹(trajectories)的平均成本,与最佳计划加上一次重新尝试的成本进行比较。一个包含 12 步的注定失败的分支,其成本往往高于一个包含 3 步的失败计划加上一个包含 4 步且奏效的第二个计划。注定失败分支中浪费的步骤并不是“探索”——它们是对一个无法实现的目标进行的局部优化。

驱动决策的置信度信号

最难的部分不是添加原语,而是决定何时触发它。“当置信度低于阈值 X 时放弃”是一个显而易见的思路,但它把决策放在了完全错误的地方:当前正投入于某个计划的模型,是评估该计划是否无望的最糟糕角色。让模型最初致力于某个计划的那种提示敏感性(prompt-sensitivity),也会在受到质疑时让它更加坚持己见。

更好的信号来自规划器自身的自我评估之外。例如,一个“无进展检测器”,当连续的观察结果返回的是智能体已经拥有的信息时,它会发出提醒——这不作为硬性停止,而是作为当前计划未生成新状态的证据。一个“目标差距检测器”,比较智能体当前的内部状态与请求的最终状态,并询问差距是否随着每一步而缩小。一个“循环检测器”,标记具有相似参数的重复工具调用。这些是测试框架可以针对规划器行为进行的观察,它们比要求规划器对自己评分更诚实。

生产系统中的一种常见模式是将低置信度视为一种“提议”(propose)而非“承诺”(commit):当任何外部信号触发时,框架要求规划器用一两句话阐述当前策略是否仍然正确。规划器的回答成为框架可以结合自身信号一并考虑的证据。放弃的决定是由测试框架做出的,而不是模型,但包含了模型的推理。

阈值是特定于产品的。可以廉价验证其工作(例如运行测试套件)的代码智能体可以容忍更长的注定失败的分支,因为验证本身就是放弃信号。而对于每向用户提出一个澄清问题都需要付出高昂摩擦成本的客服智能体,则需要更早且无声地放弃。没有通用的数值;团队必须根据浪费步骤的成本与过早放弃的成本来选择一个数值,并且必须对系统进行充分的测量(instrument),以便对其进行微调。

生产环境遥测应追踪的内容

如果放弃原语成为循环的一部分,它也必须成为遥测(telemetry)的一部分。大多数团队已经追踪的指标——任务成功率、每个任务的平均步数、每个任务的成本——无法告诉你你的放弃逻辑是否在做正确的事情。

真正能说明问题的指标包括:

  • 每个任务类型的放弃率。 放弃率与同类任务显著不同的任务,要么存在规划问题(第一个计划总是错的),要么存在定义问题(任务比评估集捕捉到的更难)。
  • 放弃后的成功率。 放弃后有多少比例引导出了一个奏效的第二个计划?如果比例很高,说明该原语物有所值。如果很低,智能体只是在增加成本而没有增加解决方案,且阈值设定得过于激进。
  • 轨迹长度分布。 如果最长的 5% 轨迹主要是由那些 最终 成功的任务组成的,那么放弃逻辑就过于保守了。如果它们主要是失败的任务,那么放弃逻辑在另一方面也过于保守——智能体应该更早放弃。
  • 注定失败计划的成本 vs. 转向的成本。 这是原语存在的全部意义。直接追踪它。

如果没有这些遥测数据,该原语只是一种无法被验证的架构观点。有了它,原语就变得可调——随着模型的演进、工具集的增加以及任务分布的变化,团队可以调整这个旋钮。这种可调性才是核心意义所在。智能体循环中的硬编码阈值是技术债,因为下个季度的正确阈值绝不会是本季度的正确阈值。

架构的演进

放弃原语之所以重要的深层原因,在于它暗示了智能体循环(agent loops)形态的变化。continue/return/retry 这种词汇将计划视为模型正在 执行 的事情,其中每一步都在局部范围内判定成功或失败。而放弃原语则将计划视为模型正在 下注 的对象,每一次迭代的问题都是:“基于我现在的认知,这个注看起来还值得下吗?”第一种词汇借鉴自过程式编程。第二种则借鉴自搜索与规划,这才是真正深入探讨过何时该放弃某个分支的研究领域。

提供 continue、return 和 retry 的框架为智能体的工作交付了错误的抽象。大多数智能体循环所缺失的原语,是那种既能承认当前轨迹是错误的,又不必承认整个任务无法完成的原语。在这一原语作为系统框架的一等公民存在之前 —— 并且对状态、副作用、面向用户的消息以及上下文移交有明确的契约定义 —— 每个智能体都会在注迷途的分支上不断钻研优化,而每个团队也都会继续将症状诊断为模型问题,而忽略了真正的问题其实出在他们让模型运行的那个循环里。

References:Let's stay in touch and Follow me for more thoughts and updates