AI时代,使用大模型学习LangChain (4)——LangGraph

前言

LangChain 作为一个2022就已经开源的框架,在设计上也是满足当时的需求,在大模型还没有那么强的时间点,AI能执行一个线性的流程就已经能满足了人们的想象和期待了。

时间再回到今天,在大模型能力爆炸式增长下,人们使用大模型的方式也逐步进化,Claude Code、OpenCode、Cusor等Agent的出现,Context、Rule、Skills、Harness等概念的流行,极大扩展了Agent的能力边界。线性的LangChain显然满足不了现在大模型的能力。在2025年这个AI爆发的时间节点,出现了LangGraph。

LangGraph

不同于LangChian的链式设计,LangGraph它将应用程序建模为一个图(Graph),通过节点、边和状态的协同工作来编排复杂的工作流。

为什么需要 LangGraph?

LangChain 的组合是线性的A → B → C → D,数据单向流动。

但很多 AI 场景需要循环和分支

makefile 复制代码
Agent 循环:     LLM ⇄ 工具执行(需要循环)
审批工作流:     提交 → 审查 ⇄ 修改 → 批准(需要条件和循环)
多 Agent:       Supervisor → Researcher → Writer → Supervisor(复杂路由)

这些用纯 LangChain 很难表达,LangGraph 就是为此设计的。

三个核心概念

LangGraph 的所有能力都建立在三个概念之上:State(状态)、Node(节点)、Edge(边) 。理解三者的协作方式,就理解了 LangGraph 的全部。它们的关系可以一句话概括:State 是共享数据、Node 改 State、Edge 决定下一个 Node 是谁

1. State(状态)------ 图的"全局共享数据"

State 是整个图运行期间所有节点共享的数据结构。前端的朋友会对这个概念非常熟悉,他和前端里的State概念很像,表示在程序中流转的数据。状态在图中的节点执行时传递,每个节点在执行后使用其返回值更新此内部状态。图更新其内部状态的方式由所选图类型或自定义函数定义。

State 用 Annotation.Root 声明式定义,每个字段可以指定自己的 reducer------也就是"当节点更新这个字段时,应该怎么和现有值合并"。这个设计至关重要:

  • messages 字段:每轮对话产生的 AI 消息和工具结果都该追加 到历史里,所以 reducer 是 [...current, ...update]。如果没有这个,每一步都会把之前的历史覆盖掉,Agent 就"失忆"了。
  • iteration_count 这种字段:默认 reducer 是直接覆盖,因为计数器只要最新值。

这种"每个字段自带合并策略"的设计让多分支并行更新 State 时不会互相打架------A 节点追加 messages、B 节点更新 retrieved_docs,两边的 partial update 用各自的 reducer 合并,互不干扰。

2. Node(节点)------ 图的"处理单元"

Node 是图里干活的函数。每个节点接收完整的当前 State ,执行一段逻辑(调 LLM、跑工具、查数据库、做校验......),然后返回部分更新Partial<State>)。

关键设计:节点只返回它要更新的字段,不返回完整的新 State。框架拿到这个 partial 后,会用对应字段的 reducer 自动合并进当前 State。这带来三个直接后果:

  1. 节点之间解耦 ------你写 agent 节点时不需要知道 tools 节点存在,只要从 state.messages 读、往里写就行。
  2. 并行安全 ------多个节点可以同时更新不同字段,因为 reducer 处理合并。
  3. 可 Checkpoint ------节点本身是无状态纯函数(输入 state → 输出 update),所有状态都在图里,所以每一步的状态快照能被完整序列化保存。
typescript 复制代码
async function callModel(state: typeof State.State): Promise<Partial<typeof State.State>> {
  const response = await model.invoke(state.messages);
  return { messages: [response] };  // 只更新 messages 字段
}

3. Edge(边)------ 图的"流转规则"

