延伸阅读:
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 是图中的执行单元。它通常是一个函数,负责做一件相对明确的事情,比如:
- 调用模型生成计划
- 调用检索工具搜索资料
- 对工具结果做归纳
- 判断是否继续
- 格式化最终输出
一个好的节点通常具备两个特征:
- 职责单一
- 输入输出清晰
这不是代码洁癖,而是工程可维护性的前提。节点太“胖”,图看起来简单,实际上调试会很痛苦;节点太“碎”,图会变得噪声很多,难以理解。真正有经验的做法,通常是在“可复用的业务步骤”粒度上拆节点,而不是按每一行逻辑拆。
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 架构,通常会把系统动作分成两类:
- 低风险、可自动执行
- 高风险、必须有 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_queriesretrieve_sourcesread_sourcessynthesize_evidence
这样做的好处是:
- 更容易单独评估检索质量
- 更容易缓存和复用中间结果
- 更容易插入失败恢复与限流策略
3. 写作节点不该直接消费原始搜索结果
如果 write_report 直接看一堆原始网页摘录,常见结果是:
- 证据噪声太大
- 输出不稳定
- 引用关系不清晰
- 容易把检索阶段的错误直接传递到成文
更稳妥的模式通常是:
- 检索阶段生成结构化证据
- 分析阶段对证据进行冲突消解和可信度判断
- 写作阶段只消费整理后的 evidence object
这背后的原则很简单:写作应该建立在整理过的证据层之上,而不是直接面对原始噪声。
4. 需要显式失败路径
很多教程只展示 happy path,但真实系统必须考虑:
- 搜索服务不可用怎么办
- 结果为空怎么办
- 抓到的页面质量很差怎么办
- 模型在某一步输出无法解析怎么办
- 某个工具重复失败时是否降级
在图结构里,这些都不该只是 try/except 吞掉,而应该尽量变成显式状态和显式路径,比如:
retry_searchfallback_to_cached_resultsrequest_human_helpabort_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 的许多问题。