发布日期

第 01 讲 · 课程总览与 LangGraph 设计哲学

理解 LangGraph 核心设计哲学:BSP 超步模型、Actor/Channel 通信、为何不用纯 DAG 框架

学习目标

  • 理解 LangGraph 要解决的核心问题:有状态、可中断、可恢复的多步/多 Agent 编排
  • 建立 BSP(Bulk Synchronous Parallel)/ Pregel 超步模型 的心智模型。
  • 理解为什么 LangGraph 选择 Actor + Channel 而不是纯 DAG。
  • 知道整门课会拆解哪些机制、以什么顺序。

为什么不是"又一个 DAG 框架"

很多编排框架(包括 LCEL 的 RunnableSequence)本质是 DAG:节点是纯函数,边是数据依赖, 一次执行从入到出跑完即结束。这类模型在以下需求面前会很别扭:

  1. 循环:Agent 要"思考 → 调用工具 → 再思考",需要回边。DAG 不允许环。
  2. 持久状态:多轮对话、长任务要在中途存档、断电后续跑。DAG 没有"状态"概念。
  3. 人在回路(HITL):执行到一半暂停,等人审批再继续。DAG 跑完才返回。
  4. 动态并行:运行时才知道要 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) 一组通道作为触发条件;
  • 从通道 输入;
  • 向通道 输出。

节点之间从不直接调用对方,全部通过通道间接通信。这带来三个好处:

  1. 解耦:节点不需要知道下游是谁,只管写通道;谁订阅了就会在下一步被触发。
  2. 可持久化:状态全在通道里,把通道快照下来就是一个 Checkpoint,天然支持续跑。
  3. 可并行:同一超步内互不依赖的节点可以并行,因为它们读的是同一份不可变快照。

"通道 + 版本号 + 触发"这套机制是全课的地基,模块二会专门讲。

一次 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.pyPregel 类的 docstring(约 449–476 行), 找出它对 Plan / Execute / Update 的描述。
  • README.mdlibs/langgraph/README.md 里找 LangGraph 对自己的定位描述。

小结

  • LangGraph = BSP 超步执行 + Actor/Channel 通信 + Checkpoint 持久化
  • 黄金法则:步内不可变,步间才可见
  • 这三件事撑起了循环、状态、中断、动态并行四大能力。

下一讲:搭好调试环境,画出整个仓库的源码地图。