一、 引言:为什么复杂的任务需要"计划与执行"分离?
在前面探讨的 ReAct 或简单多智能体协作中,智能体通常是"走一步看一步"。这种动态决策在处理短流程任务时非常灵活,但在面对需要几十步甚至上百步流转的超长周期任务时,会暴露两个致命缺陷:
-
目标迷失(Goal Drifting): 随着执行步骤的增加,上下文被大量的中间观察结果(Observations)填满,大模型容易忘记最初的终极目标。
-
效率低下: 每次行动都需要大模型重新推理全局,Token 消耗呈指数级上升,且容易在某个局部错误中陷入无意义的死循环。
为了攻克这一难题,业界提出了 Plan-and-Execute(计划-执行) 架构。其核心思想是将"高层宏观规划"与"底层微观执行"彻底解耦:
-
Planner(计划者): 负责将用户的复杂需求拆解为一张有序的"步骤清单(Todo List)"。
-
Executor(执行者): 聚焦于当前分配到的具体单一子任务,调用工具将其攻克。
-
Replanner(重规划者): 根据执行者的反馈结果,动态更新、删除或重组剩余的步骤清单。
这种架构不仅大幅提升了任务成功率,还让整个执行过程具备了极高的可观测性和人类可干预性。
二、 计划-执行架构的状态机模型
在 LangGraph 中,我们可以通过一个包含循环的有向图来完美复刻这个架构。整个系统的状态流转如下:
-
用户输入需求 -> Planner 生成步骤清单。
-
检查清单,如果仍有未完成的步骤 -> 提取第一步交给 Executor。
-
Executor 调用工具完成任务,输出阶段性成果。
-
Replanner 审视成果,结合原计划,动态更新步骤清单(增加新步骤或标记已完成),然后跳回步骤 2。
-
清单全部完成 -> 输出最终结果(END)。
三、 基于 LangGraph 的核心代码实现
1. 定义全局有状态的 PlanState
我们需要一个结构来记录原始输入、当前的计划清单、已经完成的步骤以及最终的输出。
Python
from typing import List, Tuple, TypedDict, Annotated
import operator
# 定义计划的结构
class PlanState(TypedDict):
input: str # 用户的原始需求
plan: List[str] # 动态的步骤清单
past_steps: List[Tuple[str, str]] # 已完成的步骤记录,格式为 (步骤描述, 执行结果)
response: str # 最终输出结果
2. 编写 Planner 节点
Planner 只负责宏观拆解,不负责具体执行。
Python
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 使用 Pydantic 强制让大模型输出结构化的计划列表
class ActPlan(BaseModel):
steps: List[str] = Field(description="为了完成最终目标,需要按顺序执行的独立步骤列表")
planner_llm = llm.with_structured_output(ActPlan)
def planner_node(state: PlanState):
prompt = ChatPromptTemplate.from_template(
"你是一个高层项目规划专家。请针对用户的最终需求,拆解出一个详细的、按顺序执行的步骤计划。\n"
"最终需求: {input}\n"
"请只返回步骤列表。"
)
planner_chain = prompt | planner_llm
result = planner_chain.invoke({"input": state["input"]})
return {"plan": result.steps}
3. 编写 Executor 节点
Executor 专注于处理 state["plan"] 中的第一个任务,并结合 past_steps 提供的上下文信息。
Python
from langchain.agents import load_tools
# 假设这里引入了标准的搜索或计算工具
def executor_node(state: PlanState):
current_task = state["plan"][0] # 获取当前需要执行的第一步
past_steps = state["past_steps"]
context = "\n".join([f"步骤: {task}\n结果: {res}" for task, res in past_steps])
prompt = ChatPromptTemplate.from_template(
"你是一个高效的执行者。请根据已有的上下文信息,完成当前的特定任务。\n"
"已有历史信息:\n{context}\n\n"
"当前需要完成的任务: {current_task}\n"
"请直接输出该步骤的执行结果或答案。"
)
executor_chain = prompt | llm
response = executor_chain.invoke({"context": context, "current_task": current_task})
# 将当前任务和结果作为元组返回,用于追加到 past_steps 中
return {"past_steps": [(current_task, response.content)]}
4. 编写 Replanner 节点(动态调整核心)
Replanner 是系统的灵魂,它决定了系统是继续执行、修改计划还是直接收尾。
Python
class ActReposition(BaseModel):
action: str = Field(description="下一步动作:如果是继续执行后面的计划,返回 'continue';如果任务已圆满完成,返回 'final'。")
new_steps: List[str] = Field(default=[], description="如果发现原计划有遗漏或需要调整,返回更新后剩余的步骤列表。如果无需调整,留空。")
final_response: str = Field(default="", description="如果 action 为 'final',在此填入给用户的最终完美答复。")
replanner_llm = llm.with_structured_output(ActReposition)
def replanner_node(state: PlanState):
prompt = ChatPromptTemplate.from_template(
"你是一个项目监控与重规划专家。\n"
"原始需求: {input}\n"
"原定剩余计划: {plan}\n"
"已执行的步骤及结果:\n{past_steps}\n\n"
"请评估当前进展。如果目标已达成,请设置 action 为 'final' 并给出最终答复。\n"
"如果目标未达成,请设置 action 为 'continue'。你可以根据最新情况调整或重写剩余计划(new_steps)。"
)
context = "\n".join([f"步骤: {task}\n结果: {res}" for task, res in state["past_steps"]])
remaining_plan = state["plan"][1:] # 移除刚刚已经执行过的那一步
replanner_chain = prompt | replanner_llm
result = replanner_chain.invoke({
"input": state["input"],
"plan": remaining_plan,
"past_steps": context
})
if result.action == "final":
return {"response": result.final_response, "plan": []}
else:
# 如果大模型返回了更新后的步骤,则使用新步骤;否则使用移除第一步后的剩余原计划
updated_plan = result.new_steps if result.new_steps else remaining_plan
return {"plan": updated_plan}
5. 构建并编译 LangGraph 工作流
有了这三个节点,我们用条件路由将它们串联起来。
Python
from langgraph.graph import StateGraph, END
workflow = StateGraph(PlanState)
# 注册节点
workflow.add_node("Planner", planner_node)
workflow.add_node("Executor", executor_node)
workflow.add_node("Replanner", replanner_node)
# 设置入口
workflow.set_entry_point("Planner")
# 确定基础连线
workflow.add_edge("Planner", "Executor")
workflow.add_edge("Executor", "Replanner")
# 核心条件路由:根据 plan 清单是否为空,决定是进入下一次循环还是终止
def should_continue(state: PlanState):
if not state["plan"]:
return END
return "Executor"
workflow.add_conditional_edges(
"Replanner",
should_continue,
{
"Executor": "Executor",
"END": END
}
)
app = workflow.compile()
四、 执行效果演示
向该系统输入一个复杂的、依赖递进关系的模糊任务:
输入: "分析 2026 年生成式 AI 在医疗行业的最新三个应用场景,并挑出其中落地最快的一个场景评估其潜在法律风险。"
在 app.stream() 的运行过程中,系统的执行轨迹如下:
-
Planner 拆出初始清单:
-
步骤1: 检索 2026 医疗 AI 的最新三个应用场景。
-
步骤2: 对比三个场景的落地成熟度,选出最快的一个。
-
步骤3: 针对该成熟场景分析其数据隐私与法律风险。
-
-
Executor 执行步骤1,返回三个场景(AI影像辅助诊断、电子病历自动生成、AI靶点预测)。
-
Replanner 检查发现步骤1完成,计划正常,命令继续。
-
Executor 执行步骤2,对比后得出"电子病历自动生成"由于不直接触及核心临床诊断,落地速度最快。
-
Replanner 在这一步大模型敏锐地发现:原计划的步骤3范围太广,它自主在剩余计划中临时增加了"查阅医疗数据跨境传输新规"这一子步骤。
-
Executor & Replanner 循环交替,直到所有动态调整后的清单清空。
-
END 输出结构高度严密、逻辑天衣无缝的深度报告。
五、 生产环境的最佳实践总结
-
执行器的容错性(Fallback): 实际生产中,
Executor内部通常包裹着一个标准的 ReAct Agent,允许它在执行某一个单一子步骤时可以多次尝试调用各种不同的工具。 -
长文本记忆压缩: 随着循环迭代,
past_steps积累的文本量会极大。在 Replanner 输入前,可以引入一个"总结节点",专门将过去几步的 Observation 提炼压缩,只保留核心 Data 指标。 -
人机协同(Human-in-the-loop): 在 LangGraph 的
Planner到Executor的连线中间,可以配置interrupt_before=["Executor"]。这样系统生成计划后会暂停,等待人类架构师在后台点击"确认"或手动微调 Todo List 后再开始执行,这是目前企业落地复杂 AI 工作流时最稳妥的做法。