1. 为什么这四个词总被放在一起聊
如果你最近在折腾 Agent 开发,大概率会在同一个下午里连续刷到 ReAct、Plan-and-Execute、Reflexion、Tree of Thoughts 这四个词。它们经常被塞进同一张架构图里,配上箭头和循环,看起来像一套组合拳。但真到写代码的时候,很多人会卡在一个很实际的问题上:这四个东西到底是并列的四种方案,还是可以叠在一起用的四层机制?
我的结论是:它们不是同一维度的东西。ReAct 解决的是"单步怎么想、怎么动"的问题,Plan-and-Execute 解决的是"多步怎么排、怎么调"的问题,Reflexion 解决的是"做完之后怎么复盘、怎么改"的问题,Tree of Thoughts 解决的是"在关键分叉口怎么并行探索、怎么剪枝"的问题。把它们当成四个互斥的选项去选型,是新手最容易踩的第一个坑。
这篇内容我打算按"实际搭一个能跑通的 Agent"的顺序来讲,而不是按论文发表顺序讲。因为论文顺序会让你觉得它们是四个独立模块,而工程顺序会让你发现它们其实是四个可以按需挂载的决策层。适合谁看?如果你已经写过最基础的"调一次模型、解析一次输出、执行一次工具"的循环,但发现它在稍微复杂点的任务上就开始胡言乱语、反复绕圈、或者一条道走到黑,那这篇就是写给你的。
我会把每个机制的核心逻辑拆开,讲清楚它到底在解决什么失败模式,然后给出可落地的伪代码结构和参数建议,最后聊几个我在实际项目里踩过的坑。全程不堆公式,尽量用"你写代码时会遇到什么"的视角来讲。
2. ReAct:把"想"和"做"缝进同一个循环
2.1 ReAct 真正解决的问题不是"推理",而是"推理和行动的脱节"
很多人第一次看 ReAct 的论文,会觉得它只是让模型在输出答案前先写一段"Thought"。这个理解太浅了。ReAct 的核心贡献是把推理轨迹(Reasoning trace)和动作轨迹(Action trace)交织在同一个生成序列里,让模型在每一步都能看到"我上一步做了什么、得到了什么观察、现在该想什么"。
在没有 ReAct 之前,常见的做法是两段式:先让模型规划一整串步骤,再逐步执行。这个做法在简单任务上没问题,但一旦中间某步的工具返回和预期不符,整个计划就废了,因为模型在规划阶段并没有看到真实的观察结果。ReAct 把观察(Observation)插回循环里,模型每一步都能基于最新事实重新决策,这才是它真正的价值。
用生活化的类比:两段式规划像出门前把一整天的路线全定死,ReAct 像边走边看导航,遇到堵车就重新算路。前者效率高但脆,后者灵活但每步都要多花一点推理成本。
2.2 一个最小可用的 ReAct 循环长什么样
我不打算贴某个框架的 API 调用,因为框架会变,逻辑不会变。下面这个结构是我自己反复用过、也推荐给团队新人的骨架:
def react_loop(task, tools, max_steps=8): scratchpad = [] for step in range(max_steps): prompt = build_prompt(task, scratchpad, tools) output = llm.generate(prompt) thought, action, action_input = parse(output) scratchpad.append(("thought", thought)) if action == "finish": return action_input observation = tools[action](action_input) scratchpad.append(("action", action, action_input)) scratchpad.append(("observation", observation)) return "达到最大步数仍未完成"关键点有三个。第一,scratchpad必须把 thought、action、observation 三类都存下来,只存 action 和 observation 会让模型丢失推理上下文。第二,parse这一步是整个循环最脆弱的地方,后面会专门讲。第三,max_steps是必须的,没有它,模型在工具反复报错时会无限循环下去。
2.3 解析输出这件事,比你想的更容易翻车
ReAct 的经典格式是Thought: ... Action: ... Action Input: ...,然后靠正则去抽。我早期项目里最常出的问题不是模型不会推理,而是模型在 Action 那一行多写了一句解释,或者把 Action Input 写成了自然语言而不是结构化参数,导致解析失败。
我的处理经验是:不要指望模型永远守格式,要在解析层做容错。具体做法是解析失败时不要直接抛异常终止,而是把"格式错误"本身作为一条 observation 塞回 scratchpad,让模型自己纠正。实测下来,模型看到"你的上一条输出无法解析,请严格按 Thought/Action/Action Input 三行格式重写"之后,第二次基本都能改对。
另一个经验是工具数量别一次给太多。当工具超过十个,模型选错工具的概率会明显上升,因为它要在长列表里做匹配。我的做法是按任务类型分组,每轮只暴露当前子任务相关的三到五个工具,这个改动对准确率的提升比换更大的模型还明显。
2.4 ReAct 的边界:它不擅长长程规划
ReAct 是贪心的,每一步只看当前最优。这在需要五步以上、且步骤之间有强依赖的任务上会出问题。比如"先查A,根据A的结果决定查B还是C,再根据B/C的结果汇总",ReAct 经常会在中途忘记最初的目标,或者陷入局部最优。
这不是模型能力问题,是机制问题。贪心策略天然缺乏全局视野。所以当你的任务开始出现"必须先规划再执行"的特征时,就该考虑下一节的 Plan-and-Execute 了。
3. Plan-and-Execute:先画图纸,再动工
3.1 它和 ReAct 的分工到底在哪
Plan-and-Execute 把流程切成两个阶段:Planner 负责把任务拆成一串有序的子步骤,Executor 负责逐步执行每个子步骤。执行器本身可以是一个 ReAct 循环,也可以是简单的单次工具调用。这个结构的关键在于,规划是一次性的全局视角,执行是局部的细节处理。
为什么这个分工有效?因为"拆解任务"和"执行任务"需要的思维模式不一样。拆解需要的是全局抽象能力,执行需要的是局部精确能力。让同一个模型在同一个上下文里同时干这两件事,它容易在细节里迷失全局。分开之后,Planner 的上下文里只有任务描述和步骤列表,干净;Executor 的上下文里只有当前子步骤和工具,聚焦。
3.2 规划粒度:太粗会失控,太细会僵化
这是 Plan-and-Execute 落地时最需要调的一个参数。规划太粗,比如只拆成"调研、分析、输出"三步,那 Executor 在每一步里还是要自己做大量决策,等于没规划。规划太细,比如把"打开网页、定位元素、点击按钮"都写进计划,那计划一旦遇到页面结构变化就全废,而且 Planner 的 token 消耗会爆炸。
我的经验值是:每个子步骤应该对应"一次可独立验证的产出"。比如"获取某产品的官方定价信息"是一个合适的粒度,因为它有明确的完成标志(拿到价格)。而"研究市场"太粗,"点击第三个链接"太细。你可以用这个标准去检验你的规划输出:每个步骤能不能用一句话说清楚"做完它之后我手里多了什么"。
3.3 计划不是圣旨:重规划机制怎么加
纯 Plan-and-Execute 最大的风险是计划与现实脱节。Executor 执行到第三步发现第二步的假设错了,如果还硬着头皮往下走,结果必然是错的。所以生产级的 Plan-and-Execute 一定要有重规划(Replan)机制。
我的做法是在 Executor 每完成一步后,加一个轻量的检查:当前步骤的产出是否满足下一步的前置条件?如果不满足,触发 Replan,把"已完成步骤 + 实际产出 + 剩余目标"重新喂给 Planner,让它生成新的剩余计划。这个检查不需要很重,有时候就是一次简单的模型判断,但它能把"一条道走到黑"的概率降下来一大截。
注意:Replan 不要每步都触发,否则 Planner 会被频繁调用,成本和延迟都上去了。只在"产出与预期明显不符"或"连续两步没有进展"时触发,性价比最高。
3.4 什么时候该用 Plan-and-Execute,什么时候别用
适合的场景:步骤数多(五步以上)、步骤间有依赖、任务可以提前大致拆解。比如报告生成、多源信息汇总、复杂的表单填写流程。
不适合的场景:任务高度依赖实时反馈、每一步的结果都会大幅改变后续方向。比如开放式对话、实时调试、探索性搜索。这些场景下 ReAct 的贪心反而更合适,因为规划出来的东西大概率用不上。
我见过不少团队一上来就上 Plan-and-Execute,觉得"有规划显得高级",结果在交互式任务上表现还不如纯 ReAct。选型要看任务形态,不是看机制复杂度。
4. Reflexion:让 Agent 学会"吃一堑长一智"
4.1 反思不是重试,区别很大
很多人把 Reflexion 理解成"失败了就重试"。这是误解。单纯重试是把同样的输入再跑一遍,模型大概率犯同样的错。Reflexion 的核心是在失败之后,先生成一段语言化的反思,说明"为什么失败、下次该怎么改",然后把这段反思作为额外上下文注入到下一次尝试里。
这个区别很关键。重试是盲目重复,反思是带信息的迭代。前者靠运气,后者靠积累。用生活类比:重试是同一道题再做一遍,反思是做完之后对答案、分析错因、再做一遍。
4.2 反思该反思什么:三个层次
我在实际项目里把反思分成三个层次,按需使用:
- 结果层反思:只关注"最终答案对不对"。适合有明确对错标准的任务,比如数学题、代码测试。反思内容通常是"上次算错了某一步,这次注意"。
- 过程层反思:关注"执行路径有没有问题"。适合工具调用类任务。反思内容通常是"上次选错了工具,应该先用搜索再用计算"。
- 策略层反思:关注"整体方法对不对"。适合开放式任务。反思内容通常是"上次直接给结论太草率,应该先收集证据再下判断"。
层次越高,反思越抽象,泛化能力越强,但也越容易空泛。我的经验是:有明确验证信号的任务用结果层,工具类任务用过程层,只有开放式创作类任务才值得上策略层。
4.3 反思记忆怎么存、怎么取
Reflexion 要真正发挥作用,反思不能只存在当前这次任务的上下文里,得存下来,下次遇到类似任务能取出来用。这就涉及一个轻量的记忆机制。
我的做法是用一个简单的向量库存反思文本,每条反思带上任务类型标签。新任务开始时,用任务描述去检索最相关的几条反思,注入到系统提示里。检索条数控制在三到五条,太多会污染上下文,太少起不到作用。
这里有个容易忽略的点:反思要带"适用条件"。比如"遇到需要计算的任务时,先确认单位再计算"这条反思,如果不带条件,模型可能在不需要计算的任务上也去确认单位,反而添乱。所以存反思的时候,最好让它自己生成一句"这条经验适用于什么情况"。
4.4 反思的代价:延迟和成本会翻倍
Reflexion 不是免费的。每次失败都要多一次模型调用去生成反思,如果任务本身要跑好几轮,成本会明显上升。所以我的建议是:只在"失败代价高"或"同类任务会反复出现"的场景下用 Reflexion。一次性任务、失败也无所谓的任务,直接重试就够了。
另外,反思的轮数要有上限。我一般设两到三轮,超过还没成功就说明要么任务本身超出能力,要么反思方向错了,继续下去只是烧钱。
5. Tree of Thoughts:在关键分叉口做并行探索
5.1 ToT 的本质是搜索,不是提示词技巧
Tree of Thoughts 经常被当成一种"高级提示词"来讲,但它本质上是把推理过程建模成一棵树,每个节点是一个中间状态,每个分支是一种可能的下一步,然后用搜索算法(BFS、DFS 或带评估的启发式搜索)去探索。它解决的是"单条推理链一旦走错就无法回头"的问题。
ReAct 和 Plan-and-Execute 都是线性的,一条路走到黑。ToT 允许你在关键节点展开多个候选,评估每个候选的前景,然后决定往哪个方向深入。这在需要"试错"或"多角度思考"的任务上优势明显,比如谜题求解、方案设计、需要权衡取舍的决策。
5.2 评估函数是 ToT 的命门
ToT 能不能用好,九成取决于评估函数。你需要一个机制去判断"当前这个中间状态有多大希望通向正确答案"。这个评估可以是模型自己打分,也可以是用规则或外部工具验证。
模型自评的坑在于:它倾向于给自己刚生成的候选打高分,缺乏区分度。我的应对办法是让评估和生成用不同的提示,甚至不同的模型。生成用擅长发散的,评估用擅长判断的,两者分开,评分会客观很多。
另一个办法是让评估输出"这个状态是否已经确定错误"而不是"这个状态有多好"。二分类比打分更稳定,因为模型对"明显错了"的判断比对"有多好"的判断可靠得多。用这个思路做剪枝,效果比打分排序好。
5.3 分支因子和深度:两个必须控制的参数
ToT 的搜索空间是指数增长的。分支因子为 b、深度为 d 时,最坏情况要评估 b 的 d 次方个节点。所以这两个参数必须严格控制。
我的经验值:分支因子控制在 2 到 3,深度控制在 3 到 4。超过这个范围,成本和延迟会失控,而且大部分分支其实是无效的。如果任务确实需要更深的探索,宁可分段做多次 ToT,也不要一次性把树铺太大。
还有一个实用技巧:在展开分支之前,先让模型判断"当前节点是否值得展开"。如果当前状态已经接近答案,或者明显是死路,就直接跳过展开,省下大量评估调用。
5.4 ToT 适合嵌在哪里
ToT 不适合作为整个 Agent 的主循环,因为太贵。它更适合嵌在关键决策点。比如 Plan-and-Execute 里 Planner 拆解任务时,如果拆解方案有多种可能,可以用 ToT 探索几种拆法再选最优。或者 ReAct 在某一步面临多个工具选择且难以判断时,用 ToT 展开评估。
把它当成一个"重型决策工具",只在真正需要权衡的地方调用,而不是全程开着。这个定位能让它在成本和效果之间取得平衡。
6. 四个机制怎么组合成一个能打的 Agent
6.1 分层组合:不是四选一,是四层可插拔
讲到这里,组合思路就清楚了。我的推荐架构是:
- 外层用 Plan-and-Execute 做骨架,负责把任务拆成有序子步骤,并在必要时重规划。
- 每个子步骤内部用 ReAct 做执行,负责在局部灵活调用工具、处理观察。
- 在 Planner 拆解或 ReAct 遇到关键分叉时,按需调用 ToT做多方案探索。
- 整个任务结束后,用 Reflexion 做复盘,把经验存进记忆,供后续任务检索。
这四层不是必须全上。简单任务只用 ReAct 就够;中等任务加 Plan-and-Execute;需要试错的加 ToT;需要跨任务积累的加 Reflexion。按需挂载,别为了架构完整而堆机制。
6.2 一个组合后的执行流程示例
假设任务是"调研三款竞品并输出对比报告":
- Planner 拆解为:确定竞品清单、逐个收集信息、整理对比维度、生成报告。
- 对"确定竞品清单"这一步,如果候选很多,用 ToT 展开几种筛选标准,评估后选一个。
- 每个"收集信息"子步骤用 ReAct 执行,灵活调用搜索、抓取、解析工具。
- 如果某款竞品信息收集失败,触发 Reflexion,反思是搜索词不对还是数据源选错,带着反思重试。
- 全部完成后,Reflexion 生成一条过程层反思,比如"竞品调研时先确认官方渠道再搜第三方",存入记忆。
这个流程里,四个机制各司其职,没有一个是多余的。
6.3 状态管理:组合架构最容易崩的地方
多个机制组合之后,状态管理会变成最大的工程挑战。ReAct 有自己的 scratchpad,Plan-and-Execute 有步骤列表和执行状态,ToT 有搜索树,Reflexion 有反思记忆。这些状态如果混在一起,模型会晕,你调试也会晕。
我的做法是分层隔离:每个机制维护自己的状态容器,机制之间只通过明确定义的接口传递信息。比如 ReAct 完成后只向上返回"子步骤产出",不把整个 scratchpad 暴露给 Planner。ToT 完成后只返回"选定的方案",不返回整棵树。这样每个机制的上下文都干净,出问题也容易定位。
6.4 成本与延迟的现实权衡
组合架构的效果通常更好,但成本和延迟也更高。我做过一个粗略的对比,同样一个中等复杂度的任务:
| 架构 | 相对调用次数 | 相对延迟 | 适合场景 |
|---|---|---|---|
| 纯 ReAct | 1x | 1x | 简单工具调用 |
| Plan-and-Execute | 1.5x | 1.3x | 多步有依赖任务 |
| 加 Reflexion | 2.5x | 2x | 需要跨任务积累 |
| 加 ToT | 4x 以上 | 3x 以上 | 关键决策需探索 |
这个表不是精确数据,是量级参考。核心结论是:每加一层机制,成本都会上一个台阶。所以选型时要问自己"这个任务真的需要这一层吗",而不是"能不能加上"。
7. 我在实际项目里踩过的几个坑
7.1 坑一:把 ReAct 的 max_steps 设太大
早期我为了"给模型足够空间",把 max_steps 设成 20。结果发现模型在工具报错时会反复尝试同一个错误操作,一路烧到第 20 步才停。后来改成 8,并且在连续两次相同错误时主动中断,成本降了一半,成功率反而没降。教训是:步数上限不是越大越好,配合错误检测才有意义。
7.2 坑二:Reflexion 的反思污染了后续任务
有段时间我发现 Agent 在简单任务上也开始"过度谨慎",明明一步能做完的事,它非要先反思一下。排查后发现是反思记忆检索太宽泛,把不相关的反思也注入了。修复方法是给每条反思加严格的任务类型标签,检索时先按类型过滤再按相似度排序。这个改动之后,误注入基本消失了。
7.3 坑三:ToT 的评估函数和生成用了同一个提示
一开始我图省事,生成候选和评估候选用的是同一个系统提示,只是任务描述不同。结果评估几乎没有区分度,选出来的分支和随机选差不多。后来把评估单独拆出来,用完全不同的提示,明确要求它"找出这个方案的致命缺陷",评分才变得有参考价值。这个坑让我意识到,评估和生成是两种不同的认知任务,不能共用一套提示。
7.4 坑四:组合架构的日志没做分层
多机制组合之后,调试变成噩梦,因为所有机制的日志混在一起。后来我强制要求每个机制的输出带上来源标签,日志按机制分层打印。这样出问题时能快速定位是哪一层的问题,而不是对着一大坨输出发呆。这个改动看起来是小事,但对开发效率的提升非常明显。
8. 给不同阶段开发者的上手建议
如果你刚接触 Agent,我的建议是先把纯 ReAct 跑通,重点练两件事:工具描述怎么写清楚、输出解析怎么做容错。这两件事做好了,后面加任何机制都顺。
如果你已经能稳定跑 ReAct,下一步加 Plan-and-Execute,重点练规划粒度的把控和重规划触发条件的设计。这两个参数调好了,多步任务的稳定性会有质的提升。
如果你在做需要跨任务积累的产品,再加 Reflexion,重点练反思的层次选择和记忆检索的精度。别一上来就上策略层反思,从结果层开始,稳了再往上走。
如果你面对的是需要权衡取舍的复杂决策,再考虑 ToT,重点练评估函数的独立性和分支剪枝策略。记住它是重型工具,别全程开着。
最后分享一个我自己的判断标准:当你发现 Agent 的失败模式是"想得不够"时,加 ToT;是"记不住"时,加 Reflexion;是"没全局观"时,加 Plan-and-Execute;是"单步执行不稳"时,回去打磨 ReAct。对症下药,比盲目堆机制有效得多。