LangGraph

用图结构构建有状态的多步骤 Agent 工作流 —— LangGraph 核心概念、设计模式与实战

19 min read Part of Agent · Ch. 9
← 上一层级:学习路径 · Part 03 · Agent 设计与工程

延伸阅读

LangGraph

flowchart TD
  S([START]) --> N1[Node: Plan]
  N1 --> N2[Node: Tool Call]
  N2 --> N3{是否继续?}
  N3 -->|继续| N1
  N3 -->|需人工确认| N4[Human in the Loop]
  N4 --> N1
  N3 -->|完成| E([END])
  ST[(Shared State / Checkpointer)] -.读写.-> N1
  ST -.读写.-> N2
  ST -.读写.-> N4

先看定义:LangGraph 不是“再写一层链式调用”,而是把 Agent 的控制流、状态流和恢复能力 显式建模出来。它用图来组织多步骤推理与执行,因此天然适合循环、分支、人工介入、持久化和失败恢复等 Agent 场景。(LangChain 文档)


为什么需要 LangGraph

如果你只是在做一个简单的 LLM 流水线,比如:

  • 用户问题改写
  • 检索知识库
  • 把检索结果塞进 Prompt
  • 生成答案

那么线性的 Chain 或 LCEL 往往已经够用。

问题在于,Agent 不是线性流水线。Agent 的核心不是“把几个步骤串起来”,而是“在运行过程中不断决定接下来该做什么”。一旦进入这种模式,系统就会自然出现几类需求:

  • 循环:模型可能需要多轮“思考 → 调工具 → 看结果 → 再思考”
  • 条件分支:是否需要工具、是否要重试、是否要升级处理,都要根据当前状态决定
  • 共享状态:每一步都不只是产出一个字符串,而是在更新同一个任务上下文
  • 暂停与恢复:遇到高风险动作时,系统要能停下来等人确认;服务中断后,要能从中间恢复
  • 可观测与可调试:你不只是想知道最终答案对不对,还想知道系统为什么走到了这一步

这正是 LangGraph 的切入点:把 Agent 从“Prompt 驱动的黑箱流程”变成“可编排、可恢复、可调试的状态机”。官方也明确把持久化、人机协同、时间回溯(time travel)和复杂任务编排作为 LangGraph 的核心能力,而不是附加功能。(LangChain 文档)

从工程上看,LangGraph 解决的不是“怎么多调几次模型”,而是:

  • 怎么让一个复杂任务有清晰的执行结构
  • 怎么让这个结构在失败、重试、中断、人工介入时仍然可控
  • 怎么让 Agent 从 Demo 走向更接近生产系统的形态

它到底是什么:图,而不是链

很多人第一次接触 LangGraph,会把它理解成“支持循环的 LangChain”。这样理解不算错,但不够准确。

更准确的说法是:

LangGraph 是一个围绕共享状态运行的图执行框架。节点读取状态、返回部分状态更新;边定义控制流;检查点系统保存每一步执行快照,从而支持恢复、人工介入、分叉调试和长期运行。(LangChain)

它的基本抽象有三个。

核心概念

1. Node:节点

Node 是图中的执行单元。它通常是一个函数,负责做一件相对明确的事情,比如:

  • 调用模型生成计划
  • 调用检索工具搜索资料
  • 对工具结果做归纳
  • 判断是否继续
  • 格式化最终输出

一个好的节点通常具备两个特征:

  1. 职责单一
  2. 输入输出清晰

这不是代码洁癖,而是工程可维护性的前提。节点太“胖”,图看起来简单,实际上调试会很痛苦;节点太“碎”,图会变得噪声很多,难以理解。真正有经验的做法,通常是在“可复用的业务步骤”粒度上拆节点,而不是按每一行逻辑拆。

2. Edge:边

Edge 决定控制流,也就是“下一步去哪里”。LangGraph 里的边不是装饰,而是你的流程逻辑本身。

常见有三类:

  • 普通边:固定从 A 到 B
  • 条件边:根据当前状态决定后续路径
  • 起点 / 终点:由 START 进入,由 END 结束

如果说 Node 负责“做什么”,那 Edge 负责“什么时候做、做完去哪”。这使得 LangGraph 更接近一个显式工作流系统,而不是隐式嵌在 Prompt 里的流程。

