【从0搭建AI智能体·8】ReAct vs Plan-and-Execute:两种 Agent 编排模式对比

📚 本文是《从 0 搭建你的 AI 智能体》专栏第 8 篇。
上一篇:《给 Agent 接搜索:实时联网问答》 | 专栏目录:点此查看全部

标签建议:AI Agent ReAct Plan-and-Execute 智能体编排 大模型 LLM

📌 前言:从「一问一答」到「自己想办法」

到目前为止,我们的 Agent 还停留在「反应式」——你问一句,它调个工具,答一句。但真正的智能体,要能应对复杂的多步骤任务

「帮我查一下北京和上海这周末的天气,如果都是晴天,就推荐两个城市各自适合的户外活动,并对比哪个更适合带孩子。」

这个任务需要:查两次天气 → 判断天气 → 分别推荐活动 → 综合对比。模型不能一步到位,它得自己规划步骤、逐步执行、根据中间结果调整

怎么让模型学会「规划」?业界有两大主流编排模式:ReAct(边想边做)和 Plan-and-Execute(先规划再执行)。它们是构建复杂 Agent 的两种基本「思维骨架」。这一篇把它们讲透、对比清楚、各给一份可跑代码,让你知道什么任务该用哪种

💡 本文适合谁:已掌握 Function Calling、想让 Agent 处理复杂多步任务的开发者。阅读约 14 分钟。

目录

  1. 两种模式的核心区别(一图看懂)
  2. 模式一:ReAct(推理-行动循环)
  3. ReAct 代码实现
  4. 模式二:Plan-and-Execute(先规划后执行)
  5. Plan-and-Execute 代码实现
  6. 关键对比:什么任务用哪个
  7. 两者结合:Plan + ReAct 混合模式
  8. 常见坑与工程建议
  9. FAQ
  10. 总结

① 两种模式的核心区别(一图看懂)

一句话概括:ReAct 是「走一步看一步」,Plan-and-Execute 是「先画好地图再上路」。

【ReAct:边想边做】
  想 → 做 → 看结果 → 再想 → 再做 → 看结果 → ... → 答
  (每一步都根据上一步的结果,临时决定下一步)

【Plan-and-Execute:先规划后执行】
  ┌─ 规划:一次性列出所有步骤 [1,2,3,4]
  └─ 执行:按计划逐步做 1→2→3→4 → 汇总答
  (先想清楚全部步骤,再依次执行)
  • ReAct 灵活,能随机应变,但可能「绕路」、步数不可控;
  • Plan-and-Execute 有全局规划、步骤清晰,但计划一旦定死,遇到意外不好调整。

理解了这个根本区别,下面的代码和选型就都好懂了。


② 模式一:ReAct(推理-行动循环)

ReAct = Reasoning(推理)+ Acting(行动)。它把「思考」和「行动」交织成一个循环:

Thought(想):我需要先知道北京的天气
Action(做):调用 get_weather("北京")
Observation(看):北京晴,25℃
Thought(想):北京是晴天,再查上海
Action(做):调用 get_weather("上海")
Observation(看):上海多云
Thought(想):信息够了,可以回答了
Answer:……

其实你在第 3 篇写的 run_agent_loop(带 max_steps 的工具调用循环),就是 ReAct 的最简实现——每轮让模型看着已有结果决定下一步,直到它给出最终答案。现代大模型的 Function Calling 已经内建了这套「推理-行动」能力,你不用手写 Thought/Action 的解析。


③ ReAct 代码实现

基于 Function Calling 的 ReAct 循环(复用第 3 篇的骨架):

先准备好工具(沿用第 3 篇的天气 + 计算工具,这里给出完整可跑的精简版):

import os, json
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()
client = OpenAI(api_key=os.getenv("AGENT_KEY"), base_url=os.getenv("API_BASE_URL"))
MODEL = "standard-agent-v1"


def get_weather(city):
    return {"北京": "晴,30℃", "上海": "多云,26℃", "广州": "雷阵雨,31℃"}.get(city, f"暂无{city}数据")

