⚙️ Pregel 引擎概念

这是 LangGraph 的"黑盒揭秘"章。前三章你学会了怎么用 StateGraph,本章看它底层是什么——StateGraph 编译后变成 Pregel 引擎,用 BSP 三阶段循环执行。我们用图解讲清概念(按你的选择,讲到入门级,不逐行精读算法源码)。

本章目标

  • 理解 StateGraph 编译后变成什么(CompiledStateGraph 继承 Pregel)
  • 掌握 Pregel 的 BSP 三阶段模型(Plan → Execute → Update)
  • 理解 Actor(PregelNode)和 Channel(通道)的角色
  • 明白"声明式图"如何降级为"通道读写",打通 LG2 的 Channel 伏笔
  • 知道想深入引擎该读哪些源码文件(进阶指引)
📚 本章定位:概念入门

按学习计划,引擎部分讲到"概念到入门级"。我们会用图解讲清BSP 模型和 Channel 机制,但不逐行精读 _algo.py/_loop.py。章末给出进阶阅读指引,供日后深入。

StateGraph 编译后变成什么

LG1 我们说"compile 后才能执行"。那 compile 到底做了什么?看源码:

📄 langgraph/graph/state.py:1391 · CompiledStateGraph 继承 Pregel python
class CompiledStateGraph(Pregel):
    """StateGraph 编译后的产物。它继承自 Pregel!

    也就是说:你写的 StateGraph(声明式图)
    编译后变成了一个 Pregel 实例(执行引擎)。

    attach_node / attach_edge 方法把"图的节点和边"
    翻译成"Pregel 的 actor + channel 订阅"。
    """
    ...
⚠️ 关键洞察

CompiledStateGraph 继承自 Pregel!这意味着你写的每一个 StateGraph,本质都是一个 Pregel 实例compile() 做的事,就是把"人能看懂的节点和边"翻译成"Pregel 能执行的 actor 和 channel"。这一层翻译就是 LG1-LG4 所有便利的代价/来源。

StateGraph add_node / add_edge (声明式,人看) .compile() attach_node attach_edge CompiledStateGraph(Pregel) actors + channels (机器执行)
图 LG5.1 · compile() 把声明式图翻译成 Pregel 的 actor + channel

Actor 与 Channel:Pregel 的两大原语

Pregel 源自 Google 的图计算论文,基于 BSP(Bulk Synchronous Parallel,批同步并行)模型。它的世界由两类东西组成:

📄 langgraph/pregel/main.py:449 · Pregel 官方定义(节选) python
class Pregel(PregelProtocol):
    """Pregel 管理 LangGraph 应用的运行时行为。

    ## 概览
    Pregel 把【actor】和【channel】组合成一个应用。
    - Actor 从 channel 读数据,向 channel 写数据
    - 执行被组织成多个 step,遵循【Pregel 算法 / BSP 模型】

    每个 step 包含三个阶段:
    - Plan  :决定本步执行哪些 actor
    - Execute:并行执行选中的 actor(本步写入对其他 actor 不可见!)
    - Update:用 actor 写入的值更新 channel

    重复,直到没有 actor 要执行,或达到最大步数。

    ## Actor
    一个 actor 就是一个 PregelNode。
    它订阅 channel、读 channel、写 channel。

    ## Channel
    Channel 用于 actor 间通信。每个 channel 有:
    - 值类型 / 更新类型 / 更新函数(update)
    - update 接收一系列更新,修改存储的值
    """

对应回你熟悉的 StateGraph 概念

StateGraph(高层)Pregel(底层)说明
节点(node 函数)Actor(PregelNode)每个 add_node 的函数 → 一个 actor
状态字段(State 的 key)Channel(通道)每个状态字段 → 一个 channel(LG2 伏笔回收)
reducerChannel 的 update 函数add_messages → BinaryOperatorAggregate 通道
边 / 条件边channel 订阅关系边变成"actor 订阅哪些 channel"
invoke 一次多个 BSP step一次调用 = 引擎跑若干步

BSP 三阶段模型 ★

这是本章的核心。Pregel 的每个 step("super-step")严格经历三个阶段。注意它和 F3 章讲的三阶段是同一回事——因为 LangGraph 的循环就是 Pregel 的循环:

invoke 输入 每个 super-step(循环直到无 actor) ① Plan 规划 决定本步执行哪些 actor (订阅了"刚更新"channel 的) _algo.py: prepare_next_tasks ② Execute 执行 并行执行所有选中 actor ⚠️ 本步写入对他人不可见 _runner.py: PregelRunner ③ Update 更新 用 actor 写入更新 channel (调 reducer 聚合) _algo.py: apply_writes
图 LG5.2 · ★ BSP 三阶段:Plan → Execute → Update,循环直到无 actor(和 F3 章是同一个循环!)
⚠️ 最关键的理解:Execute 阶段的"不可见性"

