我的 AI Agent 接到任务后陷入了死循环,反复用几乎一样的参数重试同一个工具几十次,既不放弃也不换方法,直到耗尽预算把任务和钱都烧没了,我对着 Agent 推理循环不保证收敛这个坑排查了大半天的复盘
这是我做"自主推理的 AI Agent"时,栽的一个关于"循环与收敛"的大跟头。它让我明白:一个能自己"思考→行动→观察→再思考"的 Agent,虽然很强大,但它的这个循环不保证会停下来、也不保证会朝正确方向前进——它完全可能原地打转、反复犯同一个错,像一个钻进牛角尖出不来的人。
需求是做一个 ReAct 范式的 Agent:它在一个循环里"思考下一步→调用工具→观察结果→再思考",直到完成任务。大多数任务它都能漂亮地完成。可某次,它接到一个任务后,卡死了——不是崩溃,而是无穷无尽地循环:
# ReAct Agent 主循环(有问题的版本)
def run_agent(task):
history = [task]
while True: # ★★★ 致命: while True, 没有终止上限! ★★★
thought = llm.think(history) # 模型思考下一步
if thought.is_final_answer:
return thought.answer # 只有模型"自己决定结束"才会退出
# 否则调用工具
result = call_tool(thought.tool, thought.args)
history.append(result)
# 然后无限循环下去...
这个 while True 循环,在 Agent "正常推进"时没问题——它思考、行动、观察,一步步逼近答案,最后给出 final answer 退出。可那次,它陷入了一个死循环:它调用一个工具,工具返回了一个它"理解不了"的错误;它"思考"了一下,决定……用几乎一模一样的参数,再调一次同一个工具;结果还是同样的错误;它再"思考",又决定再试一次……就这样,它在"同一个失败动作"上反复横跳了几十次,既不肯放弃,也不会换个思路,更不会向我求助。直到把我设的 LLM 调用预算烧得精光,任务失败,钱也白花了。我盯着日志里那几十条几乎一模一样的"思考→调用同一工具→同样的错误",意识到问题的本质:我给了 Agent 一个"自主循环"的能力,却没有给它"循环必须收敛/必须终止"的任何保障。
第一件事:看清真相——Agent 的推理循环不保证收敛,可能原地打转
我去深入反思了 Agent 这种"自主循环"架构的脆弱性,才彻底明白这个"死循环烧钱"的根源——Agent 的"思考→行动→观察"循环,本质是由不确定的、概率性的大模型在驱动;它不像传统算法那样有数学上保证收敛/终止的逻辑;它完全可能陷入"重复同一个无效动作""在几个动作间来回横跳""越想越偏"等不收敛的状态,而我那个 while True 没有任何机制能打破它。
Agent 推理循环不收敛的真相
# 1. Agent(ReAct等)的本质: 一个"思考→行动→观察→再思考"的【循环】,
# 由大模型在每一步决定"下一步做什么", 直到模型认为任务完成。
# 2. 关键问题: 这个循环【不保证收敛, 也不保证终止】!
# - 驱动它的是【概率性的大模型】, 不是有数学保证的算法;
# - 模型可能在某一步"卡住", 然后在错误的状态里【打转】, 而没有任何
# 内在机制强迫它"必须取得进展"或"必须停下来"。
# 3. 常见的不收敛模式:
# a) 重复同一动作: 工具报错 → 它用几乎一样的参数再试 → 还报错 → 再试...(本文)
# b) 来回横跳: 在动作A和动作B之间反复切换, 不前进
# c) 越想越偏: 一步步偏离正确方向, 越走越远
# d) 不会放弃/求助: 卡住了既不承认失败、也不向人求助, 死磕
# e) 误以为没完成: 任务其实做完了, 但它judged为没完成, 继续做
# 4. 我的代码致命点: while True + "只有模型自己说完成才退出"
# - 把"循环何时停"完全交给了【不可靠的模型】;
# - 模型一旦陷入不收敛, 就没有任何外力能打断它 → 无限循环 → 烧光预算。
# 5. 根本认知: "自主性"是Agent的力量, 但自主性也意味着"可能失控";
# 一个能自己决定做什么的系统, 也可能自己决定"一直做错的事";
# 必须用【外部的、确定性的约束】给这种自主性套上"缰绳和安全网"。
# 核心: Agent的思考-行动循环由概率性大模型驱动, 不保证收敛或终止, 可能重复无效动作/来回横跳/
# 死磕不放弃; while True式地把"何时停"完全交给模型是危险的, 必须用外部确定性约束兜底。
真相大白,我幡然醒悟。原来 Agent 的"思考→行动→观察"循环,本质是由不确定、概率性的大模型在每一步决定"下一步做什么";它不像传统算法那样有数学上保证收敛/终止的逻辑。这个循环不保证收敛、也不保证终止——常见的不收敛模式有:重复同一动作(工具报错就用几乎一样的参数再试,本文)、来回横跳(在 A、B 间反复切换不前进)、越想越偏、不会放弃/求助(卡住了死磕)、误以为没完成。而我代码的致命点是 while True + "只有模型自己说完成才退出"——把"循环何时停"完全交给了不可靠的模型;模型一旦陷入不收敛,就没有任何外力能打断它,无限循环、烧光预算。根本认知是:"自主性"是 Agent 的力量,但自主性也意味着"可能失控"——一个能自己决定做什么的系统,也可能自己决定"一直做错的事";必须用外部的、确定性的约束,给这种自主性套上"缰绳和安全网"。
第二件事:正解——用外部确定性约束给自主循环套上缰绳和安全网
搞懂了原理,正解就清晰了:用最大步数/预算硬上限、重复动作检测、把工具错误清晰反馈给模型、允许 Agent 承认失败/求助等外部确定性机制,给自主循环兜底。
def run_agent(task, max_steps=15, budget=None):
history = [task]
recent_actions = [] # 记录最近的动作, 用于检测重复
for step in range(max_steps): # ★ 1. 硬上限: 最多走 max_steps 步, 绝不while True
# ★ 2. 预算检查
if budget and budget.spent() >= budget.total:
return "已达预算上限, 任务未完成"
thought = llm.think(history)
if thought.is_final_answer:
return thought.answer
# ★ 3. 重复动作检测: 如果在反复做同一个动作, 打破它
action_sig = (thought.tool, str(thought.args))
recent_actions.append(action_sig)
if recent_actions.count(action_sig) >= 3: # 同一动作做了3次
# 明确告诉模型"你在重复, 换个思路", 而不是让它继续
history.append("提示: 你已多次尝试同一操作且失败, 请换一种方法, 或承认无法完成。")
recent_actions.clear()
continue
result = call_tool(thought.tool, thought.args)
# ★ 4. 工具错误要【清晰反馈】给模型(别让它瞎猜/盲目重试)
if result.is_error:
history.append(f"工具返回错误: {result.error}。请分析原因, 调整方法, 别重复同样的调用。")
else:
history.append(result)
# ★ 5. 到达最大步数仍没完成: 优雅兜底, 而非无声死循环
return "已达最大步数, 任务未能完成。已尝试: " + summarize(history)
# ====== 正解补充: 允许并引导 Agent "承认失败/求助" ======
# - 在system prompt里明确告诉它: "如果多次尝试仍无法完成, 请明确说'我无法完成',
# 而不是无限重试。" → 给它一个"体面退出"的选项, 别逼它死磕。
# - 对需要的场景, 让它能"向人类求助"(human-in-the-loop)。
# ====== 正解补充: 让每一步都"必须有进展"的设计 ======
# - 检测"是否在取得进展"(信息增量/状态变化), 长时间无进展就介入。
# - 给Agent清晰的子目标和终止条件, 让它知道"什么算完成"。
# 核心: 给自主循环套外部确定性约束——最大步数/预算硬上限(绝不while True)、重复动作检测并打破、
# 工具错误清晰反馈引导换策略、允许承认失败/求助、到顶优雅兜底; 用确定性给概率性的自主性兜底。
修复的核心,是"用外部确定性约束给自主循环兜底"。第一,最大步数硬上限——for step in range(max_steps) 最多走 N 步,绝不 while True。第二,预算检查——超预算就停。第三,重复动作检测——记录最近动作,同一动作做了 3 次就明确告诉模型"你在重复,换个思路或承认无法完成",打破循环。第四,工具错误清晰反馈——把错误信息和"别重复同样的调用"的提示反馈给模型,别让它瞎猜盲目重试。第五,到达上限优雅兜底——返回"已达最大步数,任务未能完成"而非无声死循环。还要允许并引导 Agent 承认失败/求助(在 system prompt 里给它"体面退出"的选项、需要时 human-in-the-loop),以及让每一步必须有进展(检测信息增量、给清晰子目标和终止条件)。归根结底:给自主循环套外部确定性约束——最大步数/预算硬上限、重复动作检测并打破、工具错误清晰反馈引导换策略、允许承认失败/求助、到顶优雅兜底;用确定性给概率性的自主性兜底。
第三件事:Agent 自主性相关的其他常见坑
排查后我把 Agent "自主性"相关的其他常见坑也系统梳理了一遍。
Agent 自主性相关的其他坑
# 1. 推理循环不收敛(本文): 死循环/重复动作。→ 步数上限+重复检测+兜底。
# 2. 没有预算/成本控制: 自主多步=不可预测的token消耗。→ 设预算硬上限。
# 3. 工具错误不反馈或反馈不清: Agent不知错在哪, 只能瞎试。→ 清晰反馈错误。
# 4. 不会"放弃/求助": 卡住也死磕。→ 给它体面退出和求助人类的选项。
# 5. 目标/子目标迷失: 多步后忘了最初要干嘛、或偏离目标。→ 始终带着明确目标。
# 6. 误判任务完成: 没做完却说完成, 或做完了还继续。→ 明确的完成判定标准。
# 7. 无进展检测缺失: 长时间原地踏步无人察觉。→ 检测进展, 无进展就介入。
# 8. 缺少可观测性: Agent在循环里干了啥不可见, 出问题难排查。→ 记录每步思考/动作。
# 共同根源: Agent的"自主性/Agency"是把双刃剑——它带来强大的自主解决问题能力,
# 也带来"可能自主地、不受控地做错事/陷入循环/烧钱"的风险; 自主越强, 越需要约束和监控。
# 核心: Agent的自主性需要用"外部约束(步数/预算上限)、引导(明确目标/允许求助/清晰反馈)、
# 监控(进展检测/可观测性)"来驾驭; 自主不等于放任, 给它缰绳、安全网和仪表盘。
排查让我把 Agent 自主性的其他坑也梳理清了。一、推理循环不收敛(本文)。二、没有预算控制(自主多步=不可预测的 token 消耗)。三、工具错误不反馈或不清(Agent 只能瞎试)。四、不会放弃/求助(卡住死磕,给它体面退出选项)。五、目标/子目标迷失(多步后忘了要干嘛)。六、误判任务完成。七、无进展检测缺失。八、缺少可观测性(干了啥不可见,记录每步思考/动作)。它们的共同根源是:Agent 的"自主性/Agency"是把双刃剑——它带来强大的自主解决问题能力,也带来"可能自主地、不受控地做错事/陷入循环/烧钱"的风险;自主越强,越需要约束和监控。核心是:Agent 的自主性需要用"外部约束(步数/预算上限)、引导(明确目标/允许求助/清晰反馈)、监控(进展检测/可观测性)"来驾驭;自主不等于放任,给它缰绳、安全网和仪表盘。下面这张图,是这次死循环的成因与解法:
第四件事:Agent 自主循环的"约束/引导/监控"三类机制对照表
这次踩坑后,我把驾驭 Agent 自主循环的机制按"约束、引导、监控"三类整理成一张表。
| 类别 | 机制 | 作用 |
|---|---|---|
| 约束(硬限制) | 最大步数、token预算上限 | 无论如何不会无限/烧爆 |
| 约束 | 重复动作检测、超时 | 打破死循环/卡死 |
| 引导(往对的方向) | 清晰目标、工具错误反馈 | 帮它取得进展、别瞎试 |
| 引导 | 允许承认失败/求助人类 | 给体面退出, 别逼死磕 |
| 监控(看得见) | 记录每步思考/动作 | 可观测, 出问题能排查 |
| 监控 | 进展检测、异常告警 | 无进展/异常时及时介入 |
这张表把驾驭 Agent 的手段分成了清晰的三类。核心是:驾驭一个自主的 Agent,需要"约束(给它划定不可逾越的硬边界,如步数/预算)、引导(帮它朝正确方向前进,如清晰目标/错误反馈/允许求助)、监控(让它的行为可见可控,如日志/进展检测)"这三类机制协同——它们分别回答"它最多能做到哪""它该往哪走""它现在在干嘛"。它给我的最大启发是:"管理一个自主的 AI Agent",和"管理一个能力强但需要引导的下属/团队",有着惊人的相似:你既要给他明确的边界和规则(约束:别超预算、别死磕)、又要给他清晰的目标和及时的反馈(引导:知道做什么、做错了告诉他)、还要对他的工作保持可见和监督(监控:知道他在干嘛、卡住了能介入)。这其实揭示了一个有趣的趋势:随着 AI Agent 越来越自主、越来越像一个"能动的执行者"而非"被动的函数",我们对它的"对待方式",也越来越从"调用一个工具"转向"管理一个'下属'"——管理学里那些关于"授权与控制、目标与反馈、信任与监督"的智慧,正在变得和工程技术同样重要。用"约束+引导+监控"三位一体地驾驭自主 Agent、像管理一个能干但需引导的下属一样管理它——是构建可靠 Agent 系统的核心心法。
第五件事:自主性的"光谱"——给多大自由要权衡
这次也让我思考了一个更本质的问题:到底该给 Agent 多大的自主权?
| 自主程度 | 特点 | 适用 / 风险 |
|---|---|---|
| 低(固定流程) | 按预设步骤走, 几乎不自主决策 | 可靠可控, 但不灵活 |
| 中(受约束的自主) | 能自主但有硬约束+关键处人工确认 | 多数生产场景的平衡点 |
| 高(完全自主) | 放手让它自己规划执行 | 灵活强大, 但难控、易失控 |
这张表道出了自主性是个"光谱"。核心是:Agent 的自主程度不是"有或无",而是一个从"固定流程(低自主、可靠但不灵活)"到"完全自主(高自主、强大但易失控)"的光谱;而大多数生产场景的最佳点,往往在中间——"受约束的自主"(让它自主决策以发挥灵活性,但用硬约束和关键处的人工确认来保证可控)。它给我的深刻启发是:"给系统/AI 多大的自主权",本身是一个需要根据场景的风险和收益来权衡的设计决策,而不是"越自主越先进越好";自主性带来灵活和能力,但也带来不可控和风险;在"错误代价高"的场景(涉及钱、删数据、对外操作),应该收紧自主、加更多约束和人工确认;在"错误代价低、需要灵活探索"的场景,才可以放开更多自主。这让我对设计 Agent 有了更成熟的判断:不要盲目追求"全自主的炫酷",而要冷静地问:"这个任务,出错的代价有多大?我能容忍多少不可控?据此,我该把自主性的旋钮拧到哪个位置?";在自主性的光谱上,为每个具体场景找到那个"灵活性与可控性的最佳平衡点",才是工程上成熟的做法。把自主性当成一个可调的旋钮、根据场景的风险收益权衡该给多大自由——是这个死循环坑,在技术之上,带给我的关于"如何设计自主系统"的更高层思考。
第六件事:设计 Agent 自主循环时,我现在的判断习惯
现在每当我设计一个 Agent 的自主循环,我都会按这张图先想清楚:
这张图的精髓,是"绝不 while True,给自主循环加硬上限、破循环、清晰反馈、允许退出、可观测、按风险定自主度"。绝不 while True、加最大步数+预算硬上限;检测重复动作和无进展并打破/介入、工具出错清晰反馈引导换策略、卡住允许承认失败/求助、记录每步保持可观测。高风险操作还要收紧自主、关键处人工确认。这套习惯,让我设计 Agent 时,从"while True 让它自己跑"变成了"给自主循环套上缰绳、安全网和仪表盘"——核心始终是:Agent 自主循环不保证收敛,必须用外部确定性约束兜底,自主不等于放任。
我立下的几条规矩
这场"Agent 死循环烧光预算"的事故,换来了我做 AI Agent 时,刻进骨子里的几条铁律:
- Agent 推理循环不保证收敛。它可能原地打转、反复犯同一个错。
- 绝不用 while True。必须有最大步数和 token 预算硬上限。
- 检测并打破重复动作。反复做同一失败动作要提示它换思路。
- 工具错误要清晰反馈给模型。别让它瞎猜、盲目重试。
- 允许 Agent 承认失败/求助。给它体面退出,别逼它死磕。
- 保持可观测 + 进展检测。记录每步,无进展及时介入。
- 按风险定自主度。高风险收紧自主、加人工确认。
附:一个内建多重安全机制的 Agent 循环骨架
这次踩坑后,我把"步数上限 + 预算 + 重复检测 + 错误反馈 + 优雅兜底"全部内建进了一个 Agent 循环骨架,新 Agent 都从它起步,从根上杜绝"while True 失控":
from dataclasses import dataclass, field
@dataclass
class AgentResult:
status: str # "completed" | "max_steps" | "budget" | "gave_up" | "stuck"
answer: str = ""
steps: int = 0
trace: list = field(default_factory=list) # 每步记录, 保证可观测
def run_agent(task, tools, *, max_steps=15, max_budget_tokens=None,
max_repeat=3) -> AgentResult:
history = [task]
recent = [] # 最近动作签名, 检测重复
spent = 0
trace = []
for step in range(max_steps): # ★ 硬上限, 绝不while True
if max_budget_tokens and spent >= max_budget_tokens:
return AgentResult("budget", steps=step, trace=trace) # ★ 预算兜底
thought = llm.think(history)
spent += thought.tokens_used
trace.append({"step": step, "thought": thought.text, "tool": thought.tool}) # ★ 可观测
if thought.is_final_answer:
return AgentResult("completed", thought.answer, step, trace) # 正常完成
if thought.give_up: # ★ 允许它体面承认失败
return AgentResult("gave_up", thought.reason, step, trace)
sig = (thought.tool, str(thought.args))
recent.append(sig)
if recent[-max_repeat:].count(sig) >= max_repeat: # ★ 重复动作检测
history.append("你在重复同一无效操作。请换一种完全不同的方法, "
"或如果确实无法完成, 明确说明放弃。")
recent.clear()
continue
try:
result = call_tool(thought.tool, thought.args, tools)
history.append(f"结果: {result}")
except Exception as e:
# ★ 工具错误清晰反馈, 引导换策略(而非让它盲目重试)
history.append(f"工具出错: {e}。请分析原因后换方法, 不要重复同样的调用。")
return AgentResult("max_steps", steps=max_steps, trace=trace) # ★ 到顶优雅兜底
# 调用方根据 status 处理: completed正常用; max_steps/budget/stuck/gave_up 则降级/告警/转人工
# r = run_agent(task, tools, max_steps=15, max_budget_tokens=50000)
# if r.status != "completed": alert_and_fallback(r)
# 核心: 把步数上限/预算/重复检测/错误反馈/承认失败/可观测trace/优雅兜底, 全内建进Agent循环骨架;
# 返回结构化的status让调用方对各种"非正常结束"都能妥善处理, 从根上杜绝while True失控。
这个 Agent 循环骨架,是我这次踩坑后最有价值的工程沉淀。它把我用一次"烧光预算"换来的所有教训——步数硬上限、预算上限、重复动作检测、工具错误清晰反馈、允许体面放弃、每步可观测 trace、到顶优雅兜底——全部内建进了一个标准的 Agent 主循环;此后新写的 Agent 都从它起步,从第一行起就不可能 while True 失控。它还有一个关键设计:返回一个带 status 的结构化结果(completed/max_steps/budget/gave_up/stuck),而不是简单返回一个答案或抛异常——这让调用方能显式地、分门别类地处理每一种"非正常结束"(超步数、超预算、卡住、放弃),做相应的降级、告警或转人工,而不是只考虑"成功"这一条路。这正是我想分享的核心思想:设计一个"可能以多种方式结束(包括各种失败/异常结束)"的系统时,要把"它会怎样结束"显式地、完整地建模出来(用一个状态枚举),并让调用方对每一种结束方式都有明确的应对;而不是只设计"happy path(成功)",把所有"不成功"的情况都笼统地当成"出错"或干脆不管。因为对于 Agent 这种"结果高度不确定"的系统,"没成功"是常态而非例外,且"没成功"有很多种(超时、超预算、卡住、主动放弃),每一种可能需要不同的处理;把这些"失败的形态"都正视、建模、并妥善处理,才能构建出真正健壮、可运维的 Agent 系统。把 Agent 的多种结束状态显式建模、让每种"非正常结束"都有明确应对——这,是我用一次死循环的事故,换来的、关于"如何构建健壮的自主系统"的实用架构智慧。
写在最后
回头看,这场由"Agent 推理循环不收敛"引发的、死循环烧光预算的事故,真正教给我的,远不止"加个最大步数"这一个技巧。它让我对"自主性"这个 AI Agent 时代的核心特质,有了一次又向往又敬畏的深刻认识。我栽跟头,根源是我对 Agent 的"自主性"抱着一种天真的乐观:我以为给了它"自己思考、自己决定下一步"的能力,它就会聪明地、收敛地把任务做完——我把"自主"等同于了"可靠地朝目标前进"。可我忽略了硬币的另一面:一个能"自己决定做什么"的系统,同样有能力"自己决定一直做错的事";自主性赋予它解决问题的灵活,也同样赋予它陷入混乱、原地打转、不受控失控的可能;而驱动它的大模型是概率性的、没有收敛保证的,这种"失控的可能"就成了实实在在的风险。我给了它自由,却没给它"用好自由的边界和保障"。这让我领悟到一个深刻的认知:"自主性(Agency)"是一种强大但危险的力量——它越强,系统能自主完成的事越多(力量),但它能自主搞砸的事也越多、越不可预测(危险);赋予一个系统自主性,绝不能只赋予"自由",而必须同时赋予与之匹配的"约束、引导和监督";就像人类社会赋予个人自由的同时,也必须有法律、规则和监督来防止自由被滥用。这其实是 AI Agent 时代一个根本性的工程命题:随着我们构建的系统越来越"自主、能动",我们的工作重心,正从"编写确定的、一步步的指令(告诉它怎么做)",转向"为自主的系统设计好目标、边界、激励和安全网(告诉它做什么、不能越过什么界、卡住了怎么办),然后驾驭它的自主";如何"驾驭自主"——既充分释放它的能力,又牢牢守住可控的底线——是这个时代工程师必须修炼的新功课。对自主性怀有"既善用其力量、又敬畏其风险、并用约束引导监督去驾驭它"的成熟态度——这,是我用一次 Agent 死循环的事故,换来的、关于 AI Agent、也关于如何与"自主系统"共处的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次写 Agent 循环时,第一行就写下 for step in range(max_steps) 而不是 while True,那我对着那几十条一模一样的死循环日志、看着预算一点点烧光的这大半天,就值了。
—— 别看了 · 2026