跑复杂任务时,Agent 最容易出现的一种糟糕状态就是原地打转。
比如某个正则没搜到内容,模型就换着花样连续调 3~4 次完全相同的 grep_search;或者某个文件不存在,它依然反复用同一个路径去调用 view_file。每一次调用返回的都是空结果或错误,但模型依然固执地在原地重试。
以前处理死循环的手段一般很粗暴:
- 硬跑到 max_turns 耗尽:白白烧掉几十轮 API 调用,最后报个“超出最大轮次”崩溃;
- 检测到重复直接 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 变更工具:只读工具(如
grep、view_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 预算,也让长任务的完成率有了明显改善。