🔁 Agent 循环理论

把上一章的"单次工具调用"升级为"思考-行动-观察"的多步循环——这是 LangChain、LangGraph、OpenCode 三套源码共同的骨架。本章的图会在后面每一章反复出现,请务必吃透。

本章目标

  • 掌握 ReAct 范式(Thought → Action → Observation 循环)
  • 理解 Agent 循环的状态机三阶段模型(Plan / Execute / Update)
  • 能画出 Agent 循环核心图,并说出"停止条件"如何决定循环退出
  • 建立三大框架的映射关系:LangChain AgentExecutor、LangGraph 图、OpenCode runLoop 都是同一个循环的不同实现

从"单次"到"循环"

上一章我们看到一次工具调用的完整往返:模型输出 tool_calls → 执行 → 回传结果 → 模型再输出。Agent 循环就是把这个往返不断重复,直到模型认为任务完成、不再调用工具为止。

关键问题来了:谁来决定"继续"还是"停止"? 答案是——LLM 自己。这就是 F1 章说的"控制权反转"。判断逻辑只有两种结果:

📌 记住这个二态

Action(继续) / Finish(停止) —— 这正是 LangChain 源码里 AgentActionAgentFinish 两个类的语义(LC7 精读)。整个 Agent 循环就是在这两个状态之间反复判断。

ReAct 范式:思考 + 行动

Agent 该怎么"决定下一步"?最经典、至今最常用的范式叫 ReAct(Reason + Act,2022 年论文提出)。它的核心是让 LLM 在每一步都显式地"思考"一下,再决定行动:

Thought(思考) "我需要先查天气" Action(行动) 调用 search_weather Observation(观察) {"temp": 25, "晴"} Finish(停止) 最终回答用户 不再调工具 循环 把观察喂回 继续思考
图 3.1 · ★ ReAct 循环 —— 贯穿全教程的核心图(Thought → Action → Observation,直到 Finish)

用文字还原一次完整的 ReAct 跑动

假设用户问:"北京和上海哪个更热?" 一个 ReAct Agent 的内部过程:

📄 ReAct 的典型轨迹(文字版,真实实现用结构化 tool_calls)
用户: 北京和上海哪个更热?

【第 1 轮】
Thought: 我需要分别查两个城市的气温才能比较。先查北京。
Action: search_weather(location="北京")
Observation: {"temp": 25, "weather": "晴"}

【第 2 轮】
Thought: 北京 25 度。现在查上海。
Action: search_weather(location="上海")
Observation: {"temp": 30, "weather": "多云"}

【第 3 轮】
Thought: 北京 25 度,上海 30 度,上海更热。信息齐全,可以回答了。
Finish: 上海更热,30 度,北京 25 度。
💡 现代实现的演变

早期 ReAct 用文字把 Thought/Action/Observation 拼成一段文本喂给模型(解析"Action: xxx"这种格式)。现代实现(LangChain 的 create_tool_calling_agent、LangGraph 的 create_react_agent)直接用模型原生的工具调用能力,Thought 隐藏在模型内部,Action 体现为结构化的 tool_calls。但循环骨架完全一样

状态机三阶段模型

换一个更工程化的视角看,每一轮循环(一个 "step")内部其实经历三个阶段。这个三阶段模型是理解 LangGraph 的 Pregel 引擎(LG5)和 OpenCode runLoop(OC2)的关键:

① Plan 规划 读取当前状态 决定要执行哪些任务 "这一轮做什么?" ② Execute 执行 调用 LLM / 跑工具 产生新的输出 "本步结果是什么?" ③ Update 更新 把结果写回状态 判断是否继续 "还要再来一轮吗?" 若未结束,回到 Plan 开启下一轮
图 3.2 · 单步三阶段模型(Plan → Execute → Update)—— LangGraph Pregel 的 BSP 循环正是这个结构

三阶段在三大框架中的对应

同一个三阶段模型,不同框架用不同名字实现,但本质一致。这张表是本教程最重要的"对照表"之一,建议截图保存:

