Skip to main content

如果 Agent 陷入了死循环怎么办?

· 4 min read

Agent 死循环不是极端情况,是给模型自由度必须付的代价。

  • 直接原因:工具调用失败后盲重试——不换参数,反复撞同一堵墙。
  • 深层原因:循环缺少收敛信号,Thought 和 Action 之间没有"该停了"的显式判断。
  1. 第一层防御max_steps 硬限制,两行代码兜住 80% 的翻车。
  2. 第二层防御:把 Finish 做成一个 Action,循环有了天然终止条件。
  3. 第三层防御system prompt 里写死"重试失败就换策略",主动避障。
  • 工程底线:step_id + 全局超时 + 连续重复 Action 检测,别信 Agent 能自己收敛。
  • 三层防御一起上,单靠任何一层都有盲区。

Agent 为什么会死循环

ReAct 循环的代价是模型在"想"和"做"之间可以无限切——它每轮都在评估"还能不能再调个工具多搞点信息",而不是"够不够了"。模型没有内置的"停"。

实际跑下来,死循环分三种:

类型症状根因
重试死循环工具返回 error → 相同参数再调 → 继续 error模型分不清"暂时失败"和"逻辑错误"
摇摆死循环A 工具 → B 工具 → A 工具 → B 工具两个工具互相依赖,谁都没给够决策信息
探索死循环上下文越来越长,模型忘了原始任务没有目标锚定,开始"看看还有啥能玩的"

三种里最致命的是第一种——短、快、密集,几秒内烧掉大量 token。摇摆型至少还在换工具,重试型是纯浪费。

三层防御,缺一不可

只加 max_steps 我踩过坑:模型在 step 49 返回了一句"抱歉我还在分析中"——用户体验比直接报错还差。单靠一层防御总有盲区。

第一层:硬限制

MAX_STEPS = 20
for step in range(MAX_STEPS):
response = agent.step()
if response.is_final:
return response.content
return "任务超时,已终止"

max_steps 是保险丝,不是终止策略。它的作用是防止无限烧 token,但不保证截止时任务已完成。

设多少?代码生成 30-50 步,信息检索 10-15 步。通用原则:宁可在生产环境让 Agent 提前终止,也不要让它无限烧 token

第二层:让模型主动喊停

在 system prompt 中加一个 Finish Action:

当你确认任务已完成或已收集足够信息时,
调用 finish 工具并给出最终答案。不要继续调用其他工具。

关键点:Finish 必须是显式的工具调用,不是让模型自己决定"下面我开始回答"。Thought 没有约束力——模型可以在 Thought 中说"我觉得够了"然后继续调下一个工具。

两种方式我对比过:

  • 只靠 prompt 说"完成任务后直接回答" → 模型大约 30% 概率继续调工具
  • 把 Finish 做成一个 Action → 终止率接近 100%

区别在于:前者是"建议",后者是"接口契约"。模型对 API 契约的遵守远比自然语言指令严格。

第三层:重试自动避障

这是性价比最高的优化——system prompt 里加一条:

如果同一个工具调用连续失败两次,必须换一个方法。
不要用相同的参数重试失败的调用。

模型默认行为是"再试一次",很多场景重试确实有效——网络抖动、工具偶发异常。但它区分不了"暂时失败"和"用错参数"——后者重试一万次也没用。

主动避障 + max_steps 配合,死循环率能压到很低的水平。

工程侧的兜底

Agent 侧的约束不够,工程侧也得有防线——说白了,不该完全信任一个 LLM 驱动的循环能自己收敛。

  • step_id:每次 Action 带递增 id,前端渲染时检测连续 N 次相同 Action 就截断
  • 全局超时:不管跑到第几步,60 秒没完成就熔断
  • 重复 Action 检测:连续 3 次调同一工具且参数完全相同,直接终止

这三个是"不相信 Agent 的防御"。工程上把 trust-but-verify 反过来:verify first, trust later。

说穿了

死循环不是 bug,是交互式架构的必然产物。你把决策权交给模型,就得同时给它边界——max_steps 是墙,Finish Action 是门,retry 规则是导航。三层缺一层都能跑,但总会在某个场景下翻车。

具体数字取决于你的任务,但一条原则不变:宁可让 Agent 提前终止并告知用户,也不要让它默默烧 token。