03 · LangGraph:把 Agent 当成一台可暂停的状态机
| 仓库 | langchain-ai/langgraph |
| Star | 39.9k(2026-08-19) |
| 版本 | 1.2.11 |
| 语言 | Python / TypeScript |
| 许可证 | MIT |
| 层级 | Runtime 运行时(LangChain 和 DeepAgents 都跑在它上面) |
| 一句话 | 不是「更强的 LangChain」,而是给 Agent 用的持久执行引擎 |
一、它解决的不是 LLM 问题,是分布式系统问题
先看一个真实场景:
一个合同审查 Agent,跑到第 8 步要人工签字。审批人第二天早上才看邮件。
用普通框架,你有两个选择:进程挂在那儿等 12 小时,或者从头重跑。LangGraph 给的第三个选择是:把状态写进数据库,进程退出,明天开个新进程从第 8 步继续。
这就是它的定位 —— durable execution(持久执行)。同一层里还有 Temporal、Inngest 这些通用引擎,它们不懂 LLM 但持久执行做得同样好;LangGraph 的差异是它把 LLM 场景(消息归并、流式、工具中断)做成了一等公民。
四个核心能力:
| 能力 | 含义 |
|---|---|
| Durable execution | 崩溃 / 退出后从检查点恢复,不是从头 |
| Streaming | 状态级、步骤级、token 级三种粒度 |
| Human-in-the-loop | 任意位置中断,人改完状态再继续 |
| Persistence | 线程内(checkpointer)+ 跨线程(store)双层 |
二、核心抽象:State + Node + Edge
LangGraph 的心智模型只有三个词:
- State:一个 TypedDict,整张图共享
- Node:一个函数,
(state) -> 状态更新片段 - Edge:谁跑完之后跑谁,可以是条件的
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
# ① State:整张图共享的一份数据,所有节点读它、改它
class State(TypedDict):
# Annotated[类型, reducer]:add_messages 就是 reducer,
# 它规定「节点返回新消息时是追加,而不是覆盖」——详见下一小节
messages: Annotated[list, add_messages]
# ② Node:一个普通函数,入参是当前状态,返回「要改哪些字段」
def call_model(state: State):
# 只返回 messages 这一个字段的增量,其余字段原样保留
return {"messages": [llm.invoke(state["messages"])]}
# ③ Edge:把节点连起来,决定谁跑完之后跑谁
builder = StateGraph(State)
builder.add_node("model", call_model) # 注册节点,名字随便起
builder.add_edge(START, "model") # 入口 → model
builder.add_edge("model", END) # model → 结束
graph = builder.compile() # 编译成可执行对象
Reducer:整个设计里最关键的一行
Annotated[list, add_messages] 的意思是:节点返回 {"messages": [x]} 时,不是把 messages 覆盖成 [x],而是追加。
没有 reducer 会怎样?
| 无 reducer(默认覆盖) | 有 add_messages | |
|---|---|---|
节点返回 {"messages": [新消息]} | 历史全没了 | 追加到历史后面 |
| 两个节点并行返回 | 后写的赢,前面的丢 | 两边都保留 |
Reducer 是 LangGraph 能安全做并行分支的根本原因 —— 它把「多个节点同时改同一个字段」从竞态变成了确定性合并。这和 CRDT 的思路是一样的。
三、Checkpointer:状态落盘,随时可续
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
checkpointer = InMemorySaver() # 存「图状态快照」——会话内的短期记忆(生产要换 Postgres)
store = InMemoryStore() # 存「应用数据」——跨会话的长期记忆
# 编译时把两者挂上,图才具备持久化能力
graph = builder.compile(checkpointer=checkpointer, store=store)
result = graph.invoke(
{"messages": [{"role": "user", "content": "Hi, my name is Bob."}]},
# thread_id 是这次会话的唯一标识,也是你的「持久化游标」:
# 下次还传 thread-1 就接着上次的状态跑;换一个值就是全新会话
{"configurable": {"thread_id": "thread-1"}},
)

Checkpointer vs Store:两套持久化,别搞混
| Checkpointer | Store | |
|---|---|---|
| 存什么 | 图状态快照 | 应用自定义的 KV 数据 |
| 作用域 | 单个 thread | 跨 thread |
| 记忆类型 | 短期、会话内 | 长期、跨会话 |
| 用于 | 对话连续、HITL、时间旅行、容错 | 用户偏好、事实、共享知识 |
| 怎么访问 | config 里传 thread_id | 节点里读写 item |
thread_id 是你的持久化游标 —— 复用同一个 id 就接着上次跑,换一个就是全新会话。