大家好 👋,我是 Moment,目前正在使用 Next.js、NestJS、LangChain 开发 DocFlow。这是一个面向 AI 场景的协同文档平台,集成了基于
Tiptap的富文本编辑、NestJS后端服务、实时协作与智能化工作流等核心模块。在这个项目的持续打磨过程中,我积累了不少实战经验,不只是
Tiptap的深度定制、编辑器性能优化和协同方案设计,也包括前端工程化建设、React 源码理解以及复杂项目架构实践。如果你对 AI 全栈开发、文档编辑器、前端工程化或者 React 源码相关内容感兴趣,欢迎添加我的微信
yunmz777一起交流。觉得项目还不错的话,也欢迎给 DocFlow 点个 star ⭐

在之前的内容里面我们一直在用 LangChain 写链、写 Agent,从最简单的模型调用到工具绑定、路由分发、自定义工作流,走了一整套流程。到这里自然会遇到一个问题:随着应用逻辑越来越复杂,LangChain 原有的编排方式开始显得吃力。链是线性的,Agent 是循环的,但真实世界里的流程往往是图状的,有分支、有合并、有回环、有需要等待人工确认的节点。LangGraph 就是为了解决这个问题而出现的。
为什么需要 LangGraph
用 LangChain 写 AgentExecutor 时,底层逻辑是一个简单循环:调模型、看要不要用工具、用完工具再回来、再调模型。这个模型对于简单的工具调用场景足够用,但一旦遇到以下几种情况,就开始捉襟见肘。
第一种是多步骤分支。假设需要先判断用户意图,然后根据意图走完全不同的子流程,子流程结束后还需要汇总结果再回复用户。AgentExecutor 的循环模型表达这类逻辑,需要把分支全部塞进提示词,或者用条件回调硬写,代码很快就乱成一团。
第二种是状态持久化。用户和 Agent 聊了几十轮,中途关掉了页面,下次再打开希望从上次停下的地方继续。LangChain 本身没有原生的持久化机制,记忆模块只是把消息列表临时存在内存里,进程一停就没了。
第三种是人机协同。工作流执行到某个敏感节点,需要暂停下来等人类审核,审核通过后才能继续往下跑。这种"执行中途打断、人工介入、再恢复"的场景,在 AgentExecutor 里几乎无法干净地实现。
LangGraph 把上面这些问题都纳入了核心设计。它的思路是把整个 Agent 或工作流建模成一张图,节点是计算步骤,边是流转路径,状态是在整张图上流动的数据。图可以有条件边,可以有回边,可以在任意节点打断并恢复,状态可以持久化到数据库。
LangGraph 的核心思路
理解 LangGraph 最好的方式是先搞清楚它的三个基本概念:状态、节点和边。
状态是图执行过程中一直流动的数据对象,可以把它想象成贯穿整个流程的"共享变量包",每个节点都可以读取里面的内容,也可以往里写新的内容。最常用的状态定义是 MessagesAnnotation,它把状态简化为一个消息列表,非常适合对话类应用。如果需要追踪工具调用次数、用户身份、中间计算结果等自定义字段,也可以用 Annotation 自己定义状态结构。
节点是图里的计算单元,每个节点就是一个普通的异步函数,接收当前状态作为参数,执行完后返回需要更新的状态字段。节点可以承担调用模型、执行工具、查询数据库、等待人工审核等任何有意义的计算步骤。
边是节点之间的连接。普通的边直接指向下一个节点,条件边则根据当前状态的内容动态决定下一跳,类似代码里的 if/else。图的执行从特殊的 __start__ 节点开始,到 __end__ 节点结束。
执行时,用户消息随状态流入 callModel 节点,模型回复追加到消息列表后随状态流出,整个过程一进一出,结构极其简单。如需在代码里取出结果,用 result.messages.at(-1) 拿最后一条即可。
下面这张图把五个关键步骤画在一条主线上,如下图所示。

