Agent三大范式-ReAct-Plan-and-Solve-Reflection
范式(Paradigm)是一种思维框架,它决定了 Agent 面对任务时的基本行为模式——先做什么、怎么决策、何时停止。
Agent 三大范式:ReAct、Plan-and-Solve、Reflection
为什么需要不同范式:任务结构决定执行策略
范式(Paradigm)是一种思维框架,它决定了 Agent 面对任务时的基本行为模式——先做什么、怎么决策、何时停止。
在理解具体范式之前,先想一个问题:同样是"帮我完成这个任务",为什么不同的任务需要不同的执行策略?
答案在于任务结构的差异。不同类型的任务有不同的结构特征,这些特征决定了什么样的执行策略最有效。
信息获取型任务("查一下 A 公司的最新财报数字")的结构特点是:需要实时数据,不知道需要查几步,每一步的结果决定下一步查什么。这类任务适合边想边做的策略——先试一步,看结果,再决定下一步,不需要提前规划全局。如果强行先做完整规划,会发现根本规划不了,因为第二步的内容取决于第一步查到了什么。
多步骤复杂任务("写一份完整的市场调研报告")的结构特点是:任务边界清晰,可以分解成明确的子任务,各子任务有依赖关系。这类任务适合先规划后执行——一次性想清楚整个任务的结构,按计划推进,方向不会跑偏。如果"边想边做",很可能在某个细节上钻进去,忘了整体目标。
质量敏感型任务("帮我审查这份法律合同")的结构特点是:输出质量至关重要,存在评估标准,但第一次做未必能达到标准。这类任务适合执行后反思——做完检查,不满意就改,直到质量达标。第一稿通常不是最好的,需要迭代。
三种范式——ReAct、Plan-and-Solve、Reflection——分别针对这三种任务结构。理解这一点,范式选择就从"记住规则"变成了"匹配任务结构"。
图:ReAct(边想边做)、Plan-and-Solve(先规划再执行)、Reflection(执行后反思迭代)三种范式的执行流程对比。
面对同一个任务——"帮我分析一下这份财报,给出投资建议"——不同的 Agent 范式会走出完全不同的路径:
- 一个 Agent 可能立刻去搜索公司背景,边搜边想,最后给出结论(ReAct)
- 另一个 Agent 会先列出分析框架——收入趋势、利润结构、现金流、行业对比——然后逐项执行(Plan-and-Solve)
- 还有一个 Agent 在给出初稿之后,会自己审查一遍,发现"我忽略了汇率风险",然后补充修改(Reflection)
这三种行为模式,对应了三种不同的范式。理解它们的工作原理和适用边界,是设计 Agent 架构的基础。
ReAct:边想边做
这个范式解决什么问题
在 ReAct 出现之前,让 LLM 使用工具的方式有两种:
- 让 LLM 直接生成工具调用,没有思考过程,模型容易在无需工具的情况下乱调工具
- 先让模型做完整推理,再执行,但推理脱离了真实数据,质量不高
ReAct 的洞察是:推理和行动应该交织在一起,不是先想好再做,也不是盲目行动。 每次行动之前先显式地思考"为什么要做这一步",每次行动之后观察结果再重新思考。这种思考-行动-观察的循环,比单纯推理或单纯行动都要好。
核心机制
ReAct(Reasoning + Acting 的缩写)来自 Shunyu Yao et al. 2022 年的论文。它的核心思路是:让模型交替输出"思考"和"行动",每次行动后把结果喂回去,继续下一轮思考。
循环是这样的:
一个典型的 ReAct 执行过程长这样:
Thought: 用户要分析财报,我需要先找到这份财报的具体数据。
Action: search("阿里巴巴 2024年Q3财报")
Observation: 营收为2365亿,同比增长5%,净利润436亿...
Thought: 有了基础数据,现在需要和上个季度对比。
Action: search("阿里巴巴 2024年Q2财报")
Observation: 营收为2432亿,净利润344亿...
Thought: Q3 营收环比下降,但利润增长明显。需要了解利润率变化原因。
Action: search("阿里巴巴 2024Q3 利润率改善原因")
Observation: 主要来自云计算业务盈利提升和降本增效...
Thought: 现在有足够信息了,可以给出分析。
Final Answer: 阿里巴巴Q3营收小幅下降但利润改善明显,核心原因是...
关键点:每次行动之前都有思考过程,模型不是盲目调用工具,而是先推理"现在应该做什么、为什么"。每次观察结果都会被追加进对话历史,模型的下一步决策基于完整的上下文。
代码实现
验证目的:通过一个包含搜索和计算工具的 ReAct Agent,观察"Thought-Action-Observation"循环的实际运行过程,理解 LLM 如何在每一步根据上一步的观察结果决定下一步行动。
from openai import OpenAI
import json
client = OpenAI()
def search(query: str) -> str:
"""模拟搜索工具"""
# 实际场景接入 Tavily / SerpAPI 等真实搜索 API
mock_results = {
"阿里巴巴 2024Q3": "营收2365亿元,同比增长5%,净利润436亿元,云计算收入增长7%",
"阿里巴巴 2024Q2": "营收2432亿元,净利润344亿元",
}
for key, value in mock_results.items():
if key in query:
return value
return f"搜索结果:{query} 的相关信息..."
def calculate(expression: str) -> str:
"""模拟计算工具"""
try:
result = eval(expression) # 实际场景应使用安全的表达式解析
return str(result)
except Exception as e:
return f"计算错误: {e}"
tools = [
{
"type": "function",
"function": {
"name": "search",
"description": "搜索互联网获取最新信息",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "执行数学计算",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式"}
},
"required": ["expression"]
}
}
}
]
def react_agent(user_input: str) -> str:
messages = [
{
"role": "system",
"content": "你是一个分析助手。遇到需要数据支撑的问题,先搜索再分析,每步都先想清楚再行动。"
},
{"role": "user", "content": user_input}
]
tool_map = {"search": search, "calculate": calculate}
max_iterations = 10 # 防止无限循环的安全上限
for iteration in range(max_iterations):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
message = response.choices[0].message
# LLM 决定直接回答,ReAct 循环结束
if message.tool_calls is None:
return message.content
# LLM 决定调用工具(ReAct 的 Action 阶段)
messages.append(message)
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f"[Action] {func_name}({func_args})")
result = tool_map[func_name](**func_args)
print(f"[Observation] {result[:100]}...")
# 把观察结果追加进对话(ReAct 的 Observation 阶段)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
# 循环继续,LLM 基于 Observation 进入下一轮 Thought
return "达到最大迭代次数,任务未完成"
# 运行示例
result = react_agent("分析一下阿里巴巴最近两个季度的财务表现,计算利润同比增长率")
print(result)
预期输出示例:
[Action] search({'query': '阿里巴巴 2024Q3'})
[Observation] 营收2365亿元,同比增长5%,净利润436亿元,云计算收入增长7%...
[Action] search({'query': '阿里巴巴 2024Q2'})
[Observation] 营收2432亿元,净利润344亿元...
[Action] calculate({'expression': '(436-344)/344*100'})
[Observation] 26.744...
分析:阿里巴巴2024年Q3净利润436亿元,环比增长约26.7%,主要受益于云计算业务盈利能力提升...
ReAct 的适用场景与局限
适用场景:
- 信息检索类任务,不知道要查几步才能得到答案
- 需要根据中间结果动态调整查询策略
- 任务路径高度不确定,需要灵活应对
局限:
- 面对复杂多步骤任务容易"迷失"。它没有全局规划,每一步都是局部决策,可能在某个分支上越走越偏,忘记了最初的目标
- 没有质量保证机制,给出答案就结束,无法自动识别"这个答案质量不够好"
- token 消耗相对分散,每步都要输出 Thought 文字
Plan-and-Solve:先谋后动
这个范式解决什么问题
ReAct 的核心问题是:每一步只看当前局势,没有全局视野。这对简单的信息检索任务没有问题,但对结构复杂的任务就会出现问题。
想象一下:你要写一份包含市场分析、竞争对手分析、财务预测、风险评估四个部分的商业报告。如果用 ReAct,Agent 可能在市场分析部分搜索了太多数据,花了大量 token,结果到了财务预测部分发现已经接近上下文窗口限制,只能草草了事。
Plan-and-Solve 解决的就是这个问题。先把任务完整拆解成步骤列表,给整个执行过程一张"地图",再按计划逐步执行。 有了地图,就不会在某个局部迷路。
核心机制
Plan-and-Solve 来自 Lei Wang et al. 2023 年的论文《Plan-and-Solve Prompting》。核心思想是:先完整地把任务拆解成子任务列表,然后按计划逐步执行。
Planner 负责全局视角:读入问题,输出一个完整的步骤列表。这一步要求 LLM 在动手之前想清楚全程,类似于项目启动前先写工作计划。
Executor 负责单步执行:每次只处理一个步骤,但能看到之前所有步骤的执行结果。上下文不断积累,最后一步拿到完整的历史,输出最终答案。
代码实现
用一道多步数学应用题来演示两阶段架构:
小明周一买了 15 个苹果,周二又买了周一数量的 2 倍,周三吃掉了 5 个。请问小明现在一共有多少个苹果?并计算平均每天新增(不含周三)多少个苹果?
import ast
import re
from openai import OpenAI
client = OpenAI()
class Planner:
"""
规划阶段:把问题拆解成清晰的步骤列表。
输出格式:Python list,每个元素是一个步骤描述字符串。
"""
def __init__(self):
self.system_prompt = """你是一个任务规划专家。
将用户的问题拆解成清晰的执行步骤,以 Python list 格式输出。
要求:
1. 每个步骤是一个字符串,描述具体要做什么
2. 步骤要足够细,每步只做一件事
3. 最后一步必须是"汇总前面所有结果,给出最终答案"
4. 只输出 Python list,不要有任何其他文字
输出示例:
["步骤1:计算...", "步骤2:根据步骤1的结果计算...", "步骤3:汇总前面所有结果,给出最终答案"]"""
def plan(self, question: str) -> list[str]:
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": question},
],
temperature=0,
)
raw_output = response.choices[0].message.content.strip()
# 安全解析:只接受字面量,不执行任意代码
try:
steps = ast.literal_eval(raw_output)
if isinstance(steps, list) and all(isinstance(s, str) for s in steps):
return steps
except (ValueError, SyntaxError):
pass
# 容错:尝试用正则提取列表内容
items = re.findall(r'"([^"]+)"', raw_output)
if items:
return items
# 兜底:把原始输出作为单步计划
return [raw_output, "汇总前面所有结果,给出最终答案"]
class Executor:
"""
执行阶段:逐步执行计划,维护 history 字符串积累上下文。
每步执行都能看到所有历史结果,确保上下文完整。
"""
def __init__(self):
self.system_prompt = """你是一个严谨的执行者。
你会收到:原始问题、完整计划、当前要执行的步骤、以及之前步骤的执行结果。
只执行当前步骤,给出该步骤的结果,不要超前执行其他步骤。
输出要简洁,只写计算过程和结果,不要废话。"""
def execute_step(
self,
question: str,
full_plan: list[str],
current_step: str,
step_index: int,
history: str,
) -> str:
plan_text = "\n".join(f"{i+1}. {s}" for i, s in enumerate(full_plan))
user_content = f"""原始问题:{question}
完整计划:
{plan_text}
之前步骤的执行结果:
{history if history else "(这是第一步,暂无历史结果)"}
当前执行步骤(第 {step_index + 1} 步):{current_step}
请执行当前步骤,给出结果:"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": user_content},
],
temperature=0,
)
return response.choices[0].message.content.strip()
class PlanAndSolveAgent:
"""
组合 Planner 和 Executor 的完整 Agent。
"""
def __init__(self):
self.planner = Planner()
self.executor = Executor()
def run(self, question: str) -> str:
print(f"\n问题:{question}")
print("=" * 60)
# 阶段一:规划
print("\n[规划阶段]")
steps = self.planner.plan(question)
for i, step in enumerate(steps):
print(f" {i + 1}. {step}")
# 阶段二:执行
print("\n[执行阶段]")
history = ""
for i, step in enumerate(steps):
print(f"\n步骤 {i + 1}:{step}")
result = self.executor.execute_step(
question=question,
full_plan=steps,
current_step=step,
step_index=i,
history=history,
)
print(f"结果:{result}")
history += f"\n步骤 {i + 1}({step})的结果:\n{result}\n"
return result # 最后一步的结果就是最终答案
if __name__ == "__main__":
agent = PlanAndSolveAgent()
question = (
"小明周一买了 15 个苹果,周二又买了周一数量的 2 倍,"
"周三吃掉了 5 个。请问小明现在一共有多少个苹果?"
"并计算周一和周二平均每天新增多少个苹果?"
)
answer = agent.run(question)
print(f"\n最终答案:{answer}")
预期输出:
问题:小明周一买了 15 个苹果...
============================================================
[规划阶段]
1. 计算周一购买的苹果数量
2. 计算周二购买的苹果数量(周一的2倍)
3. 计算周三之后剩余的苹果数量(扣除吃掉的5个)
4. 计算周一和周二平均每天新增苹果数
5. 汇总前面所有结果,给出最终答案
[执行阶段]
步骤 1:计算周一购买的苹果数量
结果:周一购买 15 个苹果。
...
最终答案:小明现在共有 40 个苹果。周一和周二平均每天新增 22.5 个苹果。
Plan-and-Solve 的局限与改进方向
最大局限:计划是静态的。Planner 基于有限的初始信息制定计划,执行中遇到意外很难灵活调整。如果 Planner 本身规划有误,整个任务都会跑偏。
改进方向:动态重规划。在执行每个步骤之后,检查"计划还需要调整吗"。如果需要,重新调用 Planner 更新剩余步骤。这本质上是把 Plan-and-Solve 和 ReAct 融合——宏观上有计划,微观上可以灵活调整。LangGraph 的一些高级用法就走这个路子。
Plan-and-Solve 的适用场景
适用场景:
- 任务结构清晰,步骤可以提前确定
- 主要靠推理而非工具调用的场景(如数学题、结构化分析)
- 任务步骤之间有明确的依赖关系
不适用场景:
- 需要实时获取外部信息,查询结果高度不确定
- 任务路径依赖中间结果,无法提前规划
Reflection:做完再审
这个范式解决什么问题
ReAct 和 Plan-and-Solve 都在解决"怎么把任务执行完"的问题。但还有另一类问题:怎么把任务执行好。
第一稿往往不是最好的。法律文件需要多轮审查,代码需要测试和优化,分析报告需要逻辑自洽。人类的创作过程就是这样:写初稿、反思、修改、再反思。
Reflection 背后有一个实践观察:当你用不同的 Prompt 引导同一个 LLM,它能以不同的方式处理同一个内容。用"请生成一份报告"引导时,LLM 倾向于流畅地输出;用"请找出这份报告的问题"引导时,LLM 倾向于批评性地审查。
更准确的理解:这不是说 LLM 有两种独立的"能力模式"——LLM 本质上始终在做条件概率预测。不同的是 Prompt 上下文:生成 Prompt 引导模型续写内容,评估 Prompt 引导模型寻找问题。改变 Prompt,就改变了模型输出的方向。
把这两种 Prompt 策略拆分成 Actor(执行者)和 Critic(评审者)两个角色,交替工作,就能利用 LLM 的自我改进能力。
核心机制
Reflection 范式来自 Shinn et al. 2023 年的《Reflexion》论文。核心思想是:让 Agent 在执行之后,增加一个自我评估和反思的环节,根据反思结果决定是否重试、如何改进。
Actor(执行者):负责执行任务,生成输出。关注"做出来",不关注"是否最优"。
Critic(评审者):负责评估 Actor 的输出,指出问题和改进方向。关注"质量是否足够好",用明确的评估标准衡量。
关键设计原则:Critic 提示词的质量决定了整个 Reflection 机制是否有效。如果 Critic 提示词太宽泛,LLM 会给出"还不错,继续吧"这类无意义的反馈,Reflection 就变成了无效的循环调用。Critic 必须有明确的评估维度清单。
代码实现
以代码优化为例,演示从"能用"到"高效"的迭代过程:
from openai import OpenAI
from dataclasses import dataclass, field
client = OpenAI()
# ============================================================
# 三个核心提示词:Actor、Critic、Refine
# ============================================================
ACTOR_PROMPT = """你是一个 Python 专家。
请根据用户需求,直接给出可运行的 Python 函数,包含函数定义、必要注释和时间复杂度说明。"""
CRITIC_PROMPT = """你是一个严苛的代码审查员,专门挑算法效率问题。
审查标准(必须逐项检查):
1. 时间复杂度:是否存在更优的算法?O(n²) 能否优化到 O(n log n)?O(n√n) 能否优化到 O(n log log n)?
2. 空间复杂度:内存使用是否可以优化?
3. 边界处理:是否处理了 n=0、n=1、负数等边界情况?
4. 代码质量:变量命名是否清晰?是否有重复逻辑可以抽取?
重要规则:你的职责是找问题,不是表扬。
如果确实无法继续优化(已经是理论最优),在最后一行单独写:NO_IMPROVEMENT_NEEDED"""
REFINE_PROMPT_TEMPLATE = """你是一个 Python 专家。
根据代码审查意见,改进原有代码。
原始代码:
{original_code}
审查意见:
{feedback}
请根据审查意见,给出改进后的完整代码。改动的部分需要在注释里说明改进原因。"""
# ============================================================
# 记忆:追踪每次迭代的输出和反馈
# ============================================================
@dataclass
class IterationRecord:
version: int
code: str
feedback: str = ""
@dataclass
class ReflectionMemory:
records: list[IterationRecord] = field(default_factory=list)
def add(self, version: int, code: str, feedback: str = ""):
self.records.append(IterationRecord(version=version, code=code, feedback=feedback))
def get_trajectory(self) -> str:
"""把完整迭代历史格式化成文字"""
lines = []
for record in self.records:
lines.append(f"=== 版本 {record.version} ===")
lines.append(record.code)
if record.feedback:
lines.append(f"--- 审查意见 ---")
lines.append(record.feedback)
lines.append("")
return "\n".join(lines)
# ============================================================
# ReflectionAgent:初始执行 → 循环(Critic 评估 → Actor 改进)
# ============================================================
class ReflectionAgent:
def __init__(self, max_iterations: int = 3):
self.max_iterations = max_iterations
def _execute(self, task: str) -> str:
"""Actor:初次执行任务"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": ACTOR_PROMPT},
{"role": "user", "content": task},
],
temperature=0,
)
return response.choices[0].message.content.strip()
def _critique(self, code: str) -> tuple[bool, str]:
"""Critic:评估代码质量,返回(是否需要继续改进, 反馈内容)"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": CRITIC_PROMPT},
{"role": "user", "content": f"请审查以下代码:\n\n{code}"},
],
temperature=0,
)
feedback = response.choices[0].message.content.strip()
need_improvement = "NO_IMPROVEMENT_NEEDED" not in feedback
return need_improvement, feedback
def _refine(self, original_code: str, feedback: str) -> str:
"""Actor:根据 Critic 的审查意见改进代码"""
prompt = REFINE_PROMPT_TEMPLATE.format(
original_code=original_code,
feedback=feedback,
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0,
)
return response.choices[0].message.content.strip()
def run(self, task: str) -> tuple[str, ReflectionMemory]:
memory = ReflectionMemory()
print("\n[初次执行]")
current_code = self._execute(task)
print(current_code[:200] + "...")
memory.add(version=0, code=current_code)
for i in range(self.max_iterations):
print(f"\n[第 {i + 1} 轮 Critic 评审]")
need_improvement, feedback = self._critique(current_code)
print(f"审查意见:{feedback[:200]}...")
if not need_improvement:
print(f"审查员认为已无需改进,提前终止迭代。")
break
print(f"\n[第 {i + 1} 轮 Actor 改进]")
refined_code = self._refine(current_code, feedback)
print(refined_code[:200] + "...")
memory.records[-1].feedback = feedback
memory.add(version=i + 1, code=refined_code)
current_code = refined_code
return current_code, memory
if __name__ == "__main__":
agent = ReflectionAgent(max_iterations=3)
task = "写一个函数 find_primes(n),返回所有小于等于 n 的素数列表"
final_code, memory = agent.run(task)
print("\n" + "=" * 60)
print("最终代码:")
print(final_code)
实际效果:
- 版本 0(初始执行):通常是试除法,时间复杂度 O(n√n)
- Critic 反馈:指出效率不足,建议使用埃拉托斯特尼筛法,时间复杂度可优化到 O(n log log n)
- 版本 1(第一轮改进):筛法实现,带完整边界处理
- 第二轮 Critic 审查:如果边界处理和代码质量都通过,输出 NO_IMPROVEMENT_NEEDED,循环提前终止
从 O(n√n) 到 O(n log log n),在 n=100000 的场景下,实际运行时间差了 10 倍以上。
Reflection 的成本收益
成本:每多一轮迭代,就多 2 次 LLM 调用(Critic + Refine)。如果迭代 3 轮,总共需要 7 次调用(1 次初始执行 + 3 次 Critic + 3 次 Refine)。延迟从几秒变成几十秒,token 消耗增加 7 倍。
收益:输出质量显著提升。对于代码生成、技术文档、分析报告类任务,Critic 往往能发现人工 review 才能发现的问题。
适合使用的场景:
- 代码生成:专门针对时间复杂度、空间复杂度、边界情况
- 技术文档:检查逻辑完整性、技术准确性
- 分析报告:检查论据是否充分、结论是否有数据支撑
- 任务不紧急、对质量要求高
不适合的场景:
- 实时对话:响应时间从 2 秒变成 20 秒,体验直接崩了
- 简单查询:天气查询、数学计算不需要 Reflection
- 有确定性答案的任务:查数据库查到了就是查到了,Reflection 没有意义
三种范式的核心区别
| 维度 | ReAct | Plan-and-Solve | Reflection |
|---|---|---|---|
| 核心思路 | 边想边做 | 先规划后执行 | 执行后反思改进 |
| 解决的问题 | 如何灵活应对意外 | 如何保持全局视野 | 如何保证输出质量 |
| 适合任务 | 信息检索类,路径不确定 | 复杂多步骤类,结构清晰 | 高质量输出类,有评估标准 |
| 对意外的适应 | 强 | 弱(计划静态) | 中(可迭代改进) |
| 全局视野 | 弱 | 强 | 中 |
| 输出质量保证 | 无 | 无 | 有(多轮迭代) |
| 延迟 | 中 | 低(一次规划) | 高(多次迭代) |
| Token 消耗 | 中 | 中 | 高(成倍增加) |
组合使用:三种范式的最佳实践
实际项目里,三种范式通常组合使用。一个典型的高质量 Agent 架构是:
- Plan-and-Solve 负责宏观规划,保证方向正确
- ReAct 负责执行每个子任务,灵活应对信息检索中的意外
- Reflection 负责最后把关,保证整体输出质量
这个组合在代码生成、深度研究、复杂报告撰写这类任务上效果很好。
范式选择指南
选择要点:
- 任务路径是否确定? 确定就用 Plan-and-Solve,不确定就用 ReAct。
- 对输出质量要求高不高? 高就加 Reflection。
- 延迟和成本能接受多高? Reflection 会显著增加两者,不能无限堆叠。
把所有任务都套上 Reflection 是常见的过度设计——简单查询也迭代三轮,用户等了十几秒。同样,在简单问答任务上用 Plan-and-Solve,花大量时间规划,然后发现执行只需要一步,规划完全是浪费。理解每种范式的边界,才能在正确的场景用正确的范式。