LangGraph 源码深潜:Plan-and-Execute 与 Subgraph 运行机制解剖
核心问题: LangGraph 如何用
StateGraph + Node + Conditional Edge + Subgraph + partial state update表达 Plan-and-Execute 范式,并支持执行进度、步骤校验、失败重规划和最终响应?源码主线:
StateGraph.add_node()→StateGraph.add_conditional_edges()→StateGraph.compile()→CompiledStateGraph.invoke()→Pregel super-step→planner_node / executor_node / route_next_step→state update / END前置文章: 第 6 篇 StateGraph 源码解剖、第 7 篇 Conditional Edge 与 Router、第 8 篇 Reducer 与并行状态合并
依赖基线:
langgraph==1.2.7源码基线:
langchain-ai/langgraphtag1.2.7,release commit5931a5f阅读边界: 本文只解释 Plan-and-Execute 如何落到 LangGraph 的图构建、状态更新、条件路由、子图调用与运行时调度;不展开 LLM 如何生成高质量计划、不展开具体搜索工具实现、不展开 Checkpoint/HITL 的完整源码。
0. 本篇在源码学习主线中的位置
前几篇已经建立了 LangGraph 的三个基础认知:
StateGraph 负责把状态、节点、边组织成图结构
Conditional Edge 负责在节点执行后根据当前 state 选择下一个节点
Reducer 负责在多节点并发写入同一个 state key 时定义合并规则本篇进入更接近 Agent 范式的一层:
Plan-and-Execute ↓planner node 生成计划 ↓executor node 或 executor subgraph 执行当前步骤 ↓verifier node 检查步骤结果 ↓conditional edge 决定继续、重规划或结束本篇只解决:
- 计划列表应该如何进入
state。 current_step_index如何控制执行进度。- 每一步执行结果如何写回
state。 route_next_step如何通过 conditional edge 驱动循环。executor应该直接作为节点,还是封装成子图。replan是覆盖旧计划、追加修复步骤,还是生成新计划版本。
本篇不展开:
- 复杂 Planner Prompt 的设计。
- LLM 计划质量评估指标。
- 工具调用源码细节。
- Checkpoint / Interrupt 的完整恢复机制。
- Multi-Agent Supervisor 的完整实现。
1. 本篇问题、学习目标与能力边界
1.1 核心问题
LangGraph 如何把 Plan-and-Execute 从“提示词里的计划步骤”变成可执行、可路由、可重试、可重规划的状态图?
1.2 学习目标
完成本篇后,读者必须能够:
- 设计承载计划的
State schema,明确plan_steps、current_step_index、step_results、final_answer的职责。 - 解释
planner_node如何返回 partial state update,并由 LangGraph 合并进全局状态。 - 解释
executor_node如何读取当前步骤、执行任务、写回步骤状态与执行结果。 - 解释
route_next_step如何通过add_conditional_edges()控制继续执行、重规划或结束。 - 判断 executor 应该直接调用工具、作为普通节点,还是封装为独立 subgraph。
- 判断 replan 应该覆盖计划、追加修复步骤,还是保留计划版本历史。
1.3 能力边界
| 能力 | 本篇是否覆盖 | 说明 |
|---|---|---|
| Plan-and-Execute 状态建模 | 是 | 重点讲 plan_steps、current_step_index、step_results |
| Planner Node 源码机制 | 是 | 重点讲节点注册、运行、partial update |
| Executor Node / Subgraph | 是 | 重点讲普通节点与子图两种落地方式 |
| Step Verifier 与 Replan 路由 | 是 | 重点讲条件边与状态驱动循环 |
| 复杂工具调用源码 | 否 | 已在 Tool Calling 篇讲解 |
| Checkpoint / HITL 恢复机制 | 否 | 后续 Checkpoint 与 Interrupt 篇展开 |
| 多 Agent 协同计划 | 否 | 后续 Multi-Agent / Supervisor 篇展开 |
2. 核心概念与最小心智模型
2.1 一句话定义
Plan-and-Execute 在 LangGraph 中不是一个内置魔法类,而是一种由
planner node、executor node/subgraph、step verifier、conditional edge和共享state共同表达的图运行模式。
它负责:
- 先生成结构化计划;
- 再按步骤执行;
- 每步后检查结果;
- 根据 state 决定继续、重试、重规划或结束。
它不负责:
- 自动保证计划质量;
- 自动选择最合适工具;
- 自动防止无限循环;
- 自动解决所有业务失败。
这些仍然需要 schema、verifier、router、recursion limit、checkpoint、middleware 和业务规则配合。
2.2 最小心智模型
user_request ↓planner_node ↓plan_steps + current_step_index ↓execute_step ↓step_results + updated plan_steps ↓check_step_result ↓route_next_step ├── execute_step ├── replan └── final_response2.3 核心术语
| 术语 | 源码对象 | 语义 | 不要误解为 |
|---|---|---|---|
| Planner Node | StateGraph.add_node("planner_node", fn) | 生成计划并写入 state 的节点 | 框架内置 Planner 类 |
| Plan State | plan_steps / current_step_index | 计划结构与执行游标 | 普通临时变量 |
| Executor Node | 普通节点或 compiled subgraph | 执行当前步骤 | 一定要是 ToolNode |
| Step Verifier | 普通节点 | 检查当前步骤是否成功 | 必须是 LLM |
| Replan | conditional edge 目标节点 | 失败后修正计划 | 只是在 prompt 里说重新规划 |
| Subgraph | CompiledStateGraph 作为节点 | 把复杂执行器模块化 | Python 函数嵌套调用的同义词 |
| Partial Update | 节点返回的 dict | 对 state 的局部更新 | 完整状态覆盖 |
2.4 与相邻抽象的边界
| 对象 | 负责什么 | 不负责什么 | 与本篇对象的关系 |
|---|---|---|---|
StateGraph | 注册状态、节点、边并编译图 | 不自动生成计划 | Plan-and-Execute 的承载结构 |
add_conditional_edges() | 根据 path function 选择后继节点 | 不执行业务任务 | 控制 continue / replan / final |
| Reducer | 合并并发或追加式状态更新 | 不判断步骤是否成功 | 用于保存步骤日志、消息、工具结果 |
ToolNode | 执行模型生成的 tool calls | 不管理计划游标 | 可作为 executor subgraph 内部节点 |
Command | 同时返回 state update 与 goto | 不替代所有条件边 | 可用于更动态的步骤执行跳转 |
| Checkpointer | 保存执行快照 | 不设计计划结构 | 支持长任务恢复与人工确认 |
3. 完整执行链路
3.1 高层链路
用户输入 ↓StateGraph(TravelState) ↓add_node(planner_node / execute_step / check_step_result / replan / final_response) ↓add_edge + add_conditional_edges ↓compile 生成 CompiledStateGraph ↓graph.invoke(initial_state) ↓Pregel 按 super-step 调度节点 ↓节点返回 partial state update ↓根据 route_next_step 继续、重规划或 END3.2 最小代码骨架
观察目标:下面代码展示 Plan-and-Execute 的最小骨架。重点不是计划质量,而是观察 plan_steps 和 current_step_index 如何驱动图循环。
from typing_extensions import TypedDict, Literalfrom langgraph.graph import StateGraph, START, END
class PlanStep(TypedDict): step_id: int task: str status: str
class TravelState(TypedDict): user_request: str plan_steps: list[PlanStep] current_step_index: int step_results: list[str] final_answer: str | None
def planner_node(state: TravelState): return { "plan_steps": [ {"step_id": 1, "task": "确认目的地和天数", "status": "pending"}, {"step_id": 2, "task": "查询景点和交通信息", "status": "pending"}, {"step_id": 3, "task": "生成每日行程", "status": "pending"}, ], "current_step_index": 0, "step_results": [], }
def execute_step(state: TravelState): index = state["current_step_index"] step = state["plan_steps"][index]
result = f"执行步骤 {step['step_id']}:{step['task']}"
updated_steps = [dict(s) for s in state["plan_steps"]] updated_steps[index]["status"] = "done"
return { "plan_steps": updated_steps, "step_results": state["step_results"] + [result], "current_step_index": index + 1, }
def route_next_step(state: TravelState) -> Literal[ "execute_step", "final_response",]: if state["current_step_index"] >= len(state["plan_steps"]): return "final_response" return "execute_step"
def final_response(state: TravelState): return { "final_answer": "\n".join(state["step_results"]) }
builder = StateGraph(TravelState)
builder.add_node("planner_node", planner_node)builder.add_node("execute_step", execute_step)builder.add_node("final_response", final_response)
builder.add_edge(START, "planner_node")builder.add_edge("planner_node", "execute_step")builder.add_conditional_edges("execute_step", route_next_step)builder.add_edge("final_response", END)
graph = builder.compile()
result = graph.invoke({ "user_request": "我想去东京玩 5 天", "plan_steps": [], "current_step_index": 0, "step_results": [], "final_answer": None,})代码解释:
planner_node不执行任务,只生成计划列表和游标初始值。execute_step每次只处理current_step_index指向的一个步骤。- 执行结束后,
current_step_index + 1表示下一轮执行下一个步骤。 route_next_step不修改状态,只读取状态并返回下一个节点名。final_response读取所有步骤结果并生成最终输出。
3.3 对象流转
| 阶段 | 输入类型 | 核心函数 | 输出类型 | 状态变化 |
|---|---|---|---|---|
| 构建状态 | type[TravelState] | StateGraph.__init__() | StateGraph | 解析 state schema |
| 注册节点 | Callable | StateGraph.add_node() | StateGraph | 保存 node spec |
| 注册边 | str, str | StateGraph.add_edge() | StateGraph | 保存固定边 |
| 注册路由 | Callable | StateGraph.add_conditional_edges() | StateGraph | 保存 branch spec |
| 编译图 | builder | StateGraph.compile() | CompiledStateGraph | 生成 Pregel 应用 |
| 执行 planner | TravelState | planner_node() | Partial[TravelState] | 写入计划 |
| 执行 step | TravelState | execute_step() | Partial[TravelState] | 更新步骤状态和游标 |
| 路由 | TravelState | route_next_step() | str | 决定后继节点 |
| 结束 | TravelState | final_response() | Partial[TravelState] | 写入最终回答 |
3.4 时序链路
Caller │ │ graph.invoke(initial_state) ▼CompiledStateGraph │ │ 初始化 state channels ▼planner_node │ │ return {plan_steps, current_step_index} ▼state merge │ │ edge: planner_node → execute_step ▼execute_step │ │ return {updated plan_steps, step_results, current_step_index + 1} ▼route_next_step │ ├── execute_step │ └── final_response → END3.5 正常结束条件
一次正常 Plan-and-Execute 执行完成的条件是:
current_step_index >= len(plan_steps) ↓route_next_step 返回 final_response ↓final_response 写入 final_answer ↓final_response → END最终结果保存在:
result["final_answer"]中;完整执行轨迹可通过 stream(..., stream_mode="updates")、checkpoint history 或 LangSmith trace 观察。
4. 源码地图、关键文件与阅读顺序
4.1 核心目录
langgraph/├── graph/│ ├── state.py # StateGraph / CompiledStateGraph│ ├── graph.py # 基础 Graph / CompiledGraph│ └── _branch.py # BranchSpec / conditional edge 路由├── pregel/│ ├── main.py # Pregel 可执行应用入口│ ├── _loop.py # super-step 循环│ ├── _algo.py # 任务准备、写入应用、channel 更新│ ├── _read.py # ChannelRead / PregelNode 读取│ └── _write.py # ChannelWrite / 写入表达├── channels/│ ├── last_value.py│ ├── binop.py│ └── base.py└── types.py # Command / Send / Interrupt 等控制类型4.2 关键文件
| 优先级 | 文件 | 核心对象 | 阅读目的 |
|---|---|---|---|
| 1 | langgraph/graph/state.py | StateGraph、CompiledStateGraph | 理解节点、边、branch、compile 如何构建 |
| 2 | langgraph/graph/_branch.py | BranchSpec | 理解 route_next_step 如何被包装与执行 |
| 3 | langgraph/pregel/main.py | Pregel | 理解 compiled graph 为什么是可执行应用 |
| 4 | langgraph/pregel/_loop.py | Pregel loop | 理解 super-step 调度 |
| 5 | langgraph/pregel/_algo.py | 写入应用与任务准备 | 理解 partial update 如何合并 |
| 6 | langgraph/channels/ | LastValue、BinaryOperatorAggregate | 理解覆盖与追加语义 |
| 7 | langgraph/types.py | Command、Send | 理解动态 goto 与 map-reduce 扩展 |
4.3 推荐阅读顺序
1. StateGraph.__init__2. StateGraph.add_node3. StateGraph.add_edge4. StateGraph.add_conditional_edges5. StateGraph.compile6. CompiledStateGraph.attach_node / attach_branch7. BranchSpec.run / _route / _finish8. Pregel.invoke / stream9. Pregel super-step loop10. channel update / state merge4.4 不建议的阅读顺序
不建议直接从 pregel/_loop.py 开始阅读。Pregel 是运行时调度核心,如果还没有理解 StateGraph 如何把节点、边和 state channel 编译成 Pregel 节点,直接读调度循环会很难判断哪些 channel 来自 state key、哪些 channel 来自 branch、哪些写入来自节点返回值。
也不建议从 ToolNode 开始阅读 Plan-and-Execute。ToolNode 只解决工具调用,不解决计划游标、计划状态、步骤循环和重规划。
5. 对象模型、继承关系与协议边界
5.1 核心对象关系
StateGraph builder ↓ compile()CompiledStateGraph ↓ inherits / wraps Pregel executable semanticsPregel runtime ↓ schedules nodes by channels and super-stepsNode function ↓ returns partial state updateChannel / reducer ↓ merges update into state5.2 对象职责
| 对象 | 生命周期 | 输入 | 输出 | 核心职责 |
|---|---|---|---|---|
StateGraph | 构建期 | schema、node、edge | builder | 保存图声明,不执行 |
CompiledStateGraph | 编译后 | graph spec | runnable graph | 暴露 invoke/stream/ainvoke |
Pregel | 运行时 | input state、config | state / stream | 按 super-step 调度节点 |
BranchSpec | 构建期 + 运行时 | path function | next node(s) | 条件路由 |
| Node function | 运行时 | full state | partial update | 执行业务逻辑 |
| Channel | 构建期 + 运行时 | updates | merged value | 管理 state key 合并 |
| Subgraph | 编译后 | parent-transformed state | partial output | 模块化复杂 executor |
5.3 协议边界
StateGraph 协议 负责:描述 state schema、nodes、edges、branches 不负责:实际执行节点
Pregel 协议 负责:运行 super-step、调度节点、传播写入 不负责:理解业务计划语义
Node 协议 负责:读取 state,返回 partial update 不负责:直接修改全局 state
Branch 协议 负责:读取 state,返回后继节点名或 END 不负责:执行业务任务
Subgraph 协议 负责:把一组内部节点封装成一个外部节点 不负责:自动适配父子图 state schema5.4 稳定接口与内部实现
| 类型 | 对象 | 文章中的使用原则 |
|---|---|---|
| 公共 API | StateGraph、add_node()、add_edge()、add_conditional_edges()、compile() | 可用于工程示例 |
| 公共 API | CompiledStateGraph.invoke()、stream()、ainvoke() | 可用于工程运行 |
| 公共 API | Command、Send | 可用于动态控制和 map-reduce |
| 扩展接口 | subgraph as node | 可用于模块化执行器 |
| 内部实现 | BranchSpec、Pregel loop、channel write | 只用于理解源码,不建议业务代码依赖 |
6. 源码阅读策略与证据标准
6.1 本篇阅读策略
先看公开代码骨架 ↓确认 Plan State 字段 ↓沿 add_node / add_edge / add_conditional_edges 看构建期 ↓沿 compile 看 builder 如何变成 CompiledStateGraph ↓沿 invoke / stream 看运行时 super-step ↓沿 branch 看 continue / replan / final 的路由 ↓沿 channel / reducer 看 partial update 如何合并 ↓回到工程判断:executor 用 node 还是 subgraph6.2 证据等级
| 标记 | 含义 | 写作要求 |
|---|---|---|
| 源码事实 | 当前正式版源码可证明 | 附 tag/commit 源码链接 |
| 官方契约 | 官方文档或 API Reference 明确承诺 | 附官方文档链接 |
| 简化伪代码 | 对真实控制流的压缩表达 | 明确标注“伪代码” |
| 作者推断 | 根据调用链得出的设计理解 | 明确使用“从调用关系可以推断” |
| 工程建议 | 面向项目实践 | 说明适用条件 |
6.3 本篇证据清单
| 结论 | 证据类型 | 文件或文档 | 定位 |
|---|---|---|---|
StateGraph 是 builder,需 compile 后执行 | 官方契约 / 源码事实 | state.py、StateGraph reference | 类文档与 compile() |
| 节点签名是 State → Partial | 官方契约 / 源码事实 | Graph API、state.py | StateGraph 类文档 |
| 图执行基于 super-step 与 message passing | 官方契约 | Graph API | graph algorithm 描述 |
| subgraph 可以作为节点或在节点内调用 | 官方契约 | Subgraphs docs | subgraph communication |
| conditional edge path function 选择后继节点 | 官方契约 / 源码事实 | Graph API、_branch.py | branch route |
Command 可以组合 state update 与 goto | 官方契约 | types.Command reference | Command 签名 |
7. 构建期源码解剖
本章回答:
用户写下
planner_node、execute_step、route_next_step与子图后,LangGraph 如何把这些函数注册、归一化、校验并编译成可执行图?
7.1 构建期职责
| 输入 | 归一化动作 | 构建结果 |
|---|---|---|
TravelState | 解析 state schema 和 channels | self.channels / self.schemas |
planner_node | 推断或使用节点名,包装成 runnable | StateNodeSpec |
| 固定边 | 校验起点终点,保存 edge | self.edges |
route_next_step | 包装为 BranchSpec | self.branches[source] |
| executor subgraph | 作为 runnable node 注册 | node spec 中 action 为 compiled graph |
| compile 参数 | checkpointer/cache/interrupt/debug | CompiledStateGraph |
7.2 构建期总链路
StateGraph(TravelState) ↓_add_schema 解析 state fields ↓add_node 注册 planner / executor / verifier / replan / final ↓add_edge 注册固定执行顺序 ↓add_conditional_edges 注册 route_next_step ↓compile 校验图结构并 attach node / edge / branch ↓CompiledStateGraph7.3 StateGraph.__init__() 与 Plan State schema 源码解剖
职责与所处阶段
构建期。负责把 TravelState 解析成图的状态协议,确定哪些字段可以被节点读取和写入。
真实源码签名
以下签名来自当前正式版源码的公开构造入口:
class StateGraph(Generic[StateT, ContextT, InputT, OutputT]): def __init__( self, state_schema: type[StateT], context_schema: type[ContextT] | None = None, *, input_schema: type[InputT] | None = None, output_schema: type[OutputT] | None = None, **kwargs, ) -> None: ...调用方与被调用方
builder = StateGraph(TravelState) ↓StateGraph.__init__ ↓_add_schema(state_schema) ↓_get_channels(schema)输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | type[TravelState] | 用户定义的 TypedDict state schema |
| 输出 | StateGraph | builder 对象 |
| 状态变化 | self.channels、self.schemas | 保存每个 state key 对应的 channel |
| 副作用 | 无外部副作用 | 只修改 builder 内部结构 |
细粒度伪代码
以下为保留关键控制流的简化伪代码,不是源码逐字复制:
def __init__(state_schema, context_schema=None, input_schema=None, output_schema=None): # 1. 初始化图结构容器 self.nodes = {} self.edges = set() self.branches = defaultdict(dict) self.waiting_edges = set()
# 2. 初始化状态相关容器 self.schemas = {} self.channels = {} self.managed = {}
# 3. 保存 schema 边界 self.state_schema = state_schema self.input_schema = input_schema or state_schema self.output_schema = output_schema or state_schema self.context_schema = context_schema
# 4. 解析主状态 schema self._add_schema(self.state_schema)
# 5. 解析输入输出 schema,禁止 managed value 进入 I/O schema self._add_schema(self.input_schema, allow_managed=False) self._add_schema(self.output_schema, allow_managed=False)
# 6. 标记尚未编译 self.compiled = False逐段解释
第 1 段初始化的是图结构容器:nodes 保存节点,edges 保存固定边,branches 保存条件边。Plan-and-Execute 里的 planner_node → execute_step 是固定边,而 execute_step → route_next_step 是条件边。
第 2 段初始化的是状态模型容器:channels 是理解 LangGraph state 的关键。plan_steps、current_step_index、step_results 并不是运行时随便塞进 dict 的字段,而是在 schema 解析阶段就被注册成可读写 channel。
第 4、5 段说明 state_schema、input_schema、output_schema 可以不同。Plan-and-Execute 常见做法是:输入只需要 user_request,内部状态包含 plan_steps 与 step_results,输出只暴露 final_answer。如果不显式定义,默认三者都等于 state_schema。
正常路径
TravelState TypedDict ↓_get_channels ↓plan_steps / current_step_index / final_answer 等字段变成 channels ↓StateGraph builder 可注册节点和边关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
| 字段没有 reducer | 使用默认覆盖式 channel | 适合 current_step_index、final_answer |
字段有 Annotated[..., reducer] | 使用 reducer channel | 适合 step_results、messages、日志 |
| input/output schema 包含不允许的 managed value | 抛出 ValueError | 防止运行时管理字段暴露给外部 I/O |
设计原因与工程影响
Plan-and-Execute 中,plan_steps 和 current_step_index 是执行控制字段。它们必须是 state schema 的一部分,因为后续节点和条件边都依赖它们。不要把它们藏在函数局部变量里,否则每个节点之间无法共享执行进度。
源码证据
libs/langgraph/langgraph/graph/state.py::StateGraph.__init__https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/state.py
7.4 add_node() 注册 Planner / Executor / Verifier 源码解剖
职责与所处阶段
构建期。负责把普通 Python 函数或 Runnable 转换为图节点,并保存节点名、执行函数、输入 schema、retry/cache/error/timeout 策略。
真实源码签名
def add_node( self, node: str | StateNode[NodeInputT, ContextT], action: StateNode[NodeInputT, ContextT] | None = None, *, defer: bool = False, metadata: dict[str, Any] | None = None, input_schema: type[NodeInputT] | None = None, retry_policy: RetryPolicy | Sequence[RetryPolicy] | None = None, cache_policy: CachePolicy | None = None, error_handler: StateNode[Any, ContextT] | None = None, destinations: dict[str, str] | tuple[str, ...] | None = None, timeout: float | timedelta | TimeoutPolicy | None = None, **kwargs,) -> Self: ...调用方与被调用方
builder.add_node("planner_node", planner_node) ↓StateGraph.add_node ↓coerce_to_runnable / StateNodeSpec ↓self.nodes[name] = spec输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | node name + callable | 如 "planner_node" 与 planner_node 函数 |
| 输出 | Self | 支持链式调用 |
| 状态变化 | self.nodes | 新增节点定义 |
| 副作用 | 无外部副作用 | 仅更新 builder |
细粒度伪代码
def add_node(node, action=None, *, input_schema=None, retry_policy=None, cache_policy=None, error_handler=None, destinations=None, timeout=None): # 1. 如果 node 不是字符串,说明用户传的是函数或 Runnable if not isinstance(node, str): action = node node_name = infer_name_from_callable_or_runnable(action) else: node_name = node
# 2. 校验节点名 if node_name is None: raise ValueError("Node name must be provided") if node_name in self.nodes: raise ValueError("Node already exists") if node_name in {START, END}: raise ValueError("Reserved node name")
# 3. 推断输入 schema node_input_schema = input_schema or self.state_schema self._add_schema(node_input_schema, allow_managed=False)
# 4. 将 action 归一化为 runnable-like node runnable = coerce_to_runnable(action, name=node_name)
# 5. 保存节点规格 self.nodes[node_name] = StateNodeSpec( runnable=runnable, metadata=metadata, input_schema=node_input_schema, retry_policy=retry_policy, cache_policy=cache_policy, error_handler=error_handler, destinations=destinations, timeout=timeout, )
return self逐段解释
第 1 段解决命名问题。builder.add_node(planner_node) 会使用函数名作为节点名;builder.add_node("planner_node", planner_node) 则显式指定节点名。源码解剖时建议显式写节点名,便于后续 add_edge() 和 trace 对齐。
第 3 段说明每个节点可以有自己的输入 schema。Plan-and-Execute 的 planner_node 往往读取完整 state,但 executor subgraph 可能只接收一个 ExecutorState,这时需要通过 wrapper 转换。
第 4 段说明节点函数会被归一化为可运行对象。节点并不是运行时直接裸调函数,而是进入 LangGraph 的 runnable 调度体系。
正常路径
planner_node function ↓add_node ↓StateNodeSpec ↓compile 时 attach 为 PregelNode关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
| 未显式传节点名 | 从函数名推断 | 适合 demo,不适合大型项目 |
| 节点名重复 | 抛错 | 防止边指向歧义 |
| 已 compile 后继续 add_node | 通常会警告 | 已编译图不一定同步更新 |
| 传入 compiled subgraph | 作为 Runnable 节点保存 | 可实现 executor subgraph |
设计原因与工程影响
Plan-and-Execute 的节点通常比普通 Workflow 更有状态依赖。显式节点名可以让 trace、conditional edge、error handler 更清晰。对于 executor,如果内部包含多个步骤、工具和校验,推荐把 executor 编译成 subgraph 再作为节点注册。
源码证据
libs/langgraph/langgraph/graph/state.py::StateGraph.add_nodehttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/state.py
7.5 add_conditional_edges() 注册 route_next_step 源码解剖
职责与所处阶段
构建期。负责把 route_next_step 这样的路径函数注册为 branch,让 LangGraph 在节点执行后根据 state 决定下一步。
真实源码签名
def add_conditional_edges( self, source: str, path: Callable[..., Hashable | Sequence[Hashable]] | Runnable[Any, Hashable | Sequence[Hashable]], path_map: dict[Hashable, str] | list[str] | None = None,) -> Self: ...调用方与被调用方
builder.add_conditional_edges("execute_step", route_next_step) ↓StateGraph.add_conditional_edges ↓BranchSpec.from_path(path, path_map, infer_schema=True) ↓self.branches[source][branch_name] = branch_spec输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | source node + route function | "execute_step" 与 route_next_step |
| 输出 | Self | builder |
| 状态变化 | self.branches | 保存条件边定义 |
| 副作用 | 无外部副作用 | 仅保存 branch spec |
细粒度伪代码
def add_conditional_edges(source, path, path_map=None): # 1. 校验 source 是否可作为边起点 if source == END: raise ValueError("END cannot have outgoing edges")
# 2. 将 path 函数转换为 branch spec branch = BranchSpec.from_path( path=path, path_map=path_map, infer_schema=True, )
# 3. 生成 branch 名称,避免同一 source 下重复 branch_name = branch.name or "condition" if branch_name in self.branches[source]: raise ValueError("Branch already exists for source")
# 4. 保存到 builder self.branches[source][branch_name] = branch
return self逐段解释
第 1 段说明 END 是终止哨兵,不能再作为 source 注册条件边。Plan-and-Execute 的循环必须挂在 execute_step、check_step_result 或 validate_plan 之后,而不是挂在 END 后。
第 2 段是核心:route_next_step 不会立即执行,而是被包装成 BranchSpec,等待运行时某个 source node 执行完成后再调用。
第 4 段说明条件边保存于 self.branches[source],不是普通 self.edges。这就是固定边和条件边的源码边界。
正常路径
execute_step 执行完成 ↓BranchSpec.run(route_next_step) ↓返回 execute_step / replan / final_response ↓Pregel 写入对应 branch channel关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
route_next_step 返回节点名 | 进入对应节点 | 继续执行或结束前节点 |
返回 END | 图分支终止 | 当前执行路径结束 |
| 返回多个节点名 | 触发多个后继 | 可并行执行 |
配置 path_map | 将业务标签映射到节点名 | 解耦业务状态和节点名 |
| 返回不存在节点 | 编译或运行时失败 | 路由定义错误 |
设计原因与工程影响
Plan-and-Execute 的核心不是简单循环,而是“每步执行后根据状态选择下一步”。add_conditional_edges() 把这个控制权从节点内部 if/else 中抽离出来,使执行逻辑和路由逻辑分离。
源码证据
libs/langgraph/langgraph/graph/state.py::StateGraph.add_conditional_edgeslibs/langgraph/langgraph/graph/_branch.py::BranchSpec.from_pathhttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/state.pyhttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/_branch.py
7.6 Executor Subgraph 构建源码解剖
职责与所处阶段
构建期。负责把复杂 executor 拆成独立子图,然后作为父图的一个节点使用。
真实源码签名
子图本身仍通过 StateGraph.compile() 生成:
subgraph = subgraph_builder.compile()parent_builder.add_node("execute_step", subgraph)调用方与被调用方
executor_builder = StateGraph(ExecutorState) ↓executor_builder.add_node(...) ↓executor_graph = executor_builder.compile() ↓parent_builder.add_node("execute_step", executor_graph)输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | ExecutorState 或 parent TravelState | 子图输入 state |
| 输出 | partial state update | 返回给父图合并 |
| 状态变化 | 子图内部 state / 父图 state | 取决于是否共享 schema key |
| 副作用 | 可能有 | 子图内部可能调用工具 |
细粒度伪代码
def build_executor_subgraph(): # 1. 定义 executor 私有状态 class ExecutorState(TypedDict): current_step: PlanStep tool_results: list[str] step_status: str step_error: str | None
# 2. 构建子图 executor = StateGraph(ExecutorState) executor.add_node("prepare_step", prepare_step) executor.add_node("call_tools", call_tools) executor.add_node("verify_step", verify_step)
# 3. 添加内部边 executor.add_edge(START, "prepare_step") executor.add_edge("prepare_step", "call_tools") executor.add_edge("call_tools", "verify_step") executor.add_edge("verify_step", END)
# 4. 编译为可执行子图 return executor.compile()
def parent_execute_step_node(parent_state): # 5. 将父图 state 映射为子图输入 sub_input = { "current_step": parent_state["plan_steps"][parent_state["current_step_index"]], "tool_results": [], "step_status": "running", "step_error": None, }
# 6. 调用子图 sub_output = executor_graph.invoke(sub_input)
# 7. 将子图输出映射回父图 partial update return convert_executor_output_to_parent_update(parent_state, sub_output)逐段解释
第 1–4 段说明子图本身也是 StateGraph → compile() 的结果。它可以拥有自己的私有状态,例如 tool_results、step_error,这些字段不一定暴露给父图。
第 5–7 段是父子图 state 边界。官方文档中给出了两种通信模式:父子图 state 不同则在节点函数里手动转换;父子图共享 key 则可直接把 compiled subgraph 作为节点。Plan-and-Execute 中,如果 executor 内部复杂,通常推荐使用不同 schema + wrapper 转换,避免父图 state 被执行细节污染。
正常路径
parent TravelState ↓wrapper 提取 current_step ↓executor subgraph.invoke(ExecutorState) ↓subgraph 执行内部工具/校验 ↓wrapper 转成 TravelState partial update关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
| executor 只有一个简单工具调用 | 可直接写普通 node | 不需要 subgraph |
| executor 内部有多个步骤 | 使用 subgraph | 模块边界更清晰 |
| 父子图 state schema 完全相同 | 可直接作为 node 注册 | 子图读写父图 channels |
| 父子图 state schema 不同 | wrapper 转换 | 隔离执行器内部状态 |
| 子图抛异常 | 由父节点异常路径处理 | 可进入 replan 或失败 |
设计原因与工程影响
Plan-and-Execute 的 executor 经常会膨胀:查工具、重试、校验、整理结果、记录 trace。把它作为子图可以防止父图变得过大,也能让 executor 独立测试。但子图边界越强,父子 state 映射成本也越高。
源码证据
libs/langgraph/langgraph/graph/state.py::StateGraph.compilelibs/langgraph/langgraph/graph/state.py::StateGraph.add_nodehttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/state.py
7.7 compile() 组装 Plan-and-Execute 图源码解剖
职责与所处阶段
构建期结束。负责把 builder 中保存的 schema、nodes、edges、branches 编译为可执行的 CompiledStateGraph。
真实源码签名
def compile( self, checkpointer: Checkpointer = None, *, cache: BaseCache | None = None, store: BaseStore | None = None, interrupt_before: All | list[str] | None = None, interrupt_after: All | list[str] | None = None, debug: bool = False, name: str | None = None,) -> CompiledStateGraph: ...调用方与被调用方
graph = builder.compile() ↓StateGraph.compile ↓validate graph structure ↓CompiledStateGraph(...) ↓attach_node / attach_edge / attach_branch ↓validate compiled graph输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | builder 内部状态 | nodes、edges、branches、channels |
| 输出 | CompiledStateGraph | 可执行图 |
| 状态变化 | self.compiled = True | builder 标记已编译 |
| 副作用 | 无外部副作用 | 只构造 compiled object |
细粒度伪代码
def compile(checkpointer=None, cache=None, store=None, interrupt_before=None, interrupt_after=None, debug=False, name=None): # 1. 校验 builder 图结构 self.validate( interrupt_before=interrupt_before, interrupt_after=interrupt_after, )
# 2. 准备输出和 stream channels output_channels = infer_output_channels(self.output_schema) stream_channels = infer_stream_channels(self.channels)
# 3. 构造 CompiledStateGraph compiled = CompiledStateGraph( builder=self, channels=self.channels, nodes={}, input_channels=START, output_channels=output_channels, stream_channels=stream_channels, checkpointer=checkpointer, cache=cache, store=store, interrupt_before_nodes=interrupt_before, interrupt_after_nodes=interrupt_after, debug=debug, name=name, )
# 4. 挂载所有普通节点 for node_name, node_spec in self.nodes.items(): compiled.attach_node(node_name, node_spec)
# 5. 挂载固定边 for start, end in self.edges: compiled.attach_edge(start, end)
# 6. 挂载等待边和条件分支 for starts, end in self.waiting_edges: compiled.attach_edge(starts, end)
for source, branches in self.branches.items(): for branch_name, branch_spec in branches.items(): compiled.attach_branch(source, branch_name, branch_spec)
# 7. 校验 compiled graph compiled.validate()
self.compiled = True return compiled逐段解释
第 1 段保证图结构合法,例如不能有孤立节点、不能从未知节点出发、interrupt 节点名必须存在。
第 3 段说明 CompiledStateGraph 不是简单复制 builder,而是把 state channels、节点、边、运行时参数一起组装成 Pregel 应用。
第 4–6 段是最重要的组装过程:Plan-and-Execute 的固定顺序边、条件路由、子图节点都在这一步进入 compiled graph。
正常路径
builder.nodes / edges / branches ↓compile ↓compiled.nodes / channels / triggers / writes ↓invoke 时可调度关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
| 图没有入口 | 校验失败 | 无法执行 |
| 边指向未知节点 | 校验失败 | 防止运行时找不到节点 |
| 条件边目标未知 | 校验失败或运行失败 | 路由定义错误 |
| compile 后再改 builder | 警告或不会影响已编译对象 | 应重新 compile |
设计原因与工程影响
Plan-and-Execute 的执行图往往包含循环。compile() 必须把这些循环边、条件分支和 channel 写入规则提前编译好,运行时才能高效调度。业务代码应该把 compile 当作“图结构冻结点”。
源码证据
libs/langgraph/langgraph/graph/state.py::StateGraph.compilelibs/langgraph/langgraph/graph/state.py::CompiledStateGraphhttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/state.py
7.8 构建期产物
| 产物 | 保存的信息 | 运行时用途 |
|---|---|---|
self.channels | state key 与 reducer/channel | 合并 partial update |
self.nodes | planner/executor/verifier/final 节点 | 编译为 PregelNode |
self.edges | 固定边 | 顺序调度 |
self.branches | route function | 条件路由 |
CompiledStateGraph | 可执行 Pregel 图 | invoke/stream/ainvoke |
| executor subgraph | 内部执行图 | 作为父图节点运行 |
8. 运行时主链源码解剖
本章回答:
构建完成后,一次
graph.invoke()如何真正执行 Plan-and-Execute 的 planner、executor、router 和 final 节点?
8.1 运行时入口
| 调用方式 | 公开入口 | 核心内部入口 | 返回类型 |
|---|---|---|---|
| 同步调用 | graph.invoke(input) | Pregel.stream() / loop | 最终 state |
| 异步调用 | graph.ainvoke(input) | async Pregel loop | 最终 state |
| 流式调用 | graph.stream(input, stream_mode="updates") | event stream | 节点更新流 |
| 批量调用 | graph.batch(inputs) | Runnable batch | list[state] |
8.2 运行时总链路
graph.invoke(initial_state) ↓初始化 input channels ↓START 激活 planner_node ↓planner_node 返回 plan_steps / current_step_index ↓state channels 合并 update ↓固定边激活 execute_step ↓execute_step 读取当前步骤并写回结果 ↓route_next_step 读取最新 state ↓进入 execute_step / replan / final_response ↓所有节点 inactive 且无消息在途 ↓返回最终 state8.3 输入归一化源码解剖
职责与所处阶段
运行时。负责把用户传入的 dict 初始化为图 state 的 channel 值。
真实源码签名
公开入口继承 runnable 语义:
def invoke(self, input: InputT, config: RunnableConfig | None = None, **kwargs) -> OutputT: ...调用方与被调用方
graph.invoke(initial_state) ↓Pregel.invoke ↓Pregel.stream ↓loop.input / input channel writes输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | dict | 初始 TravelState |
| 输出 | channel writes | 写入图输入 channels |
| 状态变化 | 初始化 state | user_request 等字段进入 channel |
| 副作用 | 可选 checkpoint | 取决于是否配置 checkpointer |
细粒度伪代码
def invoke(input_state, config=None): # 1. 归一化 RunnableConfig config = ensure_config(config)
# 2. 创建运行时 loop loop = create_pregel_loop( input=input_state, config=config, channels=compiled.channels, nodes=compiled.nodes, )
# 3. 执行 stream,但只收集最终输出 final_output = None for event in loop.stream(): if event_is_output(event): final_output = event_to_output(event)
# 4. 返回最终 state 或 output schema 投影 return final_output逐段解释
第 1 段说明 compiled graph 仍然遵守 Runnable 协议,所以 config 可以传入 recursion_limit、thread_id、tags 等运行时配置。
第 2 段将 input_state 写入图的输入 channel。Plan-and-Execute 的 user_request、空 plan_steps、初始 current_step_index 会在这里成为可被 planner node 读取的 state。
第 3 段说明 invoke() 本质上可以理解为消费一次 stream,只是最终只返回结果,而不是每步事件。
正常路径
initial_state dict ↓input channels ↓START message ↓planner_node active关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
| 输入缺少必需 key | 可能由 schema 或节点访问时报错 | 输入不完整 |
| 传入额外 key | 取决于 schema 类型与 channel | 可能忽略或保留 |
| 配置 checkpointer + thread_id | 保存执行状态 | 支持恢复 |
| 配置 recursion_limit | 限制循环步数 | 防止无限执行 |
设计原因与工程影响
Plan-and-Execute 是状态驱动范式,初始 state 必须包含足够信息让 planner 生成计划,也必须给后续字段提供默认值。建议工程上统一由 build_initial_state() 函数创建初始状态,避免调用方漏字段。
源码证据
libs/langgraph/langgraph/pregel/main.py::Pregel.invokelibs/langgraph/langgraph/pregel/main.py::Pregel.streamhttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/pregel/main.py
8.4 Config、Context 与 State 传播源码解剖
数据边界
| 数据 | 来源 | 生命周期 | 下游消费者 |
|---|---|---|---|
| Config | invoke(..., config=...) | 单次运行或线程运行 | runtime、节点、子图 |
| Context | context_schema + runtime context | 单次运行 | 节点函数 |
| State | TravelState channels | 整个图执行 | planner/executor/router |
细粒度伪代码
def run_node(node, current_state, config, runtime): # 1. 从 channels 读取当前 state 快照 state_snapshot = read_state_from_channels(current_state)
# 2. 准备节点参数 if node_accepts_runtime(node): args = (state_snapshot, runtime) else: args = (state_snapshot,)
# 3. 调用节点函数或 runnable update = node.invoke_or_call(*args, config=config)
# 4. 将节点返回值转换成 channel writes writes = map_update_to_channel_writes(update)
return writes逐段解释
第 1 段说明节点读到的是当前 super-step 开始时的一致状态快照。Plan-and-Execute 中,execute_step 应基于最新合并后的 current_step_index 执行。
第 3 段说明节点可以是普通函数,也可以是 Runnable 或 compiled subgraph。子图作为节点时,也通过统一运行接口被调用。
第 4 段说明节点返回的是 partial update,不是直接改 state。真正合并发生在 channel 写入应用阶段。
8.5 planner_node 运行源码解剖
职责与所处阶段
运行时主链。负责生成完整计划,并初始化执行游标。
真实源码签名
用户节点函数签名:
def planner_node(state: TravelState) -> dict: ...调用方与被调用方
Pregel runtime ↓PregelNode for planner_node ↓planner_node(state) ↓ChannelWrite(plan_steps, current_step_index)输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | TravelState | 初始状态,包含 user_request |
| 输出 | dict | plan_steps、current_step_index、可选 plan_version |
| 状态变化 | 覆盖计划字段 | 写入新计划 |
| 副作用 | 不建议有 | planner 应尽量纯函数化 |
细粒度伪代码
def planner_node(state): # 1. 读取用户请求 user_request = state["user_request"]
# 2. 调用 LLM / 规则 planner 生成结构化计划 plan_steps = build_plan_steps(user_request)
# 3. 校验计划基本结构 if not plan_steps: return { "plan_error": "empty_plan", "plan_status": "invalid", }
# 4. 标准化 step_id 与 status normalized_steps = [] for i, step in enumerate(plan_steps): normalized_steps.append({ "step_id": i + 1, "task": step["task"], "status": "pending", })
# 5. 返回 partial state update return { "plan_steps": normalized_steps, "current_step_index": 0, "step_results": [], "plan_status": "ready", }逐段解释
第 2 段中的 build_plan_steps() 可以是 LLM structured output,也可以是规则模板。LangGraph 不关心计划如何生成,只关心节点返回什么 state update。
第 3 段是工程上必须补的计划校验。不要让空计划进入 executor,否则 current_step_index 会立即越界或直接进入 final。
第 5 段是源码机制重点:返回值不会立即“赋值给全局变量”,而是转换为 channel writes,由 Pregel 在 super-step 结束时统一合并。
正常路径
user_request ↓planner_node ↓{plan_steps, current_step_index=0} ↓state merge ↓execute_step关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
| 计划为空 | 写入 plan_status=invalid | 路由到 replan 或失败 |
| 计划字段缺失 | validation node 捕获 | 不进入 executor |
| planner 抛异常 | Pregel 节点失败 | 可由 retry/error handler 处理 |
| planner 返回完整 state | 仍按 partial update 合并 | 可能覆盖无关字段,工程上不推荐 |
设计原因与工程影响
Planner 应该只负责计划生成和初始游标,不应该执行工具。否则 Plan-and-Execute 会退化成“一个节点做所有事”,失去可观测、可重试、可重规划的优势。
源码证据
libs/langgraph/langgraph/graph/state.py::StateGraph.add_nodelibs/langgraph/langgraph/pregel/_write.py::ChannelWritehttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/state.py
8.6 execute_step 运行源码解剖
职责与所处阶段
运行时主链。负责读取 current_step_index 指向的步骤,执行该步骤,并写回结果与新的游标。
真实源码签名
def execute_step(state: TravelState) -> dict: ...调用方与被调用方
Pregel runtime ↓execute_step node ↓read state["plan_steps"][current_step_index] ↓call tool / subgraph / LLM ↓return partial update输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | TravelState | 最新计划与游标 |
| 输出 | dict | 步骤状态、结果、游标更新 |
| 状态变化 | plan_steps、step_results、current_step_index | 推进执行进度 |
| 副作用 | 视步骤而定 | 可能调用工具或外部 API |
细粒度伪代码
def execute_step(state): # 1. 读取执行游标 index = state["current_step_index"] steps = state["plan_steps"]
# 2. 边界检查 if index < 0 or index >= len(steps): return { "step_error": "index_out_of_range", "execution_status": "failed", }
# 3. 取出当前步骤 current_step = steps[index]
# 4. 标记 running,避免重复执行时状态不清 updated_steps = copy_steps(steps) updated_steps[index]["status"] = "running"
# 5. 执行当前步骤 try: step_result = run_step_task(current_step, state) updated_steps[index]["status"] = "done" next_index = index + 1 execution_status = "ok" step_error = None except RecoverableStepError as exc: step_result = None updated_steps[index]["status"] = "failed" next_index = index execution_status = "recoverable_failed" step_error = str(exc) except Exception as exc: raise
# 6. 返回 partial update update = { "plan_steps": updated_steps, "current_step_index": next_index, "execution_status": execution_status, "step_error": step_error, }
if step_result is not None: update["step_results"] = state["step_results"] + [step_result]
return update逐段解释
第 1–2 段说明 current_step_index 是 Plan-and-Execute 的运行时游标。它不是为了展示给用户,而是为了让图知道下一次 executor 应执行哪个步骤。
第 5 段分出可恢复错误和不可恢复异常。可恢复错误应写入 state,让 route_next_step 决定是否 replan;不可恢复异常应继续抛出,由 LangGraph runtime、retry policy 或上层系统处理。
第 6 段说明步骤结果、步骤状态和游标必须一起更新。只写 step_results 不写 current_step_index 会导致重复执行同一步;只写 current_step_index 不写结果会丢失执行证据。
正常路径
current_step_index = 0 ↓execute step 1 ↓step_results += result_1 ↓current_step_index = 1关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
| index 越界 | 写入失败状态 | route 到 replan/final/error |
| 步骤执行成功 | status=done,index+1 | 继续下一步 |
| 可恢复失败 | status=failed,index 不变 | route 到 replan |
| 不可恢复异常 | raise | 由 runtime/retry 处理 |
设计原因与工程影响
current_step_index 是执行进度的单一事实来源。不要同时用多个字段表达执行位置,例如 current_step_id、next_step、remaining_steps 混用,否则路由会变得难以维护。
源码证据
libs/langgraph/langgraph/pregel/_loop.pylibs/langgraph/langgraph/pregel/_algo.pyhttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/pregel/_loop.py
8.7 route_next_step 运行源码解剖
职责与所处阶段
运行时主链。负责在每次步骤执行后读取最新 state,并决定进入继续执行、重规划或最终响应。
真实源码签名
用户定义:
def route_next_step(state: TravelState) -> Literal[ "execute_step", "replan", "final_response",]: ...框架注册:
builder.add_conditional_edges("check_step_result", route_next_step)调用方与被调用方
check_step_result / execute_step 完成 ↓BranchSpec.run ↓route_next_step(state) ↓BranchSpec._finish ↓write branch:to:<target>输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | TravelState | 最新合并后的 state |
| 输出 | str / END / list | 后继节点 |
| 状态变化 | 无 | route function 不应修改 state |
| 副作用 | 不建议有 | 应保持纯路由 |
细粒度伪代码
def route_next_step(state): # 1. 如果执行出现可恢复失败,进入 replan if state.get("execution_status") == "recoverable_failed": return "replan"
# 2. 如果计划本身无效,进入 replan if state.get("plan_status") == "invalid": return "replan"
# 3. 如果还有未执行步骤,继续 executor if state["current_step_index"] < len(state["plan_steps"]): return "execute_step"
# 4. 所有步骤完成,进入最终响应 return "final_response"框架侧 branch 伪代码:
def branch_route(input_state): # 1. 调用用户 path function result = route_next_step(input_state)
# 2. 归一化为目标列表 if not isinstance(result, list): destinations = [result] else: destinations = result
# 3. path_map 映射业务标签到节点名 destinations = [path_map.get(d, d) for d in destinations]
# 4. 校验目标 for dest in destinations: if dest is None or dest == START: raise ValueError("Invalid branch destination")
# 5. 写入 branch channel,激活后继节点 return write_to_branch_channels(destinations)逐段解释
第 1–2 段把失败优先级放在继续执行之前。否则步骤失败后 current_step_index 可能没变,图会无限重复执行同一步。
第 3 段是正常循环条件。只要游标还没到计划末尾,就返回 execute_step。
第 4 段不要直接返回 END,而是先进入 final_response。这样可以把所有步骤结果整理成用户可读结果。如果你不需要 final node,也可以让 route 直接返回 END。
正常路径
execute_step done ↓route_next_step ├── current_step_index < len(plan_steps) → execute_step └── current_step_index >= len(plan_steps) → final_response关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
execution_status=ok 且仍有步骤 | 返回 execute_step | 继续循环 |
execution_status=recoverable_failed | 返回 replan | 重新规划 |
current_step_index >= len(plan_steps) | 返回 final_response | 准备结束 |
返回 END | 当前分支终止 | 不再进入 final node |
| 返回多个节点 | 并行分支 | 需 reducer 合并输出 |
设计原因与工程影响
路由函数应该只根据 state 做决策,不应该写 state。这样可以让执行逻辑、校验逻辑、路由逻辑分离,便于测试与复盘。
源码证据
libs/langgraph/langgraph/graph/_branch.py::BranchSpec.runlibs/langgraph/langgraph/graph/_branch.py::BranchSpec._routelibs/langgraph/langgraph/graph/_branch.py::BranchSpec._finishhttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/_branch.py
8.8 final_response 运行源码解剖
职责与所处阶段
运行时主链末尾。负责将执行过程中的结构化结果转换成最终输出字段。
真实源码签名
def final_response(state: TravelState) -> dict: ...调用方与被调用方
route_next_step returns "final_response" ↓Pregel activates final_response node ↓final_response(state) ↓return {"final_answer": ...} ↓edge final_response → END输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | TravelState | 已完成的 plan 和 step_results |
| 输出 | dict | final_answer |
| 状态变化 | final_answer | 写入最终答案 |
| 副作用 | 不建议有 | 应只生成结果 |
细粒度伪代码
def final_response(state): # 1. 读取计划和执行结果 steps = state["plan_steps"] results = state["step_results"]
# 2. 检查是否所有步骤都完成 unfinished = [s for s in steps if s["status"] != "done"] if unfinished: return { "final_answer": "计划未完整执行,无法生成最终行程。", "final_status": "incomplete", }
# 3. 聚合结果 answer = render_final_answer( user_request=state["user_request"], steps=steps, results=results, )
# 4. 写入最终字段 return { "final_answer": answer, "final_status": "done", }逐段解释
第 2 段是防御式校验。即使路由逻辑判断所有步骤已执行,也建议 final node 再做一次状态完整性检查。
第 4 段只写最终输出字段,不应再修改计划和步骤结果。否则 final node 会同时承担“总结”和“修复计划”两个职责。
正常路径
all plan_steps done ↓final_response ↓final_answer ↓END关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
| 所有步骤 done | 生成最终答案 | 正常结束 |
| 有步骤 pending/failed | 写入 incomplete | 可回到 replan 或结束失败 |
| results 为空 | 写入失败说明 | 暴露执行异常 |
设计原因与工程影响
Final node 是输出边界。它应该只负责把 state 转换成用户或 API 需要的最终格式。复杂业务中建议使用结构化输出 schema,例如 FinalTravelPlan,避免从自由文本中再解析。
源码证据
libs/langgraph/langgraph/graph/state.py::CompiledStateGraphlibs/langgraph/langgraph/pregel/main.py::Pregel.invokehttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/pregel/main.py
8.9 运行时主链总结
input TravelState ↓planner_node returns plan partial update ↓state merge ↓execute_step reads current_step_index ↓execute_step returns step result + index update ↓route_next_step reads latest state ↓loop / replan / final ↓final_response writes final_answer9. 关键分支、异常与边界
本章回答:
当计划无效、步骤失败、路由返回多目标、executor 是子图或图循环过长时,LangGraph 如何分流、恢复、终止或失败?
9.1 分支矩阵
| 分支类型 | 触发条件 | 核心函数 | 结果 |
|---|---|---|---|
| 继续执行 | current_step_index < len(plan_steps) | route_next_step | 回到 execute_step |
| 正常完成 | current_step_index >= len(plan_steps) | route_next_step | 进入 final_response |
| 重规划 | execution_status=recoverable_failed | route_next_step | 进入 replan |
| 直接终止 | 路由返回 END | branch _finish | 当前分支停止 |
| 并行执行 | 路由返回多个节点 | branch _finish | 多节点下一 super-step 并行 |
| 框架保护 | 超过 recursion_limit | Pregel loop | 抛出图递归限制错误 |
9.2 同步与异步分支
| 维度 | 同步路径 | 异步路径 |
|---|---|---|
| 入口 | invoke() | ainvoke() |
| 节点函数 | 同步 callable | async callable / runnable |
| 子图 | subgraph.invoke() | await subgraph.ainvoke() |
| 工具调用 | 同步工具 | async 工具 |
| 资源语义 | 简单直观 | 更适合 I/O 密集 executor |
| 降级行为 | 同步节点不能安全取消 | async 节点可配合 timeout |
9.3 Batch、Stream、Parallel 或路由分支
Plan-and-Execute 不是单个对象,而是一种图结构,因此:
Batch 由 CompiledStateGraph / Runnable batch 提供。
Stream 由 CompiledStateGraph.stream 提供,可观察 planner、executor、replan 等节点 update。
Parallel 由图边、conditional edge 多目标返回、Send API 或同一 super-step 多节点激活提供。
路由 由 add_conditional_edges / BranchSpec 提供。如果 route_next_step 返回多个 executor 节点,例如同时执行“查景点”和“查天气”,后续聚合字段必须有 reducer,否则多节点同时写同一个 key 可能冲突。
9.4 异常分类
| 异常类别 | 抛出位置 | 是否可恢复 | 处理策略 | 是否反馈上层 |
|---|---|---|---|---|
| 计划为空 | planner_node / validate_plan | 是 | route 到 replan | 是 |
| 计划字段错误 | validate_plan | 是 | 修复或重规划 | 是 |
| index 越界 | execute_step | 是 | 写入错误状态并 replan | 是 |
| 工具可恢复失败 | execute_step / subgraph | 是 | retry 或 replan | 是 |
| 子图异常 | executor subgraph | 视情况 | 捕获转 state 或向上抛 | 是 |
| 无限循环 | Pregel runtime | 否 | recursion_limit 终止 | 是 |
| 非预期系统异常 | 任意节点 | 否 | 抛出并告警 | 是 |
9.5 异常路径源码解剖
职责与所处阶段
分支与异常阶段。负责把可恢复错误转成 state,让路由处理;把不可恢复错误交给 runtime。
细粒度伪代码
def check_step_result(state): # 1. 读取执行状态 status = state.get("execution_status") error = state.get("step_error")
# 2. 成功路径 if status == "ok": return { "last_step_valid": True, "decision": "continue", }
# 3. 可恢复失败:让 route 进入 replan if status == "recoverable_failed": return { "last_step_valid": False, "decision": "replan", "replan_reason": error, }
# 4. 输入或状态异常:终止或人工接管 if status == "failed": return { "last_step_valid": False, "decision": "abort", "abort_reason": error, }
# 5. 未知状态:不要沉默吞掉 raise ValueError(f"Unknown execution_status: {status}")逐段解释
第 3 段体现 Plan-and-Execute 的核心恢复机制:失败不是立即中止,而是写入 replan_reason,让 route_next_step 进入 replan。
第 5 段不要把未知状态当成功处理。Agent 系统中最危险的 bug 是“失败被伪装成成功”。
9.6 Retry、Fallback 与恢复边界
| 机制 | 适用条件 | 不适用条件 | 幂等要求 |
|---|---|---|---|
| Retry | 查询类工具失败、LLM 输出格式错误 | 写操作、扣费、发邮件 | 必须可重复执行 |
| Fallback | 模型服务失败、检索服务不可用 | 需要强一致业务结果 | fallback 结果要标记来源 |
| Replan | 当前步骤不可完成但目标仍可达 | 目标本身无效 | 需保留失败原因 |
| Repair | 计划格式错误、少字段 | 事实缺失或任务不清 | 不涉及外部副作用 |
| Human Handoff | 高风险动作、连续失败 | 低风险自动查询 | 需要 checkpoint |
9.7 停止条件与保护上限
正常结束:route_next_step 返回 final_response,final_response → END提前结束:route function 直接返回 END人工中断:executor 或 verifier 调用 interrupt,等待 Command(resume=...)框架保护:达到 recursion_limit异常失败:节点抛出未处理异常9.8 能力边界
| 容易误判的能力 | 实际提供者 | 本篇对象的真实职责 |
|---|---|---|
| 自动生成高质量计划 | LLM / planner prompt / structured output | LangGraph 只承载计划状态 |
| 自动执行工具 | ToolNode / BaseTool / 自定义节点 | Executor node 决定如何调用 |
| 自动避免无限循环 | recursion_limit + 路由设计 | route 只返回后继节点 |
| 自动合并并发结果 | Reducer / channel | 节点只返回 update |
| 自动恢复长任务 | Checkpointer | Plan state 只是可保存数据 |
10. 扩展机制与框架协作
本章回答:
Plan-and-Execute 在工程中如何与 subgraph、Command、Send、reducer、checkpoint、tool calling 和 streaming 协作?
10.1 扩展点总览
| 扩展点 | 扩展方式 | 执行时机 | 可修改内容 | 约束 |
|---|---|---|---|---|
| Planner schema | Pydantic / TypedDict | planner 输出 | 计划结构 | 需可序列化 |
| Executor subgraph | compiled graph as node | 执行步骤时 | 内部工具与校验 | 需处理父子 state 映射 |
| Conditional edge | add_conditional_edges | 节点后 | 下一节点 | route 不应有副作用 |
Command | node return | 节点执行后 | update + goto | 更动态但可视化弱 |
Send | branch return | map-reduce 分发 | 并行任务 | 需要 reducer 汇总 |
| Reducer | Annotated | update 合并时 | 状态合并规则 | 需避免非幂等副作用 |
| Checkpointer | compile(checkpointer=...) | super-step 边界 | 保存状态 | 需 thread_id |
| Stream | graph.stream | 运行时 | 观察 updates | 不改变执行语义 |
10.2 Command 作为动态计划跳转源码解剖
职责与所处阶段
扩展阶段。允许节点同时返回 state update 和下一跳目标,减少额外 conditional edge。
真实源码签名
Command( *, graph: str | None = None, update: Any | None = None, resume: dict[str, Any] | Any | None = None, goto: Send | Sequence[Send | N] | N = (),)调用方与被调用方
execute_step returns Command(update=..., goto="replan") ↓Pregel runtime ↓apply update ↓activate goto target输入、输出与状态变化
| 项目 | 类型 | 说明 |
|---|---|---|
| 输入 | state | 当前执行状态 |
| 输出 | Command | update + goto |
| 状态变化 | Command.update | 合并进 state |
| 副作用 | 无额外 | 控制流由 goto 决定 |
细粒度伪代码
def execute_step_with_command(state): index = state["current_step_index"] step = state["plan_steps"][index]
try: result = run_step_task(step, state) update = { "step_results": state["step_results"] + [result], "current_step_index": index + 1, "execution_status": "ok", } goto = "execute_step" if index + 1 < len(state["plan_steps"]) else "final_response" return Command(update=update, goto=goto)
except RecoverableStepError as exc: return Command( update={ "execution_status": "recoverable_failed", "step_error": str(exc), }, goto="replan", )逐段解释
Command 把“状态更新”和“下一跳”合并到同一个节点返回值中。它适合高度动态的流程,但会让控制流分散在节点内部,不如 add_conditional_edges() 清晰。
正常路径
node returns Command(update=..., goto="next") ↓apply update ↓activate next关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
goto 是节点名 | 激活该节点 | 动态跳转 |
goto 是多个目标 | 多节点并行 | 需要 reducer |
goto=END | 终止 | 当前路径结束 |
| update 与 reducer 冲突 | 抛错或按 channel 处理 | 取决于 state key |
设计原因与工程影响
对于 Plan-and-Execute,入门建议优先使用 add_conditional_edges(),因为控制流更可视化;当执行节点内部必须基于工具结果即时跳转时,再使用 Command。
源码证据
libs/langgraph/langgraph/types.py::Commandhttps://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/types.py
10.3 Send 与步骤并行执行源码解剖
职责与所处阶段
扩展阶段。允许一个路由函数返回多个带独立输入的目标,用于 map-reduce 风格并行执行。
细粒度伪代码
def route_parallel_research(state): # 1. 根据计划中的 research 步骤拆分多个子任务 research_tasks = extract_research_tasks(state["plan_steps"])
# 2. 将每个子任务发送给同一个或不同节点 sends = [] for task in research_tasks: sends.append( Send( "research_executor", { "user_request": state["user_request"], "research_task": task, }, ) )
return sends逐段解释
Send 不是简单返回节点名,而是返回“目标节点 + 该目标节点输入”。它适合并行检索、并行工具调用、多子计划执行等场景。
关键分支与异常路径
| 条件 | 行为 | 结果 |
|---|---|---|
多个 Send 写同一个结果 key | reducer 合并 | 适合 research_notes |
| 没有 reducer | 并发写冲突 | 抛出 concurrent update 错误 |
| 子任务很多 | 并行提升吞吐 | 成本和限流压力增加 |
设计原因与工程影响
Plan-and-Execute 中,所有步骤不一定必须串行。信息收集类步骤可以并行,决策和写操作应串行。是否并行不是框架决定,而是业务语义决定。
10.4 与相邻框架组件的协作
| 相邻组件 | 输入协议 | 输出协议 | 协作边界 |
|---|---|---|---|
| Structured Output | 用户请求 / 上下文 | plan_steps | 生成结构化计划,不执行 |
| ToolNode | AIMessage.tool_calls | ToolMessage | 执行工具,不管理计划 |
| Reducer | state updates | merged state | 合并多步骤日志、检索结果 |
| Checkpointer | state snapshots | resumable thread | 保存计划执行进度 |
| Interrupt | pause payload | resume value | 高风险步骤人工确认 |
| Streaming | runtime events | updates / values | 观察计划执行轨迹 |
10.5 公共扩展接口与内部实现
业务代码可以依赖: StateGraph add_node add_edge add_conditional_edges compile Command Send CompiledStateGraph.invoke / stream
业务代码避免依赖: BranchSpec 内部字段 Pregel 私有 loop 状态 channel 内部写入格式 未文档化的 branch channel 名称10.6 自定义扩展示例:旅行规划 executor subgraph
class ExecutorState(TypedDict): current_step: PlanStep tool_results: list[str] step_status: str step_error: str | None
def prepare_step(state: ExecutorState): return {"step_status": "prepared"}
def call_tools(state: ExecutorState): return {"tool_results": [f"工具结果:{state['current_step']['task']}"]}
def verify_step(state: ExecutorState): if not state["tool_results"]: return {"step_status": "failed", "step_error": "empty_tool_results"} return {"step_status": "done"}说明:
- 子图接收
ExecutorState,不直接暴露父图全部 state。 - 子图输出由 wrapper 转换为父图 partial update。
- 子图异常应被捕获并转成父图
execution_status。 - 子图内部可以有自己的 retry、cache、tool node 和 verifier。
- 如果需要恢复子图内部状态,应配合 checkpointer。
10.7 选择扩展还是重写流程
| 条件 | 选择普通节点 | 选择子图 | 选择 Command |
|---|---|---|---|
| 每步只有一个简单函数 | 是 | 否 | 否 |
| 每步需要多个内部阶段 | 否 | 是 | 视情况 |
| 需要独立测试 executor | 否 | 是 | 否 |
| 路由逻辑清晰固定 | 是 | 是 | 否 |
| 跳转高度动态 | 否 | 视情况 | 是 |
| 需要图可视化清晰 | 是 | 是 | 不优先 |
11. 工程决策与适用场景
11.1 适用场景
| 场景 | 是否推荐 | 原因 |
|---|---|---|
| 旅行规划 | 是 | 先规划、再检索、再合成,非常适合 Plan-and-Execute |
| 报告生成 | 是 | 可拆分研究、写作、审稿步骤 |
| 订单履约 | 是,但需 Workflow 约束 | 高风险动作需要 verifier 和 HITL |
| 简单 FAQ | 否 | 过度复杂,普通 RAG 即可 |
| 开放聊天 | 否 | 计划步骤不稳定,成本高 |
| 代码修复 | 是 | 定位、修改、测试、验证天然多步 |
11.2 工程决策表
| 决策点 | 推荐选择 | 前提 | 风险 |
|---|---|---|---|
| 计划存哪里 | state["plan_steps"] | 多节点需要共享 | schema 太松会难校验 |
| 进度怎么存 | current_step_index | 串行计划 | 并行计划需改为 task queue |
| 步骤结果怎么存 | step_results + reducer 或覆盖列表 | 需要复盘 | 结果过大会污染上下文 |
| replan 怎么做 | 保留失败原因,生成新 plan version | 需要可追溯 | 直接覆盖会丢历史 |
| executor 形式 | 简单用 node,复杂用 subgraph | 看步骤复杂度 | 过早子图化增加成本 |
| 终止保护 | recursion_limit + route 条件 | 存在循环 | 设置过低会误杀长任务 |
11.3 性能、可靠性与安全边界
性能: 串行步骤会增加延迟;可并行的信息收集步骤可用 Send 或多分支,但需要 reducer。
可靠性: Planner 可能生成不可执行计划;必须有 validate_plan 和 step verifier。
安全: 写操作步骤必须经过权限校验、幂等控制和人工确认。
可观测性: 必须记录 plan version、current_step_index、step status、tool result、replan reason。12. 常见误区与源码纠正
12.1 误区:Plan-and-Execute 是 LangGraph 内置类
错误原因:很多教程把范式和框架 API 混在一起,好像存在一个 PlanAndExecuteAgent。
源码事实:
LangGraph 提供 StateGraph、Node、Edge、Branch、Reducer、Subgraph。Plan-and-Execute 是你用这些基础抽象组合出来的图结构。工程影响:
如果以为框架自动管理计划质量,就会忽略 validate_plan、step verifier 和 replan。
12.2 误区:计划列表可以放在局部变量里
错误原因:初学者会把计划当成函数内部临时变量。
源码事实:
节点之间只通过 state 共享信息。node 返回 partial update,LangGraph 合并到 state channels。工程影响:
计划不进 state,executor、verifier、router 都无法稳定读取计划。
12.3 误区:current_step_index 只是展示字段
错误原因:看起来只是一个整数。
源码事实:
route_next_step 和 execute_step 都依赖 current_step_index 控制循环。它是执行游标,不是 UI 字段。工程影响:
游标更新错误会导致重复执行、跳步或提前结束。
12.4 误区:每一步执行都应该直接调用工具
错误原因:把 executor 等同于 tool call。
源码事实:
executor 可以是普通节点、ToolNode、Runnable、CompiledStateGraph 或 wrapper 函数。工程影响:
复杂步骤直接堆在一个函数里会难以测试、难以恢复,也难以观测。
12.5 误区:replan 就是覆盖旧计划
错误原因:只关注最终能不能完成。
源码事实:
state update 是按 key 合并或覆盖。你可以覆盖 plan_steps,也可以追加 plan_versions。这是 schema 与 reducer 的设计问题。工程影响:
直接覆盖会丢失失败原因和历史计划,不利于评估与排查。
12.6 误区:conditional edge 可以顺便改状态
错误原因:路由函数拿到了 state,容易顺手修改。
源码事实:
path function 的职责是返回后继节点,不是返回 partial update。状态更新应由节点或 Command 完成。工程影响:
在路由函数里做副作用会让执行不可预测,也不利于 checkpoint 恢复。
12.7 误区:循环一定会自动结束
错误原因:只写了 route_next_step 的正常条件。
源码事实:
LangGraph 会按照条件边继续调度,直到 END 或无活跃节点。如果路由一直返回 execute_step,就会依赖 recursion_limit 保护。工程影响:
必须显式设计结束条件,并在 config 中设置合理的 recursion_limit。
13. 最终心智模型与掌握检查
13.1 构建期心智模型
TravelState schema ↓StateGraph builder ↓add_node 注册 planner / executor / verifier / replan / final ↓add_edge 注册固定顺序 ↓add_conditional_edges 注册步骤路由 ↓compile 生成 CompiledStateGraph13.2 运行时心智模型
initial state ↓planner_node 写入 plan_steps ↓execute_step 读取 current_step_index ↓执行一个 step ↓写入 step result + index update ↓route_next_step 决定继续 / replan / final ↓END13.3 分支与异常心智模型
正常路径 → execute_step 循环直到 plan_steps 全部 done分支路径 → recoverable failure 进入 replan可恢复异常 → 写入 execution_status / step_error,由 route 处理不可恢复异常 → 抛给 Pregel runtime / retry / 上层系统保护上限 → recursion_limit 防止无限循环13.4 一句话总结
Plan-and-Execute 在 LangGraph 中不是一个独立 Agent 类,而是通过
StateGraph构建计划状态、执行节点、校验节点、条件路由和可选子图来表达;运行时每个节点返回 partial state update,Pregel 调度器按边和 super-step 推进状态,条件边根据最新 state 决定继续执行、重规划或结束。
13.5 掌握检查
- 能说清
planner_node和execute_step的职责边界。 - 能说明
plan_steps为什么必须写入 state。 - 能说明
current_step_index如何驱动循环。 - 能画出
planner → executor → verifier → route → executor/final/replan图结构。 - 能解释节点返回 partial update 后如何进入 state。
- 能说明什么时候 executor 应该使用 subgraph。
- 能说明 replan 覆盖和追加的差异。
- 能说明正常结束和 recursion_limit 保护性终止的区别。
- 能区分
add_conditional_edges与Command(goto=...)的使用边界。 - 能为旅行规划助手设计一套可回放的 plan state。
14. 参考资料与下一篇衔接
14.1 官方概念文档
-
LangGraph Graph API Overview
https://docs.langchain.com/oss/python/langgraph/graph-api -
LangGraph Use the Graph API
https://docs.langchain.com/oss/python/langgraph/use-graph-api -
LangGraph Subgraphs
https://docs.langchain.com/oss/python/langgraph/use-subgraphs -
LangGraph Streaming
https://docs.langchain.com/oss/python/langgraph/streaming -
LangGraph PyPI
https://pypi.org/project/langgraph/
14.2 官方 API Reference
-
StateGraph
https://reference.langchain.com/python/langgraph/graph/state/StateGraph -
StateGraph.add_node
https://reference.langchain.com/python/langgraph/graph/state/StateGraph/add_node -
StateGraph.add_conditional_edges
https://reference.langchain.com/python/langgraph/graph/state/StateGraph/add_conditional_edges -
StateGraph.compile
https://reference.langchain.com/python/langgraph/graph/state/StateGraph/compile -
Command
https://reference.langchain.com/python/langgraph/types/Command
14.3 官方源码
-
langgraph/graph/state.py::StateGraph
https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/state.py -
langgraph/graph/_branch.py::BranchSpec
https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/graph/_branch.py -
langgraph/pregel/main.py::Pregel
https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/pregel/main.py -
langgraph/pregel/_loop.py
https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/pregel/_loop.py -
langgraph/types.py::Command
https://github.com/langchain-ai/langgraph/blob/5931a5f/libs/langgraph/langgraph/types.py
14.4 下一篇衔接
下一篇进入:
第 10 篇:Checkpoint / Thread / Durable Execution 源码解剖需要继续回答:
thread_id 如何定位一条执行线?checkpoint 在哪个 super-step 边界保存?StateSnapshot 如何记录 values、next、tasks 与 metadata?get_state / get_state_history 如何读取执行历史?Plan-and-Execute 长任务如何在失败或人工确认后恢复?