用户发消息进入状态,callModel 节点读取、调用模型、追加回复,状态带着结果流到终点。
再复杂一点,加上工具调用和条件路由,图就具备了循环能力,如下图所示。

加入工具节点和条件边后,调用模型、执行工具、再次调模型形成完整的回路,整个逻辑一眼就能读懂。
LangGraph 和 LangChain 怎么分工
LangGraph 负责"流程怎么跑",它本身不绑定任何模型供应商,也不提供工具的具体实现,只管图的执行调度、状态的流转与持久化。LangChain 负责"工具和模型是什么",它提供的 ChatOpenAI、tool、HumanMessage、提示模板、检索器这些组件,是节点函数里真正要调用的东西。
两者的关系是分层叠加,而不是二选一,如下图所示。

LangGraph 在上层负责调度与状态,LangChain 在下层提供模型与工具,两者分工明确、协同运作。
如果不确定自己的场景该用哪个,可以对照下面这张表。
| 场景 | 推荐 |
|---|---|
| 单次问答、简单链式调用 | LangChain |
| 一个模型加几个工具的轻量 Agent | LangChain |
| 多步骤、有明确分支的工作流 | LangGraph |
| 需要持久化对话或状态可回溯 | LangGraph |
| 多 Agent 协作、任务拆解 | LangGraph |
| 人机协同、需要中途暂停等待审核 | LangGraph |
LangGraph 的官方文档自己也在说,如果你的 Agent 只是一个简单的"模型加工具循环",用 LangChain 的 createReactAgent 快速搞定就好,没必要一开始就引入图的概念。但凡流程复杂到需要明确画出来才能讲清楚,就是 LangGraph 发力的时候了。
最小可运行的骨架
先把三个依赖装好。
bash
pnpm add @langchain/langgraph @langchain/core @langchain/openai
然后搭出下面三个文件的骨架,后面章节的示例都会在这个基础上扩展。
src/model.ts 负责模型初始化,集中管理密钥与接口地址,方便在多个图文件里复用。
ts
// src/model.ts
import { ChatOpenAI } from "@langchain/openai";
export const model = new ChatOpenAI({
model: "deepseek-chat",
apiKey: "sk-60816d9be57f4189b658f1eaee52382e",
configuration: { baseURL: "https://api.deepseek.com" },
});
src/graph.ts 定义图的结构,目前只有一个调用模型的节点。
ts
// src/graph.ts
import { StateGraph } from "@langchain/langgraph";
import { MessagesAnnotation } from "@langchain/core/messages";
import { model } from "./model";
async function callModel(state: typeof MessagesAnnotation.State) {
const response = await model.invoke(state.messages);
return { messages: [response] };
}
const graph = new StateGraph(MessagesAnnotation)
.addNode("callModel", callModel)
.addEdge("__start__", "callModel")
.addEdge("callModel", "__end__");
export const app = graph.compile();
src/index.ts 是入口,执行一次图并打印模型回复。
ts
// src/index.ts
import { HumanMessage } from "@langchain/core/messages";
import { app } from "./graph";
const result = await app.invoke({
messages: [new HumanMessage("你好,介绍一下 LangGraph")],
});
console.log(result.messages.at(-1)?.content);
现在这个骨架已经是真正可以运行的 LangGraph 应用了:输入一条用户消息,callModel 节点调用模型后把响应追加到状态里,图执行完后取出最后一条消息打印。下一章的 Quickstart 会在这个基础上加入工具绑定、条件边和 checkpointer 持久化,让图逐渐"活"起来。
小结
LangGraph 出现是因为 LangChain 的链式和循环模型在多分支、持久化、人机协同这类复杂场景下力不从心,它用状态、节点、边三个概念把工作流建模成图,状态贯穿全图流动,节点负责处理状态,边决定下一跳的走向。LangChain 和 LangGraph 不是竞争关系,前者提供模型与工具,后者负责编排与调度,两者叠加才是完整的应用架构。后面所有章节的示例都会在 model.ts、graph.ts、index.ts 这三个文件的骨架上扩展。