ARTICLE · 1179190
【LangGraph源码精读】第01期|LangGraph的心脏是Google的Pregel?BSP模型到底是什么 ?
【LangGraph源码精读】第01期|LangGraph的心脏是Google的Pregel?BSP模型到底是什么?
30篇源码精读系列·第01篇(执行引擎层第一篇)。你用LangGraph写了好几个Agent,每次调用app.invoke()就跑起来了。但你有没有想过:LangGraph内部到底用什么算法在调度节点?为什么多个节点能并行执行还不冲突?为什么每个节点执行完都能自动保存状态?答案是Pregel——一个来自Google 2010年论文的图计算模型。这篇从论文到源码,把Pregel BSP模型讲透。
一、开头:你每天都在用Pregel,但可能从来没听说过它
每次你调用app.invoke(),LangGraph内部都在跑一个叫Pregel的执行引擎。
你可能从来没在文档里看到过"Pregel"这个词——它被藏在CompiledStateGraph的父类里,被藏在invoke()方法的深处。但它是LangGraph的心脏,没有它,你写的StateGraph只是一张不能执行的蓝图。
三个问题,先自测一下:
- ● LangGraph为什么叫"Graph"?它和普通的DAG工作流有什么本质区别?
- ● 两个节点并行执行,同时修改同一个状态字段,为什么不会数据冲突?
- ● 为什么LangGraph能支持循环(ReAct的思考-行动-观察循环),而大多数工作流框架不支持?
这三个问题的答案,都在Pregel里。
这篇是30篇源码精读的第01篇,也是执行引擎层的第一篇。 搞懂了Pregel,后面的Channel、Checkpoint、并发、重试就都通了。搞不懂Pregel,后面的源码只会越看越懵。
二、Pregel是什么?从Google论文到LangGraph
2.1 Google Pregel:2010年的图计算框架
Pregel是Google在2010年发表的论文《Pregel: A System for Large-Scale Graph Processing》中提出的大规模图计算框架。最初用于PageRank、最短路径、社区发现等图算法。
Pregel的核心思想是BSP(Bulk Synchronous Parallel,整体同步并行) 计算模型。简单说:把计算分成一轮一轮的"超级步"(Super-step),每一轮里所有顶点并行计算,计算完后统一同步,再进入下一轮。
LangGraph的Pregel是Google Pregel的"Agent编排版"——把顶点换成了节点函数,把消息换成了Channel写入,把图算法换成了Agent工作流。但核心的BSP计算模型完全一致。
2.2 为什么Agent编排需要图计算模型?
你可能会问:Agent编排不就是"步骤A→步骤B→步骤C"吗?用个简单的循环不就行了,为什么要搬一个图计算模型过来?
因为Agent不是线性的,它是一张有环的图。
ReAct Agent的"思考→行动→观察→思考→行动→观察..."就是一个典型的环。普通的DAG执行器不支持环,而Pregel的Super-step天然就是一个循环——每一轮Super-step都可能触发下一轮,直到没有新任务为止。
这就是LangGraph和其他Agent框架最本质的区别:它把Agent编排建模为图计算问题,用Pregel BSP模型来解。
三、为什么选Pregel?三个天然优势
LangGraph选择Pregel不是偶然,而是因为Pregel模型天然解决了Agent编排的三个核心难题。
| 天然支持并行 | ||
| 天然支持Checkpoint | ||
| 天然支持循环 |
这三个优势是其他模型很难同时具备的。
比如Actor模型支持并行和循环,但不支持全局一致的Checkpoint(因为Actor之间是异步消息,没有全局同步点)。比如简单的状态机支持循环和Checkpoint,但不支持并行。只有BSP模型三者兼备。
代价是什么? BSP模型有同步屏障(Barrier)——快的节点必须等慢的节点执行完,才能进入Update阶段。如果一个Super-step里有一个特别慢的节点(比如调用大模型),其他节点都得等它。这是BSP模型的固有缺点,LangGraph通过并发执行和超时控制来缓解。
四、BSP三阶段:Plan → Execute → Update
每个Super-step分为三个阶段,严格按顺序执行,阶段之间有同步屏障。这是Pregel最核心的机制。
4.1 三阶段总览
| Plan(计划) | |||
| Execute(执行) | |||
| Update(更新) |
4.2 Plan阶段:谁该干活?
Plan阶段是每个Super-step的起点。它回答一个问题:这一轮哪些节点该执行?
判断逻辑很简单:
- 1. 检查所有Channel的版本号,找出上一个Super-step中被更新的Channel
- 2. 通过trigger_to_nodes映射表,找到订阅这些Channel的节点
- 3. 为每个节点构造一个PregelExecutableTask(包含节点名、输入快照、执行函数、写入缓冲区、配置)
关键理解:节点不是被"调用"的,而是被"触发"的。 你在StateGraph里定义的边(A→B),编译后变成了"B订阅A写入的Channel"这样的订阅关系。A执行完写入Channel,Channel版本号变化,下一个Super-step的Plan阶段就会触发B。
这就是为什么LangGraph支持条件边——条件边本质上是"根据当前状态决定写入哪个Channel",不同的Channel触发不同的节点。
4.3 Execute阶段:各干各的,互不干扰
Execute阶段是并行执行的核心。所有待执行节点同时运行,但有一个关键约束:每个节点拿到的是Super-step开始时的状态快照,不是实时状态。
| 输入冻结 | ||
| 输出缓冲 | ||
| 状态副本隔离 |
这就是并行不冲突的秘密:不是加锁,而是隔离。 每个节点在自己的副本上干活,干完把结果交出来,由Update阶段统一合并。不需要锁,不需要事务,天然线程安全。
4.4 Update阶段:统一合并,确定顺序
Update阶段是三个阶段中最关键的。所有节点执行完后,Pregel按确定的顺序把缓冲区的写入合并到Channel。
确定顺序是结果可复现的保证。 同样的输入,不管节点执行的先后顺序如何(因为并行),Update阶段总是按节点名排序合并,所以最终状态永远一样。这就是LangGraph能做到"确定性执行"的原因。
冲突检测是安全网。 如果两个节点在同一个Super-step里都写了同一个LastValue类型的Channel(覆盖语义),而且没有reducer,Pregel会直接抛InvalidUpdateError,而不是静默覆盖其中一个。这逼着你在设计状态时就考虑好并行写入的合并策略。
五、Pregel源码文件地图:9个核心文件
Pregel执行引擎的源码在libs/langgraph/langgraph/pregel/目录下,共9个核心文件。搞清楚每个文件的职责,读源码就有了地图。
| main.py | |||
| _loop.py | |||
| _algo.py | |||
| _runner.py | |||
| _checkpoint.py | |||
| _call.py | |||
| _write.py | |||
| _read.py | |||
| _retry.py |
阅读顺序建议: 先读main.py了解Pregel类的结构,再读_loop.py理解Super-step循环,然后读_algo.py看Plan和Update的具体算法,最后读_runner.py和_checkpoint.py。_call/_write/_read/_retry是细节,可以用到再看。
六、PregelLoop:Super-step的调度器
PregelLoop是BSP循环的核心实现。它有两个子类,分别对应同步和异步执行:
| SyncPregelLoop | ||
| AsyncPregelLoop |
PregelLoop内部追踪的关键状态:
recursion_limit默认25是什么意思? 它限制的是Super-step的数量,不是节点执行次数。对于一个ReAct Agent,每一轮"思考→行动"是2个Super-step,所以默认25意味着最多12轮思考-行动循环。超过就抛GraphRecursionError。这是防止Agent死循环的安全机制。
七、一次Super-step的完整执行(7步)
以一个简单的并行图为例(A和B并行执行,然后C执行),追踪一个Super-step的完整过程:
注意第4步的Barrier(同步屏障)。 这是BSP模型的标志性特征——所有节点必须都执行完,才能进入Update。如果A是个快速的本地计算,B是个慢速的大模型调用,A必须等B。这就是BSP模型的代价。
八、终止条件:什么时候停下来?
PregelLoop在以下4种情况下终止:
| 没有新任务 | ||
| 达到recursion_limit | ||
| 遇到interrupt | ||
| 节点抛出异常 |
最常见的终止是"没有新任务"。 注意不是"到达END节点"——END是一个虚拟节点,它不订阅任何Channel,也不执行任何逻辑。到达END意味着最后一个真实节点的输出没有触发任何新节点,Plan阶段发现没有待执行任务,自然终止。
九、设计权衡:为什么BSP而不是Actor模型?
这是一个值得深入思考的问题。Actor模型(比如Akka、Orleans)也支持并行和消息传递,为什么LangGraph选择了BSP而不是Actor?
LangGraph选择BSP的核心原因:Agent编排需要确定性和可恢复性。
Agent的执行过程需要可预测(同样的输入产生同样的输出)、可中断(随时暂停恢复)、可回溯(回到任意步骤重跑)。这些需求只有BSP模型能满足。Actor模型虽然性能更高,但缺乏全局一致性,做不到这些。
这是一个典型的"用性能换确定性"的设计决策。 对于Agent编排来说,确定性比极致性能更重要——Agent的瓶颈通常在大模型调用(秒级),而不是执行引擎的同步屏障(毫秒级)。
十、面试关联:Pregel相关的5道高频题
这5道题能答上来,Pregel层面的面试就稳了。 后面的源码精读会把每道题的答案深入到具体代码行。
十一、本期小结
Pregel执行引擎,核心就6句话:
- 1. Pregel是LangGraph的心脏,基于Google 2010年论文的BSP模型。 LangGraph把Agent编排建模为图计算问题,把顶点换成节点,把消息换成Channel写入,核心计算模型完全一致。
- 2. 每个Super-step分三阶段:Plan→Execute→Update。 Plan确定待执行节点(Channel更新触发),Execute并行执行(状态副本隔离、输出缓冲),Update按确定序合并(无冲突、可复现)。三阶段之间有同步屏障。
- 3. 并行不冲突的秘密是隔离,不是加锁。 Execute阶段每个节点有独立状态副本,输出先缓冲;Update阶段所有节点执行完后统一按确定序合并。不需要锁,不需要事务,天然线程安全。
- 4. Pregel天然支持三个Agent核心需求:并行、Checkpoint、循环。 并行来自Execute阶段的并发执行;Checkpoint来自Super-step边界的全局一致状态;循环来自Super-step本身就是一个循环(有新任务就继续)。这是其他模型很难同时具备的。
- 5. 源码9个核心文件,最核心的是_loop.py(PregelLoop)和_algo.py(prepare_next_tasks/apply_writes)。 PregelLoop有Sync和Async两个子类,分别对应invoke和ainvoke。recursion_limit默认25,限制的是Super-step数量。
- 6. 选择BSP而不是Actor,是"用性能换确定性"的设计决策。 Agent编排需要可预测、可中断、可回溯,这些需求只有BSP的全局一致性能满足。Agent的瓶颈在大模型调用,不在执行引擎的同步屏障。
十二、下一期预告
下一期(源码02),执行引擎层第二篇,深入节点调度的源码:
【LangGraph源码精读】第02期|并行执行不冲突?状态副本+确定序合并太巧妙了
从prepare_next_tasks()到apply_writes(),逐行拆解节点调度和状态合并的源码实现,搞懂PregelLoop是怎么把三阶段跑起来的。
你在使用LangGraph时遇到过并行执行的问题吗?评论区聊聊,我优先写你最关心的场景。
本文为《LangGraph源码精读30讲》系列第01篇。内容基于LangGraph v1.2.11源码结构梳理,核心文件位于libs/langgraph/langgraph/pregel/目录。版本更新后部分细节可能变化。如有错漏欢迎指正。















































































