LangGraph 框架分享:从 LangChain 到智能体编排
LangGraph 框架分享:从 LangChain 到智能体编排
适合想要入门 LangGraph 的开发者。全文力求通俗,把概念讲透,把坑点说清,并用少量代码示例帮你建立手感。
一、LangChain:AI 应用的乐高积木
在进入 LangGraph 之前,我们需要先认识一下 LangChain。你甚至可以把它想象成一个巨型乐高套装——里面塞满了各种标准化零件:提示词模板、文档加载器、向量数据库连接器、输出解析器、记忆模块、工具调用接口……你能想到的和大模型交互的组件,它基本都帮你封装好了。
LangChain 最经典的使用方式,是链式调用(Chain)。什么叫链式调用?就是像工厂流水线一样,把零件首尾相连:A 的输出变成 B 的输入,B 的输出再传给 C,一步接一步,数据从头流到尾。比如要做一个“根据文档回答问题”的 RAG 应用,你的流水线可能是:加载文档 → 文本切割 → 向量化存储 → 检索相关段落 → 拼接提示词 → 调用大模型生成回答。在 LangChain 里,你用几行代码就能把这些步骤串起来,非常丝滑。
链式调用的好处是直观、简单、开发快。对很多业务流程固定的场景来说,这就足够了。但很快,人们开始不满足于“固定流水线”——他们想要的是智能体(Agent),一个能自己判断情况、自己决定调用什么工具、自己规划步骤的 AI 程序。
这时候,链式调用的局限就暴露了出来:
-
只会走直线,不会拐弯
当 Agent 需要根据中间结果来决定下一步做什么时,线性链很难优雅地实现分支。比如,用户问题如果简单就闲聊,如果复杂就去查数据库。在链式结构里,这种“看情况走岔路”的逻辑写起来非常别扭。 -
状态容易弄丢,一出错就全盘崩溃
多轮对话、多步推理时,Agent 需要记住之前的对话历史、工具返回的结果、自己的思考过程。一条链上,如果中间某个环节挂了,之前积累的信息就没了,整个流程必须从头再来。这就像你在玩没有存档功能的游戏,打到一半突然断电,只能含泪重开。 -
很难实现“思考-行动-观察-再思考”的循环
一个典型的智能体,比如 ReAct 模式,会反复做这件事:我现在知道什么?我该调用哪个工具?工具返回了什么?基于新信息我该如何调整计划?这是一个循环,而不是一条从头走到尾的直线。传统的链式结构天生不支持环。 -
人很难参与进来
在企业场景中,有些决策不能让机器自己拍板。比如合同审批、大额交易、敏感内容发送,必须有人工介入。你很难在一条自动传送带上说:“到这里先停一下,等我确认了再继续。” 线性链没有原生的“暂停-恢复”能力。
正因有了这些痛点,LangChain 团队才做出了 LangGraph。LangGraph 并不是要取代 LangChain,而是作为一个更底层的编排引擎,专门解决这些复杂流程控制的问题。事实上,从 2025 年开始,LangChain 里自带的 Agent 功能,底层已经是跑在 LangGraph 的图上面了。这两者的关系可以这样理解:LangChain 是零件商店加简易传送带,LangGraph 则是智能调度中心和地铁轨道系统。
二、认识 LangGraph:把工作流从“传送带”升级为“地铁图”
如果 LangChain 的链式调用是一条固定的厂区传送带,那么 LangGraph 就是把你的业务逻辑画成了一张城市地铁图。
在 LangGraph 的世界里,一个应用被定义为一个有向图(可以包含环)。图由三种核心元素组成:
- 节点(Node):代表一个具体的处理步骤,比如调用一次大模型、执行一个搜索工具、做一次判断。你可以把它想象成地铁站,每个站完成一项特定的任务。
- 边(Edge):连接节点,定义执行顺序。普通边就是单向铁轨,从 A 站直接开到 B 站;条件边则像道岔,系统会根据当前状态自动选择开往不同的站台。
- 状态(State):一张在所有节点之间共享的“大白板”。每个节点都可以读白板上的内容,也可以在上面写东西。LangGraph 会自动帮你把白板从一个节点传到下一个节点,所有信息都不会丢。这个白板就是 Agent 的工作记忆。
因为有了这张灵活的图,很多以前难以实现的能力现在变得很自然:
- 动态路由:你可以让 Agent 在运行时根据条件选择不同的分支,就像地铁调度系统根据客流调整列车走向。
- 循环执行:“大模型思考 → 调用工具 → 工具返回结果 → 大模型再次思考”,这个闭环在图上就是一条环线,天然支持。
- 内置存档与断点续跑:LangGraph 可以在每一步执行后自动存档。万一程序挂了,你能从最近的存档点接着跑,而不必重头开始。
- 人机协同:在任何节点,你都可以设置“人工检票口”,让程序暂停下来,等你点个头再放行。
这套设计的底层数学灵感,来自 Google 的 Pregel 模型。简而言之,它把整个图的计算过程切分成一个个“超级步”,每步并行执行所有就绪的节点。这种天生的并行能力,也让 LangGraph 可以轻松支持流式输出、多个工具同时调用等进阶需求。
三、LangGraph 核心模块详解
下面我们把这几个核心概念拆开揉碎了看,并配合一点点代码,帮你建立实际的手感。
3.1 State:Agent 的“共享记事本”
State 是整个图的心脏,也是你在编程时最先要设计的东西。你可以把它理解成一个所有节点共用的 JSON 对象,里面定义了你希望在整个工作流中追踪的字段。
在实际代码中,我们通常用 TypedDict 来定义 State,这样既清晰又能获得类型检查的好处。一个关键的设计是 Reducer(合并策略):当多个节点都想更新同一个字段时,新数据是覆盖旧数据,还是追加到后面?这就需要 Reducer 来决定。
举个例子:你的聊天消息历史,肯定是希望不断追加的,而不是新消息把旧消息顶掉。LangGraph 为此提供了内置的 add_messages 合并函数。
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
# messages 字段会按顺序追加,不会覆盖
messages: Annotated[list[BaseMessage], add_messages]
# 下面这些字段没有特殊 Reducer,新值会直接覆盖旧值
search_result: str
next_action: str
user_profile: dict
有了这样的记事本,后续每个节点都可以直接读写 state["messages"],所有历史都在里面,再也不怕上下文丢失。
3.2 Node:执行具体任务的小兵
一个节点,本质上就是一个普通的 Python 函数。它接收当前 State 作为输入,然后返回一个字典,里面只包含你想要更新的字段。LangGraph 会自动把这个字典合并回全局 State 中。
函数的签名通常是这样的:
def my_node(state: AgentState) -> dict:
# 1. 从 state 中读取需要的信息
user_input = state["messages"][-1].content
# 2. 执行本节点的核心逻辑(调用 LLM、搜索、运算等)
result = some_processing(user_input)
# 3. 返回想要更新的字段
return {"search_result": result, "messages": [AIMessage(content="处理完成")]}
节点可以做的事五花八门:调用大模型、执行数据库查询、调用外部 API、做数据格式转换,甚至就是一段纯 Python 逻辑。设计节点的核心原则是单一职责:一个节点最好只做一件明确的事。这样你的图会像乐高一样易于组合、测试和复用。
3.3 Edge:控制列车走向的轨道和道岔
定义了节点之后,我们通过边把它们连成工作流。LangGraph 有三种边:
-
普通边:无条件的,从 A 指向 B,执行完 A 就一定去 B。
-
条件边:在路口放一个“路由器函数”,它根据当前 State 决定下一步往哪走。这实现了 if/else 的动态分支。
-
循环边:如果条件边指向了之前已经执行过的节点,就形成了循环。比如,工具调用后需要再次回到思考节点,这就是一个典型的 Agent 循环。
下面这段代码展示了如何构建一个包含条件分支的图:
from langgraph.graph import StateGraph, END
# 定义路由器函数:根据 next_action 字段的值决定走向
def route_after_think(state: AgentState) -> str:
return state["next_action"] # 可能返回 "search" 或 "respond"
# 创建图
graph = StateGraph(AgentState)
# 添加节点
graph.add_node("think", think_node)
graph.add_node("search", search_node)
graph.add_node("generate", generate_node)
# 先无条件从 START 走到 think 节点
graph.set_entry_point("think")
# 从 think 节点出发,根据条件走向不同分支
graph.add_conditional_edges(
"think",
route_after_think,
{
"search": "search", # 如果 next_action 是 "search",就去搜索节点
"respond": "generate" # 如果是 "respond",就去生成回答节点
}
)
# 搜索完成后,可以去生成节点,也可以再回到 think 继续思考
graph.add_edge("search", "think") # 形成循环
graph.add_edge("generate", END) # 生成完毕后结束
这样,一个能根据情况决定调用工具还是直接回答,并且能够反复迭代的智能体就初具雏形了。
3.4 Checkpointer:让 Agent 拥有“存档”和“读档”能力
你可能会问:如果智能体已经循环了十步,突然服务器重启了,之前的努力不就白费了?LangGraph 的 Checkpointer(检查点保存器) 就是来解决这个的。
你只需要在编译图的时候传入一个 Checkpointer,框架就会在每个节点执行前后自动保存 State 的快照。这些快照可以存到内存里(测试用)、本地 SQLite 数据库里(个人项目用),或者生产环境下的 PostgreSQL 里。
这个能力的意义远超故障恢复:
时间旅行调试:你可以随时把状态回滚到任意历史快照,然后修改输入从那里重新运行。这在调试复杂逻辑时堪称神器。
实现人工干预:程序在某个节点执行前自动存档并暂停,等你通过外部指令确认后,再从存档点继续。这个我们马上会讲到。
启用存档只需要在编译时多传一个参数:
from langgraph.checkpoint.sqlite import SqliteSaver
# 使用 SQLite 做持久化
with SqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
app = graph.compile(checkpointer=checkpointer)
# 之后用 app 来运行,并传入 thread_id 作为会话标识
3.5 Human-in-the-Loop:在关键决策点让人把关
在很多严肃的业务场景里,有些操作不能让 AI 自己说了算。比如:发送营销邮件给客户、删除数据库记录、审批合同等。LangGraph 原生的中断机制让“人工审批”变得极其自然。
实现方式:你在编译图时,指定在某些节点前“打一个断点”。当程序运行到该节点时,它会自动暂停,把当前 State 保存起来,然后通知你:“我已经到这里了,下一步要执行这个节点,请确认。”
你可以在外部系统里展示当前状态,让人来做决策。确认后,你告诉 LangGraph 继续执行,它就会从刚刚保存的断点处接着跑,就像什么都没发生过一样。整个过程对图的结构没有任何侵入,你只需要在调用时设置 interrupt_before参数。
3.6 其他锦上添花的能力
除了以上核心模块,还有两个概念值得简单了解:
-
Command:一种更灵活的路由方式。你可以在节点内部直接指定接下来去哪个节点,而不用单独定义条件边。适合在节点逻辑里顺便决定走向的场景。
-
子图(Subgraph):当你的工作流越来越复杂时,可以把一部分节点打包成一个“子图”,在主图中像普通节点一样使用。这就像把一段地铁线封装成一个换乘大站,让主图保持简洁。
四、实战中的注意事项与避坑指南
概念和模块都清楚了,但真正上手时依然有不少坑。以下是我从自身和社区经验中总结出的关键提醒。
4.1 状态设计:能省则省,规整第一
State 里的每一个字段,都会在每次存档时被写入数据库。所以千万别把大段的原文、图片 base64 这种东西直接塞进 State。State 里只放“目录索引”和必要的最小信息,大数据则丢到外部存储(如 Redis、S3),State 里只留一个file_id。
同时,务必将对话历史类的字段配上add_messages合并器,其他字段慎重对待,避免节点间意外覆盖。用TypedDict 或 Pydantic 约束字段类型,相当于给记事本画了格子,不容易写歪。
4.2 子图和父图状态必须“说同一种语言”
如果你的主图里用了一个自制的子图,那么子图使用的 State 结构必须和主图兼容:要么完全一样,要么子图的 State 字段是主图 State 字段的子集。如果主图用TypedDict,子图也必须是TypedDict;混用dataclass会直接报错。这是新手最容易遇到的“编译通过,一跑就崩”的坑之一。
4.3 持久化配置要看场景下菜碟
- 开发调试:用 MemorySaver,速度飞快,重启后清空。
- 单机小项目:用 SqliteSaver,简单可靠。
- 生产环境:选 PostgresSaver,它能稳定支持高并发和大状态量。不要用 MongoDB,因为它的单文档大小有限制(16 MB),一个大状态快照就可能把数据库写爆。
- 记得设置 TTL:检查点会越存越多,必须配置自动过期删除策略,否则你的数据库迟早被撑满。
4.4 给循环加上“刹车”
Agent 很容易陷入自我反思的死循环,一次次调用工具却得不到有用信息。一定要设置最大递归步数和总超时时间。在节点的 router 函数里,如果发现步数超过阈值,就强制导向END,并给出默认答复或求助人类。
4.5 每一次外部调用都要考虑“不靠谱”
你的 Agent 节点可能会调用第三方 API、查询数据库、上传文件。任何一个外部依赖都有概率超时、返回错误码,甚至长时间无响应。务必为每条外部请求加上:
- 超时时间(例如 30 秒)
- 重试机制(最多重试 2 次,且要有间隔)
- 降级策略(重试仍失败,返回一个兜底数据或者让流程走安全出口)
另外,在设计节点时尽量考虑幂等性。因为故障恢复时,同一个节点可能会被重复执行,可能造成重复发邮件、重复扣款。使用业务唯一键来防止此类副作用。
4.6 没有可观测性,调试等于摸黑走迷宫
复杂图一旦跑起来,你看不到内部到底走了哪条路、哪个节点改了哪个字段,排查问题会痛不欲生。因此:
- 开启 checkpoint 日志,记录每步状态。
- 用
graph.get_graph().draw_png()把图画出来,贴在团队文档里,让大家对流程一目了然。 - 生产环境强烈建议对接 LangSmith 或同类平台,它们能展示每次运行的完整拓扑路径、每个节点的耗时和输入输出,定位问题效率提升数倍。
五、总结
到此为止,我们完成了从 LangChain 到 LangGraph 的完整旅程,核心线索其实很简单:
LangChain 是零件超市和传送带,帮你快速搭出线性处理链路,胜任简单、固定的场景。
LangGraph 是智能调度中心和地铁轨道图,它用 节点(处理单元)、边(控制流) 和 状态(共享记忆) 三要素,把工作流从一条直线升级为一张可分支、可循环、可中断的动态图。它内建了状态持久化、条件路由、人工干预、流式执行等企业级能力,让搭建真正可靠的 AI 智能体成为可能。
一句话帮你记住怎么选:如果任务像流水线,用 LangChain;如果需要思考、分支、循环、人干预,那就交给 LangGraph。
希望这篇分享能帮大家建立起对 LangGraph 清晰且自洽的认知。剩下的,就是在实际项目中去画图、去写节点、去踩坑、去存档恢复,然后笑着看你的智能体在错综复杂的地铁网里自如地奔跑。

浙公网安备 33010602011771号