ReAct 在哪里会撞墙?上一篇我们说 ReAct 的贪心策略——每一步只看当前状态,决定下一个行动。大多数情况下这很好用,但有一类任务会让它步履蹒跚。想象你要 Agent 完成这个任务:搜索 Python、Java、Go 三种语言的发布年份,按时间从早到晚排序,然后计算 Python 和 Go 相差多少年。用 ReAct 执行,可能的轨迹:Action: web_search("Python发布年份") Action: web_search("Java发布年份") Action: web_search("Go发布年份") Action: calculator("...") (有时候会多走几步,甚至重复搜索)这并不离谱,但有一个潜在问题:ReAct 在行动之前没有"全局规划"。它不知道任务总共需要多少步,不知道哪一步依赖哪一步,也不知道当前走到哪里了。每一步都是"当前最优解",不是"整体最优路径"。对于有明确依赖关系的多步任务,这就像不看地图边走边问路——能到,但弯路不少。Plan-and-Solve的思路是:先用 LLM 制定完整的行动计划,然后按计划逐步执行。Plan-and-Solve 的两阶段架构这个范式来自 2023 年的论文 Plan-and-Solve Prompting,核心思想分两步:Phase 1 — Plan(规划):让 LLM 以上帝视角分析整个任务,输出一个有序的步骤列表。这一阶段不执行任何操作,只是"想清楚怎么做"。Phase 2 — Solve(执行):按照计划,逐步执行每个步骤。每步可以调用工具,上一步的结果会注入到下一步的上下文里。加上生产环境必备的容错机制,完整的架构是:任务 │ ▼ [Plan 节点] ← LLM 生成 3-7 步计划(不执行,只规划) │ ▼ [Execute 节点] ← 执行当前步骤(内嵌 ReAct,可调工具) │ ├─ 步骤失败? ─→ [Replan 节点] ← 根据已完成进度重新规划剩余步骤 │ │ │ └──────────────┐ │ ▼ ├─ 还有步骤? ─→ 回到 Execute Execute(继续) │ └─ 全部完成? ─→ [Finalize 节点] ← 输出最终答案 │ ▼ END和 ReAct 的关键区别:ReAct 是一个无边界的循环,Plan-and-Solve 是一个有终点的序列。用 LangGraph 实现:State + GraphLangGraph 是实现这个架构的理想工具——它把 Agent 建模成一个状态机(StateGraph),状态在节点之间流转。状态设计fromtypingimportTypedDictclassPlanSolveState(TypedDict):task:str# 用户原始任务plan:list[str]# 当前计划(步骤列表)completed_steps:list[str]# 已完成步骤(含结果摘要)current_step_index:int# 当前执行到第几步(0-based)step_result:str# 本步骤的执行结果replan_count:int# 已重新规划的次数final_answer:str# 最终答案状态是整个图的"血液"——所有节点都从这里读取输入,向这里写入输出。设计好状态,架构就成功了一半。Plan 节点defplan_node(state:PlanSolveState)-dict:messages=[SystemMessage(content=PLANNER_SYSTEM),# 规划专家 promptHumanMessage(content=f"任务:{state['task']}"),]response=llm.invoke(messages)plan=parse_plan(response.content)# 解析 "1. xxx\n2. xxx" 格式return{"plan":plan,"current_step_index"