3. State:状态

State 是 LangGraph 最关键的设计。官方对 StateGraph 的定义非常直接:图中的节点通过读取共享状态并返回部分状态更新来通信。某些状态字段还可以定义 reducer,用来合并来自不同步骤的更新。(LangChain)

这意味着:

  • 节点不是彼此直接传值
  • 节点面对的是同一个“任务上下文”
  • 每一步做的事情,本质上是对上下文的一次增量修改

这和很多初学者写的 Agent 很不一样。初学者常见做法是把前一步输出原封不动塞给下一步,直到最后难以维护;而 LangGraph 倡导的是:把运行过程建模成状态演化,而不是字符串接力


State 为什么是 LangGraph 的核心

很多文章介绍 LangGraph 时,会把重点放在“它支持循环”。但更重要的其实是:它让状态变成一等公民

看一个简化的状态定义:

from typing import TypedDict, Annotated
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]
    user_goal: str
    plan: list[str]
    current_step: int
    retrieved_docs: list[dict]
    draft_answer: str
    need_human_approval: bool
    error: str | None

graph = StateGraph(AgentState)

这里真正重要的不是 TypedDict 这个语法细节,而是它表达了一种设计观:

  • messages 是对话历史
  • user_goal 是任务目标
  • plan 是计划
  • retrieved_docs 是外部证据
  • draft_answer 是当前草稿
  • need_human_approval 是控制信号
  • error 是异常信息

这比“把所有内容糊成一个大 prompt”高级得多。因为一旦状态被显式建模,很多工程能力才可能成立:

  • 你知道每一步到底依赖什么
  • 你知道哪些字段可以安全重算,哪些不行
  • 你知道哪里该持久化,哪里该截断上下文
  • 你知道调试时应该看哪个字段,而不是盯着一大段自然语言猜

状态不是“越多越好”

不过,状态显式不等于状态越大越好。LangGraph 新手很容易踩两个坑:

坑一:把所有中间内容都塞进 State

这样做会让状态越来越臃肿,带来三个问题:

  • 序列化和持久化成本变高
  • 模型上下文污染加重
  • 很难区分“关键事实”和“临时草稿”

更稳妥的做法是:

  • State 只放跨节点真正需要共享的信息
  • 大对象(原始网页、长文档、二进制结果)尽量只存引用或摘要
  • 能重算的临时值,不一定需要持久化

坑二:把 State 当数据库

State 适合表达一次运行中的上下文,但不等于你的长期业务数据层。用户画像、权限规则、知识库文档、审计日志,通常应该在外部系统中管理,State 里放必要引用或快照即可。

一个简单判断标准是:

如果某个字段脱离本次运行仍然有独立业务意义,它往往不该只存在于 LangGraph 的 State 里。


从“链式调用”到“状态机”的思维转变

理解 LangGraph,关键不是学会几个 API,而是完成一个思维转变:

在 Chain 里,你通常想的是:

  • 第一步输出什么给第二步
  • 第二步再输出什么给第三步

在 Graph 里,你更应该想的是:

  • 当前系统处于什么状态
  • 这个状态下允许哪些动作
  • 做完这个动作,状态如何变化
  • 什么条件下结束、重试、升级、回退或人工接管

这本质上更接近:

  • 有限状态机
  • 工作流编排
  • 分布式任务执行中的 saga / checkpoint 思维

只是它面对的是 LLM 驱动的非确定性系统。

这也是为什么 LangGraph 更适合复杂 Agent:它承认 LLM 输出的不确定性,然后用显式控制流和状态约束把这种不确定性圈住。


常见模式

LangGraph 并没有强迫你用某一种 Agent 结构,但在实践里,几个模式非常常见。

1. ReAct Agent:最常见,也最容易失控

最经典的图结构通常是这样:

START → call_model → should_continue?
                        ├─ yes → call_tools → call_model
                        └─ no  → END

这个模式的优点是直观:

  • 模型先做判断
  • 决定是否要调用工具
  • 读取工具结果后继续推理
  • 直到可以结束

它对应的正是大家熟悉的 ReAct 循环。

但 ReAct 最大的问题也恰恰来自它的灵活性:

  • 模型可能进入无效循环
  • 同一个工具可能被反复调用
  • 工具返回噪声时,模型容易被带偏
  • 每次循环都会增加 token、延迟和成本

