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合并器,其他字段慎重对待,避免节点间意外覆盖。用TypedDictPydantic 约束字段类型,相当于给记事本画了格子,不容易写歪。

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 清晰且自洽的认知。剩下的,就是在实际项目中去画图、去写节点、去踩坑、去存档恢复,然后笑着看你的智能体在错综复杂的地铁网里自如地奔跑。

posted @ 2026-04-27 15:18  阿斯拉达  阅读(10)  评论(0)    收藏  举报