阶段LangChain
AgentExecutor
LangGraph
Pregel 引擎
OpenCode
runLoop
① Plan 规划 组装 prompt + 历史 prepare_next_tasks
_algo.py:371
读取消息、组装 system prompt
② Execute 执行 调 LLM,解析出 Action/Finish PregelRunner 并发跑节点
_runner.py:135
handle.process() 调 LLM
processor.ts:625
③ Update 更新 执行工具、追加历史 apply_writes 写通道
_algo.py:232
处理 tool_calls、判断 continue/stop
停止条件 返回 AgentFinish 无任务可执行 / 到达 END assistant 无 tool_calls → break
🧭 现在不必深究,建立印象即可

表里的源码行号现在看不懂完全正常。这一章的目的,是让你脑子里先装下"所有 Agent 框架都在实现同一个三阶段循环"这个认知。等学完后面三大框架的源码,再回来看这张表,会有豁然开朗的感觉——这正是 OC6 概念对照表要做的事。

停止条件:循环何时结束

循环不能无限跑下去。Agent 必须有明确的停止条件,常见有四种:

停止条件含义框架体现
自然完成 LLM 主动停止调工具,给出最终回答 LangChain AgentFinish;OpenCode assistant 无 tool_calls
步数上限 防止无限循环,强制最多 N 步 max_iterations;OpenCode agent.steps
到达终点节点 图的执行流到 END 节点 LangGraph ENDconstants.py:28
人工/中断 被外部中断(人在回路、取消) LangGraph interrupt;OpenCode 取消信号
⚠️ 死循环(doom loop)是真实风险

当 LLM 反复用相同参数调用同一个工具却得不到进展,就会陷入死循环。生产级 Agent 必须检测并打断它——OpenCode 在 processor.ts 里专门有 doom_loop 检测(连续 3 个相同工具调用就触发权限询问,见 processor.ts:354)。这是工业级 Agent 才会考虑的细节,OC4 会讲。

一张图统合三大框架

把前面所有概念收束成一张"大图"。这就是本教程后续所有章节要反复回到的总参照系

用户输入 Agent 循环(while 未到达停止条件) LLM 推理 决定: 调工具? 还是 已完成? 判断 工具执行 真实副作用 返回结果 💾 状态 / 记忆(消息历史 + 工具结果) 输出 调工具 喂回历史 最终回答 已完成 → 退出
图 3.3 · ★ Agent 循环总参照图——三大框架都在实现这个结构

三大框架如何实现这个循环

同一个循环,三种工程实现路线。这是本教程的核心叙事线:

🐍

LangChain 路线

用 AgentExecutor 编排:一个 while 循环,每轮调 LLM,解析出 AgentAction/AgentFinish,执行工具。最直接,但状态管理较简单。

第二阶段学LC7-LC8
🕸️

LangGraph 路线

把循环建模成图:节点是处理函数,边是控制流(含条件边实现"调工具 vs 完成"的判断)。状态显式流转,可中断、可恢复、可并行。

第三阶段学LG1-LG8

OpenCode 路线

生产级 TypeScript 实现runLoop 的 while(true) 显式循环,配合流式处理器、权限系统、上下文压缩。最贴近真实产品。

第四阶段学OC1-OC6
🎯 学习策略建议

现在你脑子里已经有了同一个循环的三种视角。后面的学习就是逐层填充细节

  • LangChain 阶段:先掌握循环里每个"零件"(消息、提示、模型、工具)长什么样。
  • LangGraph 阶段:把零件升级成有状态的图,学会中断、恢复、并行。
  • OpenCode 阶段:看生产产品如何把这些零件打磨成可靠系统。

小结

基础理论完结,进入实战

恭喜!你现在有了完整的理论锚点。接下来进入 第二阶段 · LangChain 系统,从最基础的"消息"开始,逐个零件拆解。第一站LC1 · 消息体系——看看 LangChain 如何封装 system/user/assistant/tool 这四种角色。