Skip to content

Q8 · 什么是 LangGraph?它与 LangChain 有什么区别? ​

一位用户提交退款申请,系统要先查订单,再找适用的店铺政策,整理出建议,最后由员工决定是否批准。理想情况下,这些动作顺序执行就行。但真实业务会遇到三种打断:政策资料没找到,需要再查一次;员工暂时不在线,任务要停下来等;服务在等待期间重启,先前查到的资料不能丢。

LangGraph 适合处理的,正是“下一步不总是固定,而且任务可能暂停”的流程。 它让我们把每一步、步骤之间的去向,以及一路积累的数据写清楚。LangChain 则提供模型、消息、工具等常用能力,还提供开箱即用的 Agent。两者常常一起使用。

本文的退款政策、订单与结果均是假设数据,只用来解释程序如何流转,不代表任何平台的真实规则。

先把这些词和名字认清 ​

词或名字在本文里是什么意思退款案例中的对应物
LangChain构建大模型应用的一组组件和较高层的 Agent 入口连接模型、订单查询工具和政策检索器
LangGraph把任务写成“数据如何变化、下一步去哪里”的可执行图退款审核的整条执行路线
Agent可以根据当前情况决定下一步动作的程序;通常会调用模型和工具判断还要不要查资料的审核助手
State(状态)这一笔任务目前知道什么,是结构化数据,不是画面状态订单号、已找到的政策、查询次数、审批结果
Node(节点)读取当前状态、完成一个动作、返回状态更新的步骤“查政策”“写审核建议”
Edge(边)一步结束后去往哪一步的连接“查完政策后去判断”
条件边读取状态后,按条件选下一步的边政策找到了去写建议;没找到再查
Reducer(合并规则)多次更新同一个状态字段时如何合并新证据追加到列表,还是覆盖旧值
Checkpointer(检查点保存器)按一次任务的标识保存图的状态快照,以便继续执行员工审批前,把已查到的资料和进度保存
thread_id一次连续执行的标识;恢复时靠它找到同一条任务线退款申请 A123 对应的一条审核线程
幂等同一外部操作重复请求,也不会造成重复的业务结果退款接口收到重复请求,不会退两次钱

先记住最核心的三个:State 是随任务走的数据,Node 是处理数据的一步,Edge 决定下一步。 “Graph”就是把这些节点和边连起来的图。图中某个节点可以调用大模型,也可以只是一个普通的 Python 函数;使用 LangGraph 并不意味着每一步都要让模型思考。

一个退款申请怎样在图中前进 ​

假设用户提交订单 A123 的申请。为了说明过程,我们把这笔申请的状态写成:

json
{
  "order_id": "A123",
  "policy_text": null,
  "lookup_count": 0,
  "suggestion": null,
  "approved": null
}

order_id 是订单号;policy_text 是查到的政策正文,null 表示还没有;lookup_count 记录查了几次;suggestion 是待生成的审核建议;approved 留给员工填写。它们都属于同一笔申请,不会自动变成所有用户共享的资料库。

实际执行可以拆成下面几步:

  1. 查订单:确认 A123 存在、属于当前用户,拿到签收时间等事实。这一步通常调用业务系统,而不是让模型凭空猜。
  2. 查政策:按商品类别和生效日期寻找政策。如果没找到,就把 lookup_count 加一,并记录“政策仍缺失”。
  3. 决定下一步:根据状态选择路径。找到了政策,去生成建议;没找到且未达到补查上限,回到“查政策”;超过上限,转人工补资料。
  4. 生成建议:把订单事实与政策放在一起,得到“可能符合/不符合/资料不足”的建议和依据。建议不是退款动作。
  5. 等待人工审批:员工可以批准、拒绝或要求补充信息。图在等待时暂停,并把当前进度保存。
  6. 执行结果:只有审批通过且权限检查也通过时,才调用退款接口;拒绝则说明原因。真实支付动作要另做防重处理。

LangGraph 条件边:资料齐全则生成建议,不齐全则回到查询资料

图里只画了第 2~4 步:条件边让“资料不全”回到查询。回环必须设退出条件,否则查不到资料时可能一直调用工具、增加费用,甚至卡死。下面把转人工和最终审批也接进来:

这张图表达的是控制流程,不是“模型自动有退款权”。特别是金额阈值、用户归属和是否允许退款等确定性规则,应由程序或可靠的业务服务核验,不能只凭模型的一段文字。

节点怎样修改状态 ​

