LangGraph 1.x · Plan-and-Execute评估
1. 背景

Plan-and-Execute 模式是一种 “先计划、后执行、按计划循环迭代” 的 Agent 设计模式。它的核心思想是:先输出一份结构化的执行计划,将复杂任务拆解为若干明确步骤,然后严格按照步骤逐步执行;当某一步需要外部信息或能力(如搜索、计算等)时,则触发工具调用,完成后继续推进计划,直到满足终止条件。
实际应用场景
以 Cursor 为例:当 Cursor 要编写复杂的代码时,通常会先执行规划,生成一个 to-do list,之后按照这个 to-do list 的顺序依次执行任务。这种”先规划、后执行”的方式能够有效处理复杂任务,避免遗漏关键步骤。
Plan Mode 的三层结构
Plan Mode 通常可以抽象出三层结构:
- 计划层:用自然语言清晰列出步骤、编号、依赖、输入输出。
- 执行层:逐步消费计划中的步骤,产出结果或中间态。
- 工具层:在执行层遇到”需要能力/数据”的节点时,面向具体工具进行调用(如价格查询、表达式计算等)。
优势
这种”先规划、后执行”的范式具备以下优势:
- 可解释性强
- 可回溯
- 易扩展(新增工具即可拓展能力)
在设计 Agent 时已经被广泛采用。
2. 构建 Plan-and-Execute Agent
使用 LangGraph 来构建一个简单的 Plan-and-Execute Agent,节点结构包括:
- Plan Node(计划节点)
- Execute Node(执行节点)
- Tool Node(工具节点)
- Conditional Edge(条件边)
2.1 基础准备
定义工具
首先开发需要用到的工具(tools)。这里定义了两个函数:一个是查询汇率信息,另一个是计算器,然后利用 LangChain 的 @tool 装饰器,将它们包装成可被 LLM 调用的工具。
代码说明:
1 | from langchain.tools import tool |
关键点说明:
@tool装饰器会将普通函数转换为 LangChain 的工具对象,LLM 可以识别并调用- 工具函数的文档字符串(docstring)会被 LLM 读取,用于理解工具的用途和参数
TOOL_DICT用于在执行阶段快速查找和调用对应的工具
定义模型
定义大语言模型(LLM),这里使用 DeepSeek Chat 模型。然后将模型与工具进行绑定,使 LLM 能够识别和调用我们定义的工具。
代码说明:
1 | from langchain_openai import ChatOpenAI |
关键点说明:
bind_tools()方法会将工具的函数签名和描述信息传递给 LLM- 绑定后,LLM 在生成回复时可能会返回
tool_calls字段,表示需要调用工具 - 工具调用信息包含工具名称、参数和调用 ID,用于后续执行
定义状态
定义 Agent 运行过程中需要维护的状态。自定义了 PlanAndExecuteState,它继承 LangGraph 的 MessagesState,可以自动保存消息列表(包括用户消息、AI 回复、工具调用结果等),并且额外扩展了两个属性,分别表示生成的计划和当前执行的步骤。
代码说明:
1 | from langgraph.graph.message import MessagesState |
关键点说明:
MessagesState提供了消息管理功能,自动处理消息的追加和更新plan字段存储计划节点生成的执行计划,供执行节点使用step字段用于跟踪执行进度,便于调试和日志记录- 状态对象在节点之间传递,每个节点可以读取和修改状态
2.2 Plan Node(计划节点)
Plan Node 是整个 Agent 的第一个节点,负责根据用户提出的问题,生成详细的、结构化的执行计划。这个计划将指导后续的执行步骤。
代码说明:
1 | from state import PlanAndExecuteState |
工作流程说明:
- 节点接收包含用户问题的状态
- 构造包含 System Prompt 和用户消息的完整消息列表
- 调用 LLM 生成执行计划(纯文本格式,如”1. 查询美元汇率 2. 查询欧元汇率 3. 计算兑换金额”)
- 将计划保存到状态中
- 返回更新后的状态,流程进入下一个节点
注意:为了方便演示,直接将工具描述信息写到 System Prompt 中了。更理想的方案是接入 MCP(Model Context Protocol)或使用 LangChain 的工具描述自动生成功能,感兴趣的同学可以自己实现。
2.3 Execute Node(执行节点)
Execute Node 是 Agent 的核心执行节点,负责根据生成的计划逐步执行任务。LLM 会根据当前计划、对话历史和工具调用结果,决定下一步操作:可能是直接回复,也可能是调用工具。
代码说明:
1 | from state import PlanAndExecuteState |
工作流程说明:
- 节点读取状态中的计划(由 plan_node 生成)
- 构造包含计划和完整对话历史的消息列表
- 调用 LLM,LLM 会:
- 分析当前应该执行计划的哪一步
- 如果需要数据,生成工具调用请求(
tool_calls字段) - 如果可以直接回答,生成文本回复(
content字段)
- 将 LLM 的回复保存到消息历史中
- 更新步骤计数器
- 返回状态,由条件边决定下一步流向
关键点:
- LLM 的回复可能包含
tool_calls(需要调用工具)或content(直接回复) - 如果回复末尾包含 “Final Answer”,表示任务完成
- 步骤计数器用于跟踪执行进度,便于调试
2.4 Tool Node(工具节点)
Tool Node 负责执行工具调用。当 Execute Node 中的 LLM 生成工具调用请求时,流程会进入 Tool Node,执行实际的工具调用,并将结果保存到消息历史中,供后续节点使用。
代码说明:
1 | from state import PlanAndExecuteState |
工作流程说明:
- 节点从最后一条消息中提取
tool_calls信息 - 遍历每个工具调用请求:
- 提取工具名称和参数
- 从
TOOL_DICT中查找对应的工具对象 - 使用参数调用工具函数
- 将结果封装为
ToolMessage并添加到消息历史
- 返回更新后的状态,流程回到
execute_node继续执行
关键点:
ToolMessage必须包含tool_call_id,用于关联对应的工具调用请求- LLM 在后续回复时,会根据
tool_call_id匹配工具调用结果 - 一个 LLM 回复可能包含多个工具调用,需要遍历处理
- 工具调用结果会被添加到消息历史中,供后续节点使用
2.5 条件边(Conditional Edge)
定义好节点之后,还需要一个 Conditional Edge(条件边)来控制节点之间的流转规则。条件边会根据当前状态决定下一步应该执行哪个节点。
代码说明:
1 | from state import PlanAndExecuteState |
工作流程说明:
- 条件边函数接收当前状态作为参数
- 检查最后一条消息(LLM 的回复)中是否包含 “Final Answer”
- 如果包含,返回
END,流程结束 - 如果不包含,返回
"tool_node",流程进入工具节点
关键点:
- 条件边是 LangGraph 中实现分支逻辑的关键机制
- 返回值必须是已定义的节点名称或
END - 这里的判断逻辑比较简单,实际应用中可能需要更复杂的条件判断
- “Final Answer” 标识是在 System Prompt 中约定的终止条件
2.6 构造 Workflow(工作流)
State、Node 和 Edge 都定义好了之后,就可以构造完整的 Workflow(工作流)了。Workflow 定义了整个 Agent 的执行流程和节点之间的连接关系。
代码说明:
1 | from langgraph.graph import StateGraph, START, END |
工作流结构说明:
整个 Agent 的执行流程如下:
1 | START → plan_node → execute_node → [条件判断] |
- START → plan_node:工作流开始,进入计划节点生成计划
- plan_node → execute_node:计划生成后,进入执行节点开始执行
- execute_node → [条件判断] :
- 如果包含 “Final Answer”,流程结束(END)
- 否则进入 tool_node 执行工具调用
- tool_node → execute_node:工具调用完成后,回到执行节点继续执行
- 循环执行:步骤 3-4 会循环执行,直到 LLM 输出 “Final Answer”
关键点:
StateGraph是 LangGraph 的核心,用于构建有状态的工作流compile()方法将图编译成可执行的状态机invoke()方法执行整个工作流,从 START 到 END- 状态在节点之间传递,每个节点可以读取和修改状态
- 条件边实现了分支逻辑,使工作流能够根据条件选择不同的执行路径
这样,一个完整的 Plan-and-Execute Agent 就构建完成了!
3. 结果演示
测试一下:执行主函数,向 agent 提问 “我想用100美元和50欧元兑换人民币,总共能兑换多少人民币?”。
代码说明:
1 | if __name__ == "__main__": |
预期执行流程:
Plan Node:生成计划
1
2
31. 查询美元对人民币的汇率
2. 查询欧元对人民币的汇率
3. 计算100美元和50欧元总共能兑换多少人民币Execute Node(第1步):执行计划第1步
- LLM 决定调用
get_exchange_rate工具查询美元汇率 - 生成工具调用请求
- LLM 决定调用
Tool Node:执行工具调用
- 调用
get_exchange_rate({"currency": "USD"}) - 返回:”美元对人民币汇率:1 USD = 7.25 CNY”
- 调用
Execute Node(第2步):执行计划第2步
- LLM 看到美元汇率结果,决定调用
get_exchange_rate查询欧元汇率 - 生成工具调用请求
- LLM 看到美元汇率结果,决定调用
Tool Node:执行工具调用
- 调用
get_exchange_rate({"currency": "EUR"}) - 返回:”欧元对人民币汇率:1 EUR = 7.85 CNY”
- 调用
Execute Node(第3步):执行计划第3步
- LLM 看到两个汇率结果,决定调用
calculate计算总兑换金额 - 调用
calculate("100 * 7.25 + 50 * 7.85") - 返回计算结果:1107.5
- LLM 看到两个汇率结果,决定调用
Execute Node(最终):生成最终答案
- LLM 整合所有信息,生成最终答案
- 输出:”100美元可以兑换725元人民币,50欧元可以兑换392.5元人民币,总共可以兑换1117.5元人民币。Final Answer”
- 条件边检测到 “Final Answer”,流程结束
4. 总结
4.1 Plan-and-Execute 模式的核心流程
Plan-and-Execute 模式的核心流程可以概括为以下五个阶段:
计划阶段(Plan):根据用户问题生成结构化的执行计划
- 将复杂任务拆解为若干明确步骤
- 每个步骤都有清晰的输入输出说明
执行阶段(Execute):按照计划逐步执行
- LLM 根据当前步骤和上下文决定下一步操作
- 可能需要调用工具获取外部数据或能力
工具调用(Tool Calling):当需要外部能力时,调用相应工具
- 提取工具调用请求中的参数
- 执行工具函数并获取结果
- 将结果保存到消息历史中
循环迭代(Iteration):执行完成后判断是否结束
- 如果未完成,继续执行下一步
- 工具调用结果会作为上下文传递给下一次执行
终止条件(Termination):当检测到终止标识时,任务完成
- 通过条件边判断是否应该结束
- 返回最终答案给用户
4.2 模式优势
Plan-and-Execute 模式具有以下优势:
- 可解释性强:每个步骤都有明确的计划,用户可以清楚地看到 Agent 的执行过程
- 可回溯:完整的消息历史记录了整个执行过程,便于调试和问题排查
- 易扩展:新增工具只需定义函数并用
@tool装饰,无需修改核心逻辑 - 结构化:计划提供了清晰的任务分解,有助于 LLM 更好地理解和执行任务
4.3 适用场景
这种模式特别适合处理:
- 复杂的多步骤任务:需要多个步骤才能完成的任务
- 需要外部数据的任务:需要查询数据库、调用 API 等
- 需要计算的任务:需要进行数学计算、数据分析等
- 需要可解释性的任务:用户需要了解 Agent 的执行过程
4.4 改进方向
在实际应用中,可以考虑以下改进:
- 更智能的计划生成:使用更先进的规划算法,考虑步骤之间的依赖关系
- 动态计划调整:根据执行结果动态调整计划
- 错误处理:添加错误处理和重试机制
- 工具描述自动化:使用 MCP 或自动生成工具描述,而不是手动编写
- 更复杂的终止条件:不仅依赖 “Final Answer”,还可以考虑步骤完成度、时间限制等
通过 LangGraph 构建 Plan-and-Execute Agent,我们可以轻松实现一个功能强大、可解释、易扩展的智能助手。



























