Agent 范式演进:从 ReAct 到 Plan-Act-Observe-Reflect
做 Agent 开发,我越来越觉得一件事:选范式比选模型重要。
模型能力不够可以换,但范式选错了,整个架构就是别扭的。你见过那种项目吗?明明用 ReAct 就能搞定的事,非得上 Plan-and-Execute,结果每次对话都要等三秒才开始回复,用户体验一塌糊涂。也见过反过来的——复杂任务用纯 ReAct 硬扛,结果 Agent 在那死循环转圈,Token 烧了一堆,啥也没干成。
今天聊聊三代 Agent 范式:ReAct、Plan-and-Execute、Reflexion。不是纯理论,最后会结合我自己的 NVC 项目讲讲选型思路。
第一代:ReAct —— 推理与行动的交替
ReAct 是 2022 年出来的,论文标题就很直白:ReAct: Synergizing Reasoning and Acting in Language Models。核心想法很简单——让 LLM 一边想一边做。
传统做法是两种极端:要么纯推理(Chain-of-Thought,光想不做),要么纯行动(直接调工具,不想为什么调)。ReAct 把两者揉在一起,搞了个循环:
Thought: 我需要查一下今天的天气
Action: search("今天北京天气")
Observation: 北京今天晴,25°C
Thought: 用户问的是要不要带伞,晴天不用带
Action: respond("今天北京是晴天,25度,不用带伞")
画成流程图大概是这样:
三个角色轮流上场:Thought 负责推理下一步该干嘛,Action 负责实际执行(调工具、查数据库、发请求),Observation 把执行结果喂回去。循环往复,直到任务完成。
ReAct 的优势很明显。简单。真的就是简单。你不需要额外的规划模块,不需要复杂的状态管理,一个 while 循环加个 LLM 调用就能跑起来。灵活性也强——Agent 可以根据中间结果随时调整策略,不用被预设计划绑死。可解释性也不错,每一步的 Thought 都在那摆着,出了问题你能回溯。
但用久了你会发现问题。
缺乏全局视野。ReAct 是走一步看一步,Agent 不知道后面还有多少步。就像你去超市买东西,走进去才想起来要买什么,每次都是现想现找。效率低不说,还容易漏东西。
容易死循环。这是我踩过最深的坑。Agent 遇到一个它解决不了的问题,会反复尝试同一个方案。Thought 说"我再试试",Action 执行一遍,Observation 说"还是不行",然后 Thought 又说"我再试试"……Token 哗哗地烧,最后要么触发最大步数限制,要么你手动杀掉。
代码层面,一个典型的 ReAct 循环长这样:
import openai
def react_loop(user_input: str, tools: dict, max_steps: int = 10):
messages = [{"role": "user", "content": user_input}]
for step in range(max_steps):
response = openai.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools,
)
msg = response.choices[0].message
# 没有工具调用,说明任务完成
if not msg.tool_calls:
return msg.content
# 执行工具调用
for call in msg.tool_calls:
tool_name = call.function.name
tool_args = call.function.arguments
result = tools[tool_name](**tool_args)
messages.append(msg)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": str(result),
})
return "达到最大步数,任务未完成"
简单粗暴,能用。但生产环境你得加很多保护:超时、重试、死循环检测、Token 预算控制。
第二代:Plan-and-Execute —— 先想清楚再动手
2023 年出来的 Plan-and-Execute(论文:Plan-and-Solve Prompting),思路是"磨刀不误砍柴工"。先让 LLM 做一个完整计划,然后按计划逐步执行。
Plan:
1. 查询用户的订单列表
2. 筛选出最近30天的订单
3. 计算总金额
4. 生成消费报告
Execute: 按计划逐步执行...
架构上比 ReAct 多了一层:
Planner 和 Executor 是两个角色。Planner 只负责规划,Executor 只负责执行。有些实现里这两个用不同的模型——Planner 用大模型(推理能力强),Executor 用小模型或专用模型(成本低、速度快)。
全局视野是 Plan-and-Execute 最大的优势。Agent 在开始执行之前就知道整个任务要分几步,哪些步骤可以并行,哪些有依赖关系。不会像 ReAct 那样走一步看一步,也不会因为中间某个意外就迷失方向。
减少无效调用也是个实实在在的好处。规划阶段就能判断哪些步骤不需要,避免了 ReAct 里那种"试了才发现不需要"的浪费。
但代价也不小。
灵活性不足。计划一旦定了,执行过程中遇到意外情况,调整成本很高。你去买菜,计划是先买鸡蛋再买牛奶,结果鸡蛋卖完了——ReAct 的反应是"那我先去买牛奶,回头再看看有没有鸭蛋";Plan-and-Execute 的反应是"等等,我得重新规划一下"。
延迟更高。用户说一句话,你得先花两秒钟规划,再开始执行。对于实时对话场景,这两秒钟的等待是致命的。用户会觉得你的 Agent 很"迟钝"。
def plan_and_execute(user_input: str, tools: dict):
# 第一步:规划
plan = create_plan(user_input)
# 这一步就要花 1-2 秒
# 第二步:逐步执行
results = []
for step in plan.steps:
result = execute_step(step, tools)
results.append(result)
# 检查是否需要调整计划
if needs_replanning(step, result):
plan = create_plan(user_input, context=results)
# 重新规划又要花时间
return synthesize_results(results)
代码结构上看,Plan-and-Execute 比 ReAct 复杂不少。你需要维护一个计划对象,处理计划调整的逻辑,还要考虑计划执行失败的回退策略。
第三代:Reflexion —— 执行后反思
Reflexion 是 2023 年的另一篇论文,核心思想用一句话概括:做完之后想一想,下次怎么能做得更好。
Reflexion 的循环多了一个关键步骤:反思。执行完之后,不是直接结束或者重试,而是先停下来想想——刚才哪里做得不好?为什么会失败?下次应该怎么做?
这个反思会存进一个"记忆库"里。下次再遇到类似问题,Agent 会先查记忆,看看以前踩过什么坑。有点像人类的经验积累——第一次做某件事可能笨手笨脚,但复盘几次之后就越来越熟练。
自我纠错是 Reflexion 最大的亮点。ReAct 和 Plan-and-Execute 都没有这个能力——它们失败了就是失败了,下次遇到同样问题还是会犯同样错误。Reflexion 不一样,它能从失败中学习。
但额外开销是绕不开的问题。每次执行完都要反思,反思本身就要调一次 LLM,还要维护记忆库。对于简单任务,这个开销可能比任务本身还大。
反思质量也不稳定。LLM 的反思不一定靠谱——它可能反思了个寂寞,也可能反思出错误的经验。你得设计好反思的模板和评估标准,不然这个机制就是摆设。
def reflexion_loop(task: str, tools: dict, memory: list, max_trials: int = 3):
for trial in range(max_trials):
# 查询历史反思
relevant_reflections = search_memory(memory, task)
# 执行任务(带上历史反思)
result = execute_with_reflection(task, tools, relevant_reflections)
# 评估结果
score = evaluate_result(task, result)
if score >= 0.8: # 满意
return result
# 不满意,生成反思
reflection = generate_reflection(task, result, score)
memory.append(reflection) # 存入记忆
return result # 达到最大重试次数
实战:NVC 项目的选型
理论讲完了,聊聊实战。
我在做的 NVC(非暴力沟通)练习平台是一个实时对话助手,帮用户在冲突场景下更好地表达自己。技术栈是 Spring Boot 4.0 + Java 21(虚拟线程)+ Spring AI 2.0 + PostgreSQL + pgvector + Redis。这个场景有几个特点:
- 延迟敏感——用户在对话中,等三秒才回复是不可接受的
- 任务相对简单——每次交互就是理解和改写一句话,不需要复杂规划
- 需要持续对话——不是一次性任务,是多轮对话
这三个特点直接排除了 Plan-and-Execute。两秒的规划延迟在实时对话里就是灾难。
Reflexion 呢?理论上很诱人——Agent 可以从每次对话中学习,越用越好。但问题在于,反思是需要时间的。你不能让用户等你反思完了再回复。
最终我选了 ReAct 作为主体范式,但做了两个关键改造:
改造一:异步反思
反思不能不做,但也不能阻塞主流程。NVC 的方案是通过 EvaluationTriggerHook 在 evaluate_nvc 工具成功后异步触发 Wiki 自动生成:
// EvaluationTriggerHook - 评估触发 Hook
// 在 evaluate_nvc 工具执行成功后,异步触发 Wiki 自动生成
public class EvaluationTriggerHook implements ToolHook {
@Override
public void afterToolCall(ToolCall call, ToolResult result, ToolContext context) {
if ("evaluate_nvc".equals(call.getName()) && result.isSuccess()) {
// 异步触发 Wiki 生成,不阻塞主流程
CompletableFuture.runAsync(() -> {
wikiWriteTool.generateFromEvaluation(result, context);
}, executorService);
}
}
}
EvaluateNvcTool 负责判断用户的表达是否符合非暴力沟通的四步法(观察不评判、表达感受而非指责、说出需要、提出请求)。评估成功后,EvaluationTriggerHook 异步触发 Wiki 生成,存进知识库,供后续 rag_search 检索参考。
用户感受到的是毫秒级的响应,Agent 实际上在后台悄悄积累知识。
改造二:IntentRouter 意图预路由
ReAct 的一个痛点是"走一步看一步",那我能不能在开始之前就多看几步?
NVC 的方案是在 AgentLoop 入口加一个 IntentRouter——在 ReAct 循环开始之前,通过正则匹配快速识别高置信度意图,跳过 LLM 直接执行工具:
// IntentRouter.java
public AgentResult execute(PracticeContext context) {
// 1. 意图预路由(跳过 ReAct 循环)
IntentMatch match = intentRouter.match(context.getUserMessage());
if (match != null && match.getConfidence() >= 0.8) {
// 高置信度意图 → 直接执行工具,不走 LLM
return toolExecutor.executeDirectly(match.getToolName(), match.getArgs());
}
// 2. 低置信度 → 进入 ReAct 循环
return reactLoop(context);
}
IntentRouter 的匹配规则:
| 意图 | 匹配模式 | 对应 Tool |
|---|---|---|
| profile_update | “我是/我的职业是/帮我记录到档案” | ProfileUpdateTool |
| profile_query | “看看档案/查看档案” | ProfileQueryTool |
| dashboard_query | “练习数据/练习统计” | DashboardQueryTool |
正则匹配 50ms 内就能完成意图判断。虽然不能完全避免 ReAct 的"走一步看一步"问题,但高频操作(查档案、看数据)直接跳过 LLM,延迟从 2-3 秒降到 500 毫秒。
这三块组合在一起,形成了 NVC 项目的 Agent 架构:
实线是主流程,虚线是异步流程。用户只感受到实线的速度,但虚线在持续积累 NVC 实践知识。
选型建议
最后给个简单的选型参考:
| 场景 | 推荐范式 | 理由 |
|---|---|---|
| 实时对话 | ReAct | 延迟低,响应快 |
| 复杂多步任务 | Plan-and-Execute | 全局视野,减少浪费 |
| 需要持续优化 | Reflexion | 能从错误中学习 |
| 实时 + 持续优化 | ReAct + 异步反思 | 两全其美 |
没有银弹。选范式就像选工具——锤子很好,但你不能拿它拧螺丝。理解每个范式的优缺点,结合自己的场景做取舍,才是正道。
我的 NVC 项目选 ReAct 不是因为 ReAct 最好,而是因为它最适合。如果你的场景是批量处理文档、生成报告这类非实时任务,Plan-and-Execute 可能是更好的选择。如果你在做代码生成、数学推理这类需要反复试错的任务,Reflexion 的自我纠错能力会让你事半功倍。
关键是想清楚你的场景需要什么,不需要什么。延迟敏感?那就别上 Plan-and-Execute。任务复杂?那就别指望纯 ReAct。需要持续优化?那就考虑 Reflexion 或者异步反思。
选对了范式,后面的路会顺很多。
更多推荐


所有评论(0)