def calculate(expr):
    import ast, operator
    ops = {ast.Add: operator.add, ast.Sub: operator.sub,
           ast.Mult: operator.mul, ast.Div: operator.truediv, ast.USub: operator.neg}
    def ev(n):
        if isinstance(n, ast.Constant): return n.value
        if isinstance(n, ast.BinOp):    return ops[type(n.op)](ev(n.left), ev(n.right))
        if isinstance(n, ast.UnaryOp):  return ops[type(n.op)](ev(n.operand))
        raise ValueError("不支持")
    try: return str(ev(ast.parse(expr, mode="eval").body))
    except Exception as e: return f"计算错误:{e}"

FUNCS = {"get_weather": get_weather, "calculate": calculate}
TOOLS = [
    {"type": "function", "function": {"name": "get_weather",
        "description": "查城市天气,用户问天气/气温时用",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}},
    {"type": "function", "function": {"name": "calculate",
        "description": "算数学表达式,需精确计算时用",
        "parameters": {"type": "object", "properties": {"expr": {"type": "string"}}, "required": ["expr"]}}},
]

有了工具,ReAct 循环就是让模型「走一步看一步」:

def react_agent(question, max_steps=8):
    messages = [
        {"role": "system", "content":
            "你是一个会逐步推理的助手。面对复杂问题,先思考需要哪些信息,"
            "逐个调用工具获取,再综合得出答案。一步步来,不要急于下结论。"},
        {"role": "user", "content": question},
    ]

    for step in range(max_steps):
        msg = client.chat.completions.create(
            model=MODEL, messages=messages, tools=TOOLS, tool_choice="auto"
        ).choices[0].message

        if not msg.tool_calls:               # 模型认为信息够了,给出最终答案
            return msg.content

        messages.append(msg)
        for tc in msg.tool_calls:            # 执行本轮要求的工具(可能多个)
            args = json.loads(tc.function.arguments)
            result = FUNCS[tc.function.name](**args)
            print(f"[Step {step+1}] {tc.function.name}({args}) -> {result}")
            messages.append({"role": "tool", "tool_call_id": tc.id, "content": str(result)})

    return "已达最大步数,任务可能过于复杂。"

特点:整个过程模型「走一步看一步」,每次都基于最新的 Observation 决定下一步。灵活,适合步骤数不确定、需要随机应变的任务。

⚠️ ReAct 的软肋:容易「绕路」或「钻牛角尖」。模型可能反复调用无用的工具、或在错误方向上越走越远。max_steps 是必须的刹车。


④ 模式二:Plan-and-Execute(先规划后执行)

Plan-and-Execute 把任务分成两个明确阶段:

  1. Plan(规划):让模型先把任务拆解成一个有序的步骤清单,不执行;
  2. Execute(执行):按清单逐步执行,每步可调工具,最后汇总。

好比出差前先列好行程表(订票→酒店→会议→返程),再照着一项项办,而不是走一步想一步。

用户任务
   │
   ▼
[规划阶段] 模型输出:
   1. 查北京天气
   2. 查上海天气
   3. 判断是否都晴天
   4. 分别推荐活动并对比
   │
   ▼
[执行阶段] 按 1→2→3→4 逐步做,每步结果传给下一步
   │
   ▼
汇总所有步骤结果 → 最终答案

⑤ Plan-and-Execute 代码实现

分两段:先让模型规划出 JSON 步骤清单,再逐步执行。

def make_plan(question):
    """规划阶段:让模型输出步骤清单"""
    resp = client.chat.completions.create(
        model=MODEL, temperature=0.2,
        messages=[
            {"role": "system", "content":
                "你是任务规划器。把用户任务拆解成有序的执行步骤,"
                "只输出 JSON 数组,每个元素是一个步骤的描述字符串,不要其他文字。"
                '例如:["查询北京天气", "查询上海天气", "对比并推荐"]'},
            {"role": "user", "content": question},
        ],
    )
    content = resp.choices[0].message.content.strip()
    # 容错:剥掉可能的代码块标记
    content = content.replace("```json", "").replace("```", "").strip()
    return json.loads(content)


