超越暴力中止:智能体的分级循环治理(警告 → 阻断 → 停止)
内容提要
针对智能体重复调用工具的死循环,文章提出分级循环治理机制:第二次相同调用正常执行并注入提示引导模型自我纠正;第三次跳过执行并返回合成错误;第四次安全结束回合并保留上下文供用户介入。机制通过规范化参数哈希匹配,并区分只读与写入工具设置不同阈值。
延伸解读
为什么粗暴中止会丢失上下文
文章指出,传统处理循环的两种方式都有明显缺陷:放任运行直到 max_turns 耗尽,会浪费大量 API 调用并最终以笼统的“超出最大轮次”错误崩溃;而一旦检测到重复就立即抛异常中止,则会让已积累的调查上下文和进度全部丢失。这两种做法都没有区分模型是暂时卡住还是真正陷入死循环,因此分级治理的核心思路是先引导、再物理阻断、最后才安全停止。
只读与写入工具应区别对待
文章强调,判断模型是否真的在循环需要区分工具类型。反复用只读工具(如 grep、view_file)检查同一文件往往是良性的,可以容忍更高阈值;但用相同参数重复执行写入类操作(如 write_to_file 或写命令)则具有危险性。因此运行时将工具归类为 ReadOnly 或 Mutating,并对修改状态的操作施加更严格的阈值,以降低误判和风险。
规范化参数哈希避免误判
为了防止因 JSON 键顺序不同而产生漏判,守卫在计算 canonical_args_hash 前会递归排序 JSON 对象的键。这样 {"a": 1, "b": 2} 和 {"b": 2, "a": 1} 会得到相同的指纹。这一细节确保“相同调用”的判定基于语义等价而非字面顺序,从而让分级升级的触发更准确,减少因格式差异导致的漏报或误报。
合成错误维持协议完整性
在第二级阻断中,守卫会短路调用,跳过物理文件 I/O 或命令执行,并在协议层合成一个结构化错误返回给模型。文章解释,LLM 协议要求每个 tool_use 都必须有对应的 tool_result,返回合成错误既保持了协议对等性,又避免了无谓的计算浪费。这种设计让模型在收到阻断信号后被迫改变策略,而不是继续重复无效调用。
Q&A
智能体陷入重复调用工具的死循环时,传统的暴力中止方法有什么问题?
传统方法要么让循环一直运行到 max_turns 耗尽,浪费大量 API 调用并以通用错误崩溃;要么一检测到重复就立即抛出异常中止任务,导致累积的调查上下文和进度全部丢失。
分级循环治理机制的三层升级阶梯分别是什么?
第一层 Warn:第二次相同调用正常执行,并在工具结果中注入引导提示;第二层 Block:第三次相同调用跳过实际执行,返回合成错误;第三层 Halt:第四次重复调用时安全结束当前回合并触发 RepeatedToolCalls,保留上下文供用户介入。
Warn 层是如何工作的?模型会因此自我纠正吗?
当同一工具以相同规范化参数第二次出现时,工具正常执行,完成后运行时在 ToolResult 末尾追加系统通知,提醒模型不要重复相同调用,应调整搜索模式、更改目标路径或切换工具。大多数模型会在下一轮立即调整策略。
Block 层拦截调用时,为什么返回合成错误而不是直接不返回结果?
因为 LLM 协议要求每个 tool_use 都必须有对应的 tool_result。返回合成错误(ToolResultContent::Error)既保持了协议一致性,又避免了浪费计算资源。
Halt 层触发后,用户还能继续任务吗?
可以。Halt 层会结束当前回合并触发 RepeatedToolCalls,UI 和日志会明确标识出有问题的工具名称和参数。之前的会话历史完整保留在磁盘上,用户可以通过后续提示引导智能体,而无需从头重新运行。
如何确保参数匹配时不会因为 JSON 键顺序不同而漏判重复调用?
守卫在计算 canonical_args_hash 之前,会递归地对 JSON 对象的键进行排序,这样 {"a": 1, "b": 2} 和 {"b": 2, "a": 1} 会生成相同的指纹,避免因键顺序变化导致的误判。
只读工具和写入工具在循环治理中是否有不同的阈值?
是的。工具被分类为 ReadOnly 或 Mutating,对于会修改状态的写入工具,会应用更严格的阈值,因为重复执行相同的写入操作风险更高。