所以在工程上,ReAct Agent 不应只靠模型“自己收敛”,而应该增加硬约束,比如:

  • 最大迭代次数
  • 工具调用白名单
  • 重复调用检测
  • 失败升级策略
  • 置信度或证据充分性判断

这意味着,LangGraph 让你可以做循环,但好的系统设计应该先想清楚如何让循环停下来。

2. Plan-and-Execute:把“想”和“做”拆开

另一类常见模式是先规划,再执行:

START → create_plan → execute_step → check_progress
                                        ├─ continue → execute_step
                                        ├─ replan   → create_plan
                                        └─ done     → END

这类结构比纯 ReAct 更“有工程感”,因为它把两个容易混在一起的问题拆开了:

  • 规划:总体上要做哪些步骤
  • 执行:当前一步到底怎么完成

它的优势在于:

  • 更容易审查计划是否合理
  • 更容易做人工介入
  • 更容易把不同节点替换成不同能力的模型
  • 更容易记录进度和失败位置

但它也有代价:

  • 计划本身可能不靠谱
  • 计划过细会导致僵化
  • 计划过粗又失去指导意义
  • 外部环境变化时,需要重规划机制

一个成熟实现通常不会把“计划”当成圣经,而会把它视为可被执行反馈修正的中间产物

3. Supervisor / Router:多 Agent 协作的常见外壳

多 Agent 经常被讲得很热闹,但真正落地时,最常见的结构其实并不神秘:

START → supervisor
           ├─ researcher → supervisor
           ├─ coder      → supervisor
           ├─ writer     → supervisor
           └─ finish     → END

Supervisor 的职责不是“比别的 Agent 更聪明”,而是负责几个相对朴素但重要的事情:

  • 根据当前状态选择下一个角色
  • 控制执行顺序
  • 合并多个子任务结果
  • 判断是否完成

这类设计的关键不在于“搞多少个 Agent”,而在于边界清不清晰。很多所谓多 Agent 系统,实际上只是把一个模糊任务切成多个同样模糊的角色,最后复杂度翻倍,收益很有限。

更务实的原则是:

  • 先证明单 Agent 不够用
  • 再拆成少量职责清晰的子 Agent
  • 最后用 Supervisor 统一调度

否则你得到的往往不是协作,而是“多份不确定性叠加”。


Human-in-the-Loop 不是点缀,而是生产系统边界

LangGraph 的一个重要能力,是把人工介入当成运行时机制,而不是临时补丁。

官方的 interrupt 机制允许在节点内部暂停执行,把当前状态保存下来,等外部输入后再继续;前提是你使用了 checkpointer,并通过 thread_id 这样的标识恢复同一条执行线程。(LangChain 文档)

这意味着你可以把很多高风险动作放进正式流程,而不是靠“上线前多祈祷一下”:

  • 发送邮件前需要审批
  • 执行 SQL 写操作前需要确认
  • 调用付费 API 前需要预算校验
  • 生成最终报告前需要编辑审阅
  • 发现证据不足时转人工补充

为什么这件事很重要

因为 Agent 的风险往往不在“生成一段错话”,而在“把错误动作执行出去”。

比如:

  • 查错资料问题不大
  • 错发邮件、错删数据、错调用生产接口就很麻烦

所以真正成熟的 Agent 架构,通常会把系统动作分成两类:

  1. 低风险、可自动执行
  2. 高风险、必须有 gate

LangGraph 的价值在于,这个 gate 不是业务代码里 scattered 的 if/else,而是可以直接成为工作流的一部分。

常见误区:把人工介入理解成“人工兜底”

人工介入不是为了弥补系统没有设计好,而是为了在以下场景下提供制度性边界:

  • 高风险不可逆操作
  • 法务 / 合规要求
  • 信息不充分但必须决策
  • 用户偏好需要最后确认
  • 模型置信度难以可靠估计

这意味着,Human-in-the-Loop 不是“系统不够智能所以找人”,而是“有些决策本来就应该保留人为最终裁量”。


Checkpointing:Agent 真正可恢复的基础设施

