第一次学 LangGraph:用一个快递案例搞懂 State、Node、Edge 和 compile

发布时间:2026/9/27 11:18:25
第一次学 LangGraph:用一个快递案例搞懂 State、Node、Edge 和 compile
我挖掘了一个巨牛的 人工智能 学习网站通俗易懂风趣幽默忍不住分享一下给大家。点击跳转到网站如果你刚开始学习 LangGraph大概率会遇到这样一种感觉State、Node、Edge、START、END、compile……每个单词单独看都不难但真正让自己从零写一个完整 Graph 时却很容易不知道该从哪里开始。尤其是第一次看到这种代码graph StateGraph(State) graph.add_node(...) graph.add_edge(...) app graph.compile() app.invoke(...)很多人都会有一种非常熟悉的感觉每一行代码好像都认识但它们为什么要这样组合起来我并没有真正理解。我刚开始学习 LangGraph 时也遇到了类似的问题。比如为什么一定要先定义 StateNode 节点本质上到底是什么Edge 和条件 Edge 有什么区别为什么 Node 里面一般都要return一个字典为什么不能直接修改statecompile()明明是“编译”为什么和 C / Java 的编译完全不一样所以这篇文章不准备单纯带你“复制代码跑通案例”。我想通过一个非常直观的「快递配送系统」把 LangGraph 最核心的一套开发流程完整串起来。我们最终要实现这样一条业务流程START → 揽收站 → 分拣中心 → 普通配送 / 加急配送 → 派送站 → END其中普通包裹走陆运加急包裹走空运整个配送过程中包裹状态、配送历史和运输里程都会保存在全局 State 中不断更新。通过这个案例你会完整理解 LangGraph 最基本的开发流程定义 State → 定义 Node → 注册节点 → 添加 Edge → 条件分支 → compile → invoke如果把 LangGraph 最核心的思想压缩成一句话其实就是State 负责保存数据Node 负责处理数据Edge 负责决定下一步去哪里。下面我们从最基础的 Graph API 开始。一、先搞懂 LangGraph 的标准开发流程构建一个 LangGraph 图实际上存在一套非常固定的开发流程。整体可以概括为 5 个步骤定义全局State创建StateGraph定义并注册Node添加Edge调用compile()编译并执行 Graph也可以进一步记成一条非常简单的路线State → Graph → Node → Edge → Compile → Invoke其中State保存整个工作流共享的数据Node执行具体业务逻辑Edge控制节点之间如何跳转compile()把前面定义好的图组装成可以执行的工作流invoke()传入初始状态真正运行整张图理解这条主线以后再看 LangGraph 的代码会轻松很多。二、一个容易误解的地方LangGraph 的 compile 到底在“编译”什么第一次看到delivery_system delivery.compile()如果之前学过 Java 或 C很容易下意识把这个compile()理解成传统意义上的“代码编译”。但实际上两者完全不是一回事。LangGraph 中的compile()并不会把 Python 代码翻译成机器码生成.exe生成 JVM 字节码做传统编译器意义上的代码链接它真正做的事情更接近于把我们前面定义好的 State、Node、Edge 和条件路由组装成一个真正可以运行的工作流对象。也就是说Java / C 的编译主要解决的是代码怎么变成计算机能够执行的程序。而 LangGraph 的 compile 主要解决的是你定义的这张图怎么变成一个真正可以执行的工作流。理解这一点之后后面的代码就不会那么容易混淆了。三、完整实战用 LangGraph 模拟一个快递配送系统接下来开始正式写代码。我们设计一个简单的快递配送系统。每一个包裹都有包裹编号始发地目的地配送优先级当前配送状态历史流转记录累计运输距离整个流程如下包裹进入系统↓揽收站↓分拣中心↓根据priority判断普通 → 标准陆运或者加急 → 空运加急↓派送站↓签收完成3.1 第一步定义 State保存整张图共享的数据LangGraph 中的State可以理解为整个工作流所有节点共享的一份数据。后续不管包裹经过揽收站、分拣中心还是派送站大家看到的都是同一个 State 中的数据。因此我们首先定义from typing import TypedDict, Annotated from operator import add class PackageState(TypedDict): package_id: str origin: str destination: str status: str history: Annotated[list[str], add] total_distance: Annotated[int, add] priority: str这里有一个非常重要的知识点State 中不同字段的更新方式并不完全一样。例如status: str默认采用覆盖更新。旧值待揽收节点返回已揽收最终就会直接变成已揽收而history: Annotated[list[str], add]则代表使用operator.add作为 reducer。因此[北京揽收]再返回[进入上海分拣中心]不会覆盖前面的数据而会变成[ 北京揽收, 进入上海分拣中心 ]这也是 LangGraph State 中非常重要的一个设计不同字段可以定义不同的状态合并规则。3.2 第二步创建 StateGraphState 定义完成之后创建整张图from langgraph.graph import StateGraph, START, END delivery StateGraph(PackageState)这里的PackageState相当于告诉 LangGraph接下来这张 Graph 中流转的数据都按照PackageState这个结构管理。需要注意此时我们只是创建了一个图构建器。里面没有节点没有边没有业务流程。因此现在还不能直接执行。3.3 第三步定义 Node 节点LangGraph 中的 Node本质上就是一个 Python 函数。例如揽收站def receive_package(state: PackageState): origin state[origin] return { status: 已揽收, history: [f在{origin}揽收] }它做的事情非常简单读取当前 State 中的origin然后告诉 LangGraph这一次节点执行完以后需要更新两个字段status history后面的分拣、运输、派送节点本质上也都是同样的逻辑。四、我学习时踩到的第一个坑Node 为什么一定要 return这个问题我第一次写 LangGraph 时也比较疑惑。例如def receive_package(state: PackageState): origin state[origin] return { status: 已揽收, history: [f在{origin}揽收] }为什么一定要return能不能直接state[status] 已揽收然后不返回理解这个问题其实就理解了 LangGraph State 更新机制很重要的一部分。Node 更推荐采用这样的思维方式读取旧 State → 执行业务逻辑 → 返回本次需要更新的数据。例如return { status: 已揽收 }相当于告诉 LangGraph我这个节点执行完成之后请把status更新成已揽收。然后 LangGraph 会再根据这个字段对应的 reducer完成真正的状态合并。因此写 Node 时不需要把完整 State 全部重新返回。比如当前 State 有package_id origin destination status history total_distance priority揽收节点只修改status history那么只需要return { status: 已揽收, history: [完成揽收] }其他字段仍然会继续保留。如果这个节点只是打印日志不修改任何 State可以def log_node(state: PackageState): print(state[package_id]) return {}所以我们可以把 Node 的写法总结成一句话函数体负责“计算”return 负责告诉 LangGraph“哪些 State 需要更新”。五、Node 写好了为什么还要 add_node例如我们已经定义def receive_package(state): ...但这个时候它还只是一个普通 Python 函数。LangGraph 并不知道它是不是 Graph 的节点节点叫什么其他节点应该怎么连接它。因此还需要delivery.add_node(揽收站, receive_package)这里实际上完成了一次绑定节点名称揽收站 ↓ 执行函数receive_package后面 Edge 使用的揽收站实际上就是这个节点的唯一标识。六、Edge决定数据下一步去哪里节点定义完以后还需要告诉 LangGraph每个节点执行完成之后下一步应该去哪个节点这就是 Edge。例如delivery.add_edge(START, 揽收站) delivery.add_edge(揽收站, 分拣中心)表示START ↓ 揽收站 ↓ 分拣中心这种路线永远不会变化因此属于固定边。但是实际业务中并不是所有流程都是固定的。例如普通包裹分拣中心 ↓ 标准配送加急包裹分拣中心 ↓ 加急配送这时候就需要add_conditional_edges()根据当前 State 动态决定下一跳。这也是 LangGraph 相比普通顺序函数调用非常重要的能力之一Graph 的执行路径可以由当前状态动态决定。七、最后一步compile invoke所有 State、Node、Edge 都设计好以后delivery_system delivery.compile()到这里前面的“图设计”才真正变成一个可以运行的 LangGraph 工作流。之后result delivery_system.invoke(package)就会从START开始运行。然后依次经过揽收 ↓ 分拣 ↓ 路线判断 ↓ 运输 ↓ 派送 ↓ END整个过程中同一个 State 会不断被各个节点读取和更新。写在最后如果第一次学习 LangGraph 时感觉概念特别多其实不用急着记各种 API。先把最核心的一条主线记住State → Node → Edge → Compile → Invoke其中State数据是什么Node数据怎么处理Edge下一步去哪Compile把整张图组装起来Invoke真正开始执行如果再压缩成一句话LangGraph 用 State 保存数据用 Node 处理数据用 Edge 控制流程。这就是整个 LangGraph 最基础的骨架。后面的条件路由循环AgentTool CallingCheckpointMemory多 Agent本质上都是在这套骨架上不断增加能力。所以刚开始学习 LangGraph 时我并不建议一上来就直接研究复杂 Agent。先自己真正写明白一次START ↓ Node ↓ 条件分支 ↙ ↘ Node Node ↘ ↙ END再去看复杂案例会容易很多。如果你也正在学习Python / LangChain / LangGraph / AI Agent / 大模型应用开发可以收藏这篇文章。以后忘记 LangGraph 的基本开发流程时只需要回来记住这一条State → Node → Edge → Compile → Invoke。