图核心概念:State、Node、Edge 与 Reducers
上一篇我们知道了 LangGraph 是 Agent Server 的「操作系统内核」。
这个内核是怎么组织一个应用的?答案是「图(Graph)」。本篇我们用一个快递配送系统做比喻,把图的三大件------状态 State、节点 Node、边 Edge ------讲透,并重点攻克最容易懵的 Reducers(状态合并机制)。
目录
- 什么是图:用快递配送系统打比方
- 状态 State:流转的「包裹信息」
- 节点 Node:处理包裹的「站点」
- 边 Edge:决定走向的「运输路线」
- 核心机制:Reducers 状态更新
- 定义状态的两种方式:TypedDict 与 Pydantic
- 本篇小结
1. 什么是图:用快递配送系统打比方
LangGraph 应用本质上是一张有向图:数据(状态)在节点之间,沿着边流动、被一步步处理。
用「快递配送」来类比最直观:
| 图概念 | 快递比喻 | 含义 |
|---|---|---|
| State(状态) | 包裹上的信息单 | 在整个流程中流转、被各环节读写的共享数据 |
| Node(节点) | 揽收点、分拣中心、派送站 | 一个个具体的处理步骤 |
| Edge(边) | 站点之间的运输路线 | 决定处理完这一步后往哪走 |
| Graph(图) | 整套配送网络 | 节点 + 边 + 状态合起来的完整流程 |
数据像包裹一样,从起点出发,经过一个个站点(节点),按路线(边)流转,最终到达终点。
2. 状态 State:流转的「包裹信息」
State 是图里所有节点共享的数据,它有三个关键特性:
- 共享性:每个节点都能读取它。
- 持久性:在整个流程中一直存在(配合持久化还能跨重启保存,后面专门讲)。
- 结构化:用明确的字段组织数据,而不是一团乱麻。
比如快递场景,State 里可以有:包裹编号、当前所在站点、处理记录等。
重要原则:节点之间不直接通信,它们只通过 State 交换信息。 节点 A 把结果写进 State,节点 B 再从 State 里读,彼此不直接调用。这样各节点彻底解耦。
3. 节点 Node:处理包裹的「站点」
节点是图里的一个处理步骤,在代码里就是一个普通的 Python 函数。
节点的规矩:
- 单一职责:一个节点只干一件事(揽收就只管揽收,分拣就只管分拣)。
- 接收 State,返回状态更新:函数拿到当前 State,处理后返回一个「要更新的字段」字典。
- 节点间不直接通信:通过读写 State 间接传递数据。
节点函数的基本长相:
python
def 某节点(state: State) -> dict:
# 1. 从 state 读取需要的数据
# 2. 做处理
# 3. 返回要更新的状态(一个字典)
return {"某字段": 新值}
4. 边 Edge:决定走向的「运输路线」
边负责连接节点、决定流程走向,主要有三类:
-
开始 / 结束路线
LangGraph 内置两个特殊节点:
START(图的入口)和END(图的出口)。 -
固定路线(普通边)
A 处理完一定去 B,写死的直线流程。
-
条件路线(条件边)
A 处理完去哪,要看 State 里的数据动态决定------满足条件走 B,不满足走 C,甚至走回 A 形成循环。
START → 揽收 → 分拣 → 派送 → END (固定路线)
┌→ 合格 → 派送 → END 质检┤ └→ 不合格 → 退回重处理(循环) (条件路线)
条件边是 LangGraph 最灵活的地方,后面的 Agent、RAG 质量闭环全靠它。
5. 核心机制:Reducers 状态更新
这是本篇最关键、也最容易踩坑的点。
5.1 一个问题:状态默认是「覆盖」的
节点返回的更新,默认会直接覆盖 State 里的同名字段。比如 State 里有个列表,你想让每个节点都往里追加一条记录,结果发现每次都被覆盖,最后只剩最后一条。
5.2 Reducer:规定「怎么合并」更新
Reducer(归并函数)用来规定:节点返回的新值,到底该怎么和 State 里的旧值合并。
写法是在定义 State 时,用 Annotated[类型, reducer函数] 给字段指定合并规则:
python
from typing import Annotated, TypedDict
import operator
class State(TypedDict):
# operator.add 表示:新值和旧值做「加法」
# 对列表就是「追加拼接」,对数字就是「累加」
messages: Annotated[list, operator.add]
count: Annotated[int, operator.add]
对比两种效果:
python
# 普通字段(无 reducer):覆盖
name: str
# 节点依次返回 "A"、"B" → 最终 name = "B"(被覆盖)
# 带 operator.add 的列表:追加
items: Annotated[list, operator.add]
# 节点依次返回 ["A"]、["B"] → 最终 items = ["A", "B"](被追加)
5.3 常用 Reducer
| Reducer | 作用 | 典型用途 |
|---|---|---|
| 不指定(默认) | 覆盖旧值 | 每次都要替换成最新值的字段,如当前结果 |
operator.add |
列表追加 / 数字累加 | 消息列表、处理记录、计数器 |
| 自定义函数 | 自己定义合并逻辑 | 复杂的去重合并、按规则更新 |
记忆点:想「追加/累加」就给字段加
Annotated[..., operator.add];想「替换」就什么都不写。 消息列表几乎总是用追加,这是后续所有案例的标配。
6. 定义状态的两种方式:TypedDict 与 Pydantic
State 的结构可以用两种方式定义:
6.1 TypedDict(推荐,性能好)
python
from typing_extensions import TypedDict
from typing import Annotated
import operator
class PackageState(TypedDict):
package_id: str
history: Annotated[list, operator.add]
- 轻量、运行快,只是给字典加了类型提示。
- 官方更推荐,性能比 Pydantic 好。
6.2 Pydantic BaseModel(校验强)
python
from pydantic import BaseModel, Field
from typing import Annotated
import operator
class PackageState(BaseModel):
package_id: str
history: Annotated[list, operator.add] = Field(default_factory=list)
- 自带数据校验,传错类型会报错。
- 功能强但性能不如 TypedDict,适合对数据合法性要求高的场景。
选型建议:默认用 TypedDict;需要严格数据校验时再用 Pydantic。
7. 本篇小结
- LangGraph 应用是一张有向图:State(状态)、Node(节点)、Edge(边)、Graph(图)。
- State 是节点共享的数据 ,有共享性、持久性、结构化;节点间不直接通信,只通过 State 交换信息。
- 节点就是普通函数:单一职责,接收 State、返回状态更新字典。
- 边分三类:开始/结束边、固定边、条件边;条件边让流程能分支和循环。
- Reducer 规定状态怎么合并 :默认覆盖;
Annotated[..., operator.add]实现列表追加、数字累加。 - 状态定义默认用 TypedDict(性能好),需要校验再用 Pydantic。
下一篇我们正式动手,用本篇的概念完整实现案例一:智能快递配送系统,走一遍「定义状态 → 加节点和边 → 编译 → 调用」的标准流程。