Edge 决定执行完一个节点之后该去哪。这是 LangGraph 能表达循环和分支的根本机制------这些控制流在 LCEL 链里是写不出来的。LangGraph 有三种边:

  • 普通边addEdge):A 执行完一定去 B。等价于"A → B"的固定连线,是最简单的顺序流转。
  • 条件边addConditionalEdges):A 执行完后调用一个路由函数 ,由路由函数根据当前 State 决定去哪个节点。等价于声明式的 switch/if-else,但路由规则写在图结构里------这意味着流程可以被可视化、被审计、被静态分析。
  • 回环边 (节点连回前序节点):这是图区别于链的根本特征。链只能 A→B→C 一条道走到底;图可以让 C→A 形成循环------这正是 Agent 反复"思考→行动→观察"的实现方式。
typescript 复制代码
// 普通边:A → B,无条件
.addEdge("agent", "tools")

// 条件边:A → ?,根据状态决定
.addConditionalEdges("agent", (state) => {
  if (state.messages.at(-1)?.tool_calls?.length) return "tools";
  return END;
})

// 回环边:tools → agent(形成循环,这就是 Agent 的 ReAct 循环)
.addEdge("tools", "agent")

三者如何协作

把上面三个概念串起来,一次完整的图执行是这样的:

sql 复制代码
用户调用 invoke(input)
   ↓
框架初始化 State(用 input 填充初始字段)
   ↓
按 Edge 规则选下一个 Node(从 START 出发)
   ↓
Node 执行:读 State → 返回 partial update
   ↓
框架按 reducer 把 update 合并进 State
   ↓
(可选)Checkpointer 保存当前 State 快照
   ↓
按 Edge 规则选下一个 Node ...... 循环直到走到 END

基础案例

typescript 复制代码
// ─── 1. 第一个 StateGraph ──────────────────
  console.log("📌 1. 构建第一个 StateGraph:简单的步骤流水线\n");

  // 定义状态结构
  const PipelineState = Annotation.Root({
    text: Annotation<string>,           // 文本内容
    step: Annotation<string>,           // 当前步骤名
    history: Annotation<string[]>({     // 步骤历史(reducer: 追加而非覆盖)
      default: () => [],
      reducer: (current, update) => [...current, ...update],
    }),
  });

  // 构建图
  const pipelineGraph = new StateGraph(PipelineState)
    // 添加节点:每个节点是一个处理函数
    .addNode("trim", (state) => {
      const trimmed = state.text.trim();
      return {
        text: trimmed,
        step: "trim",
        history: [`[trim] 去除首尾空格`],
      };
    })
    .addNode("lowercase", (state) => {
      return {
        text: state.text.toLowerCase(),
        step: "lowercase",
        history: [`[lowercase] 转为小写`],
      };
    })
    .addNode("addPrefix", (state) => {
      return {
        text: `处理结果: ${state.text}`,
        step: "addPrefix",
        history: [`[addPrefix] 添加前缀`],
      };
    })

    // 添加边:定义节点间的流转
    .addEdge(START, "trim")           // 从开始 → trim
    .addEdge("trim", "lowercase")     // trim → lowercase
    .addEdge("lowercase", "addPrefix") // lowercase → addPrefix
    .addEdge("addPrefix", END)        // addPrefix → 结束

    // 编译为可运行的实例
    .compile();

  console.log("图结构: START → trim → lowercase → addPrefix → END\n");

  const result = await pipelineGraph.invoke({
    text: "  Hello LangGraph!  ",
    step: "",
    history: [],
  });

  console.log(`  输入: "  Hello LangGraph!  "`);
  console.log(`  输出: ${result.text}`);
  console.log(`  历史: ${result.history.join(" → ")}\n`);

上面的例子,我们可以简单看到State、Node、Edge三者的关系。

  • State是全局的状态,因此作为一个StateGraph的构造参数。
  • 通过addNode添加多个Node,Node中定义了行为,行为中简单处理并返回了State.
  • 通过addEdge添加节点间流转的规则,两个参数表示这两个步骤中间要形成一个向下流通的关系。

以上就是一个线性的LangGrapha。

带分支处理的图