def execute_plan(question, steps):
    """执行阶段:带着计划和已完成结果,逐步执行"""
    done = []                                    # 累积已完成步骤的结果
    for i, step in enumerate(steps):
        context = "\n".join(f"步骤{j+1}({s}): {r}" for j, (s, r) in enumerate(done))
        msg = client.chat.completions.create(
            model=MODEL, tools=TOOLS, tool_choice="auto",
            messages=[
                {"role": "system", "content":
                    f"你在执行一个多步骤任务。总任务:{question}\n"
                    f"已完成步骤及结果:\n{context or '(无)'}\n"
                    f"现在只做当前这一步,可调用工具。"},
                {"role": "user", "content": f"执行步骤:{step}"},
            ],
        ).choices[0].message

        # 若这步要调工具,执行后再让模型给出该步结论
        if msg.tool_calls:
            tool_msgs = []
            for tc in msg.tool_calls:
                args = json.loads(tc.function.arguments)
                result = FUNCS[tc.function.name](**args)
                tool_msgs.append({"role": "tool", "tool_call_id": tc.id, "content": str(result)})
            follow = client.chat.completions.create(
                model=MODEL,
                messages=[{"role": "user", "content": f"步骤:{step}"}, msg, *tool_msgs],
            ).choices[0].message.content
            done.append((step, follow))
        else:
            done.append((step, msg.content))
        print(f"[执行完成 步骤{i+1}] {step}")

    # 汇总所有步骤,生成最终答案
    summary_ctx = "\n".join(f"{s}: {r}" for s, r in done)
    final = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": "根据各步骤结果,给用户一个完整、连贯的最终回答。"},
            {"role": "user", "content": f"任务:{question}\n各步骤结果:\n{summary_ctx}"},
        ],
    )
    return final.choices[0].message.content


def plan_execute_agent(question):
    steps = make_plan(question)
    print("规划的步骤:", steps)
    return execute_plan(question, steps)

特点:先有全局规划,执行路径清晰、可控、可展示给用户(进度条式体验),适合步骤明确的复杂任务。

5.1 两种模式跑同一个任务,看差异

用同一个复杂问题分别跑两种模式(配好第 ⑤ 节开头的工具和 .env 即可直接运行):

if __name__ == "__main__":
    q = "北京和上海哪个更热?把更热那个城市的气温数字乘以 2 是多少?"

    print("===== ReAct 模式 =====")
    print(react_agent(q))

    print("\n===== Plan-and-Execute 模式 =====")
    print(plan_execute_agent(q))

ReAct 的运行轨迹(走一步看一步,过程"涌现"):

===== ReAct 模式 =====
[Step 1] get_weather({'city': '北京'}) -> 晴,30℃
[Step 2] get_weather({'city': '上海'}) -> 多云,26℃
[Step 3] calculate({'expr': '30*2'}) -> 60
北京更热(30℃ vs 26℃),30 乘以 2 等于 60。

Plan-and-Execute 的运行轨迹(先出计划,再按表执行):

===== Plan-and-Execute 模式 =====
规划的步骤: ['查询北京天气', '查询上海天气', '比较气温得出更热的城市', '将更热城市的气温乘以2']
[执行完成 步骤1] 查询北京天气
[执行完成 步骤2] 查询上海天气
[执行完成 步骤3] 比较气温得出更热的城市
[执行完成 步骤4] 将更热城市的气温乘以2
北京更热,气温 30℃,乘以 2 得 60。

对比很直观:ReAct 的步骤是"边想边冒出来"的,Plan 是"一开始就列全了"。后者能提前把"规划的步骤"展示给用户当进度条,前者更省一次规划调用。


⑥ 关键对比:什么任务用哪个

维度ReAct(边想边做)Plan-and-Execute(先规划后执行)
思路走一步看一步先画地图再上路
灵活性高,能随机应变低,计划定死后不易调整
可控性步数不定,可能绕路步骤清晰,路径可控
可展示过程较乱能展示「计划+进度」,体验好
Token 成本简单任务省多一次规划调用,略贵
适合场景探索性、步骤不定、需应变步骤明确、流程化、要展示进度
典型例子「查资料回答一个开放问题」「按固定流程生成一份报告」

