LangGraph 深度实战:从能跑的 Agent 到可控、可恢复、能上线的工作流
这两年很多人做 Agent,第一阶段都差不多:先让模型能调用工具,再让它多轮思考,最后跑出一个还不错的 demo。问题通常出在第二阶段之后。只要你想把它接到真实业务里,麻烦就会一起冒出来:中途失败怎么办,人工审核怎么插进去,状态怎么保存,长任务怎么恢复,多个步骤之间怎么共享上下文,复杂流程到底该让模型自由发挥,还是老老实实按工作流走。
LangGraph 真正有价值的地方,不是“再做一个 Agent 框架”,而是它把 Agent 从一次性对话,变成了一个可以编排、可持久化、可中断、可恢复的状态机。 这也是为什么我会把它看成一个更偏工程的工具,而不是一个只适合 demo 的玩具。
官方文档对 LangGraph 的定位非常明确:它是一个面向长期运行、可持久化、有状态 Agent 的低层编排框架,核心能力包括 durable execution、streaming、human-in-the-loop、memory 和 subgraphs;而且 LangChain v1 的 create_agent 本身也是运行在 LangGraph 之上。换句话说,LangChain 适合快速起步,LangGraph 适合你开始认真处理控制流和可靠性的时候。
为什么很多 Agent Demo 一进生产就变形
先说结论:大多数 Agent 不是死在“模型不够强”,而是死在“流程不够稳”。你把一个 ReAct agent 跑通,不代表你已经解决了真实业务问题。线上系统关心的通常不是模型会不会想,而是这些更不性感的问题:
- 执行到一半挂了,能不能从中间恢复。
- 调用外部工具前,能不能先让人审核。
- 长任务跨多个回合,状态怎么保存。
- 不同步骤之间,哪些信息该共享,哪些不该共享。
- 复杂任务是让一个大 Agent 全包,还是拆成多个可控节点。
LangGraph 对这些问题的回答,不是“给你一个更聪明的 prompt”,而是给你一套图结构运行时:用 State 管共享状态,用 Node 表示步骤,用 Edge 和条件路由控制执行流,再用 checkpointer 做持久化。官方文档也一直在强调这一点:LangGraph 的重点不是高层 Agent 包装,而是 agent orchestration。尤其在 v1 之后,这套 graph primitives 和运行时模型保持稳定,主打的是默认可靠性,而不是再堆一层魔法抽象。
什么时候该用 LangGraph,而不是普通 Agent API
我的判断很简单。
- 如果你只是做一个能调用几种工具的聊天助手,用高层 agent API 往往更省事。
- 如果你已经开始关心恢复执行、人工审批、复杂路由、多阶段状态共享、子流程复用,那就应该认真看 LangGraph。
官方文档也基本是这个意思:LangGraph 是低层能力层,适合需要 durable execution、streaming、human-in-the-loop 和更细粒度控制的场景;如果只是想快速起步,可以先用 LangChain 的 agent abstraction。这里最重要的一点不是“谁更高级”,而是你要解决的是原型问题,还是系统问题。
一个真实可落地的案例:做一个带人工审批的邮件分诊 Agent
比起空讲概念,我更建议用一个小而完整的例子理解 LangGraph。这里我们用官方文档里很常见的一类场景:邮件处理。目标不是做一个“什么都懂”的超级 Agent,而是做一个可上线、可回放、可中断、可插人工审核的邮件分诊工作流。
假设系统要完成这些事:
- 读取一封用户邮件。
- 判断这是售后、退款、销售咨询,还是普通问题。
- 如果风险高,比如涉及退款或法律措辞,先暂停,等人工批准。
- 批准后调用不同工具,比如创建工单、写 CRM 记录、生成回复草稿。
- 任何一步失败,都能从上次状态恢复。
这类任务特别适合 LangGraph,因为它不只是“模型 + 工具”这么简单,而是一个明确的状态流。
第一步:先设计 State,不要先设计 prompt
很多人上来先写 prompt,这是做 demo 的思路。做 LangGraph,应该反过来:先定义状态。因为状态决定了后续节点之间怎么协作,也决定了哪些信息会被持久化。
from typing import TypedDict, Literal, Optional
class EmailState(TypedDict, total=False):
email_id: str
raw_email: str
category: Literal["support", "refund", "sales", "other"]
risk_level: Literal["low", "medium", "high"]
draft_reply: str
ticket_id: str
crm_note_id: str
approved: bool
error: str
这一步非常关键。State 不是顺手放几个变量,它是你的 Agent 系统边界。 哪些字段要被保存,哪些字段要给后续节点读,哪些字段会影响路由,都应该在这里想清楚。
第二步:把节点写成“窄职责”,而不是“大杂烩 Agent”
LangGraph 最适合的写法,不是让一个节点又分类、又做风险判断、又调工具、又写回复。更稳的做法是把职责拆开,让每个节点只做一件相对清晰的事。
def classify_email(state: EmailState) -> dict:
# 调模型,产出 category 和 risk_level
return {
"category": "refund",
"risk_level": "high"
}
def draft_reply(state: EmailState) -> dict:
return {
"draft_reply": "我们已经收到您的退款请求,正在处理中。"
}
def create_ticket(state: EmailState) -> dict:
return {
"ticket_id": "TICKET-1024"
}
这样做的好处是,出了问题更好调试,单个节点也更容易单测。工程里最怕的是一个“全能节点”,看起来省代码,实际上把 prompt、工具调用、业务规则和异常处理全糊在一起,后面几乎没法维护。
第三步:用条件路由,把“模型自由”限制在该限制的地方
这是 LangGraph 和普通 Agent 写法的一个核心区别。不是所有决策都该交给模型。有些决策应该由程序明确控制,比如高风险邮件必须进人工审批。这种地方就应该写成显式路由。
from langgraph.graph import StateGraph, START, END
workflow = StateGraph(EmailState)
workflow.add_node("classify_email", classify_email)
workflow.add_node("draft_reply", draft_reply)
workflow.add_node("create_ticket", create_ticket)
workflow.add_edge(START, "classify_email")
def route_after_classify(state: EmailState):
if state["risk_level"] == "high":
return "human_review"
return "draft_reply"
workflow.add_conditional_edges(
"classify_email",
route_after_classify,
{
"human_review": "human_review",
"draft_reply": "draft_reply"
}
)
这类写法背后的思路很值得借鉴:让模型负责生成判断,让系统负责执行规则。 很多 Agent 项目之所以后期一团乱,就是因为连业务硬规则都想让模型自己“理解”。这在 demo 里看着很聪明,在生产里通常很危险。
第四步:人工审批不要自己瞎造轮子,直接用 interrupt
LangGraph 一个非常实用的能力是 human-in-the-loop。官方文档里给出的机制很直接:当节点里调用 interrupt() 时,图会暂停,状态会通过 persistence 层保存下来,等外部输入回来后再恢复执行。这比很多团队自己拼 webhook + 数据库 + 状态标记靠谱得多。
from langgraph.types import interrupt
def human_review(state: EmailState) -> dict:
decision = interrupt({
"email_id": state["email_id"],
"risk_level": state["risk_level"],
"draft_reply": state.get("draft_reply", "")
})
return {"approved": decision["approved"]}
这里最重要的工程点是:想做人工审批,就一定要配 checkpointer。 官方文档明确说明 human-in-the-loop 依赖 persistence,因为系统必须能在中断后安全恢复。很多人看到 interrupt 很兴奋,结果没把持久化层接好,最后暂停是能暂停,恢复全靠运气。
第五步:持久化不是可选增强,而是 Agent 进入真实业务的入场券
这是 LangGraph 和很多轻量 Agent 库最拉开差距的地方。官方 persistence 文档写得很明白:checkpointer 不只是为了 memory,它还承担线程级状态保存、恢复执行、支持 human-in-the-loop 等职责。短期记忆本质上也是状态的一部分,会随线程一起被持久化。
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = workflow.compile(checkpointer=checkpointer)
实际生产里你当然不会一直用内存版 checkpointer,但这个例子足够说明问题:一旦你需要恢复、回放、人工介入、长任务分段执行,持久化就不再是“优化项”,而是基础设施。
Subgraph 才是复杂 Agent 系统真正该学的东西
很多人聊 LangGraph 时,注意力都放在“图”本身,但我觉得更值得学的是 subgraph。官方文档对 subgraph 的定义很直接:一个 graph 可以作为另一个 graph 的节点使用。这件事看起来只是代码组织,实际上它决定了你的系统以后能不能扩展。
比如上面的邮件系统,完全可以把“生成回复草稿”做成一个独立子图,内部再拆成:
- 提取关键诉求。
- 查历史订单或上下文。
- 生成回复草稿。
- 做语气和合规检查。
主图不需要知道子图内部怎么实现,只要知道输入输出。这种结构很适合团队协作,也很适合后面把某个子流程单独优化。真正复杂的 Agent 系统,最后都不是一个大图,而是一组能复用的子图。
Deep Agents 值得看,但别把它当银弹
最近 LangChain 体系里也在推 Deep Agents。官方文档给出的方向很明确:通过内置 task 工具生成子代理,做上下文隔离、子任务拆分和长期记忆。这条路线很适合复杂 research、规划、文件系统操作之类任务。
但我自己的判断是:Deep Agents 很适合探索复杂任务,不适合拿来替代一切显式工作流。 当你的业务里存在硬规则、审批节点、明确 SLA、确定性分支时,LangGraph 的显式 graph 仍然更可靠。能编排的地方尽量编排,必须开放探索的地方再交给 agent autonomy,这个边界感很重要。
我建议的 LangGraph 落地方法
如果你准备在项目里认真用 LangGraph,我建议按这个顺序推进:
- 先定义 State,明确哪些字段真的要跨步骤共享。
- 把节点写窄,每个节点只负责一种清晰职责。
- 把硬业务规则写成路由,不要交给模型瞎猜。
- 尽早接 checkpointer,不要等出故障才补。
- 对高风险步骤优先接 human-in-the-loop。
- 复杂流程尽早拆成 subgraph,别等一个大图长成泥球。
这套方法的核心不是“把图画漂亮”,而是让 Agent 系统逐步具备软件系统该有的可维护性。
LangGraph 适合谁,不适合谁
它很适合:
- 已经不满足于聊天式 Agent,开始做流程型 Agent 的团队。
- 需要恢复执行、人工审核、长任务持久化的系统。
- 想把“模型能力”和“业务控制”拆开的开发者。
它不太适合:
- 只想两小时内搭个工具调用 demo 的人。
- 还没搞清楚任务边界,就先上多节点多子图架构的人。
- 希望框架替自己自动解决系统设计问题的人。
说到底,LangGraph 不是让 Agent “更神”,而是让 Agent 更像一个能被工程团队接管的系统。
结论
如果只让我给一个判断,我会说:LangGraph 最值得投入时间的地方,不是它能把 Agent 变复杂,而是它能把复杂 Agent 变得可控。
在今天这个阶段,很多团队并不缺一个能调用工具的 LLM 代理,缺的是一个出了问题能恢复、关键步骤能审核、长任务能持续运行、结构上还能继续扩展的 Agent 系统。LangGraph 在这件事上,确实提供了一套比“继续堆 prompt”和“继续包一层 agent loop”更靠谱的答案。
我的建议是重度关注,但别盲目上复杂图。先从一个具体流程切入,比如客服分诊、工单处理、内容审核、销售线索处理,把 State、路由、持久化、人工审核这四件事真正跑通。你会很快发现,Agent 真正难的不是会不会思考,而是能不能稳定地把事情做完。