LangGraph 内建持久化层。官方文档把 checkpoint 描述为:图编译后如果配置了 checkpointer,会在每一步执行中保存状态快照,并按 thread 组织。这使得人机协同、记忆、时间回溯和容错执行成为可能。(LangChain 文档)

这一点非常关键,因为很多“Agent Demo”其实都缺一个前提:一旦中途中断,整个流程就得重来

而在现实世界里,中断是常态:

  • 模型接口超时
  • 工具服务失败
  • 审批隔了几个小时才回来
  • 任务本身持续几十分钟甚至更久
  • 系统版本升级后要从历史节点重新检查

有了 checkpoint 之后,系统能力会发生质变。

1. 可暂停、可恢复

运行到一半暂停,并不意味着丢失上下文。你可以在稍后恢复到同一线程继续跑。

2. 可回溯

官方提供了 time travel / replay 能力,可以从过去的某个 checkpoint 重新执行,必要时带着修改后的状态分叉出新历史。(LangChain 文档)

3. 可审计

你可以看到“当时模型看到了什么状态、做了什么决定、为什么走到这个分支”。

4. 更接近故障可恢复系统

如果某一步失败,不一定要重跑全流程,而是可以从最近一个可信检查点接着来。

这不只是“开发调试方便”

很多人第一次听到 time travel,会觉得这是个很酷的调试功能。它确实是,但更重要的是:它把 Agent 从一次性函数调用,变成了可追踪的长期过程

这对生产系统尤其重要,因为长期运行任务往往有三个现实问题:

  • 结果不是一次性产生的
  • 决策会被外部事件打断
  • 你需要知道历史上到底发生过什么

LangGraph 和 LangChain 到底是什么关系

最容易误解的一点是:LangGraph 不是 LangChain 的替代品,也不只是 LangChain 的一个附件。

更准确地说:

  • LangChain 更偏向组件层与应用层抽象 比如模型封装、提示模板、工具、检索链、中间件等
  • LangGraph 更偏向控制流与运行时层 它解决多步骤编排、共享状态、持久化、中断恢复等问题

从官方表述看,LangGraph 提供 agent orchestration,而 LangChain 提供更广义的组件和集成。LangGraph 也支持通过 Functional API 在较少改动现有代码的前提下引入 persistence、human-in-the-loop、streaming 等能力。(LangChain)

所以两者最常见的关系不是二选一,而是:

  • 用 LangChain 的组件能力搭积木
  • 用 LangGraph 组织复杂执行流程

一个更实用的判断方式

用 LCEL / 简单 Chain 就够了,如果你的场景是:

  • 单轮或短链路 RAG
  • 明确的线性数据处理
  • 没有中断恢复需求
  • 没有复杂分支
  • 没有工具循环

用 LangGraph 更合适,如果你的场景是:

  • 有明显的多步决策过程
  • 需要循环或重试
  • 需要人工审批
  • 需要状态持久化
  • 需要从中间恢复
  • 需要对执行过程进行检查和调试

一个常见误区是:只要听到“Agent”,就上 LangGraph。其实不对。很多所谓 Agent 需求,拆开看只是“多一步检索 + 多一步重写”,并不需要图执行。LangGraph 的价值在复杂性足够高时才真正体现出来。


平台与部署:不要只看框架本身

如果只把 LangGraph 看成一个本地 Python 库,你会低估它的价值。

LangChain 团队目前已经把这套能力延伸到了部署与调试层。官方文档中可以看到:

  • Agent Server:用于部署图和 agent,负责执行、状态管理、持久化,以及 assistants / threads / runs 等服务端能力。(LangChain 文档)
  • LangSmith Studio:面向 agent 的可视化开发与调试环境,可以查看执行图、检查状态、从检查点分支、修改状态继续运行。(LangChain 文档)
  • LangSmith Deployment / Cloud:把 LangGraph 应用托管到官方云环境,处理部署、扩缩容和基础设施问题。官方文档当前将其描述为面向 agent 工作负载的托管部署能力。(LangChain 文档)

这里最值得注意的不是命名,而是思路变化:

LangGraph 已经不是“写个本地工作流”的工具,而是一套从编排、持久化、调试到部署的 Agent 运行基础设施。

这也解释了为什么很多关于 LangGraph 的讨论,最后都会连到 LangSmith:前者偏运行逻辑,后者偏观测、调试、评估与部署平台。LangSmith 官方也明确强调其平台是 framework-agnostic 的,不只服务 LangGraph,但 LangGraph 显然是它支持最深的一类工作负载。(LangChain 文档)


