跳过正文
  1. Posts/

告别暴力熔断:Agent 死循环的分级治理阶梯(Warn → Block → Halt)

·4 分钟· · · #AI #Agent #Rust
目录
AI Coding - 这篇文章属于一个选集。
§ : 本文

跑复杂任务时,Agent 最容易出现的一种糟糕状态就是原地打转。

比如某个正则没搜到内容,模型就换着花样连续调 3~4 次完全相同的 grep_search;或者某个文件不存在,它依然反复用同一个路径去调用 view_file。每一次调用返回的都是空结果或错误,但模型依然固执地在原地重试。

以前处理死循环的手段一般很粗暴:

  1. 硬跑到 max_turns 耗尽:白白烧掉几十轮 API 调用,最后报个“超出最大轮次”崩溃;
  2. 检测到重复直接 throw 抛异常:立刻中断任务。但前面辛辛苦苦跑出来的所有中间状态和排查上下文,全部跟着一起泡汤了。

这两种做法都不理想。我们在运行时里加了分级工具循环守卫(Graded Loop Guard),用三级阶梯来处理。

flowchart TD
  subgraph Ingestion[工具调用入参分析]
    A[收到模型工具调用] --> B[计算参数哈希 canonical_args_hash]
    B --> C[区分工具类型: 只读幂等 vs 状态变更]
  end

  subgraph Ladder[三级处置阶梯]
    C --> D{连续相同调用次数}
    D -->|第 1 次重复 Count=2| E[Warn: 执行工具并在结果追加纠偏引导]
    D -->|第 2 次重复 Count=3| F[Block: 拦截物理执行, 返回合成错误]
    D -->|第 3 次重复 Count=4| G[Halt: 终止当前 Turn, 透传 repeated_tool_calls]
  end

  subgraph Outcome[执行与反馈]
    E --> H[模型读到提示主动修正思路]
    F --> I[省去无效 IO 开销, 迫使更换工具]
    G --> J[保留完整上下文, 明确告知打转原因]
  end

  Ingestion --> Ladder

1. 为什么不能一刀切?
#

判断模型是不是在“打转”,有几个细节必须区分:

  • 只读工具 vs 变更工具:只读工具(如 grepview_file)重复查同一个文件很多时候无害,容忍度可以高一点;但变更工具(如 write_to_file 或执行写命令)如果重复调用且入参完全一致,往往极度危险。
  • 模型自己其实有纠错能力:很多时候模型只是陷入了某个局部死胡同。如果能在工具结果里明确提醒它一句“你已经用相同参数查了 2 次且没结果,请换个思路”,大多数现代模型在下一轮都能主动换个关键词或改用其他工具。

所以守卫的设计逻辑是:先提示纠偏,再物理拦截,最后才熔断。


2. 三级阶梯的具体行为
#

在主循环执行工具前的 before_tool_call 阶段做拦截判定:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoopGuardAction {
    /// 放行:首次或正常调用
    Allow,
    /// 警告:注入引导文本到 tool result,工具照常执行
    Warn,
    /// 拦截:阻止物理执行,合成错误结果返回
    Block,
    /// 终止:结束本轮交互
    Halt,
}

第一级:Warn(引导注入)
#

当检测到相同工具、相同入参在同一任务中出现第 2 次时:

  • 工具照常执行;
  • 工具返回结果后,系统在 ToolResult 末尾追加一段系统提示:

    [系统提示] 检测到你已连续以相同参数调用该工具且无新进展。请勿重复调用,请调整搜索词、修改路径或改用其他工具。

  • 绝大部分模型读到这句提示后,下一轮就会主动改策略。

第二级:Block(合成拦截)
#

如果模型无视警告,发起了第 3 次完全相同的调用:

  • 守卫直接短路,不再跑底层真实的文件 IO 或命令执行;
  • 在协议层合成一个标准的错误结果(ToolResultContent::Error)返回给模型,告知底层拒绝重复执行;
  • 大模型协议要求每个 tool_use 必须有对应的 tool_result 闭合,合成错误既维持了协议格式,又省去了无效开销。

第三级:Halt(安全熔断)
#

如果重复调用达到第 4 次(HALT_AFTER = 4),说明模型彻底卡死了:

  • 运行时主动结束本 Turn,触发 RepeatedToolCalls
  • 前端和日志明确显示具体的工具名称与参数,告诉用户因为重复调用某个命令而暂停;
  • 之前的完整会话历史全部保留在本地,用户可以在当前进度上补一句话继续引导,不用从头重跑。

3. 入参等价性判定
#

为了防止因 JSON 键值顺序不同导致误判,守卫在计算 canonical_args_hash 时,会先对 JSON 的 Key 进行递归排序后再做哈希,保证 {"a": 1, "b": 2}{"b": 2, "a": 1} 算出来的指纹完全一致。

同时将工具分为只读(ReadOnly)和变更(Mutating),对写操作赋予更严格的拦截策略。


4. 总结
#

在实际跑任务时,大部分边缘打转在 Warn 阶段就被模型自行纠正过来了。

把粗暴的强行中止,改成由浅入深的警告、拦截与熔断阶梯,既保护了 API 预算,也让长任务的完成率有了明显改善。

AI Coding - 这篇文章属于一个选集。
§ : 本文

相关文章