一句话选型

  • 任务开放、步骤数说不准ReAct
  • 任务能提前拆成清晰步骤、想给用户看进度Plan-and-Execute

🎯 现实中,简单任务用 ReAct 就够了(现代模型的 Function Calling 天然就是 ReAct)。只有当任务复杂到「必须先想清楚全局」时,Plan-and-Execute 的规划优势才划算。别为了用而用。


⑦ 两者结合:Plan + ReAct 混合模式

生产级 Agent 常把两者结合:用 Plan 定大方向,每一步内部用 ReAct 灵活执行,兼顾全局规划和局部应变。

[Plan] 拆出大步骤:1.调研  2.分析  3.撰写
   │
   ├─ 步骤1「调研」内部:ReAct 循环(搜多次、随机应变)
   ├─ 步骤2「分析」内部:ReAct 循环
   └─ 步骤3「撰写」内部:ReAct 循环
   │
   ▼
汇总

更进一步还有 Re-planning(重规划):执行中发现计划不对,回到规划阶段调整。这就是 LangGraph 等框架里「带状态、可回退」的 Agent 图的雏形——本质还是这两种模式的组合与增强。

💡 别被框架名词唬住:LangGraph、AutoGPT、BabyAGI 这些花哨的 Agent 框架,内核都逃不出「ReAct 循环 + Plan 规划 + 状态管理」这几样。理解了本篇,你再看它们就是「换了层包装」。


⑧ 常见坑与工程建议

说明建议
无限循环ReAct 反复调工具不收敛必设 max_steps;超限优雅退出
规划解析失败Plan 输出的 JSON 不合法剥代码块标记 + try/except,失败降级为 ReAct
步骤间信息断层执行时没把前序结果传给后续累积 done 结果并注入上下文(见第⑤节)
成本失控多步骤 = 多次模型调用记录每步 Token,设总预算上限(接第10篇可观测性)
计划过度拆解简单任务被拆成一堆碎步骤规划 Prompt 里限制「最多 N 步」

🎯 一条实用工程建议:给 Plan 阶段加个「最多拆 5 步」的约束,能避免模型把「1+1」都拆成三步。规划要「够用就好」,不是越细越好。


⑨ FAQ

Q1:现代模型 Function Calling 已经能多步调用了,还需要手写 ReAct 吗?
基础多步调用不用手写(模型内建)。但当你需要掌控每一步(加校验、记录、人工介入)时,手写循环能给你控制权。

Q2:Plan-and-Execute 的计划一定要执行完吗?
不一定。可以加「提前终止」:某步发现已能回答,就跳过后续。也可加「重规划」应对意外。

Q3:两种模式对模型能力要求高吗?
Plan-and-Execute 对模型的「规划能力」要求更高,弱一点的模型可能拆解得不合理。ReAct 对单步判断要求高。用能力强的模型效果都更好。

Q4:怎么给用户展示 Agent 的执行过程?
Plan-and-Execute 天然适合:先展示计划,再逐步更新进度(配合第4篇流式输出,体验很好)。

Q5:任务失败了怎么办?
加重试和降级:某步失败重试 N 次;仍失败则如实告知用户「第 X 步未完成」,而非假装成功。


⑩ 总结

这一篇讲了让 Agent 处理复杂任务的两种「思维骨架」:

模式一句话何时用
ReAct走一步看一步开放、步骤不定、需应变
Plan-and-Execute先规划后执行步骤明确、要展示进度
混合Plan 定方向,ReAct 管执行复杂生产任务

核心认知:Agent 的「智能」,很大程度来自编排——怎么把「思考」和「行动」组织起来。 模型是引擎,编排是变速箱,两者配合才能跑好复杂路况。

下一篇我们把编排推向多个 Agent:多智能体协作——让几个各有专长的 Agent 分工合作,完成单个 Agent 搞不定的大任务。


🔜 下一篇预告:《用 LangGraph / 自研搭多智能体协作》——多个 Agent 分工合作,1+1>2。
👍 如果本文帮到你,点赞 / 收藏 / 关注,追更不迷路。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