Agent为什么会死循环?

Agent为什么会死循环?

------无限Tool调用、ReAct循环、上下文污染与业界解决方案深度解析

真正优秀的Agent,不是最聪明,而是最稳定。


一、为什么Agent会死循环?

先看几个真实案例

先说 Claude Code。2025年8月,GitHub Issue #6004 记录了一个经典故障:用户让 Claude Code 为 Go 项目补充测试文件。Claude 正确识别了需要读取的5个已有测试文件,但在读取完成后,它进入了一个诡异的循环:

go 复制代码
Read test_a.go → Read test_b.go → Read test_c.go → 
Read test_d.go → Read test_e.go → [Compact conversation] → 
Read test_a.go → Read test_b.go → ...

它反复读取同样的文件、触发同样的对话压缩、然后重新开始------但一次都没有真正创建任何测试文件。这是一个过程失败(Process Failure),而非能力不足:所有需要的信息都已在第一轮获取,但控制流卡死了。

OpenHands 社区的典型场景更直观:Agent 被告知修复一个 SyntaxError。它修改了代码、执行、仍然报错------同样的语法错误。然后它再次修改、再次执行、再次报错。循环往复。但它从来不去想:"也许我应该换一种修复方式。"

Cursor 用户也经常碰见:Agent 在修改一个 React 组件后,因为缺少可以验证"修改是否正确"的测试或检查命令,于是它反复调整同一个文件的同一段代码------细微的参数变化、改回原名、再改回新名。"没有验证手段,Agent 就不知道该停手。"

AutoGen 的多 Agent 对话更离谱:如果没有设置 max_turnsis_termination_msg,两个 Agent 可以持续对话直到 API 预算耗尽------互相讲笑话、互相夸赞、永远不会主动说"够了"。

LangGraph 开发者碰到的是另一个版本:GRAPH_RECURSION_LIMIT。当 StateGraph 中的两个节点互相连接(A → BB → A)而没有设置 END 时,图会无限执行直到触碰递归限制。

核心问题

你可能会问:为什么模型越来越强(GPT-4 → Claude 4 → Gemini 2.5),但死循环问题反而越来越突出?

答案看起来矛盾但合理:正是因为模型能力足够强,所以它们被赋予了更多自主权。 当 Agent 能自己决定"要不要调用工具""调用哪个工具""要不要继续思考"时,它也获得了"永远不停下来"的能力。

Agent 的自主性 = Agent 的安全性风险。两者成正比。


二、Agent执行流程到底是什么?

不要先讲死循环------先理解正常循环

Agent 不是"一次提问、一次回答"的对话模型。它的核心是一个循环。这个循环本身不是Bug------它是 Agent 之所以是 Agent 的根本原因。

text 复制代码
  ┌────────────┐      ┌──────────────────┐      ┌──────────┐
  │  用户输入   │─────▶│ Planner / System │─────▶│  Reason  │
  └────────────┘      └──────────────────┘      └────┬─────┘
                                                       │
                            ┌──────────────┐         │
                            │   需要工具?   │◀────────┘
                            └──────┬───────┘
                            是 /    \ 否
                           ▼        ▼
                     ┌────────┐  ┌────────┐
                     │  Tool  │  │ Finish │
                     └───┬────┘  └───┬────┘
                         │           │
                    ┌────┴────┐      │
                    │Observation│     │
                    └────┬────┘      │
                         └────────────┘

这个循环在行业内有不同叫法:Agent LoopTool Use LoopReasoning LoopAction-Observation Cycle,但本质相同------Agent 在"思考→行动→观察→再思考"之间循环,直到达成目标。

为什么一定会循环?

想象你要 Agent "帮我找到 Python 项目中所有未使用的导入"。它的执行过程天然就是循环的:

  1. Think:我需要先列出所有 Python 文件
  2. Act:执行 find . -name "*.py"
  3. Observe:得到了237个文件路径
  4. Think:需要逐个分析每个文件的导入和实际使用
  5. Act:执行 grep "import" file1.py
  6. Observe:file1.py 导入了 os, json, requests
  7. Think:需要检查哪些被实际使用了
  8. Act:分析文件中 os, json, requests 的使用
  9. Observe:requests 没有被使用
  10. ...继续处理 file2.py, file3.py...

这个循环不是 Bug,它是 Agent 完成复杂任务的必要手段。

什么时候循环变成死循环?

循环变成问题,取决于三个维度的判断:

判断维度 正常循环 死循环
进展 每轮产生新信息或状态变化 重复相同动作,无实质性推进
收敛 逐步逼近目标 偏离目标,或在原地打转
成本 消耗与复杂度匹配 消耗远超任务价值

用代码化的判断标准:

python 复制代码
# 这不是死循环
for file in project_files:
    analyze(file)  # 每次都处理不同文件
    # → 有进展,会收敛

# 这是死循环
while True:
    read_same_file()      # 反复读取同一文件
    compact_context()     # 触发压缩
    # → 无进展,不会收敛

Claude Code 的源码(query.ts)最直白地体现了这一点:它就是一个 while(true) 循环,只有当模型不再输出 tool_use、达到预算上限、或用户中断时才退出。整个 Agent 的生命就是在这个 while(true) 里度过的。


三、Agent为什么会无限调用Tool?

工具调用(Tool Call)是 Agent 与外部世界交互的唯一方式。也是死循环最常见的原因。以下是导致无限 Tool 调用的10大原因:

完整故障分析

