最近在做 Agent 项目时,有个问题一直绕不开:大模型本身是一个“思考引擎”,但怎么让它在现实任务里靠谱地“做事”,而不是东拉西扯地胡说一通?这背后核心就是 Agent 的决策与规划机制。目前社区里讨论最多、实战用得最多的四条路线——ReAct、Plan-and-Execute、Reflexion 和 Tree of Thoughts——我前后都试过一轮,这中间踩了不少坑,也理出了一些门道。这篇就把我对这四种架构的理解、实现要点和选型思路完整梳理一遍,内容偏实操,适合正在做 AI Agent、想要让智能体更可控的开发者和研究者参考。
1. 整体认知:为什么 Agent 需要一套决策与规划机制
1.1 大模型的“一步式输出”根本不够用
先看一个最基本的场景:你让大模型“帮我在 GitHub 上找一个 star 最多、最近一周有更新的 OCR 项目,并写一份对比报告”。直接丢给 GPT-4 这类模型,它能输出一个像模像样的回答,但大概率是编的——它不会真的去 GitHub 上搜索,不会真的对比 star 数,甚至可能把已经不维护的项目说得天花乱坠。原因很简单:大模型的知识停留在训练数据那一刻,而且它有“幻觉”倾向。
Agent 的核心价值恰恰在于:给大模型装上“手”和“眼睛”,让它可以调用工具、读取真实世界的数据、执行代码,并根据结果调整下一步。但问题来了——一个多步骤任务,先做什么、后做什么、遇到错误怎么办、中间结果怎么评估,这些如果全靠一次性 prompt 写死,那就是死板的剧本,根本没法应对真实世界的随机性。
所以,Agent 的“决策与规划”,本质上就是回答三件事:
- 下一步做什么(行动选择);
- 做完之后怎么评判(结果评估);
- 失败了怎么办(纠错与重启)。
ReAct、Plan-and-Execute、Reflexion、Tree of Thoughts,就是目前最主流的四套回答方式。它们不是互相替代的关系,而是针对不同任务特点的差异化方案,甚至可以在生产环境里组合使用。
1.2 四种模式的思路差异,用一个生活类比说清楚
我用一个“做饭”的类比来帮大家快速建立直觉。
ReAct(边想边做):像是“看着菜谱做菜”。每做一步,看一眼菜谱,判断火候对不对,再决定下一步。菜谱就是你给模型的 few-shot 示例,实时锅里的状态(工具返回结果)决定接下来的动作。它的特点是:行动与推理交替进行,信息实时反馈。
Plan-and-Execute(先计划再执行):像是“提前写好完整菜单”。先想清楚今天要做几道菜、每道菜的步骤、需要什么食材,然后一次性把采购和烹饪流程安排到位,再去逐项执行。特点是:先规划全局,再逐步落地,计划先于行动。
Reflexion(反思再重试):像是“做完一道菜后尝一口,发现咸了,写下‘下次少放盐’,然后重新做”。它不是改变做菜的流程框架,而是在失败后给系统增加一个“记忆修正”环节。特点是:善于从失败中学习,具备过程记忆。
Tree of Thoughts(多路树状推演):像是“同时模拟三条做菜路线,想象哪条最可能成功,再选一条执行”。每一步都生成多个候选方案,评估优劣,必要时回溯换路。特点是:在搜索空间中做决策,适合难问题。
有了这个直觉,我们再深入看每一种模式的实际实现和适用场景。
2. ReAct 深度拆解:让推理与行动交替驱动
2.1 ReAct 的核心思想与 Prompt 构造
ReAct(Reasoning + Acting)来自 2022 年 Google Research 的论文《ReAct: Synergizing Reasoning and Acting in Language Models》,核心思路非常朴素:把“思考过程”和“行动调用”交错写入模型上下文,模型每推理一句,就跟着调用一次工具,工具返回后继续推理,直到任务完成。
一个最简化的 ReAct 数据流是这样的:
Thought: 我需要查找用户问的这家公司的创始人信息 Action: search[OpenAI 创始人] Observation: 搜索结果返回 Sam Altman 等人 Thought: 找到了,我可以直接回答 Action: finish[OpenAI 创始人是 Sam Altman]在这个流程中,模型并不是一次性输出答案,而是每一步都输出结构化的文本,由 Agent 框架解析出 Action,执行工具,再把工具结果作为 Observation 继续塞回上下文。关键细节在于:
- Thought 是模型自由生成的推理轨迹,不需要额外训练,属于纯 prompt 工程范畴;
- Action 必须格式稳固,通常定义为
工具名[参数]的格式,方便代码做字符串解析(或函数调用); - Observation 是程序自动追加的,模型不控制这段内容的生成。
我在实际项目里发现,ReAct 的成败首先取决于 few-shot 示例的质量。你需要给模型展示至少 2-3 组完整的 Trajectory(轨迹)示例,而且要刻意包含一次失败后的纠正路径——比如说,搜索某个关键词返回空,模型换了个关键词再搜。没有失败示例,模型遇到边缘情况时容易不知所措。
2.2 ReAct 的实现示例与工具解析
现在主流的 LangChain、LlamaIndex 都已经把 ReAct 封装好了,但为了讲清楚机制,我建议你自己手写一个极简版本。下面这个伪代码展示了核心循环逻辑:
def react_agent(question, tools, max_steps=10): prompt = build_react_prompt(question, tools) with_history = "" # 用于存放 Thought/Action/Observation 的历史 for step in range(max_steps): response = llm(prompt + with_history) thought, action = parse_response(response) observation = execute_action(action, tools) with_history += f"Thought: {thought}\nAction: {action}\nObservation: {observation}\n" if action.type == "finish": return thought # 或者返回最终答案 return "max_steps exceeded"注意这里有几个容易踩的坑:
- stop token 要配好。如果 llm 是 OpenAI 的,建议在调用时传
stop=["Observation"],防止模型自己往下续写 Observation 导致轨迹混乱。 - 解析 Action 时要容忍格式扰动。模型偶尔会输出多余的空格、换行,甚至把 Action 写成
Action :,解析器要做归一化处理,否则 Action 被误判成无效调用后,Agent 会在死循环里打转。 - max_steps 一定要设上限。我见过一个电商客服 Agent 因为没有步数上限,在“查询物流-无查询结果-换方式查询-又无结果”之间翻来覆去跑了几十轮,token 费用直接爆表。经验值是简单任务 5 步、中等任务 10 步、复杂任务 20 步封顶。
ReAct 的适用场景非常清晰:需要工具调用、且信息在过程中逐步获取的任务。比如资料检索、API 调用、数据库问答、网页浏览等。它的优点是对动态变化的环境适应性强,缺点是每步都要等 LLM 生成推理轨迹,延迟和 token 开销都偏大,而且长时间运行可能积累很长的上下文,导致模型“忘记”最初的指令。
2.3 ReAct 的上下文化与记忆问题
我实际跑一个“多轮网页抓取”任务时,ReAct 到第十步左右,模型明显开始“失忆”——它忘了用户最初要的是什么格式的答案,反而专注于抓取过程中的琐碎分支。这其实是lost in the middle现象的表现:LLM 对超长上下文的注意力分布不均匀。
缓解办法我在项目里试过三种:
- 每轮把目标指令压缩后醒目插在 prompt 开头,辅助模型记住终点;
- 将历史轨迹用摘要替代:当轨迹超过一定长度时,用另一个 LLM 调用把前 N 轮的 Observation 浓缩成一段摘要,替换原始轨迹,控制上下文长度;
- 引入“里程碑检查点”:在轨迹中添加一个额外的结构化标记,例如
Status: 第3/5步, 已完成信息收集, 正在生成报告,让模型对当前进度有清晰的锚定点。
顺带一提,ReAct 这个名字在搜索引擎里经常和“React 前端框架”撞车,也被一些人拿去造了不少 Agent 框架图。如果你搜资料,建议关键词带上“ReAct agent”“reasoning acting”,否则容易搜到一堆前端教程。
3. Plan-and-Execute:先谋后动的全局规划派
3.1 两步走架构:Planner 与 Executor
Plan-and-Execute 的思路最早可以追溯到 2023 年 LangChain 的一篇博客,后来也被很多 Agent 框架吸收。核心是把“决策”和“执行”切成两个独立模块:
- Planner(规划器):接收用户目标,拆解成一份完整、有序的子任务清单,可能需要调用外部工具来补全计划细节;
- Executor(执行器):按计划逐项执行,每项执行完返回结果,全部完成后由 Planner(或一个单独的 Responder)生成最终输出。
一个典型的计划长这样:
Plan: 1. 搜索用户提到的竞品名称及其官网 2. 抓取竞品官网的定价页面 3. 抓取竞品官网的功能列表 4. 对比功能与价格,生成对比表 5. 输出 Markdown 格式的对比报告实现上,Planner 和 Executor 可以是同一个 LLM,也可以不同模型(比如用更强的模型做规划,用更便宜的模型做执行)。LangChain 里封装好的PlanAndExecuteAgent 我试用过,但生产环境下我更推荐自己手动拆管,因为默认实现里 Executor 返回的结果往往只是个摘要,丢失了大量原始信息,后续规划调整缺乏依据。
3.2 计划为什么有时候会“纸上谈兵”
Plan-and-Execute 最大的优点是对复杂长任务的稳定性和可解释性。计划可以展示给用户看,用户能提前确认方向对不对,Agent 也不容易在中途跑偏。但它的弱点也很明显:计划本身是大模型生成的,一旦外部环境与预期不符,整个计划就可能失效。
举一个我实际遇到的场景:让 Agent 从某个电商网站抓取商品价格,Planner 给出的计划是“先访问商品列表页 → 点击进入详情页 → 提取价格”,但执行时网站改版了,详情页 URL 结构变化,第一步就失败。如果 Executor 只是机械地报错,整个任务就停摆。
所以我在用 Plan-and-Execute 时,一定会给 Executor 加上“子任务级容错重试”与“Plan 动态修订”两个机制:
Plan Revision Policy(计划修订策略): 1. 如果某个子任务连续失败两次,调用 replanner 重新生成剩余计划; 2. 新计划必须保留已完成子任务的结果,避免重复劳动; 3. 修订次数上限建议 2-3 次,防止进入“计划-失败-再计划-再失败”的死循环。另外,关于 Executor 的形态,我踩过一个坑:默认把每个子任务都丢给一个 ReAct agent 去执行,结果是双重决策开销,本来一个 ReAct 顶多十几步就能完成的任务,变成“计划 + 多个 ReAct”,时间翻了两三倍。最终我的做法是:子任务简单就直连工具函数,只有复杂子任务才开 ReAct。这是典型的“杀鸡不用牛刀”。
3.3 适合的场景与必要前提
Plan-and-Execute 非常适合以下场景:
- 任务流程相对固定:如周报生成、定时数据采集、标准项目复盘等;
- 需要向用户展示进度条:每完成一个子任务,就更新一个勾选状态,产品体验会好很多;
- 多 Agent 协作:Planner 把计划分发到多个专用 Executor 并行执行,这是它相对 ReAct 的另一大优势。
但前提是:你对任务环境有足够的预先了解。如果任务本身就是高度开放式的探索(比如“帮我想一个创意、并分析可行性”),Plan-and-Execute 的计划阶段往往就陷入平庸,反而不如 Tree of Thoughts 这类能枚举多种思路的方案。
4. Reflexion:让 Agent 从失败里长记性
4.1 从“失败就重来”到“带着反思重来”
Reflexion 是 2023 年 Shunyu Yao(也是 ReAct 作者之一)等人提出的框架。核心思想:Agent 执行失败后,不仅简单地重试,而是先在语言层面自我反思,生成一条经验教训,然后带着这条经验重新尝试。
这里有个关键术语区分:Reflexion 里的记忆分两层。
- Working Memory(工作记忆):当前这次尝试的完整轨迹,相当于上下文窗口;
- Episode Memory(事件记忆):之前每次尝试的反思总结,跨轮次保留。
举个例子,一个 Agent 写代码反复出错。第一次运行报TypeError,Reflexion 会生成一条反思:“这次失败是因为把字符串当数字计算,下次运行前先检查数据类型。”下一条轨迹开头就把这条经验塞进 prompt,于是后续生成代码时模型就会主动做类型转换。
伪代码是这样:
def reflexion_agent(task, max_attempts=5): episode_memory = [] for attempt in range(max_attempts): result, trajectory = run_with_memory(task, episode_memory) if is_success(result): return result reflection = llm_reflect(trajectory, previous_reflections=episode_memory) episode_memory.append(reflection) return "max attempts exceeded"4.2 反思质量的三个维度:调试技巧与血泪教训
Reflexion 的效果,几乎完全取决于反思生成这一步的质量。我实测下来,让模型凭空“反思”很容易产生空话,比如“下次我要更仔细”“我要避免语法错误”这种完全没有操作性的废话。要提升反思质量,建议强制反思内容包含三个维度:
- 具体原因(Cause):明确指出是哪一步、哪个假设/工具/逻辑导致失败;
- 修正动作(Fix):给出可执行的下一步动作,而不是抽象建议;
- 验证信号(Signal):说明重试时依据什么判断“这次没有重蹈覆辙”。
下面是我在项目里常用的一种反思 prompt 模板:
你刚才执行任务失败了,请根据以下轨迹进行反思,输出 3-4 句话: - 本attempt失败的直接原因是什么? - 哪一步决策导致了偏差? - 下一次重试时,你要采取什么具体动作? - 如果重试时看到什么信号,可以判断问题已规避?实际上我在 HumanEval 类代码生成任务和分类爬虫任务上都测过。没有 Reflexion 的 ReAct 大约 30 步能跑通,加 Reflexion 后,一次失败后第二次基本就跑通,整体步数和 token 消耗下降 30%-40%。不过 Reflexion 不适合用在一路都很顺利的任务上——它本身的反射调用是有额外开销的。
4.3 记忆污染问题:反思写入上下文要过期
这里必须强调一个我在生产环境遇到的严重问题:Reflexion 的记忆是“可过期”的。当 Agent 跨多个任务复用时,上一次任务中“不要抓取那个网站,因为反爬太强”的反思,可能会被错误地带入下一次任务,反而导致 Agent 畏手畏脚。
解决办法有两种:
- 按任务域隔离记忆:给记忆打上 task name 或 embedding 标签,只有相似任务才召回;
- 给反思加时效与置信度:明确标注“这个反思是在 XX 数据下的结论”,如果后续尝试成功,提升置信度;如果后续证明经验无效,降权或删除。
我现在偏向的做法是:加一个轻量记忆管理模块,类似Memory = {task_id: [reflections]},每次新任务打开独立记忆,跨任务共享只发生在同一 playbook 内部。这样既保留 Reflexion 的跨尝试学习能力,又避免上下文污染。
5. Tree of Thoughts:在选项分支中搜索最优路径
5.1 从“单线直走”到“搜索树分叉”
Tree of Thoughts(ToT)是 Princeton 和 DeepMind 等机构在 2023 年提出的方法,通俗地说,就是把大模型的推理过程显式做成一棵搜索树。普通 CoT 或 ReAct 是单一路径走到底,而 ToT 在每一步同时生成多个候选思路,给每个候选打分评估,然后选择最优分支继续探索,遇到死路还可以回溯。
这对解决“难题”特别有效:比如逻辑推理、数学应用题、创意写作等。以一道 24 点游戏为例,普通 CoT 可能直接写一行算式,错了就没法重来;ToT 则是生成好几种中间方案,评估每种方案离目标有多远,选中可能性最大的分支继续算。
实现上,ToT 有两种主要搜索策略:
- BFS(广度优先):每层保留 Top-K 个节点继续展开,适合状态空间中需要“稳扎稳打”的任务;
- DFS(深度优先):深入一个分支直到结束,不行再回溯,适合分支不太多但单分支内部逻辑深的场景。
一个最简化伪代码(BFS 版本):
def tree_of_thoughts(question, k=3, max_depth=5): frontier = [initial_state] for depth in range(max_depth): candidates = [] for state in frontier: # 为每个节点生成 k 个候选下一步 next_steps = llm_generate_candidates(question, state, k) # 给候选打分,排序 scored = [ (llm_evaluate(question, ns), ns) for ns in next_steps ] candidates.extend(scored) candidates.sort(reverse=True) frontier = [ns for score, ns in candidates[:k]] if is_solution(frontier[0]): return frontier[0] return best_state(frontier)5.2 评估器设计:ToT 的命门所在
ToT 的评估环节(也就是判断哪个候选分支更优)极其关键,但我发现有太多教程只讲流程不讲评估。评估方式一般有两类:
- 模型自评(self-evaluation):直接让 LLM 对候选方案打分,简单但对复杂方案容易产生评价偏差;
- 规则/工具评估(heuristic/tool-based evaluation):比如代码任务,直接跑测试集看通过率;数学任务,验证中间步骤是否符合已知公式。
我在项目里强烈建议能用真实工具评估就优先用真实工具。比如在“写自动化测试脚本”这类任务里,候选脚本直接跑一遍,看几个核心断言是否通过,这比让 LLM 打分可靠得多。没法用工具评估的开放任务再用模型打分,而且要给出评分理由,避免拍脑袋打 1-10 分。
另一个实用的技巧是:候选生成时要求多样性。如果 k 个候选都是同一个思路的微小变体,搜索树的信息增益就很小。我在提示词里明确写“请给出 N 种策略差异明显的方案”,或者干脆给候选生成阶段注入不同的“启发式提示词”(比如“请从时间最优的角度设计路径”与“请从成本最优的角度设计路径”),效果会好很多。
5.3 ToT 的实际成本与工程权衡
必须坦诚说,ToT 是这四种方案里 token 开销最大、最复杂的一种。每一步要生成 k 个候选,还要给每个候选评分,成本几乎是 CoT 的数倍。如果是带宽和成本敏感的生产任务,我不建议全局使用 ToT,更推荐的做法是:
- 先跑 CoT/ReAct 试一次,失败了再进入 ToT 模式;
- 只在“难以一次到位”的推理节点开 ToT,比如法律问题分析这种从多个维度权衡的场景,而不是每个查询都树状展开。
另外,ToT 和“思维链 Self-Consistency”经常被混淆。Self-Consistency 是采样多条链投票取多数,而 ToT 是有结构地搜索和回溯,两者的差别本质是“并行多路投票”和“串行搜索 + 评估 + 回溯”的区别。如果搞混了,工程实现上会南辕北辙。
6. 四种决策模式的横向对比与实战选型建议
6.1 一图看懂四个方案的核心差异
| 特性 | ReAct | Plan-and-Execute | Reflexion | Tree of Thoughts |
|---|---|---|---|---|
| 核心机制 | 推理与行动交替 | 先规划后执行 | 失败后反思重试 | 搜索树 + 候选评估 |
| 动态适应力 | 高 | 中(需要 replan) | 中高 | 中(受限于搜索宽度) |
| 可解释性 | 中(轨迹长但透明) | 高(计划可见) | 高(反思可见) | 中(评估过程复杂) |
| Token 开销 | 中 | 中低 | 中高 | 最高 |
| 实现复杂度 | 低 | 中 | 中低 | 高 |
| 典型场景 | 信息检索/工具调用 | 任务流程固定/需进度展示 | 代码生成/多轮试错 | 逻辑难题/创意规划 |
6.2 场景匹配:你到底该用哪个?
从我踩过的坑出发,我会按下面的思路来选型:
- 大多数工具调用型 Agent 任务:ReAct 是首选,简单直接,框架成熟。当你发现失败率高时,叠加 Reflexion,而不是换掉 ReAct,通常是成本最低的优化方式。
- 长周期、流程明确的业务任务:选 Plan-and-Execute。它能把任务拆成可独立验收的模块,还方便对接人工审批环节(比如“第2步预算审批通过后再继续”)。
- 代码生成与调试类任务:ReAct + Reflexion 组合非常强。先跑代码,报错后反思,再生成修正版,这个循环在 HumanEval 之外的真实场景里也稳。
- 复杂推理、策略设计类任务:ToT 效果好,但成本高。建议封装成“困难任务专用通道”,只在普通路径失败之后启用。
6.3 实战中反复踩过的五个坑
最后梳理五个我在生产环境里亲身经历的问题,每条都是真金白银换来的教训。
坑一:Prompt 格式不稳定导致解析失败。无论 ReAct 还是 ToT,解析 Action、解析候选都必须容错。我的建议是:不要把解析逻辑写得太死,宁可先做一层正则归一化,再做字段提取,并对解析失败的情况给模型一个明确的反馈(比如“你的 Action 格式不正确,请输出工具[参数]格式再试”)。这个反馈本身就构成了一个新的 Observation 信号。
坑二:死循环检测缺失。Agent 可能重复执行同一个失败动作而不自知。我在框架里加了去重检测:如果某个 Action 的参数列表和上次完全相同且 Observation 也相同,直接判定本轮无效,强制 Agent 更换策略,而不是让它继续空转。
坑三:上下文无限膨胀。ReAct 跑得越长,上下文越大,模型越容易“迷失”。解决方案是前面提到的轨迹摘要化。我实测过:一个 20 步的轨迹,压缩成 200 token 摘要后,模型对目标的记忆保持度明显提升,答案相关性也更好。
坑四:评估器代替了任务验证。ToT 里如果只用 LLM 打分评价分支,很容易出现“高分错误分支”。最好给所有候选设计一个最小可验证关卡。比如代码任务先跑单测,数据分析任务先检查数据流关键字段的完整性。验证关卡越硬,树搜索越可靠。
坑五:忽略 Agent 安全边界。这一点很容易被忽视,但非常重要。当 Agent 有了行动和工具调用能力后,实际就是在替你做真实世界的操作——发 HTTP 请求、修改文件、调数据库。任何决策框架都必须有底层安全护栏:工具的权限最小化、敏感操作人工确认、日志可审计、执行沙箱隔离。决策规划解决的是“做什么更聪明”,安全护栏解决的是“做错事也不致命”,两者缺一不可。
6.4 我现在的工程范式:先正交再组合
经过多轮项目实践,我目前的常态架构是:基础执行走 ReAct,失败自动触发 Reflect 提升,长任务用 Plan-and-Execute 套壳,超难推理节点定点启用 ToT。这不是一个“四选一”,而是一个“正交组合”的架构。决策层按任务难度动态切换,底层工具与记忆模块共用,这样既控制了成本,又保留了对复杂任务的兜底能力。
每次给团队分享这段经验时,我都会提醒一句话:不要在概念里打转,先拿一个真实任务把四种模式分别跑一遍,记录 token 消耗、成功率、平均步数,你对该用哪个的直觉自然就准了。决策规划的最终目的,从来不是选定某个花哨框架,而是让 Agent 在真实世界的混乱中找到一条高成功率的通路。