Day 01 / 共 60 天 · 阶段 1 入门与心智

项目全景与分包

这是 60 天深度源码之旅的第一天。今天不写代码,先建立"地图感":LangGraph 到底解决什么问题、和 LangChain 是什么关系、为什么它用"图"来建模、仓库里 libs/ 那五个包各管什么、它们怎么互相依赖。地图清楚了,后面 59 天钻进任何一个角落都不会迷路。

📍 你在 60 天里的位置 · 阶段 1「入门与心智」(D01-06)
D01 全景分包 D02 跑通第一个图 D03 StateGraph D04 节点与边 D05 State/TypedDict D06 对话图+小结
后面还有:状态数据流(D07-12) · 控制流(D13-18) · Pregel 引擎(D19-26) · Channels(D27-32) · 持久化记忆(D33-40) · 中断人在环(D41-46) · 函数式与子图(D47-52) · 预制件流式(D53-58) · 生产收官(D59-60)
💡 用一个类比先兜住整门课("工厂流水线"世界观) 把一个 AI 应用想象成一条工厂流水线:每个工位(节点 node)做一道工序,工件(共享状态 state)在传送带上流转,工位做完把结果写回传送带。LangGraph 就是这条流水线的"排产调度系统"——它不生产零件(那是大模型和工具的事),它负责决定"工件下一步去哪个工位、几个工位能不能同时开工、出了废品怎么返工、下班了怎么把半成品存档明天接着做"。今天先看清整个工厂有哪几个车间。
L01

LangGraph 到底解决什么问题

官方 README 第一句就把定位讲死了——它不是"又一个 Agent 框架",而是一个底层编排框架

README.md:12(仓库根)
Low-level orchestration framework for building stateful agents.
(用于构建有状态智能体的低层编排框架)

README 紧接着列了它提供的五项"底层基础设施"(README.md:35-40):

能力大白话本课对应
Durable execution跑一半崩了/关机了,能从断点继续,不用从头再来阶段6 持久化 D33-40
Human-in-the-loop跑到一半停下来等人审批、改一改再继续阶段7 中断 D41-46
Comprehensive memory短期工作记忆 + 跨会话长期记忆D33-40(记忆/Store)
Debugging (LangSmith)把每一步执行路径、状态变化画出来看D26 调试与画图
Production deployment把这套有状态流程稳定部署到线上D59 运行时与部署
🤔 痛点:为什么普通的 while True: 调模型 + 调工具 循环不够用?你自己写一个 Agent 循环,很快会撞到四堵墙:① 中途崩了状态全丢;② 想让人插一脚审批,得把循环拆得稀碎;③ 想让"查天气"和"查股票"两个工具并行跑,得手写线程/异步还要合并结果;④ 想事后复盘"它第 3 步为什么走错了",没有任何记录。这四件事——持久化、人在环、并行合并、可观测——正是 LangGraph 用一套统一机制替你兜住的。
💡 本质:LangGraph = "状态 + 图 + 执行引擎"三件套你只描述状态长什么样(state schema)和工位之间怎么连(图),剩下的"按什么顺序跑、并行还是串行、崩了怎么恢复、每步存不存档",全交给它内置的执行引擎(后面会看到叫 Pregel)。这就是"low-level orchestration(低层编排)"的含义——它管调度,不管你每个工位里具体调什么模型。
L02

和 LangChain 到底什么关系

初学者最大的困惑就是"LangChain 和 LangGraph 是不是二选一"。看依赖就懂了——LangGraph 依赖 langchain-core,但反过来不成立:

libs/langgraph/pyproject.toml:31-38(dependencies)
dependencies = [
    "langchain-core>=1.4.7,<2",          # ← 只依赖 core(消息类型/Runnable 接口)
    "langgraph-checkpoint>=4.1.0,<5.0.0",  # 自家的持久化包
    "langgraph-sdk>=0.4.2,<0.5.0",         # 自家的客户端 SDK
    "langgraph-prebuilt>=1.1.0,<1.2.0",    # 自家的预制件(create_react_agent 等)
    "xxhash>=3.5.0",
    "pydantic>=2.7.4",
]
langchain-core只依赖 core,不是整个 langchain。core 里主要是 BaseMessage(消息类型)、Runnable(统一的"可调用"接口)这些基础积木。LangGraph 借用它们,但不依赖 langchain 里的 chains/agents。
langgraph-checkpoint / -sdk / -prebuilt都是 langgraph 自己拆出去的兄弟包(同一个 monorepo,见 L04)。核心引擎只依赖抽象的 checkpoint 接口,具体实现另装。
pydantic / xxhashpydantic 用来做状态 schema 校验(D12 会讲);xxhash 是快速哈希,缓存/去重用(D51 缓存会遇到)。