实战视角:一个更像样的 Research Agent 应该怎么设计

很多文章里的示例是这样:

def create_research_agent():
    graph = StateGraph(ResearchState)

    graph.add_node("plan", plan_research)
    graph.add_node("search", web_search)
    graph.add_node("analyze", analyze_results)
    graph.add_node("write", write_report)

    graph.add_edge(START, "plan")
    graph.add_edge("plan", "search")
    graph.add_edge("search", "analyze")
    graph.add_conditional_edges(
        "analyze",
        needs_more_info,
        {"yes": "search", "no": "write"}
    )
    graph.add_edge("write", END)

    return graph.compile(checkpointer=MemorySaver())

这个示例没有问题,但它太“课本化”了。真正上线时,一个 Research Agent 至少要多想几件事。

1. “继续搜索”不能只靠模型感觉

needs_more_info 如果只让模型回答 yes/no,很容易出现两个问题:

  • 模型过早结束,证据不足
  • 模型不断追加搜索,迟迟不收敛

更稳妥的做法通常是把判断拆成几个维度:

  • 是否已覆盖用户问题中的关键子问题
  • 当前证据是否来自足够多的来源
  • 结论之间是否存在冲突
  • 剩余不确定性是否会影响最终输出

这意味着,“是否继续”最好不是一句模糊自然语言判断,而是一个半结构化决策

2. 检索节点要区分“查询生成”和“证据管理”

许多 Demo 把 search 写成一个节点,内部完成:

  • 生成 query
  • 搜索
  • 抓网页
  • 读网页
  • 摘要
  • 去重

这在小 Demo 里可以,在工程上通常过于粗糙。更合理的拆法可能是:

  • plan_queries
  • retrieve_sources
  • read_sources
  • synthesize_evidence

这样做的好处是:

  • 更容易单独评估检索质量
  • 更容易缓存和复用中间结果
  • 更容易插入失败恢复与限流策略

3. 写作节点不该直接消费原始搜索结果

如果 write_report 直接看一堆原始网页摘录,常见结果是:

  • 证据噪声太大
  • 输出不稳定
  • 引用关系不清晰
  • 容易把检索阶段的错误直接传递到成文

更稳妥的模式通常是:

  1. 检索阶段生成结构化证据
  2. 分析阶段对证据进行冲突消解和可信度判断
  3. 写作阶段只消费整理后的 evidence object

这背后的原则很简单:写作应该建立在整理过的证据层之上,而不是直接面对原始噪声。

4. 需要显式失败路径

很多教程只展示 happy path,但真实系统必须考虑:

  • 搜索服务不可用怎么办
  • 结果为空怎么办
  • 抓到的页面质量很差怎么办
  • 模型在某一步输出无法解析怎么办
  • 某个工具重复失败时是否降级

在图结构里,这些都不该只是 try/except 吞掉,而应该尽量变成显式状态和显式路径,比如:

  • retry_search
  • fallback_to_cached_results
  • request_human_help
  • abort_with_reason

图的一个重要价值,就是把“失败也是流程的一部分”表达出来。


一个更接近生产的设计原则

如果你真的要用 LangGraph 做复杂 Agent,下面这些原则比 API 细节更重要。

1. 节点尽量做“确定性工作”,把不确定性集中起来

最难调试的系统,是每个节点都在自由发挥。

更好的做法通常是:

  • 让少数节点负责开放式推理
  • 让多数节点负责确定性变换、校验、路由和归档

比如:

  • 查询扩展可以交给模型
  • 搜索执行、去重、排序、预算检查尽量用代码做

这样系统会更稳,更容易定位问题。

2. 把“路由决策”单独做成节点

很多初学者喜欢在业务节点内部顺便决定后面去哪。这样短期省事,长期会让控制流隐身。

更好的方式是把决策显式化:

  • 一个节点负责执行
  • 另一个节点负责判断下一步路径

这样你在图上就能清楚看到“业务动作”和“调度逻辑”的边界。

3. 为循环设置预算

任何循环都要有上限,不只是次数上限,还包括:

  • token 预算
  • 时间预算
  • 工具调用预算
  • 外部 API 成本预算