text 复制代码
          ┌──────────────────┐
          │  Agent 调用 Tool  │
          └────────┬─────────┘
                   │
        ┌──────────┼──────────┐
        │          │          │
        ▼          ▼          ▼
  ┌──────────┐ ┌────────┐ ┌──────────┐
  │结果不满足│ │ Prompt │ │ 执行层面 │
  │结束条件? │ │ 设计?   │ │  问题?   │
  └────┬─────┘ └────┬───┘ └────┬─────┘
       │            │          │
  ┌────┴────┐   ┌───┴───┐  ┌───┴───┐
  │Tool结果 │   │缺少停止│  │LLM    │
  │不满足  │   │条件    │  │判断失败│
  │Tool返回│   │Tool描述│  │JSON   │
  │异常    │   │不清晰  │  │解析失败│
  │        │   │        │  │MCP异常 │
  │        │   │        │  │选错工具│
  └────┬────┘   └───┬───┘  └───┬───┘
       │            │          │
       └────────────┴──────────┘
                   │
                   ▼
          ┌──────────────┐
          │ 继续调用 Tool │
          └──────┬───────┘
                 │
                 ▼
          ┌──────────────┐
          │    有进展?    │
          └──────┬───────┘
            无 /    \ 有
           ▼          ▼
     ┌────────┐   ┌────────┐
     │ 死循环  │   │ 继续执行 │
     └────────┘   └────────┘

1. Tool结果不满足结束条件

最常见的原因。LLM 期待一个"完成"信号,但 Tool 返回的内容不够明确。

yaml 复制代码
Agent: 检查端口 3000 是否被占用
Tool: lsof -i :3000 → (空输出,端口未占用)
Agent: 不确定,再检查一次
Tool: lsof -i :3000 → (空输出)
Agent: 还是不确定,使用 netstat 检查
Tool: netstat -an | grep 3000 → (空输出)
...

根因:Tool 返回一个模糊结果(这里是一个空输出),LLM 无法判断"空输出 = 端口未占用 = 成功",于是反复验证。

2. Tool返回异常

OpenHands 的 StuckDetector 专门识别了一种模式:动作-错误循环 ------Agent 连续3次执行相同动作且均返回错误。典型场景是 Python 代码反复抛出相同的 SyntaxError,Agent 修改的不是语法错误,而是无关部分。

3. Prompt设计错误

给 Agent 的指令缺少明确的"完成标准"。比如只说"优化代码性能",但不说什么算"性能达标"(响应时间 < 200ms?内存下降 30%?),Agent 就会不断尝试优化,永远停不下来。

4. Tool描述不清晰

Tool 的 function description 如果过于模糊,LLM 就无法正确判断"这个 Tool 是否真的帮到了忙"。比如一个 Tool 叫 fetch_data,描述是 "获取数据"------获取什么数据?返回什么格式?成功标准是什么?LLM 拿到的就是一个黑盒。

5. LLM判断失败

大模型本身不是可靠的决策引擎。它可能在相同的输入下产生不同的输出,也可能因为 context 太长而"遗忘"已经完成的任务。这就是 Claude Code 无限压缩循环的根因------模型本应意识到"数据已获取 → 开始创建测试",但它判断错误,选择了重新读取。

6. 输出格式错误 / JSON解析失败

Tool 返回格式损坏时:

lua 复制代码
Tool: {"status": "succes  ← 缺少闭合引号,JSON 非法
Agent: (解析失败) 重试...
Tool: {"status": "success"}
Agent: 现在正确了,但前面已经浪费了一轮

7. MCP返回异常

MCP(Model Context Protocol)是一个分布式的 Tool 调用协议。当 MCP Server 超时、返回错误或连接中断时,Client 端的 Agent 可能将"异常"理解为"需要重试",而非"需要换方法"。

8. Tool选择错误

Agent 选用了错误的工具来解决问题。比如应该用 Replace 精确替换字符串,却用了 Write 重写整个文件------结果其他内容丢了,问题更复杂,需要更多 Tool 调用来修复。

9. 工具之间互相调用

在多 Agent 系统中,Agent A 调用 Tool B,Tool B 内部又触发 Agent C,Agent C 又调用 Tool A------形成跨 Agent 的循环。

10. 上下文信息丢失

当 Agent 执行了很多步之后,早期的关键信息可能因为上下文太长而被"遗忘"或"稀释"。Agent 可能回到起点,重新执行已经被完成的操作------不是它不想停,而是它不记得已经做过了。


四、为什么ReAct最容易死循环?

ReAct的经典模式

ReAct(Reasoning + Acting)是当前绝大多数 Agent 的基础范式。它的流程如下:

text 复制代码
  Thought ──▶ Action ──▶ Observation
     ▲                      │
     └──────────────────────┘
  • Thought:思考下一步做什么
  • Action:执行具体操作
  • Observation:观察操作结果
  • 循环:基于观察再次思考

为什么天然容易无限循环?

ReAct 论文(Yao et al., 2022)本身并没有专门讨论"无限循环"问题,但从其设计可以清晰看出三个内在风险:

风险一:没有内置终止机制

ReAct 的终止完全依赖 LLM 自行判断"任务已完成"。但 LLM 的判断本身是不确定的------同样的状态,不同的采样可能产生"继续"和"停止"两个相反的结论。更糟糕的是,当任务复杂时,LLM 倾向于过度谨慎:"我再检查一遍以确保万无一失。"

风险二:Thinking越来越长

随着循环次数增加,Thought 中包含的历史信息越来越多。每一轮 Thought 都在"回顾"之前的所有 Observation,导致 Thought 长度累加:

scss 复制代码
第1轮:Thought (50 tokens)
第2轮:Thought (150 tokens) --- 回顾了第1轮的Observation
第3轮:Thought (300 tokens) --- 回顾了第1、2轮
第5轮:Thought (800 tokens) --- LLM花费大量token在"记忆"而非"推进"

这导致两个问题:(1)Token成本指数增长;(2)LLM在长上下文中推理质量下降。

风险三:Observation污染Context

这是最隐蔽也最致命的问题。每次 Tool 调用返回的 Observation 都会进入上下文。如果 Observation 中包含了大量无关信息(比如运行测试输出了300行日志,但只有最后5行是关键错误),这些噪声会:

  • 稀释有效信息
  • 误导 LLM 关注无关细节
  • 推动上下文总量迅速膨胀

Claude Code 的源码验证了这一点:每轮迭代结束时,tool_result 以 user 消息类型拼回对话,该消息会出现在下一轮 API 调用的完整上下文中。如果一个任务需要30轮 Tool 调用,Agent 的上下文会包含30组完整的 Action-Observation 对。

改进方向

Reflexion(Shinn et al., NeurIPS 2023)提出了"语言强化学习":Agent 在任务失败后,不是简单地重试,而是生成一段"反思"------分析为什么失败、下次应该怎么做。这段反思存储在长期记忆中,帮助后续尝试避开同样的陷阱。

Self-Refine(Madaan et al., 2023)则是让 LLM 对自己的输出进行"自我批评和改进",在同一个推理步骤内迭代优化,而非反复调用外部工具。

ReWOO(Reasoning WithOut Observation, Xu et al., 2023)提出了更激进的思路:把"推理"和"工具执行"彻底解耦。Agent 先一次性生成完整的"推理链+工具调用计划",然后批量执行所有工具,最后用工具结果一次性生成最终答案。这从根本上消灭了"每轮 Observation 都回到循环起点"的结构------循环只有一轮。

说白了,这些方法没有改掉 ReAct 的底层循环逻辑。它们只是帮 Agent 更聪明地喊停,而不是让循环本身消失。


五、上下文污染(Context Pollution)

为什么Agent越来越"笨"?

如果你长期使用 Claude Code 或 Cursor Agent,一定有过这样的体验:Agent 一开始表现很聪明,但随着对话推进,它越来越迟钝、越来越容易犯错、甚至在简单任务上也出错。这不是模型变差了,是上下文被污染了。

五种核心污染形式

text 复制代码
  上下文窗口
  ┌────────────────────────────────────────────┐
  │  历史Observation    ──▶  上下文污染      │
  │  错误信息            ──▶                  │
  │  重复Tool输出        ──▶                  │
  │  Prompt膨胀         ──▶                  │
  │  Memory碎片         ──▶                  │
  └────────────────────────────────────────────┘
                          │
                          ▼
                   推理质量持续下降

Context Drift(上下文漂移)

2026年1月,一篇题为 "Agent Drift: Quantifying Behavioral Degradation in Multi-Agent LLM Systems" 的论文(arXiv:2601.04170)量化了这个现象。研究发现:

  • 任务成功率从 87.3% 下降到 50.6%(-42.0%),仅因为长时间交互
  • 人工干预需求从每任务 0.31 次暴增到 0.98 次(+216.1%)
  • 漂移在第 73 次交互时开始出现,到第 500 次交互后加速恶化

论文提出了三种漂移类型:

漂移类型 表现 示例
语义漂移 输出逐渐偏离原始任务意图 金融分析 Agent 从风险导向变成机会导向
协调漂移 多 Agent 间共识崩溃 路由 Agent 开始偏袒特定子 Agent
行为漂移 发展出初始交互中不存在的策略 合规 Agent 开始在对话历史中缓存中间结果

Context Pollution(上下文污染)

在 Agent 实际运行中,上下文窗口是一个有限资源。每次 Tool 调用的输出都会挤占这个空间。污染的来源包括:

  1. 历史 Observation 堆积:每轮循环都在上下文里留下一份 Observation。30轮过后,20%的上下文可能都是历史记录。

  2. 错误信息越来越多:每次失败的尝试都在上下文里留下错误日志。这些错误日志不仅占空间,还会误导 LLM:"上次 error 是 X,让我看看是不是又发生了 X。"

  3. 重复 Tool 输出 :当 Agent 多次调用同一个 Tool(比如反复 ls 同一个目录),重复的输出不仅浪费 Token,还制造了"回声效应"------LLM 看到的上下文中有大量相似内容。

  4. Prompt Injections 累积:Tool 输出可能包含不可信的外部内容(网页抓取、API 响应等),这些内容本质上是对 Agent 的一种"间接注入",可能改变 Agent 的行为方向。

Claude Code 的验证

Claude Code 源码分析(query.ts)证实,它的每次 API 调用都在处理不断增长的上下文。为了应对这个问题,它设计了六层上下文管理机制

层级 机制 激进程度
1 Snip 保守 --- 删除过时 Tool 消息
2 Microcompact 保守 --- 编辑 API cache key
3 Context Collapse 中等 --- 折叠旧消息为摘要行
4 Auto Compact 激进 --- Fork Agent 做摘要压缩
5 Reactive Compact 激进 --- API 413 错误后的紧急压缩
6 Manual Compact 激进 --- 用户触发 /compact

其中最值得关注的是 Auto Compact :当 Token 用量接近上限时,系统启动一个 fork 子 Agent 来总结历史对话。为了防止这项操作本身陷入死循环,它还设计了熔断器:连续失败3次后自动停止。

Memory Pollution(记忆污染)

在多轮交互中,Agent 可能形成"错误记忆":比如第一次调用 API 返回了 rate_limit 错误,Agent 可能在后续所有 API 调用前都加上了"请确保不要触发频率限制"的逻辑,即使这个限制早已被解除。这是 Memory 变成了束缚


六、目前业界如何解决死循环?(横向对比)

在研究各框架的源码和文档后,我梳理了16种通用的防死循环机制,以及它们在主流框架中的支持情况:

机制 Claude Code OpenAI Agents LangGraph OpenHands AutoGen CrewAI Google ADK Mastra LlamaIndex
Max Iteration / Turn
Tool Budget
Cost Budget
Timeout
Loop Detection
Reflection
Human Approval
Guardrails
Stop Condition
State Machine
Checkpoint/Persistence
Context Compression
Memory Summary
Confidence Score
Retry Policy
Sub-agent Isolation

注:✅ = 明确支持,❌ = 未在公开文档/源码中找到支持证据。"官方未公开具体实现"的标记为 ❌,不做事实性断言。

各机制的优缺点分析

Max Iteration / Turn --- 最简单也最常用

原理:设置最大循环次数,达到后强制终止。

优点 :零心智负担,一行配置。

缺点:是"暴力截断",任务可能未完成就被终止。

LangGraph 的做法更为优雅:它提供了 RemainingSteps 托管值,允许 Node 在达到限制前 主动感知剩余步数并优雅收尾,而不是等 GraphRecursionError 抛出来再处理。

Loop Detection --- OpenHands 的 StuckDetector

原理:通过模式识别检测重复行为。

OpenHands 的 StuckDetector 可以识别5种卡死模式,这是目前开源框架里唯一的系统化死循环检测方案。

优点 :能发现"表层看起来不同但本质重复"的循环。

缺点:检测滞后,需要循环发生几次后才能发现。

Human Approval --- 人的判断是最终的防线

原理:在关键节点暂停执行,等待人工批准。

Claude Code 的权限模式系统(permission_mode)允许开发者设置 defaultacceptEditsplandontAskbypassPermissions 等多种审批级别。AutoGen 的 human_input_mode 可以设置为 NEVERALWAYSTERMINATE

优点 :人能识别 LLM 无法识别的"无意义循环"。

缺点:打断执行流,需要人实时在线。

Guardrails --- 预防而非治疗

OpenAI Agents SDK 的 Guardrails 分为 Input 和 Output 两类:Input Guardrails 在输入阶段拦截问题,Output Guardrails 在最终输出阶段验证。但只有第一个 Agent 的 Input Guardrails 会被执行,后续 Handoff 的 Agent 不会再次触发------这是一个容易被忽视的安全边界。

Context Compression --- Claude Code 独有的深度机制

Claude Code 的六层上下文管理是目前市面上最成熟的方案。它的 Auto Compact 不是简单的截断,而是启动一个子 Agent 来"读懂并总结"历史对话。但这个机制本身也是死循环的来源------GitHub Issue #6004 就是压缩循环本身变成了问题。

Cost Budget --- 经济上的防护栏

Claude Code 的 max_budget_usd 是基于花费阈值来限制循环的:一旦 API 调用费用达到预算,循环停止。这是一个非常实用的"商业化思维"------用钱来控制风险,而非用技术手段猜测风险。


第六章展示了各框架的共同防线,但 LangGraph 的防死循环策略有本质不同------它不是靠限制次数,而是靠状态机消除循环本身。


七、Claude Code为什么很少死循环?

很多深度使用过 Claude Code 和 Cursor 的开发者都有一个共同感受:Claude Code 虽然偶尔会出问题,但整体稳定性远超同类工具。 原因不在于某个单独的魔法,而是一套精心设计的工程体系。

1. 多层上下文管理的"瑞士军刀"

如前所述,Claude Code 的六层上下文管理(Snip → Microcompact → Collapse → Auto Compact → Reactive Compact → Manual Compact)确保上下文不会无限膨胀。这解决了循环的最底层诱因------上下文污染导致判断力下降 → 判断力下降导致重复决策 → 重复决策导致更多上下文污染。

2. Sub-agent 隔离 --- 每个子任务都重新开始

这是 Claude Code 防循环效果最明显的机制之一。当 Agent 需要执行子任务时,它会 spawn 一个子 Agent。这个子 Agent 拥有全新的上下文,不继承父 Agent 的对话历史。 它完成任务后只将最终结果返回给父 Agent------父 Agent 看到的只是一条简洁的结果摘要,而不是子 Agent 的完整执行过程。

text 复制代码
  父Agent上下文
  ┌────────────────────────────────────────────┐
  │  用户查询  ──▶  分析任务                   │
  │                │                          │
  │                ▼                          │
  │        Spawn Sub-agent 1  ──▶  收到摘要1 │
  │                │                          │
  │                ▼                          │
  │        Spawn Sub-agent 2  ──▶  收到摘要2 │
  │                │                          │
  │                ▼                          │
  │         输出最终结果                      │
  └────────────────────────────────────────────┘

  Sub-agent 1 上下文          Sub-agent 2 上下文
  ┌──────────────────┐        ┌──────────────────┐
  │ 新上下文 ──▶ 任务  │        │ 新上下文 ──▶ 任务  │
  │            │     │        │            │     │
  │            ▼     │        │            ▼     │
  │        返回摘要   │        │        返回摘要   │
  └──────────────────┘        └──────────────────┘

效果:父 Agent 不会因为子任务的细节而污染上下文。即使子 Agent 内部出问题(比如某个文件反复读取),这个"局部混乱"不会扩散到全局。

3. 为什么大量使用 Markdown?

Claude Code 的核心配置通过 CLAUDE.md 文件进行。这不是巧合------Markdown 是一种结构化但自然的格式,LLM 对它理解得最好。在 CLAUDE.md 中写入的规则和项目上下文,会在每次 API 请求中被重新注入 。这意味着即使 Auto Compact 压缩了历史对话,CLAUDE.md 中的关键指令也不会丢失。

这一点和普通对话完全不同:你在对话中说的"记住 XYZ"可能被压缩掉,但写在 CLAUDE.md 里的指令是永久的。

4. 为什么强调 Project Context?

Claude Code 的 CLAUDE.md 支持多层级配置:全局级别(~/.claude/CLAUDE.md)和项目级别(项目根目录的 CLAUDE.md)。这种设计在启动时就将"你是谁、你要做什么、你的边界在哪里"编码进 System Prompt------在 Agent "自由发挥"之前先设好护栏。

5. 工具限制的精细化

Claude Code 的权限系统提供了5种模式,并且支持对单个工具进行作用域限制:

bash 复制代码
# 只允许运行 npm 相关的 Bash 命令
"Bash(npm *)"

# 允许特定工具
allowed_tools: ["Read", "Glob", "Grep"]

权限越少,出错空间越小。 一个只有 ReadGlobGrep 权限的 Agent 几乎不可能死循环------它没有可以重复调用的"写操作"。

6. 任务拆分与原子化

Claude Code 的 Task 系统(TaskCreate、TaskUpdate、TaskList)强制任务分解。当一个大型任务被拆成10个小任务时,每个小任务都有明确的"完成标准"。这天然限制了每个步骤的循环次数------你不可能在"安装 psycopg2"这个任务上循环100次而不自知。

7. Token Budget 和边际效应检测

Claude Code 的 Token Budget 系统(query/tokenBudget.ts)不仅能追踪 Token 消耗,还能检测边际效应递减------当模型在后续轮次中产出越来越少时,它会提前终止而不是继续"磨洋工"。这是一个非常工程化的"智能止损"机制。

关于 Loop Protection 的官方说明

Claude Code 的 Agent SDK 文档明确列出了 max_turnsmax_budget_usdstop_reason 作为终止机制。但官方未公开是否存在专门的"循环检测"算法(如 OpenHands 式的模式匹配)。 从 GitHub Issues 来看(#6004、#2283、#2391等),Claude Code 确实存在循环问题,所以不能说它"完全免疫"------它的优势在于让循环发生的概率大幅降低,并在发生时能较快恢复。


八、LangGraph如何避免死循环?

LangGraph 的防死循环策略与其他所有框架有本质区别。大多数框架的做法是"在 while 循环里加限制",LangGraph 的做法是"不用 while 循环,用状态机。"

核心设计哲学

text 复制代码
  传统Agent                      LangGraph
  ┌────────────────────┐         ┌────────────────────┐
  │ while True         │         │ Node A ──▶ Node B  │
  │   │                │         │   │                │
  │   ▼                │         │   ▼                │
  │ Reason             │         │ Conditional Edge   │
  │   │                │         │   │                │
  │   ▼                │         │   ▼                │
  │ Tool   ──▶ Observe │         │ Node B ──▶  END    │
  │            │       │         │   │                │
  │            ▼       │         │   └────────▶ Node A│
  │         Reason     │         └────────────────────┘
  └────────────────────┘

传统 Agent 的结构是**循环+条件判断**:

```python
while True:
    thought = llm.reason(context)
    if thought.is_final:
        break
    tool_result = execute(thought.action)
    context.append(tool_result)

LangGraph 的结构是图+状态驱动的条件跳转

python 复制代码
builder.add_conditional_edges(
    "reasoning_node",
    decide_next,  # 基于 State 决定下一步
    {
        "need_tool": "tool_node",
        "done": END      # ← 明确的终止节点
    }
)

核心区别 :在传统循环中,终止条件是一个隐式的 LLM 判断("我觉得我完成了")。在 LangGraph 中,终止是一个显式的路由决策,基于结构化 State,而非 LLM 的自然语言输出。

State驱动的条件跳转

LangGraph 的 State 是 TypedDict,每个字段都有明确的类型和 reducer:

python 复制代码
class AgentState(TypedDict):
    messages: Annotated[list, add_messages]
    task_completed: bool          # 显式的完成标志
    tool_call_count: int          # 工具调用计数
    remaining_steps: RemainingSteps  # 剩余步数(由框架管理)

条件边路由函数基于 State 做决策,而非 LLM 的文本输出:

python 复制代码
def decide_next(state: AgentState) -> str:
    if state["task_completed"]:
        return END
    if state["tool_call_count"] >= 10:
        return "force_summary"   # 超过限制 → 强制走收尾节点
    if len(state["messages"]) > 50:
        return "compress_context" # 上下文过长 → 压缩后继续
    return "continue_reasoning"

为什么这比 while 循环好? 因为 while 循环的终止条件是 LLM 的"意图"(我是否想停),而 LangGraph 的终止条件是 State 的"事实"(计数器到了、标志位设了)。前者是不可靠的,后者是确定性的。

Conditional Edge --- 让流程天生有终点

text 复制代码
  START ──▶ reasoning
              │
    ┌─────────┼─────────┬─────────┐
    │         │         │         │
    ▼         ▼         ▼         ▼
 need_tool   done    max_steps   (其他)
    │         │         │
    ▼         ▼         ▼
 tool    ──▶ END    force_finish
  executor    │         │
    │         └─────────┘
    ▼
 observation
  parser
    │
    └────────▶ reasoning

传统 while 循环的问题是:每一轮循环的终点都是下一轮循环的起点 。LangGraph 的图的问题是:每个 Node 都可以跳转到 END。 这不是语义差异,而是工程差异------在图结构中,END 是一个"物理存在"的节点,你可以通过多种路径到达它。

Interrupt / Checkpoint / Human-in-the-loop

这三者结合起来,形成了 LangGraph 最有用的一套安全机制------把人嵌入执行流。

python 复制代码
def potentially_dangerous_action(state):
    # 在执行前暂停,等待人工确认
    approval = interrupt(f"即将执行: {state['planned_action']},是否继续?")
    if approval != "yes":
        return Command(goto="abort")
    result = execute_action()
    return {"result": result}

interrupt() 会在该点暂停整个图的执行,保存一个 Checkpoint。人可以在任意时间后回来,检查当前状态,然后通过 Command(resume=...) 恢复执行。

Checkpoint 的额外价值:如果图在执行到第47步时崩溃了,重启后它不会从第1步重新开始------它从第46步的 Checkpoint 恢复。这意味着即使在异常情况下,Agent 也不会"失去进度"然后从头来过(这是一类常见的变相循环)。

Command / Send --- 动态路由

Command 允许 Node 在一次返回中同时完成"状态更新"和"控制流决策":

python 复制代码
def tool_executor(state) -> Command[Literal["reasoning", "error_handler"]]:
    try:
        result = run_tool(state["pending_action"])
        return Command(update={"result": result}, goto="reasoning")
    except ToolError as e:
        return Command(update={"error": str(e)}, goto="error_handler")

Send 实现动态并行------当你不确定需要并行执行多少个任务时:

python 复制代码
def fan_out(state):
    return [Send("sub_worker", {"task": t}) for t in state["tasks"]]

Recursion Limit --- 最后的防线

LangGraph 的 recursion_limit 相当于图中的"天花板"。每个 super-step 计数一次,默认值设计为适合复杂图的合理上限。当图达到限制时,触发 GraphRecursionError

但与传统的 max_turns 不同,LangGraph 还提供了 RemainingSteps------一个托管值,在图的内部被更新,节点可以主动读取:

python 复制代码
def my_node(state):
    if state["remaining_steps"] < 3:
        # 步数不多了,快速收尾
        return {"summary": quick_wrap_up(state)}
    # 正常执行
    return {"result": do_work(state)}

这是"主动优雅降级"而非"被动崩溃"。

为什么LangGraph推荐显式状态机而非自由循环?

  1. 可预测性:图的拓扑结构在编译时就确定了所有可能的执行路径。
  2. 可测试性:每个 Node 是纯函数(State in → State out),容易单独测试。
  3. 可观测性:每次状态转换都经过明确的 Edge,可以打点、记录、追踪。
  4. 可恢复性:Checkpoint 保存了整个 State,恢复后可以精确续接。
  5. 安全性interrupt() 在任何危险步骤前可以强制暂停。

九、如何设计不会死循环的Agent?

这一章不讲理论,只讲工程实践。

1. 限制最大步骤数 --- 最基础但最有效【必做】

python 复制代码
# Claude Code Agent SDK
agent.run(prompt, max_turns=25)

# OpenAI Agents SDK
Runner.run(agent, input, max_turns=20)

# LangGraph
graph.invoke(input, config={"recursion_limit": 50})

为什么有效? 因为"无限"是人类直觉中最大的漏洞。我们总觉得"Agent 应该能自己判断何时停止",但 LLM 的"判断"是一个概率采样过程,不是确定性的逻辑。设置硬上限就是承认这一点。

建议值:简单任务 5-10 步,中等任务 15-25 步,复杂任务 30-50 步。超过 50 步的任务应该拆分成多个子任务。

2. 限制工具调用次数 --- 比限制步骤更精确【必做】

限制步骤数是粗略的(一步可能包含多个工具调用),限制工具调用次数更直接:

python 复制代码
if tool_call_count >= 15:
    return "请基于已有信息给出最佳答案,不要再调用工具。"

为什么有效? 很多死循环是"一直在查、一直在搜、一直在读文件,但从不开始真正的输出"。限制工具调用次数就是在强制 Agent 从"收集模式"切换到"产出模式"。

3. 增加 Reflection 节点 --- 但不要让它循环【推荐】

在每个重要阶段后插入一个"反思步骤":

python 复制代码
def reflection_node(state):
    reflection = llm.generate(
        f"已完成的操作:{state['completed_actions']}"
        f"当前状态:{state['status']}"
        f"原始目标:{state['original_goal']}"
        "判断:任务是否已完成?如果是,输出 DONE;如果否,输出需要继续的下一步。"
    )
    if "DONE" in reflection:
        return {"status": "completed"}
    return {"next_action": parse_next_action(reflection)}

关键设计:Reflection 的输出应该是结构化的(DONE / CONTINUE),而不是让 LLM 自由决定。自由决定 = 可能永远不说 DONE。

4. 增加 Task Success 判断 --- 二进制的完成标准【推荐】

与其让 Agent 判断"我是不是做完了",不如给它一个可以自动验证的完成条件:

python 复制代码
task = {
    "goal": "修复 auth.py 中的类型错误",
    "success_criteria": "python -m mypy auth.py  -- 零错误",
    "max_attempts": 5
}

Cursor 的文档中强调:"Without a test or command it cannot tell it is done." 这是一个非常精准的总结。给 Agent 一个它可以运行的验证命令,让它自己检查自己。

5. 增加 Budget --- 用钱控制行为【必做】

python 复制代码
# 方式1:Token Budget
if total_tokens > 100_000:
    return force_conclusion()

# 方式2:Cost Budget(Claude Code 方式)
agent.run(prompt, max_budget_usd=2.00)

# 方式3:Time Budget
if elapsed_time > 300:  # 5分钟
    return force_conclusion()

为什么用钱? 因为技术指标(步数、Token 数)LLM 不一定理解其意义。但"这个任务只值 $2"是一个 LLM 能理解的商业约束------它会在预算内做出最优决策。

6. 减少 Observation 污染 --- 只保留有效信息【必做】

python 复制代码
def compress_observation(raw_output):
    """压缩 Tool 输出,只保留关键信息"""
    if len(raw_output) > 1000:
        return f"[工具输出过长,已压缩] 关键发现:{extract_key_info(raw_output)}"
    return raw_output

Claude Code 的 Snip 和 Collapse 机制就是做这件事。实战建议:

  • 测试输出:只保留失败的部分,截断成功的日志
  • 文件内容:只保留修改过的区域前后5行
  • 网页抓取:只保留和问题相关的段落

7. Memory 压缩 --- 有选择地遗忘【推荐】

python 复制代码
def compress_memory(messages, max_size=8000):
    """压缩对话历史"""
    # 始终保留:
    #   1. 原始任务描述
    #   2. 最近的3轮交互
    #   3. 已确认的关键发现(结构化存储)
    # 可以被压缩的:
    #   1. 中间的完整 Tool 输出
    #   2. 失败的尝试
    #   3. 中间的 Thought 过程

8. Context Summary --- 定期"重新对齐"【推荐】

每隔 10-15 步,生成一个当前状态的结构化摘要,把它作为新的起点:

python 复制代码
summary = {
    "original_goal": "...",
    "completed": ["步骤1", "步骤2", "步骤3"],
    "current_state": "正在处理步骤4,已读取3个文件...",
    "key_findings": ["发现X", "确认Y"],
    "remaining": ["步骤4", "步骤5", "步骤6"]
}
# 将 summary 替代之前的完整历史,减少上下文负担

9. 状态机设计 --- 消灭自由循环【进阶】

text 复制代码
  开始 ──▶ 分析任务 ──▶ 制定计划 ──▶ 执行步骤
                                    │
                                    ▼
                              ┌──────────┐
                              │  验证结果  │
                              └────┬─────┘
                        通过 /      \ 失败
                       ▼            ▼
                ┌──────────┐  ┌──────────┐
                │还有步骤?   │  │重试<3次?  │
                └────┬─────┘  └────┬─────┘
               是 /   \ 否    是 /    \ 否
              ▼       ▼      ▼         ▼
           执行步骤   生成摘要  执行步骤   报告失败
                        │                  │
                        └────────┬─────────┘
                                 ▼
                                结束

状态机的好处:每个状态都有明确的后继状态,没有"自由发挥"的空间。 Agent 在 "EXECUTE" 状态下就是执行,在 "VERIFY" 状态下就是验证------它不能"在执行的同时又在思考要不要继续执行"。

10. 人工审批节点 --- 关键操作前必须确认【必做】

python 复制代码
dangerous_actions = [
    "删除文件",
    "修改数据库 Schema",
    "执行 git push --force",
    "超过 $1 的 API 调用",
    "修改超过 5 个文件"
]

if action in dangerous_actions:
    await human_approval(action)

不要让人审批所有操作 (那还不如自己写)。只在不可逆操作高频操作的阈值上设置审批。


十、未来Agent为什么一定会走向"可控执行"

从"自主探索"到"确定性执行"的范式转移

2026年的 Agent 领域正在经历一个微妙的转变:开发者不再追求"让 Agent 自己想办法",而是追求"给 Agent 划定清晰的执行边界"。

这种转变体现在几个关键趋势上:

Deterministic Workflow + Agentic Intelligence

Google ADK 2.0 的口号是 "Weave deterministic code with adaptive AI reasoning"。Mastra 强调 "Use models where you need reasoning, plain functions where you don't"。这两者指向同一方向:在确定性的工作流框架中,在需要判断的节点上调用 LLM。

text 复制代码
  纯Workflow                纯Agent                  Hybrid: 2026趋势
  ┌────────────┐           ┌────────────┐          ┌──────────────────┐
  │ Step 1     │           │ Reason     │          │ Step 1: 确定性   │
  │    │       │           │    │       │          │    │             │
  │    ▼       │           │    ▼       │          │    ▼             │
  │ Step 2     │           │ Tool       │          │ Step 2: LLM推理  │
  │    │       │           │    │       │          │    │             │
  │    ▼       │           │    ▼       │          │    ▼             │
  │ Step 3     │           │ Reason     │          │ Step 3: 确定性   │
  └────────────┘           └────────────┘          │    │             │
                                                   │    ▼             │
                                                   │ Step 4: LLM决策  │
                                                   │    │             │
                                                   │    ▼             │
                                                   │ Step 5: 确定性   │
                                                   └──────────────────┘
Planning + Verification

"Source Code Agent" 论文(arXiv:2508.02721, 2025)提出了一个激进的方案:将 Agent 的操作逻辑编码为确定性的源代码蓝图。 在执行前生成一个可审查的 Plan,Plan 通过后再执行。这本质上是在 Agent 的"黑盒推理"和"白盒执行"之间加了一层"可验证的蓝图层"。

不只是线性推理,未来的 Agent 会在关键决策点上展开搜索树------评估多个可能路径、选择最优、执行、验证、回溯。这需要比目前的 ReAct 循环更复杂的执行框架。

Model Context Protocol(MCP)

MCP 的核心价值不只是"统一工具调用协议",它还通过工具发现延迟加载 的方式减少了无效调用。Agent 不需要提前加载所有工具的定义,而是在需要时才通过 ToolSearch 查找------减少了上下文污染和"选错工具"的概率。

Hybrid Agent --- Rule + LLM

"规则引擎负责下限,LLM 负责上限"正在成为共识。一个生产级的 Agent 系统应该是:

python 复制代码
if is_simple_and_clear(task):
    use_rule_based_workflow(task)  # 确定、快速、零出错
else:
    use_llm_agent(task, constraints={
        "max_turns": 25,
        "human_approval_required": True,
        "verification_command": "pytest -x"
    })

总结

这篇文章的核心论点可以归纳为一句话:

Agent 的自主性是其能力的来源,也是其风险的来源。 你给 Agent 的自由度越大,它帮你完成复杂任务的潜力越大------但它陷入死循环的概率也越大。

所以设计 Agent 不是在"让它更聪明"和"让它更稳定"之间做选择。而是用工程手段来框定聪明的边界

具体来说:

  1. 硬限制:Max Turns、Tool Budget、Cost Budget------到了就停,不商量
  2. 状态机:StateGraph、条件边、显式终止节点------用图消灭循环本身
  3. 人肉兜底:Human-in-the-loop、审批节点------关键操作必须人点头
  4. 压缩:Context Compression、Memory Summary------把上下文里的噪声挤出去
  5. 隔离:Sub-agent 独立上下文、MCP 延迟加载------局部坏了不影响全局
  6. 检测:循环模式识别、Token 异常监控------发现苗头就掐掉

真正优秀的 Agent,不是最聪明的,而是最稳定的。

在工程中,"每次都做到 80 分"远比"有时候 100 分、有时候 0 分"更有价值。


参考资料:

  1. Anthropic. Claude Code Agent SDK Documentation --- Agent Loop. code.claude.com/docs, 2026.
  2. Anthropic. Claude Code GitHub Repository. github.com/anthropics/claude-code, 2025-2026.
  3. LangChain. LangGraph Documentation --- Graph API, GRAPH_RECURSION_LIMIT. docs.langchain.com, 2026.
  4. OpenAI. OpenAI Agents SDK Documentation --- Runner, Guardrails. openai.github.io/openai-agents-python, 2026.
  5. Google. Agent Development Kit (ADK) Documentation. adk.dev, 2026.
  6. OpenHands. OpenHands GitHub Repository --- StuckDetector. github.com/OpenHands/OpenHands, 2025-2026.
  7. Microsoft. AutoGen Documentation --- Chat Termination. microsoft.github.io/autogen, 2026.
  8. Yao, S. et al. ReAct: Synergizing Reasoning and Acting in Language Models. arXiv:2210.03629, 2022.
  9. Shinn, N. et al. Reflexion: Language Agents with Verbal Reinforcement Learning. NeurIPS 2023.
  10. Xu, B. et al. ReWOO: Decoupling Reasoning from Observations for Efficient Augmented Language Models. arXiv:2305.18323, 2023.
  11. Agent Drift: Quantifying Behavioral Degradation in Multi-Agent LLM Systems. arXiv:2601.04170, 2026.
  12. Mastra. Mastra AI Agent Framework Documentation. mastra.ai, 2026.
  13. CrewAI. CrewAI Documentation. docs.crewai.com, 2026.
  14. Source Code Agent: A Framework for Deterministic LLM Agents. arXiv:2508.02721, 2025.
  15. Yoyo_Lee. Claude Code 架构深度解析. yo666666yo.github.io, 2026.
  16. Learn Cursor. Cursor Agent Stuck in a Loop Fix. learncursor.dev, 2026.
  17. Ralphable. The Claude Code 'Infinite Loop' Bug. ralphable.com, 2026.

相关推荐
Z-D-K1 小时前
考验AI的“自我“-AI对《红楼梦》后40回的改写(32)
人工智能·ai·aigc·交互·agi
触底反弹1 小时前
AI Tool Use 深度解析:大模型是如何"突破物理限制"调用外部工具的?
javascript·人工智能·后端
delishcomcn1 小时前
从“一刀切”到动态预测:AI优化烫金箔张力控制
人工智能
明志数科1 小时前
具身智能“数据工厂“的标准化产线设计——从多模态采集到VLA-ready数据集的全链路工程解析
人工智能
云烟成雨TD1 小时前
LangFlow 1.x 系列【3】入门案例
人工智能·python·agent
Wireless_wifi62 小时前
Why Choose IPQ9574 for Your WiFi 7 Solution
linux·人工智能·5g
Ar-Sr-Na2 小时前
工作路演PPT处理,交给Workbuddy
人工智能·powerpoint·workbuddy开发者分享季
阿洛学长2 小时前
Cursor下载安装使用教程(最新详细图文)
人工智能·gpt·深度学习·ai·ai编程
2603_955279702 小时前
城市与雨水的隐秘对话
人工智能