LangChain(组件库)

  • 提供"积木":模型封装、工具、提示模板、输出解析
  • 擅长"把一次调用链起来"(LCEL 管道 |
  • 无状态的线性/树形组合

LangGraph(编排引擎)

  • 提供"排产系统":状态、图、循环、并行、持久化
  • 擅长"让多个步骤有状态地反复流转"
  • 天生有状态、支持环、支持中断恢复
🍼 一句话需要"把几个调用串成一条管道"→ LangChain 够了;需要"有记忆、能循环、能并行、能中途暂停找人、崩了能恢复"→ 上 LangGraph。它俩是上下游搭配,不是竞品:节点里你完全可以用 LangChain 的模型和工具。
L03

为什么用"图"这个模型

LangGraph 的核心数据结构就是一张有向图:点是"工位",边是"下一步去哪"。看 StateGraph 类开头的官方描述就明白了这个模型的三条铁律:

libs/langgraph/langgraph/graph/state.py:131-137
class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
    """A graph whose nodes communicate by reading and writing to a shared state.

    The signature of each node is `State -> Partial<State>`.

    Each state key can optionally be annotated with a reducer function that
    will be used to aggregate the values of that key received from multiple nodes.
    """
communicate by ... shared state铁律①:节点之间不直接互相调用,而是通过一块共享状态(传送带)间接通信。A 把结果写回状态,B 从状态里读——A 根本不用知道 B 存在。这是解耦的关键。
State -> Partial<State>铁律②:每个节点的"型号"都一样——吃进当前状态,吐出状态的一部分更新(只需返回你改动的字段,不用返回整个状态)。
reducer function铁律③:多个节点同时写同一个字段时,用一个 reducer(合并函数)决定怎么合(覆盖?追加?相加?)。这解决了并行写冲突——D07 专讲。
💡 本质:为什么"图"比"链"强?链(chain)只能一条道走到黑(A→B→C)。而现实的 Agent 需要:(模型→工具→再回模型,直到答完)、分叉(根据结果决定走 A 还是 B)、并行(同时查三个数据源)。这三种形态,"有向图"天然都能表达,"线性链"表达不了环和分叉。所以 LangGraph 选了图。
📝 一个最经典的图:ReAct Agent(后面 D53 会读它源码) START → agent(想) → 有工具要调吗? →是→ tools(做) → 回到 agent→否→ END
看到那条"回到 agent"的边了吗——这就是,模型和工具来回踏步直到得出答案。链做不到,图轻松表达。
🎨 设计取舍①:为什么"节点只返回部分状态"而不是"返回完整新状态"? 朴素实现:每个节点接收完整 state、返回完整 state(像 state = node(state))。问题:节点必须小心翼翼地把没动的字段原样带回,漏带一个就丢数据;而且无法表达"多个节点各改一部分再合并"。源码实现:节点只返回改动的字段(Partial<State>),引擎负责用 reducer 把这些"局部更新"合并进主状态。代价是引擎要多做一步合并逻辑,但换来了并行写、增量更新、字段级 reducer——这些是 Agent 场景的刚需。
L04

libs 五大分包:一个 monorepo

仓库根 libs/ 下不是一个包,而是一整个 monorepo(多包同仓)。ls libs/ 的真实结果:

libs/ 目录(仓库根)
libs/
├── langgraph/              # ★ 核心引擎(本课主战场)
├── checkpoint/             # 持久化"接口 + 内存实现"基座
├── checkpoint-sqlite/      # SQLite 持久化实现
├── checkpoint-postgres/    # PostgreSQL 持久化实现(生产用)
├── checkpoint-conformance/ # 给各 checkpoint 实现跑的一致性测试套件
├── prebuilt/               # 预制件:create_react_agent / ToolNode
├── cli/                    # 命令行工具(本地起服务、部署)
├── sdk-py/                 # Python 客户端 SDK(调远程部署的图)
└── sdk-js/                 # JS 客户端 SDK
管什么本课何时深入
langgraphStateGraph、Pregel 引擎、channels、graph 构建贯穿全程(D03-32、D41-58)
checkpointBaseCheckpointSaver 抽象 + InMemorySaver + StoreD33-35、D40
checkpoint-sqlite / -postgres把存档落到真实数据库D36、D37
prebuilt开箱即用的 ReAct Agent、工具节点D53-56
cli / sdk部署与远程调用D59
🎨 设计取舍②:为什么把 checkpoint 拆成"接口包 + 多个实现包"? 核心引擎只依赖抽象的 langgraph-checkpoint(里面是 BaseCheckpointSaver 接口 + 一个内存实现)。想用 SQLite/Postgres 才额外checkpoint-sqlite/-postgres好处:① 装核心引擎不会被迫拖进 psycopg(Postgres 驱动)这种重依赖;② 第三方能自己实现一个 RedisSaver 而不用改核心;③ checkpoint-conformance 提供统一测试,保证所有实现行为一致。代价:多了几个包、版本要对齐。这是典型的"依赖倒置——核心依赖抽象,实现依赖抽象"。
🍼 记住一句langgraph 是发动机,checkpoint-* 是可换的油箱,prebuilt 是"整车成品",cli/sdk 是钥匙和遥控器。
L05

版本与元信息:pyproject 里的线索

一个陌生项目,pyproject.toml 的头部往往一眼透露"它多成熟、支持哪些 Python、官方怎么定位自己":

libs/langgraph/pyproject.toml:5-30
[project]
name = "langgraph"
version = "1.2.8"                       # 已是 1.x 稳定版
description = "Building stateful, multi-actor applications with LLMs"
requires-python = ">=3.10"              # 至少 Python 3.10
classifiers = [
    'Development Status :: 5 - Production/Stable',   # ← 生产级稳定
    'Programming Language :: Python :: 3.10',
    'Programming Language :: Python :: 3.13',        # 一直支持到 3.13
]
version = "1.2.8"已进入 1.x。这很重要:意味着 API 相对稳定,但也说明你会在源码里遇到不少 v0.x → v1.0 的兼容/弃用代码(下一讲和后面会多次撞见 Deprecated)。
multi-actor applications官方自我定位里的关键词 "multi-actor(多参与者)"——图里的多个节点就像多个 actor 各自读写共享状态。这正是它借鉴 Google Pregel 模型的地方(D19 展开)。
requires-python >=3.10用到了 3.10 的语法(如 X | Y 联合类型、match 语句)。你本地 Python 低于 3.10 装不上——D02 装环境会用到。
Production/Stable成熟度自评"生产级稳定"。配合被 Klarna、Replit 等采用(README 开头提到),可以放心地把它当成"值得逐行精读"的工业级代码。
⚠️ 边界/坑:别在 langgraph.constants 里乱 import 东西 这个 1.x 仓库对"什么是公开 API"卡得很严。比如老教程里常见的 from langgraph.constants import Send,在新版会触发弃用警告并被重定向:
libs/langgraph/langgraph/constants.py:34-46
def __getattr__(name: str) -> Any:
    if name in ["Send", "Interrupt"]:
        warn(f"Importing {name} from langgraph.constants is deprecated. "
             f"Please use 'from langgraph.types import {name}' instead.", ...)
        ...
        return getattr(import_module("langgraph.types"), name)
所以读源码时看到 __getattr__ 里一堆 warn(...deprecated...),别慌——那是官方在"温柔地搬家",告诉你新家在哪。教训:跟着最新 __all__types.py 走,别照抄老博客的 import 路径。
L06

graph 包的"对外门牌":__init__ 出口

读任何 Python 包,先看它 __init__.py__all__——那是它愿意让你用的门牌langgraph.graph 只对外露 6 样东西:

libs/langgraph/langgraph/graph/__init__.py:1-11
from langgraph.constants import END, START
from langgraph.graph.message import MessageGraph, MessagesState, add_messages
from langgraph.graph.state import StateGraph

__all__ = (
    "END",          # 特殊终点标记
    "START",        # 特殊起点标记
    "StateGraph",   # ★ 最常用:构建图的建造器
    "add_messages", # 消息列表专用 reducer(D08 深入)
    "MessagesState",# 内置的"只有 messages 一个字段"的现成 State
    "MessageGraph", # 已弃用的老写法(下面会看到)
)
StateGraph整门课 80% 时间围着它转。它是个建造器(builder):你往里 add_node/add_edge,最后 .compile() 出一个能跑的图。D03 开始逐行拆。
START / END两个"虚拟节点"标记,其实就是两个字符串常量(__start__ / __end__,见下)。用来说"图从哪进、到哪停"。
add_messages / MessagesState对话机器人的现成零件:一个负责把新消息合并进历史(去重/更新/删除),一个是配好的现成 State。D06、D08 会用它们搭第一个对话图。
MessageGraph还挂在门牌上,但源码里明确标了弃用——见下方证据。留着只为兼容老代码。

START/END 的真身其实只是两个"驻留字符串"常量,一点不神秘:

libs/langgraph/langgraph/constants.py:28-31
END = sys.intern("__end__")
"""The last (maybe virtual) node in graph-style Pregel."""
START = sys.intern("__start__")
"""The first (maybe virtual) node in graph-style Pregel."""
sys.intern 是把字符串"驻留"到全局唯一一份,之后所有 == "__start__" 的比较可以走极快的指针相等而非逐字符比。引擎里 START/END 会被频繁比较,这点微优化很值。记住结论:START/END 本质就是特殊字符串,图里"从 START 连一条边到你的节点"就是入口。

再看 MessageGraph 的弃用证据,坐实"跟新不跟老"的原则:

libs/langgraph/langgraph/graph/message.py:311-316
@deprecated(
    "MessageGraph is deprecated in langgraph 1.0.0, to be removed in 2.0.0. "
    "Please use StateGraph with a `messages` key instead.",
    category=None,
)
class MessageGraph(StateGraph):
    ...
🍼 读源码的第一动作拿到陌生包,先 cat __init__.py__all__——它就是"作者划给你的正门"。带 _ 前缀的(如 _node.py_branch.py)是内部实现,能读但别在业务里直接 import。
L07

60 天怎么走:一张学习路线图

把整门课画成一条"从会用到懂原理"的上升曲线,你随时能定位自己在哪一层:

LangGraph 60 天知识地图(10 阶段) ① 入门与心智 D01-06 ← 你在这 ② 状态与数据流 D07-12 ③ 控制流 D13-18 ④ Pregel 执行引擎 D19-26 ★深水 ⑤ Channels 通道 D27-32 ⑥ 持久化与记忆 D33-40 ★深水 ⑦ 中断与人在环 D41-46 ⑧ 函数式 API 与子图 D47-52 ⑨ 预制件与流式 D53-58 ⑩ 运行时·生产·收官 D59-60 建议顺序:先把①②③会用起来 → 再啃④⑥两个"深水区"原理 → 最后⑨⑩落到实战与部署 每天约 30 分钟:真源码走读 + 逐行讲解 + 数据结构/控制流 SVG + 设计取舍 + 边界坑
图注:阶段④ Pregel 引擎和阶段⑥ 持久化是两个"深水区",前面所有铺垫都是为了看懂它们。

👶 小白:我只想会用,非得读到 Pregel 引擎那么深吗?

👨‍🏫 老师:想"会用",阶段①②③(D01-18)足够你写出能跑的 Agent。但一旦线上出怪问题——"为什么这个节点没触发""为什么并行结果被覆盖了""checkpoint 存了啥"——你就必须懂④⑥的原理才能 debug。这门课的价值正在深水区:把黑盒变白盒。

L08

今日小结 + 动手 + 明日预告

🧠 今天你应该能回答

  • LangGraph 一句话定位是什么?(low-level orchestration framework,管调度不管零件)
  • 它和 LangChain 什么关系?(只依赖 langchain-core,是上下游搭配不是竞品;有状态/能循环/能并行是它的独门)
  • 为什么用"图"而不是"链"?(图能表达环、分叉、并行,链只能一条道走到黑)
  • 节点的统一型号是什么?(State -> Partial<State>,只返回改动的字段)
  • libs 下五大类包各管什么?(langgraph 引擎 / checkpoint 持久化 / prebuilt 预制件 / cli+sdk 部署)
  • START/END 的真身?(sys.intern 的两个特殊字符串常量)
  • 为什么 checkpoint 拆成接口包+实现包?(依赖倒置:不装 Postgres 也能用核心,第三方可自实现)

✋ 10 分钟动手(只需终端,不用写代码)

# 1. 看仓库分了哪些包
ls libs/

# 2. 看核心引擎对外露了什么门牌
sed -n '1,12p' libs/langgraph/langgraph/graph/__init__.py

# 3. 看核心引擎的依赖(印证只依赖 langchain-core)
sed -n '31,40p' libs/langgraph/pyproject.toml

# 4. 亲眼看看 START/END 就是两个字符串
sed -n '24,31p' libs/langgraph/langgraph/constants.py

# 5. 数一下核心包里有多少个 .py 文件(感受工程量)
find libs/langgraph/langgraph -name '*.py' | wc -l
💡 明日预告 · Day 02今天有了地图,明天动手装环境、跑通你的第一个 StateGraph——用最小的真实代码,让"节点/边/编译/invoke"这套流程在你机器上真正转起来。跑通那一刻,前面所有抽象概念会突然落地。
← 60 天总目录 Day 02 · 装环境 + 跑通第一个图 →