以“查政策”为例。它收到的状态可能是 {"order_id":"A123","policy_text":null,"lookup_count":0}。第一次查询仍未找到政策时,节点返回 {"lookup_count":1};框架把这一小块更新合并进状态,其余字段保留。它不必把整个状态重新返回一遍。

若第二次找到政策,节点可以返回 {"lookup_count":2,"policy_text":"适用政策正文"}。接下来的条件边读取新的 policy_text,才决定进入“生成建议”。这就是“节点负责做事,边负责选路”的具体含义。

如果状态里有 evidence(证据列表),先后两次查到的证据是要追加,还是后一次覆盖前一次?这取决于该字段的 Reducer。默认的普通字段通常采用覆盖更新;要累积消息或证据,就要明确合并规则。Reducer 只解决“数据如何拼在一起”,不负责判断两份互相矛盾的政策哪份有效。

为什么暂停后还能继续 ​

LangGraph 中断时保存状态和执行位置,之后从检查点恢复

想象员工周五下班前没来得及审批,服务周末重启。没有保存机制,程序只知道“收到过申请”,不记得查到了什么、该等谁审批;重新跑可能浪费调用,还可能误触发外部操作。

给图接入 Checkpointer 后,它会把同一 thread_id 的状态快照保存下来。恢复时提供相同的 thread_id,执行器才知道继续哪一笔任务。开发演示可用内存保存器;内存随进程退出就消失,生产环境要换成持久化存储并规划访问权限与数据保留期。跨多个任务共享的用户偏好等长期资料,则属于另一类存储,不应全塞进单笔任务的状态快照。

图中的“从这里继续”是帮助理解的比喻,不等于精确地从某一行 Python 代码继续。例如在节点内部使用人工中断,恢复时可能重新执行该节点中断前的代码。因此,把“已发退款请求”这类真实写操作放在可能重跑的位置,会造成重复执行风险。做法是把审批与退款拆成独立步骤,并给退款接口提供稳定的业务幂等键,例如“订单 A123 的第 1 次获批退款申请 ID”。检查点负责恢复图,幂等键负责防重复业务动作,两者职责不同。

用一段没有模型调用的代码看清回环 ​

先说明代码里的名字,避免看到函数名还要猜:

代码名含义
RefundReviewState这一笔退款审核的状态结构
policy_available演示用开关,模拟外部资料库里是否存在政策;真实项目应调用检索器
lookup_count当前已经尝试查询的次数
policy_found本轮是否真正找到政策
next_action流程最后给出的处理方向
collect_policy模拟“查政策”的节点函数
choose_next根据状态决定下一步的条件边函数
draft_suggestion / ask_human分别模拟“生成建议”和“转人工补资料”

代码还会用到几个 Python 和 LangGraph 的写法:TypedDict 用来声明状态有哪些字段及其类型,它只是类型说明,不会自动核验真实订单;StateGraph 是建图的对象;START 和 END 标记图的入口与出口。builder 是尚未编译的图,调用 compile() 后得到可执行的 graph;invoke(initial_state) 则是“拿初始状态运行一次”。initial_state 就是 A123 进入流程时的那张数据表。

代码刻意不接真实订单系统、模型或退款接口,所以复制后只需安装 langgraph,不会产生业务操作:

bash
pip install -U langgraph
python
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END


class RefundReviewState(TypedDict):
    order_id: str
    policy_available: bool
    lookup_count: int
    policy_found: bool
    next_action: str


def collect_policy(state: RefundReviewState) -> dict:
    count = state["lookup_count"] + 1
    # 用固定条件模拟检索:资料存在时,第二次查询才找到。
    found = state["policy_available"] and count >= 2
    return {"lookup_count": count, "policy_found": found}


def choose_next(state: RefundReviewState) -> str:
    if state["policy_found"]:
        return "draft"
    if state["lookup_count"] >= 2:
        return "manual"
    return "collect"


def draft_suggestion(state: RefundReviewState) -> dict:
    return {"next_action": f"订单 {state['order_id']}:资料齐全,交人工审批"}


def ask_human(state: RefundReviewState) -> dict:
    return {"next_action": f"订单 {state['order_id']}:资料不足,转人工补充"}


builder = StateGraph(RefundReviewState)
builder.add_node("collect", collect_policy)
builder.add_node("draft", draft_suggestion)
builder.add_node("manual", ask_human)
builder.add_edge(START, "collect")
builder.add_conditional_edges(
    "collect",
    choose_next,
    {"collect": "collect", "draft": "draft", "manual": "manual"},
)
builder.add_edge("draft", END)
builder.add_edge("manual", END)
graph = builder.compile()