否则图结构只是把“死循环”写得更优雅一点。

4. 明确哪些状态字段是“事实”,哪些是“推断”

比如:

  • 工具原始返回值更接近事实
  • 模型生成的计划、总结、判断更接近推断

这两类数据最好不要混在一起对待。否则一旦模型推断错了,后续节点可能把它当成硬事实继续传播。

5. 先设计可观测性,再设计复杂度

你当然可以做一个非常炫的多节点、多角色图。但如果上线后你不知道:

  • 为什么选了这个分支
  • 为什么在这里暂停
  • 为什么这个状态字段变成了现在这样
  • 为什么某一步重试了 4 次

那么复杂图只会让排障更难。

LangGraph 的优势恰恰在于它支持检查点、状态检查、分支回放。设计时如果不利用这些能力,等于只用了它最表面的那一层。


LangGraph 适合哪些问题,不适合哪些问题

适合

1. 长流程、可中断任务

例如:

  • 多轮研究报告生成
  • 多步骤审批
  • 跨多个工具的业务处理

这类任务最需要持久化和恢复。

2. 工具调用较多、流程不固定的 Agent

比如需要根据中间结果动态决定:

  • 是否继续搜
  • 是否改写问题
  • 是否调用不同工具
  • 是否升级为人工处理

3. 需要审计与追踪的系统

如果业务方关心的不只是结果,还有“它是怎么得到这个结果的”,LangGraph 会比黑箱式 agent loop 更合适。

4. 多角色协作但边界清晰的系统

不是所有多 Agent 都值得做,但当角色分工确实明确时,图结构很自然。

不适合

1. 简单 RAG

如果你的流程基本就是:

  • 改写问题
  • 检索
  • 回答

那先别急着上 LangGraph。

2. 极短、极快、一次性的调用路径

LangGraph 的状态、持久化和调度都有心智成本。对于“一个 prompt + 一个工具”的任务,收益通常不明显。

3. 团队尚未厘清流程边界

LangGraph 擅长表达清晰流程,不擅长替你发明流程。如果团队还没想明白:

  • 哪些步骤是必要的
  • 哪些决策点存在
  • 哪里需要人工审批
  • 哪些状态必须持久化

那先用普通代码把最小闭环跑通,往往更现实。


LangGraph 不是银弹,它解决的是“复杂流程可控”问题

把 LangGraph 吹成“Agent 终极答案”是不准确的。

它真正擅长的,是把复杂 agent 系统中那些最难管理的部分显式化:

  • 控制流
  • 共享状态
  • 人机边界
  • 失败恢复
  • 调试与回放

它并不会自动解决这些更根本的问题:

  • 模型推理质量不够
  • 工具本身不可靠
  • 检索质量差
  • 任务定义含糊
  • 成本和延迟不可接受

所以更准确的评价是:

LangGraph 不是让 Agent 变聪明的魔法,而是让 Agent 更像一个可以被工程化管理的系统。(LangChain 文档)

这也是它在今天的 Agent 生态里最重要的位置。


核心概念速查

概念一句话
Node图中的执行单元,读取状态并返回部分状态更新
Edge控制流定义,决定下一步走向
State节点共享的运行时上下文,是 LangGraph 的核心抽象
Reducer某些状态字段的合并策略,用来处理增量更新
Interrupt在运行时暂停图执行,等待外部输入后恢复
Checkpointing为每一步保存状态快照,支持恢复、审计与 time travel
Thread一条持续运行的执行上下文,恢复时通常依赖同一个 thread 标识
Time travel从历史检查点重新执行,并可分叉出新的执行路径

一句话总结

如果说前几篇文章里的很多 Agent 仍然更像“会调工具的 Prompt”,那么 LangGraph 代表的是另一种思路:

把 Agent 当成一个有状态、可恢复、可审计、可介入的执行系统来设计。

这也是为什么它会成为当下最主流的 Agent 编排框架之一:不是因为它让概念更新潮,而是因为它更贴近复杂 AI 系统真正需要面对的现实。


下一篇

理解了 LangGraph 这种“把 Agent 做成系统”的思路之后,我们再回头看一个曾经最具话题性的名字:它为什么爆红,又为什么暴露了早期 Agent 的许多问题。