Execute 阶段所有 actor 并行执行,但本步的写入对其他 actor 不可见——只有 Update 阶段把值写进 channel 后,下一步的 actor 才能看到。这叫批同步(Bulk Synchronous)。这就是为什么多个节点并行写同一字段时,reducer 能正确聚合——它们看到的是同一步开始时的快照,不会互相干扰。

用例子理解 BSP

假设图是 START → A → B → END(A、B 顺序执行):

📄 一次 invoke 的 BSP 执行轨迹
invoke({"x": 1})

【super-step 1】
  Plan  : START 更新了 input channel → 选中订阅它的 actor A
  Execute: 执行 A(读 x=1,算出新值)  ← A 写的值此刻 B 看不到
  Update : 把 A 的写入应用到 channel(如 x=2)

【super-step 2】
  Plan  : x channel 更新了 → 选中订阅它的 actor B
  Execute: 执行 B(读 x=2,算出新值)   ← 现在 B 能看到 A 的结果了
  Update : 把 B 的写入应用

【super-step 3】
  Plan  : 没有订阅者要执行了 → 循环结束
  返回最终 channel 值

如果是并行图(A、B 同时跑),它们会在同一个 super-step 的 Execute 阶段并发,但都基于同一步开始时的快照读写,互不干扰,Update 阶段用 reducer 合并。这就是 LangGraph 并行安全的基础。

Channel 类型

LG2 我们说"reducer 底层是 Channel"。现在看具体对应(源码在 channels/):

Channel 类型源码用途对应 StateGraph
LastValuelast_value.py:20存最后一个值(每步最多一个写)无 reducer 的字段(默认覆盖)
BinaryOperatorAggregatebinop.py用二元运算符累积Annotated[list, operator.add]
Topictopic.pyPubSub 主题,多值累积Send 扇出的目标
EphemeralValueephemeral_value.py临时值(每步后重置)边/分支信号通道
NamedBarrierValuenamed_barrier_value.py屏障,等所有上游完成多入边的 join 语义
💡 回收 LG2 的伏笔

LG2 说"Annotated[list, add_messages] 编译成 BinaryOperatorAggregate 通道"。现在你明白了吗:compile() 时,每个状态字段的 Annotated[type, reducer] 被翻译成对应的 Channel;add_messages 这种 reducer 就是 Channel 的 update() 函数。你写的 reducer = Channel 的更新逻辑

与 Checkpoint 的关系

Pregel 的 Update 阶段还会持久化 channel 快照——这就是 Checkpoint(LG6 详解)。每个 super-step 结束,所有 channel 的值被存盘。这让 Agent 能中断后恢复(从某个 checkpoint 续跑)。LG6 会讲透。

进阶阅读指引

🧭 想深入引擎,按顺序读这些

如果你日后想彻底搞懂 Pregel 内部,按这个顺序读源码:

  1. pregel/main.py:449 Pregel 类文档(三阶段模型,最权威)
  2. pregel/_algo.py:232 apply_writes(Update 阶段如何写 channel)
  3. pregel/_algo.py:371 prepare_next_tasks(Plan 阶段如何选 actor)
  4. pregel/_loop.py:592 tick(一个 super-step 的循环控制)
  5. pregel/_runner.py:135 PregelRunner(Execute 阶段并发执行)
  6. channels/base.py:19 BaseChannel(通道抽象)

这个顺序从"是什么"到"怎么跑",循序渐进。对日常使用 Agent 而言,理解到本章的概念级就够了,不必深入。

与生产实践对照

Pregel 概念OpenCode 对应
BSP 三阶段循环runLoop 的 while(true)(OC2 精读)
Actor(PregelNode)runLoop 内每步的处理逻辑(调LLM/执行工具)
Channel 通道状态Session DB 持久化的消息/Part
Execute 并行不可见性无(OpenCode 单步串行,靠 status 状态机)
Update + Checkpoint每步事件落库(事件溯源 EventV2Bridge)
super-step 概念runLoop 的一次 while 迭代
💡 为什么 OpenCode 不用 Pregel

OpenCode 是单 Agent 串行循环(一个会话一个 runLoop),不需要 Pregel 的"多 actor 并行 + 批同步"。它的状态管理更简单(直接存 DB),循环也更直接(while + status)。Pregel 的复杂度是为"图并行执行"服务的,而 OpenCode 这类编码 Agent 不需要。理解这点,你就知道何时该用 LangGraph、何时该用更简单的循环。

小结

下一章

引擎机制搞清楚了,回到实用功能。下一章 LG6 · 检查点与人在回路——Pregel 的 Update 阶段会存 Checkpoint,这让 Agent 能中断后恢复。我们学习 interrupt(中断)+ Command(恢复),实现"执行前需人工批准"的 Agent。