Agent决策规划四范式解析:ReAct、Plan-and-Execute、Reflexion与ToT
做Agent开发有一段时间的人,基本都会走到同一个路口:大模型本身已经能聊天、能写代码、能总结文档,但把它包装成能“自己决策、自己规划下一步做什么”的Agent时,却总感觉差一口气。差在哪?差在模型只会“生成下一个Token”,而Agent需要的是“生成下一步行动”。这也是为什么大家开始研究ReAct、Plan-and-Execute、Reflexion和Tree of Thoughts这几种决策与规划范式。它们本质上是在回答同一个问题:在什么时机,用什么方式,让模型把“思考”和“行动”正确连接起来,并且在出错之后还能自己救回来。
这篇文章我准备把这四种范式一次讲透。没有纸面空谈,也不会只丢概念,我会尽量还原我实际做Agent项目时的思考过程:为什么选这个方案、哪里容易翻车、上线时要处理哪些脏活累活。如果你正在用LangChain、CrewAI、Dify这类框架搭Agent,或者在纠结“我的业务场景到底该用ReAct还是Plan-and-Execute”,这篇文章应该能帮你不靠猜做决定。
1. 先理解Agent决策与规划的四条技术路线:从串行循环到树状搜索
1.1 Agent的“决策”到底指什么
我们常说的Agent,本质是一个“感知 → 决策 → 行动 → 观察”的闭环。感知是把用户请求、工具返回结果、历史记忆组装成上下文;决策是决定“下一步调用哪个工具、输出什么内容”;行动是真正执行一次工具调用或文本回复;观察则是把执行结果再送回上下文。这里面最核心、最影响成败的就是决策环节——它不完全靠大模型的“灵感”,而是靠一套决策框架把模型的推理能力约束在正确轨道上。
没有决策框架的裸Agent,通常会表现出两个毛病:一是走一步看一步,模型看到一个工具返回结果后就忘掉了原始目标,在无关上下文里来回打转;二是“自信地犯错”,当推理出错时模型不但不能识别,还会把错误沿着后续步骤放大,最后给你一个格式整齐、逻辑全错的答案。ReAct、Plan-and-Execute、Reflexion、Tree of Thoughts这四种范式,实际就是针对这些毛病给出的不同解药。
1.2 四种范式的地图:它们分别解决了哪一环
我把这几种范式放在一张“心智地图”里,会更容易理解:
- ReAct:推理和行动交织在同一个循环里,模型边想边做,每执行一步工具调用前先输出一段推理,再把观察结果拼回上下文。解决的是“决策与行动脱节”的问题。
- Plan-and-Execute:先让模型生成完整执行计划,再按计划逐步调用工具,遇到失败时可以做局部重规划。解决的是“长任务频繁决策后容易偏离主线”的问题。
- Reflexion:在一个任务失败后,模型对整条执行轨迹做自我反思,把“哪里错了、下次怎么做”写入记忆,然后重新尝试。解决的是“Agent没有记忆修正能力”的问题。
- Tree of Thoughts(ToT):不再只沿着一条推理路径走,而是把每一步的多个候选想法展开成树,用评估函数打分,再通过搜索(BFS/DFS)探索不同分支。解决的是“复杂推理一步错步步错”的问题。
这四种并不互斥,实际项目里经常叠加使用。我自己的经验是:ReAct是地基,Plan-and-Execute是给地基上盖框架,Reflexion是给框架装“事后复盘”,ToT则是专门处理高难度推理点的“重型武器”。下面我会按这个顺序一个一个讲。
2. ReAct:把推理轨迹和行动轨迹缝在一起的循环
2.1 为什么是“推理 + 行动”而不是“先推理再行动”
ReAct这个名字来自论文《ReAct: Synergizing Reasoning and Acting in Language Models》,核心思想非常朴素:把模型的思维链(Chain of Thought)和工具调用交错进行。传统CoT是让模型从头到尾把推理写完,最后给出答案;ReAct则是每推理一小步,就实际去查一下资料、调一下工具,把真实返回结果作为下一轮推理的输入。这样做的直接好处是,模型不会在第一步假设错误之后,后面全都建立在一个错误地基上。
我做第一个Agent时犯过很典型的错:先让模型用CoT生成完整“解题思路”,再统一调工具。结果模型在没有见过真实工具返回值的情况下,写出了不少像样的中间结论,但实际上每一步用的假设都是错的。后来切到ReAct模式,每次只规划“当前这一步”,然后立刻查数,效果立竿见影。关键区别在于:ReAct不是让模型一次性猜出整条路,而是让它不断“看到路况再拐弯”。
2.2 一个最小可跑的ReAct循环(Python伪代码)
理解ReAct最快的方式,是看一段最小实现。我在这里用最朴素的Python逻辑写,不依赖框架,方便你理解骨架:
def react_agent(query, tools, llm, max_steps=8): context = [{"role": "user", "content": query}] for step in range(max_steps): # 让模型基于当前上下文,输出Thought和Action response = llm.generate(context) thought, action = parse_response(response) # 如果模型认为已经可以给出最终答案 if action == "Finish": return thought, action["answer"] # 执行工具调用 observation = run_tool(tools, action) # 把思考、动作、观察结果全部拼回上下文 context.append({"role": "assistant", "content": response}) context.append({"role": "user", "content": f"Observation: {observation}"}) return "Reached max steps"代码里的parse_response需要按你使用的结构化输出格式来解析。实际项目里我会用JSON格式约束模型的Thought和Action,例如:
{ "thought": "用户想知道北京今天的天气,我需要先查询天气接口", "action": "get_weather", "action_input": {"city": "北京"} }这样把模型输出解析成结构化指令,比直接调用工具函数更稳定,也方便做日志审计和回滚。在我用LangChain搭的早期项目里,AgentExecutor其实就是这套循环的工程化封装:它帮你处理了Thought/Action/Action Input/Observation的格式约定,但底层逻辑和上面这段代码是一回事。
2.3 ReAct容易踩的坑:循环控制、Token爆炸、工具异常
ReAct最让我头疼的三个问题,如果你不提前处理,上线必炸。
第一个是循环控制。真实世界的工具调用经常返回巨长的日志或异常堆栈,模型看完之后可能一遍遍重复同一个失败的Action。所以我强烈建议在AgentExecutor外层加一个步数上限,同时把“重复调用同一工具相同参数”的次数计入限制,一旦超过就强制切换策略或者直接终止。
第二个是Token消耗。ReAct每执行一步就会把Thought、Action、Observation全部追加进上下文,10步下来上下文就可能破万Token。这会带来两个问题:慢,而且后面的步骤模型注意力会涣散。我常用的做法是给Observation做截断和摘要——比如工具返回2万字符,只保留前2000字符加一个“若需要更多数据请再调详情接口”的提示。这本质上是在给Agent做“记忆压缩”。
第三个是工具异常。实际业务里工具失败是常态,接口超时、返回格式不对、权限不足都很常见。你必须在循环里预设“工具调用失败时的兜底动作”:重试一次、换备用工具、还是在Observation里明确告诉模型“该工具失败,请调整计划”。我以前在电商订单Agent里吃过亏:库存查询接口偶发500,模型没有收到失败信号,反而把“查询失败”当成了“库存为零”,直接对用户说商品下架。那次事故之后,我在所有工具调用外面统一封装了返回状态码,强制把失败原因写进Observation。
3. Plan-and-Execute:先画地图再走路,必要时回退重画
3.1 为什么“边想边做”在长任务里会散
ReAct虽然灵活,但在多步骤、跨系统、长时间执行的任务里有一个致命弱点:每一步的决策都只基于当前局部上下文,缺少对全局目标的强约束。举个我做过的人力资源系统Agent例子,任务目标是“帮新员工完成入职流程:创建账号、分配工位、配置权限、发送欢迎邮件”。如果用ReAct,模型每一步都正常,但走到第三步时突然发现权限配置缺少一个参数,于是开始“自由发挥”去查其他接口,结果把流程带偏到“修改组织架构图”上。这个偏差在实际日志里非常难追查,因为每一步单独看都合理。
Plan-and-Execute的核心差异在于:在开始执行任何工具之前,先让模型生成一份完整计划。计划通常是一系列有序的步骤描述,比如:
1. 获取新员工基本信息(HR系统) 2. 创建企业邮箱账号(IT系统) 3. 分配默认权限组(RBAC服务) 4. 查询可用工位并分配(行政系统) 5. 发送入职欢迎邮件(通知服务)然后每个步骤由一个独立的“执行器”完成。执行器可以是一个简单的ReAct循环,也可以是一个固定函数。关键设计是:每完成一步,就对比“计划中的预期结果”和“实际返回结果”,如果不一致,触发重规划(Re-plan),而不是继续按原计划走。
3.2 实现分工:Plan Agent、Executor Agent、Re-planner
在工程上,我把Plan-and-Execute拆成三个独立组件:
- Plan Agent:负责把用户目标拆解为带依赖关系的任务列表。我会要求它明确标注每一步的前置条件、预期输出、需要调用的工具名。这一步的输出质量直接决定整个链路的稳定程度,所以我会在Prompt里给它提供“可用的工具清单及参数说明”,让它不要把计划做到不存在的接口上。
- Executor Agent:每个计划步骤可以单独执行。如果一个步骤本身需要多次工具调用,我会给它挂一个子ReAct循环,或者直接调用写好的函数。重点是执行器必须有明确的“成功/失败”判定,不能含糊。
- Re-planner:当执行器返回失败或结果不符合预期时,Re-planner会把“已完成步骤、失败原因、原始目标”一起发送给大模型,让它决定是修改剩余计划、跳过该步骤,还是补充新步骤。这个机制让“先计划再执行”不至于变成死板的流水线。
很多框架已经把这套模式内化了。比如CrewAI里创建多个Agent并设置process为sequential或hierarchical,本质上就是在用不同Agent承担“规划”和“执行”的角色;Dify里的“工作流”节点编排,也可以看作一种手工版的Plan-and-Execute。但我更建议你先理解原理再选框架,否则很容易被框架的抽象层限制住。
3.3 我踩过的坑:计划太细、重规划太频繁
Plan-and-Execute听起来比ReAct靠谱,但它有两个典型的反模式,都是我亲身体会过的。
第一,计划阶段不要追求“每个步骤都精确到参数”。我曾要求Plan Agent在第一步就生成“调用createUser接口的完整JSON payload”,结果计划阶段消耗了大量Token,而且只要有一个字段猜错,后面所有步骤都得重来。现在我会要求计划只做到“任务级”:明确做什么、调用什么系统、期望得到什么交付物,具体参数留给执行器临场去填。这样计划稳定得多,也抗业务字段变动。
第二,重规划不是万能的,别让Re-planner乱触发。我有一次把“执行结果不符合预期就重规划”设置得非常敏感,结果Agent在一步轻微波动后重新规划了整条链路,前面已经完成的步骤也被推翻重做。后来我给重规划加了约束:只有“明确的目标偏离”或“出现无法恢复的硬错误”时才重规划;局部小问题优先在当前步骤内补救。还要给全局重规划次数设上限,比如最多3次,超过就终止并呼叫人工。这既是技术约束,也是风控要求。
4. Reflexion:用自我反思修补Agent的短期记忆
4.1 核心思想:Agent不仅要“想”,还要“记得自己怎么想错的”
ReAct和Plan-and-Execute都在控制“当下的动作”,但都没有解决一个学习问题:Agent在同一个任务上失败后,如果换个用户再问一次类似问题,它大概率以同样的方式再失败一次。因为每一次运行的记忆都随请求结束而清空了。
Reflexion的思路非常直接:在一条轨迹执行失败后,让模型生成一段结构化的反思——失败的原因是什么、哪一步判断失误、下一次遇到类似情况应该怎么做,然后把反思存起来。当Agent再次面对同类任务时,把之前反思的内容作为额外上下文输入。听起来有点像给Agent加了个“错题本”。实际上很多团队用向量数据库保存反思,按相似度检索注入Prompt,就是最基础的落地方式。
4.2 反思的内容比反思本身更重要
我在实现Reflexion时踩过一个坎:一开始我让模型“请总结为什么失败”,结果生成的反思全是废话,比如“工具可能超时,需要更小心”。这类反思放进错题本里不但没用,还会稀释有效上下文。后来我改成结构化Prompt,强制模型按四部分输出反思:
- 目标回顾:原始任务目标是什么,我最终完成的交付物是什么。
- 失败点定位:在哪个具体步骤,观察到什么信号,偏离了目标。
- 根因判断:是计划缺陷、工具调用参数错误、上下文信息缺失,还是对结果的解读有误。
- 改进策略:如果重来一次,我会改变哪一个具体操作、补充哪一项信息、或者调整哪一步顺序。
举个例子,一个数据查询Agent失败后的反思可能是:
{ "goal": "找出上季度销售额Top 5产品", "failure_point": "在第三步误将‘订单状态’过滤条件收到全部订单,导致结果包含大量退款单", "root_cause": "漏看了工具参数说明里的status枚举值", "improvement": "调用订单列表接口前,先检查参数说明;对status字段优先采用exclude_refunded" }这样的反思文本检索效果好,也能被后续运行真正用上。注意这里我强调“最终改进策略”必须可执行、可验证,而不是“我要更仔细”。
4.3 落地时最容易忽略的两个细节
Reflexion落地时有两个容易被忽略的细节,直接影响效果。第一是触发条件,不是所有失败都要反思。如果一个任务成功完成了,其实也可以生成“成功经验”沉淀下来,这样错题本就不只是“避错本”,还是“经验库”。我建议按任务结果分级:失败任务强制反思;部分成功任务只记录偏离点;完全成功任务可以选择性记录可复用的路数。这样避免过度反思造成额外Token开销。
第二是检索时机的设计。很多实现是在新任务开始前一次性检索相关反思,然后塞进System Prompt。但如果任务中途发生了目标变化或分支探索,最好在每次关键决策前都检索一次反思,而不是只在开头注入一次。因为长期运行Agent的上下文会被后续工具输出冲淡,开头注入的反思早就排到几千Token之外了,模型根本“看不见”。我后来会在ReAct循环每轮生成Thought之前做一次向量检索,把最相关的2到3条反思拼接成Observation片段喂回去,效果比“只开头注入”稳定得多。
5. Tree of Thoughts:把决策路径展开成树,带评估和回溯
5.1 为什么会有ToT:单步贪心在复杂推理里不够用
ReAct和Plan-and-Execute本质上都是“贪心式”决策:模型在每一步选择一个它认为最好的动作,做完就走,从不对其他可能性做系统性比较。这在简单任务上够用,但一旦面对数学证明、逻辑谜题、代码调试这类“需要大量试错”的问题,贪心就容易走进死胡同。比如在调试一段复杂代码时,模型可能沿着一个错误假设一连改了三次,每次都觉得“这次能行”,结果都是白费工夫。它缺的是一种“搜索”的能力:同时保留多个可能的修复方向,哪个失败了能退回原点试另一个。
Tree of Thoughts把单条推理链扩展成树形结构。模型在每一步不是生成一个想法,而是生成多个候选想法;每个候选想法会被一个评估函数打分;然后根据搜索策略(广度优先BFS或深度优先DFS)决定下一步探索哪些分支。这个过程类似下棋时的“多算几步”:不是只看眼前掉子,而是要评估几个不同走法的后续局面。
5.2 ToT的五个基本组件
我参考原论文的框架,把ToT拆成五个组件,这样落地的时候你能清楚知道要写什么:
- 思维片段生成器(Thought Generator):给定当前状态,让模型生成若干个下一步候选想法。例如在解数独时,生成多个候选数字填空方案。这个环节的Prompt常要求“给出3种不同做法”,而不是只给一个。
- 状态评估器(State Evaluator):对每个候选想法打分或给出“可行/不可行/可能可行”的判断。打分可以用模型自己输出置信度,也可以写基于规则的判定函数(比如编译是否通过、约束是否满足)。
- 搜索算法:最常用的是BFS和DFS。BFS适合每层分支数量不多且可以横向比较的场景;DFS适合“先深入一条路看能不能走通,不通再回溯”的场景。实际项目里我一般把DFS用得更顺手,因为它对Token预算更友好。
- 回溯机制:当某个分支被评估为失败,系统需要回到最近的分叉点,选择另一个未探索过的候选分支。这是ToT区别于ReAct的核心能力。
- 终局判定:判断某个叶节点是否构成完整答案。这里需要用“验证器”,而不是模型自己说“我完成了”。例如代码任务就实际跑测试用例,数学题就做符号化简验证。
5.3 一个高度简化的ToT实现思路
这里我不给你完整代码(那会很长,而且业务绑定很深),给一个可以照着改的伪代码逻辑:
def tree_of_thoughts(problem, llm, evaluator, max_depth=5, branches=3): # 初始状态:还没任何想法 frontier = [{"state": "", "depth": 0, "path": []}] while frontier: node = frontier.pop(0) if evaluator.is_solution(node["state"]): return node["path"] if node["depth"] >= max_depth: continue # 生成 branches 个候选想法 candidates = llm.generate_candidates(node["state"], n=branches) for cand in candidates: score = evaluator.score(cand) # 只保留评分超过阈值的候选 if score > 0.5: frontier.append({ "state": node["state"] + cand, "depth": node["depth"] + 1, "path": node["path"] + [cand] }) # 可以按评分排序,优先探索高分分支 frontier.sort(key=lambda x: evaluator.score(x["state"]), reverse=True) return None当依赖的工具是代码解释器时,evaluator可以直接执行片段并看是否通过测试;当依赖的是纯语言推理时,评估器可以用“模型二重采样”方式:让另一个独立推理实例判断这个候选方向是否比当前最优方向更有利。实践里,ToT的Token消耗通常比ReAct高一个数量级,所以不要动不动就上ToT。
5.4 什么场景才值得用ToT
我的判断标准很简单:如果这个任务你用CoT测试10次,成功率还是低于50%,并且每次失败都发生在不同位置,那么值得试ToT;如果只是偶尔一次参数传错,那用ReAct加反思就够了。
我曾在代码修复Agent里局部启用ToT:当单元测试失败且Agent前面的修复尝试已经连续两次失败时,系统自动切入ToT模式,让模型同时生成3种不同的改动方案,每个方案都先打补丁跑测试,再根据结果回溯换下一个方案。这样把一次代码修复的成功率从不到40%提到了接近70%,代价是单次耗时翻了3倍。所以ToT在我心里是“高价值难题专用路径”,不是一个默认白天黑夜都开的服务。
6. 落地选型:四种范式怎么组合,以及框架选择
6.1 对照表:一眼看清四种范式的差异
我把它们放进一个对照表,方便你给团队评审或做技术选型时直接粘贴:
| 范式 | 决策方式 | 是否回溯 | 记忆能力 | 相对成本 | 典型适用场景 |
|---|---|---|---|---|---|
| ReAct | 单步交错推理+行动 | 否 | 依赖上下文窗口 | 低 | 问答、工具调用、信息检索 |
| Plan-and-Execute | 先全局规划再逐步执行 | 局部重规划 | 计划保留在上下文 | 中 | 多步骤业务流程、跨系统操作 |
| Reflexion | 事后反思+重试 | 可重试但非搜索 | 使用外部记忆库 | 中高 | 同一类任务反复失败需要经验沉淀 |
| Tree of Thoughts | 树状搜索+评估+回溯 | 是 | 搜索树辅助记忆 | 高 | 高难度逻辑推理、代码调试、复杂求解 |
实际工作中,把四种范式当成“方案A或方案B”是错的。它们是可以叠加的:我在CrewAI里搭建过一个客服工单Agent,主流程用了Plan-and-Execute把工单分类、知识检索、答复生成拆成三部曲;每个步骤内部实际用的是ReAct循环;当整个工单处理失败时,Reflexion模块会生成反思记录写进向量库;而对于那些“需要对比多个政策条款矛盾”的复杂工单,我会额外挂一个ToT分支来做决策。这个组合跑下来,比单独使用任何一种都稳定得多。
6.2 选型建议:从业务目标倒推架构
不要先问“哪个框架好”,先问自己:我的Agent最多需要几步才能完成任务?允许失败重试吗?失败后是否有人在旁边兜底?
- 如果任务通常少于5步、目标明确、单次工具调用能完成,ReAct足够。它简单、透明、好排查,别为了显得“智能”硬上Plan-and-Execute。LangChain的
AgentExecutor和OpenAI的function calling都能直接支持。 - 如果是跨系统、多步骤流程,比如“创建订单→通知仓库→更新库存”,Plan-and-Execute比ReAct更不容易跑偏。Dify里的“工作流”模式和CrewAI里的sequential Process都能帮你落地。
- 如果你们有一个高频失败场景,并且Agent能在真实环境里反复尝试,比如“自动修无效数据”,那一定要上Reflexion。把每次失败原因沉淀成检索经验,能肉眼看到失败率逐周下降。
- 如果你们要处理的是“高难度单点问题”,比如复杂SQL生成、多约束排期、数学求解,那么ToT可能是唯一有效解,但你要接受它的成本和延迟。
6.3 从我自己的项目里拿来的“生产级”建议
最后给你几条不一定写进博客,但真实有用的经验:
第一,不管用哪种范式,日志里一定要保留完整的Thought/Action/Observation轨迹。ReAct类的Agent最难的不是实现,而是出了问题你怎么快速定位。我习惯给每步打上step_id、trace_id,并把模型当时的完整输出原样存档,这样复盘Reflexion时才有的放矢。
第二,Prompt里给模型列举工具时,顺序会显著影响决策。我试过把高优先级的工具放在列表前三位,模型选择它们的概率明显更高。如果你不想让Agent频繁尝试低成功率工具,直接调整工具列表顺序比在Prompt里写“尽量别用”更管用。
第三,所有重试和回溯都要有限次。Reflexion最多重试两次,ToT最多回溯三次,Plan-and-Execute最多重规划三次。无限重试会让一个失败任务消耗巨额Token,最后还可能把API Budget烧完。给上限还有一个好处:逼着你在Agent之外设计人工兜底,这对生产系统是必须的。
第四,别忘了一开始的用户目标要始终可见。很多Agent跑偏是因为原始目标被上下文淹没。我现在的做法是,在System Prompt里恒定保留一段“原始任务目标摘要”,并且每隔若干步由另一个模型判断“当前动作是否仍然和目标相关”。这个“目标进度审计”机制,比任何花哨的决策范式都更能兜住底线。
说回选型:如果你现在还在Demo阶段,我建议你先用ReAct把一个最小闭环跑通,观察失败模式;然后在失败最多的地方引入Reflexion或Plan-and-Execute;等到碰到单点难题再看ToT。这样比一开始就搭一个超复杂的多范式编排系统要务实得多。我身边见过太多团队一上来就堆CrewAI多Agent编排,结果连最基础的ReAct轨迹还没稳定,排错成本直接翻了好几倍。Agent架构不是越复杂越好,而是“在刚好能解决问题的复杂度里,给未来留出升级空间”。