initial_state = {
    "order_id": "A123",
    "policy_available": True,
    "lookup_count": 0,
    "policy_found": False,
    "next_action": "",
}
print(graph.invoke(initial_state)["next_action"])

第一次读代码,可以只盯住四个地方:

  1. collect_policy 里,局部变量 count 是本次查询后的次数,found 是本次是否查到政策。return 后的字典只包含本节点要更新的字段;dict 表示它返回一组“字段名→值”。
  2. choose_next 不查询资料,只看更新后的状态并返回一个路线名:collect 表示再查、draft 表示写建议、manual 表示转人工。代码中 True 和 False 分别代表“是”和“否”。
  3. add_node 把函数登记为图里的步骤;add_edge 连固定路线;add_conditional_edges 把 choose_next 的三种返回值映射到三个目标节点。这里的 collect、draft、manual 是开发者自己给节点取的名字,不是 LangGraph 的特殊命令。
  4. compile() 检查并生成可运行的图;最后一行把 initial_state 交给图运行,再取出结果中的 next_action。这一行才真正触发前面登记的节点函数。

沿着 A123 走一次,比只看代码更容易理解:

时刻查询次数找到政策了吗条件边选哪里
进入图前0没有先执行 collect
第一次 collect 后1没有未达两次上限,回到 collect
第二次 collect 后2找到了进入 draft,输出“交人工审批”
图结束2找到了演示到此为止,并没有真的审批或退款

把 policy_available 改成 False 再运行:第二次仍找不到政策,流程会走 manual,输出“转人工补充”。这展示了正常路径和失败路径。真实项目中的“查两次”只是示例上限;应综合工具错误类型、时间预算和业务要求确定是否重试。代码没有用 Checkpointer,因此服务重启后的恢复要另行配置,不能从这段玩具示例推断它已经具备持久化。

LangChain 与 LangGraph 怎么分工 ​

问题LangChain 更直接的入口显式使用 LangGraph 的场景
我要调用模型、组织消息、注册工具提供这些常用抽象与集成可直接在节点中复用这些组件
我要快速做常规工具型 Agentcreate_agent 提供预制循环需要改变循环规则时再自行编排
我要自己决定何时分支、循环、暂停、恢复可写自定义代码,但复杂后不易集中审查State、Node、Edge 与持久化机制更显式
只有一次“输入→模型→输出”直接调用模型或简单 Runnable 即可没必要为了画图增加结构

这里最容易答错的是“LangChain 只能线性执行”。当前 LangChain 的 create_agent 本身构建在 LangGraph 之上;“固定的 LCEL 管道”与“LangChain 预制 Agent”不是一回事。LangGraph 也能单独使用,不要求先用 LangChain。可以把选择顺序理解为:先满足任务,再按控制需求增加编排,而不是看哪个名字听起来更高级。

如果面试官问“为什么不用一个 while 循环”,可以承认简单流程完全可以。退款审核一旦增加共享状态、人工中断、恢复、多个失败分支和执行追踪,手写循环会把这些规则散在各处;图编排的价值是把转移关系放到可检查的位置。它并不会替你设计业务规则。

面试时可以这样讲 ​

LangGraph 是面向有状态、可能长时间运行的 Agent 和工作流的底层编排框架。我会先定义 State,记录一笔任务已经知道的订单事实、政策和审批结果;再把查订单、查政策、生成建议等步骤做成 Node;用 Edge 决定下一步,资料不足时回去补查,超过上限就转人工。需要等员工审批或服务可能中断时,接入 Checkpointer 并用同一 thread_id 恢复。LangChain 提供模型、工具和较高层的 create_agent,后者本身基于 LangGraph。简单的工具调用先用现成 Agent;需要自己控制分支、暂停和恢复,再写显式 LangGraph。退款这类外部写操作还要做权限校验和幂等,检查点不能代替它们。

这段回答里的“有状态”就是任务数据会随着步骤更新;“持久化”就是把更新后的进度存下来;“幂等”就是重试时不会重复退款。如果这三个词能用 A123 的路径解释清楚,比只背框架名称更能说明你理解了原理。

参考资料 ​

章节首页 · ← Q7 · Q9 →

最后更新2026-09-26
难度P0
频率very-high
阅读22 min
主题langgraph / langchain / state
觉得有帮助?把这个链接转给正在求职的朋友 · 用 Ctrl + K 全站搜索其它题