只是线性的LangGraph仍然是链的结构,图是非线性的,可以有分支处理,也可以回退循坏。下面是一个带分支处理的图。

typescript 复制代码
// 一个简单的"审批工作流"
 const ApprovalState = Annotation.Root({
   document: Annotation<string>,
   status: Annotation<"draft" | "review" | "approved" | "rejected">,
   reviewerCount: Annotation<number>,
 });

 const approvalGraph = new StateGraph(ApprovalState)
   .addNode("submit", (state) => {
     console.log("  📄 提交文档进行审批...");
     return {
       status: "review" as const,
       reviewerCount: 0,
     };
   })
   .addNode("review", (state) => {
     const count = state.reviewerCount + 1;
     console.log(`  🔍 第 ${count} 位审查者正在审查...`);
     // 模拟:第二次审查时通过
     const newStatus = count >= 2 ? "approved" : "review";
     return {
       status: newStatus as "approved" | "review",
       reviewerCount: count,
     };
   })
   .addNode("approved", (state) => {
     console.log("  ✅ 文档已批准!");
     return {};
   })
   .addNode("rejected", (state) => {
     console.log("  ❌ 文档被拒绝");
     return {};
   })

   .addEdge(START, "submit")
   .addEdge("submit", "review")

   // 条件边:根据 status 决定下一步
   .addConditionalEdges("review", (state) => {
     console.log(`  🤔 当前状态: ${state.status}`);
     if (state.status === "approved") return "approved";
     if (state.status === "rejected") return "rejected";
     return "review"; // 继续审查
   })

   .addEdge("approved", END)
   .addEdge("rejected", END)

   .compile();

 console.log("审批工作流: submit → review ⇄ (条件循环) → approved/rejected\n");
 console.log('  模拟场景:需要2位审查者都通过\n');

 const approvalResult = await approvalGraph.invoke({
   document: "项目计划书 v2.0",
   status: "draft",
   reviewerCount: 0,
 });

 console.log(`\n  最终状态: ${approvalResult.status}`);
 console.log(`  审查次数: ${approvalResult.reviewerCount}\n`);

这里设计了一个流程,从开始之后下一步是提交,提交之后是审查,审查会产生两个分支,一个是通过,一个是拒绝,需要两位审查者都通过才能结束流程。

这里addConditionalEdges就是添加一个分支判断条件,会根据当前的State中的status参数去判断要进入哪一个Node中,review这个Node中模拟了审批的过程,并且增加了条件,必须第二次进入到review这个Node中才能将State中的Status置为approved以进入下一个最终的Nodeapproved

总结

LangGraph是在LangChain思想上的一个演进,从链式的线性执行,到了图的多状态流转、可回溯执行,更加符合了当前复杂的大模型应用。

LangGraph = 用"图"的方式编排有状态的 AI 工作流。支持循环、分支、暂停/恢复、状态持久化。

相关推荐
吃饱了得干活1 小时前
Agent 记忆系统:从短期记忆到长期记忆
python·langchain·agent
带娃的IT创业者2 小时前
PostHog 的 TypeScript 原生移植:一场开源产品工程化的自我革命
javascript·typescript·开源·开源软件·posthog·技术重构
知行合一。。。3 小时前
LangChain--11--Milvus
langchain·milvus
流浪0014 小时前
LLM 大模型三种主流接入方式详解(API / 本地部署 / SDK)
langchain·llm
二进制_博客15 小时前
LangSmith 调试
langchain·langsmith
DRXB25072021 小时前
开源自由还是生态红利?LangChain 的灵活性与小艺开放平台的鸿蒙流量池,开发者该如何抉择?
langchain·开源·harmonyos
鱼饼Y1 天前
AI时代,使用大模型学习LangChain (3)——LCEL
人工智能·langchain
用户667675093791 天前
LangChain 向量检索为什么要用 as_retriever?从 LCEL 到 RunnableParallel 一次讲清
langchain
lzjava20241 天前
LangGraph Subgraphs 子图
langchain