- 发布日期
第 01 讲 · 课程总览与 LangGraph 设计哲学
理解 LangGraph 核心设计哲学:BSP 超步模型、Actor/Channel 通信、为何不用纯 DAG 框架
学习目标
- 理解 LangGraph 要解决的核心问题:有状态、可中断、可恢复的多步/多 Agent 编排。
- 建立 BSP(Bulk Synchronous Parallel)/ Pregel 超步模型 的心智模型。
- 理解为什么 LangGraph 选择 Actor + Channel 而不是纯 DAG。
- 知道整门课会拆解哪些机制、以什么顺序。
为什么不是"又一个 DAG 框架"
很多编排框架(包括 LCEL 的 RunnableSequence)本质是 DAG:节点是纯函数,边是数据依赖, 一次执行从入到出跑完即结束。这类模型在以下需求面前会很别扭:
- 循环:Agent 要"思考 → 调用工具 → 再思考",需要回边。DAG 不允许环。
- 持久状态:多轮对话、长任务要在中途存档、断电后续跑。DAG 没有"状态"概念。
- 人在回路(HITL):执行到一半暂停,等人审批再继续。DAG 跑完才返回。
- 动态并行:运行时才知道要 fan-out 多少个子任务(如对 N 篇文档分别处理)。
LangGraph 的回答是把执行模型换成 Pregel/BSP——一个源自 Google Pregel 图计算系统的并行模型。
核心模型:BSP 超步
把一次运行切成一系列 超步(superstep)。每个超步分三个阶段:
flowchart LR
subgraph 超步N
P[Plan 规划<br/>谁该跑?] --> E[Execute 执行<br/>并行跑节点]
E --> U[Update 更新<br/>合并写入通道]
end
U -->|有新任务| P2[超步 N+1]
- Plan(规划):根据通道版本变化,决定本超步要执行哪些节点(task)。
- Execute(执行):被选中的节点并行运行。关键约束:它们的写操作不会立刻改通道, 只写进各自的缓冲区。
- Update(更新):超步结束时,把所有缓冲的写批量合并进通道,并 bump 版本号。
由此得到 BSP 的黄金法则(源码注释原文,见 pregel/main.py):
channel updates from step N are only visible in step N+1 channels are guaranteed to be immutable for the duration of the step
也就是:第 N 步的写,第 N+1 步才能读到;步内通道是不可变的。 这条规则消除了大量 并发竞态——同一超步里多个节点并行,谁也看不到对方"半成品"的写。
课程会在第 10/11 讲对照源码里的
tick()/runner.tick()/after_tick()三个函数, 把这三阶段坐实到代码。
Actor 模型 + Channel:为什么不直接传参
LangGraph 的节点更像 Actor:每个节点
- 订阅(subscribe) 一组通道作为触发条件;
- 从通道 读 输入;
- 向通道 写 输出。
节点之间从不直接调用对方,全部通过通道间接通信。这带来三个好处:
- 解耦:节点不需要知道下游是谁,只管写通道;谁订阅了就会在下一步被触发。
- 可持久化:状态全在通道里,把通道快照下来就是一个
Checkpoint,天然支持续跑。 - 可并行:同一超步内互不依赖的节点可以并行,因为它们读的是同一份不可变快照。
"通道 + 版本号 + 触发"这套机制是全课的地基,模块二会专门讲。
一次 run 的全景时序(先混个眼熟)
sequenceDiagram
participant U as 用户
participant P as Pregel
participant L as PregelLoop
participant R as PregelRunner
participant C as Checkpointer
U->>P: invoke / stream(input)
P->>L: __enter__ 加载 checkpoint → 重建 channels
L->>L: _first 写入 input
loop 每个超步
L->>L: tick() Plan: prepare_next_tasks
L->>R: runner.tick() Execute: 并行跑节点
R->>L: commit: 写入 task.writes (缓冲)
L->>L: after_tick() Update: apply_writes
L->>C: 保存 checkpoint
end
P->>U: 返回最终 state / 流式 chunk
后续模块会把这张图里每个箭头都拆开看。
使用场景:什么时候该上 LangGraph
| 需求 | 适合 LangGraph? | 说明 |
|---|---|---|
| 一条线性的 prompt → LLM → parse | 否(用 LCEL 更轻) | 没有环、没有状态 |
| ReAct/工具循环 Agent | 是 | 需要回边 + 状态 |
| 多轮对话、要记忆 | 是 | 需要 checkpoint 持久化 |
| 长任务、要断点续跑 | 是 | checkpoint + resume |
| 审批/人审中断 | 是 | interrupt + Command(resume) |
| 多 Agent 协作 | 是 | 子图 + 动态路由 |
| 对 N 个输入动态并行 | 是 | Send map-reduce |
一句话:只要有"循环、状态、中断、动态并行"中的任意一个,就值得用 LangGraph。
动手实验
写一个最小可循环的图,感受"超步":
from typing import Annotated
from typing_extensions import TypedDict
import operator
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
count: Annotated[int, operator.add] # reducer:累加
def inc(state: State):
print(f" 执行节点, 当前 count={state['count']}")
return {"count": 1}
def route(state: State):
return END if state["count"] >= 3 else "inc"
g = StateGraph(State)
g.add_node("inc", inc)
g.add_edge(START, "inc")
g.add_conditional_edges("inc", route)
app = g.compile()
print(app.invoke({"count": 0}))
观察:节点被调用 3 次(3 个超步),每次只看到"上一步合并后"的 count,体会步间可见性。
阅读作业
- 通读
libs/langgraph/langgraph/pregel/main.py中Pregel类的 docstring(约 449–476 行), 找出它对 Plan / Execute / Update 的描述。 - 在
README.md与libs/langgraph/README.md里找 LangGraph 对自己的定位描述。
小结
- LangGraph = BSP 超步执行 + Actor/Channel 通信 + Checkpoint 持久化。
- 黄金法则:步内不可变,步间才可见。
- 这三件事撑起了循环、状态、中断、动态并行四大能力。
下一讲:搭好调试环境,画出整个仓库的源码地图。