🚀 LangGraph 保姆级教程:从零构建你的第一个 AI Agent 工作流

🔥 本文 1.2w 字,从入门到实战,带你彻底搞懂 LangGraph!适合有一定 LangChain 基础的开发者。

📌 前言

你是否遇到过这些问题?

  • 用 LangChain 的 pipe() 写复杂流程时,代码变得一团乱麻?
  • 想让 AI Agent 自动循环调用工具,却不知道怎么实现?
  • 需要根据条件走不同分支,却发现 Chain 不支持?

如果你有以上困扰,LangGraph 就是你的答案!

LangGraph 是 LangChain 团队推出的工作流编排框架 ,专门用于构建复杂的 AI Agent。它用图(Graph) 的方式来定义工作流,原生支持分支、循环、状态管理,是构建生产级 AI 应用的利器。

阅读本文你将收获:

  • ✅ 理解 LangGraph 的核心概念(StateGraph、Annotation、Node、Edge)
  • ✅ 掌握状态管理和流程控制
  • ✅ 学会实现条件分支和循环流程
  • ✅ 能够独立构建 ReAct Agent

📚 目录

  1. [什么是 LangGraph](#什么是 LangGraph "#1-%E4%BB%80%E4%B9%88%E6%98%AF-langgraph")
  2. 核心概念速览
  3. [StateGraph 状态图](#StateGraph 状态图 "#3-stategraph-%E7%8A%B6%E6%80%81%E5%9B%BE")
  4. [Annotation 状态定义](#Annotation 状态定义 "#4-annotation-%E7%8A%B6%E6%80%81%E5%AE%9A%E4%B9%89")
  5. [Node 节点详解](#Node 节点详解 "#5-node-%E8%8A%82%E7%82%B9%E8%AF%A6%E8%A7%A3")
  6. [Edge 边与流程控制](#Edge 边与流程控制 "#6-edge-%E8%BE%B9%E4%B8%8E%E6%B5%81%E7%A8%8B%E6%8E%A7%E5%88%B6")
  7. 条件分支实战
  8. 循环流程实现
  9. [ReAct Agent 完整实战](#ReAct Agent 完整实战 "#9-react-agent-%E5%AE%8C%E6%95%B4%E5%AE%9E%E6%88%98")
  10. 流式输出
  11. 最佳实践与踩坑指南
  12. [附录:LLM 参数配置](#附录:LLM 参数配置 "#12-%E9%99%84%E5%BD%95llm-%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE")

1. 什么是 LangGraph

1.1 一句话定义

LangGraph = 用「画流程图」的方式构建 AI 应用

它将工作流建模为图(Graph):

  • 节点(Nodes) = 执行步骤(函数)
  • 边(Edges) = 步骤之间的连线
  • 状态(State) = 在节点间传递的数据

1.2 为什么需要 LangGraph?

场景 LangChain (pipe) LangGraph
简单线性流程 ✅ 足够 可以但没必要
条件分支 ❌ 不支持 ✅ 原生支持
循环/迭代 ❌ 难实现 ✅ 原生支持
复杂 Agent ❌ 需手写循环 ✅ 声明式定义
状态管理 ❌ 手动传递 ✅ 自动管理
可视化调试 ❌ 困难 ✅ LangGraph Studio

1.3 安装

bash 复制代码
npm install @langchain/langgraph

2. 核心概念速览

在深入细节之前,先建立全局认知:

scss 复制代码
┌─────────────────────────────────────────────────────────┐
│                  LangGraph 核心概念                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│    StateGraph (状态图容器)                                │
│    ┌───────────────────────────────────────────────┐    │
│    │                                               │    │
│    │   [START] ──→ [Node1] ──→ [Node2] ──→ [END]  │    │
│    │                 │           │                │    │
│    │                 ▼           ▼                │    │
│    │            ┌─────────────────────┐          │    │
│    │            │  State (共享状态)    │          │    │
│    │            │  { input, output }  │          │    │
│    │            └─────────────────────┘          │    │
│    └───────────────────────────────────────────────┘    │
│                                                         │
└─────────────────────────────────────────────────────────┘
组件 作用 类比
StateGraph 状态图容器 流程图画布
State 工作流数据 全局变量
Annotation 状态结构定义 TypeScript 接口
Node 执行单元 函数
Edge 连接关系 箭头
START 起始点 main()
END 结束点 return

3. StateGraph 状态图

3.1 基本用法

javascript 复制代码
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";

// 1️⃣ 定义状态结构
const MyState = Annotation.Root({
  input: Annotation({ reducer: (_, x) => x, default: () => "" }),
  output: Annotation({ reducer: (_, x) => x, default: () => "" }),
});

// 2️⃣ 创建状态图
const graph = new StateGraph(MyState)
  .addNode("step1", step1Function) // 添加节点
  .addNode("step2", step2Function)
  .addEdge(START, "step1") // 添加边
  .addEdge("step1", "step2")
  .addEdge("step2", END);

// 3️⃣ 编译
const app = graph.compile();

// 4️⃣ 执行
const result = await app.invoke({ input: "hello" });

3.2 核心 API

方法 作用 示例
addNode(name, fn) 添加节点 graph.addNode("process", processFn)
addEdge(from, to) 添加无条件边 graph.addEdge("A", "B")
addConditionalEdges() 添加条件边 见下文详解
compile() 编译图 const app = graph.compile()
invoke(state) 同步执行 await app.invoke({...})
stream(state) 流式执行 for await (const e of app.stream({...}))

4. Annotation 状态定义

4.1 什么是 Annotation?

Annotation = 告诉 LangGraph「状态长什么样」+「状态怎么更新」

把它想象成「快递单」的格式定义:

markdown 复制代码
📦 Annotation 定义「快递单」格式

   快递单上有哪些字段?
   ┌─────────────────────────────────┐
   │ 收件人: ________                │
   │ 地址:   ________                │
   │ 物品:   ________                │
   │ 备注:   ________ (可追加)       │
   └─────────────────────────────────┘

   每个站点(节点)都能看到这张单,也能修改它

4.2 基本语法

javascript 复制代码
import { Annotation } from "@langchain/langgraph";

const MyState = Annotation.Root({
  fieldName: Annotation({
    reducer: reducerFunction, // 状态如何更新
    default: defaultFunction, // 默认值
  }),
});

4.3 Reducer 详解

Reducer 决定「当节点返回新值时,怎么更新状态」:

javascript 复制代码
// 1. 替换模式 - 新值覆盖旧值
reducer: (prev, next) => next;
// 节点返回 { name: "李四" } → state.name 变成 "李四"

// 2. 累加模式 - 适用于消息列表(最常用!)
reducer: (prev, next) => [...prev, ...next];
// 节点返回 { messages: [新消息] } → 追加到 messages 数组

// 3. 数字累加
reducer: (prev, next) => prev + next;
// 节点返回 { count: 1 } → count 加 1

4.4 实际案例

javascript 复制代码
// 对话 Agent 的状态定义
const AgentState = Annotation.Root({
  // 消息列表 - 累加模式(重要!)
  messages: Annotation({
    reducer: (prev, next) => [...prev, ...next],
    default: () => [],
  }),

  // 当前任务 - 替换模式
  currentTask: Annotation({
    reducer: (_, next) => next,
    default: () => "",
  }),

  // 迭代次数 - 数字累加
  iterations: Annotation({
    reducer: (prev, next) => prev + next,
    default: () => 0,
  }),
});

4.5 为什么需要 Reducer?

痛点:多个节点都想更新同一个字段

css 复制代码
例如:消息列表
  - 节点 A 返回: { messages: [new HumanMessage("hi")] }
  - 节点 B 返回: { messages: [new AIMessage("hello")] }

❌ 没有 Reducer:节点 B 的值覆盖节点 A,用户消息丢失!
✅ 有 Reducer:  [...prev, ...next],两条消息都保留!

5. Node 节点详解

5.1 节点函数签名

javascript 复制代码
// 节点 = 接收 state,返回部分更新的函数
async function myNode(state) {
  // 从 state 读取数据
  const input = state.input;

  // 处理逻辑
  const result = await someOperation(input);

  // 只返回需要更新的字段
  return { output: result };
}

5.2 常见节点类型

javascript 复制代码
// 1. 纯处理节点 - 数据处理,可以是同步的
function processNode(state) {
  return { result: state.input.toUpperCase() };
}

// 2. LLM 节点 - 调用 AI 模型(必须异步)
async function llmNode(state) {
  const response = await llm.invoke([new HumanMessage(state.question)]);
  return { answer: response.content };
}

// 3. API 节点 - 调用外部接口(必须异步)
async function apiNode(state) {
  const data = await fetch("https://api.example.com/data");
  return { apiResult: await data.json() };
}

// 4. 工具节点 - 使用内置 ToolNode
import { ToolNode } from "@langchain/langgraph/prebuilt";
const toolNode = new ToolNode(tools);

5.3 返回值规则

javascript 复制代码
// ✅ 正确:只返回需要更新的字段
function goodNode(state) {
  return { output: "result" };
}

// ❌ 错误:不需要展开整个 state
function badNode(state) {
  return { ...state, output: "result" };
}

// ✅ 正确:返回空对象 = 不更新任何字段
function noUpdateNode(state) {
  console.log(state);
  return {};
}

6. Edge 边与流程控制

6.1 什么是 Edge?

Edge = 流程图里的「箭头」= 定义执行顺序

css 复制代码
        Edge(边)
           ↓
[START] ────────→ [NodeA] ────────→ [END]
          ↑                  ↑
       这是边             这也是边

意思:START 完了执行 NodeA,NodeA 完了结束

6.2 两种边的对比

类型 说明 比喻 代码
普通边 A 完了一定到 B 直线 addEdge("A", "B")
条件边 根据状态决定去哪 岔路口 addConditionalEdges(...)

6.3 普通边语法

javascript 复制代码
import { START, END } from "@langchain/langgraph";

// 从 A 到 B
graph.addEdge("A", "B");

// 入口
graph.addEdge(START, "firstNode");

// 出口
graph.addEdge("lastNode", END);

// 链式调用
graph.addEdge(START, "A").addEdge("A", "B").addEdge("B", "C").addEdge("C", END);

6.4 addEdge vs pipe() 对比

如果你用过 LangChain 的 pipe()

特性 pipe() addEdge()
流程类型 只能线性 A→B→C 可分支、循环
条件分支 ❌ 不支持 ✅ 支持
循环 ❌ 不支持 ✅ 支持
less 复制代码
pipe():      A ──→ B ──→ C ──→ 结束(只能往前)

addEdge():   A ──→ B ──→ C ──→ 结束
                  ↑     │
                  └─────┘  (可以循环回去!)

💡 总结:简单线性流程用 pipe,需要分支/循环用 LangGraph!


7. 条件分支实战

7.1 语法

javascript 复制代码
graph.addConditionalEdges(
  fromNode, // 源节点
  routerFunction, // 路由函数
  routeMapping // 路由映射
);

7.2 路由函数

javascript 复制代码
// 路由函数:接收 state,返回路由 key
function router(state) {
  if (state.score > 80) return "high";
  if (state.score > 60) return "medium";
  return "low";
}

// 路由映射:key → 节点名
const routeMapping = {
  high: "celebrateNode",
  medium: "normalNode",
  low: "improveNode",
};

graph.addConditionalEdges("scoreNode", router, routeMapping);

7.3 完整示例:情感分析路由

javascript 复制代码
// 路由函数
function sentimentRouter(state) {
  const sentiment = state.sentiment;
  if (sentiment.includes("积极")) return "positive";
  if (sentiment.includes("消极")) return "negative";
  return "neutral";
}

// 构建图
const graph = new StateGraph(MyState)
  .addNode("analyze", analyzeNode)
  .addNode("positive", positiveHandler)
  .addNode("negative", negativeHandler)
  .addNode("neutral", neutralHandler)
  .addEdge(START, "analyze")
  .addConditionalEdges("analyze", sentimentRouter, {
    positive: "positive",
    negative: "negative",
    neutral: "neutral",
  })
  .addEdge("positive", END)
  .addEdge("negative", END)
  .addEdge("neutral", END);

流程图:

css 复制代码
                    ┌─────────────┐
               ┌───→│  positive   │───┐
               │    └─────────────┘   │
[START]→[analyze]                     │→[END]
               │    ┌─────────────┐   │
               ├───→│  negative   │───┤
               │    └─────────────┘   │
               │    ┌─────────────┐   │
               └───→│  neutral    │───┘
                    └─────────────┘

8. 循环流程实现

8.1 核心思路

通过条件边实现循环:当条件满足时,流程回到之前的节点。

javascript 复制代码
function shouldContinue(state) {
  // 安全保护:最多迭代 N 次
  if (state.iterations >= 3) return "end";
  // 业务条件
  if (state.isComplete) return "end";
  return "continue";
}

const graph = new StateGraph(MyState)
  .addNode("process", processNode)
  .addNode("check", checkNode)
  .addEdge(START, "process")
  .addEdge("process", "check")
  .addConditionalEdges("check", shouldContinue, {
    continue: "process", // 🔄 循环回 process
    end: END,
  });

流程图:

css 复制代码
[START] ──→ [process] ──→ [check]
                ↑            │
                │  continue  │
                └────────────┤
                             │ end
                             ↓
                          [END]

8.2 ⚠️ 防止无限循环

这是最重要的!一定要设置最大迭代次数:

javascript 复制代码
const MAX_ITERATIONS = 10;

function shouldContinue(state) {
  // 1️⃣ 先检查迭代次数(安全保护)
  if (state.iterations >= MAX_ITERATIONS) {
    console.warn("⚠️ 达到最大迭代次数,强制结束");
    return "end";
  }

  // 2️⃣ 再检查业务条件
  if (state.isComplete) return "end";

  return "continue";
}

9. ReAct Agent 完整实战

9.1 什么是 ReAct?

ReAct = Reasoning + Acting,让 LLM 自主使用工具的模式:

markdown 复制代码
1. 🤔 Reasoning (推理): LLM 分析问题,决定需要什么
2. 🔧 Acting (行动): 调用工具获取信息
3. 👀 Observation (观察): 查看工具返回结果
4. 🔄 循环: 直到可以给出最终答案

9.2 流程图

css 复制代码
[START] ──→ [agent] ──→ [router]
               ↑            │
               │   tools    │ has_tools
               │  ┌─────────┴─────────┐
               │  ↓                   ↓
           [tools]              no_tools
               │                      │
               └──────────────────────↓
                                   [END]

9.3 完整代码

javascript 复制代码
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { HumanMessage } from "@langchain/core/messages";

// 1️⃣ 定义状态
const AgentState = Annotation.Root({
  messages: Annotation({
    reducer: (prev, next) => [...prev, ...next],
    default: () => [],
  }),
});

// 2️⃣ 定义工具 & 绑定到 LLM
const tools = [calculatorTool, searchTool, weatherTool];
const llmWithTools = llm.bindTools(tools);

// 3️⃣ Agent 节点:调用 LLM 做决策
async function agentNode(state) {
  const response = await llmWithTools.invoke(state.messages);
  return { messages: [response] };
}

// 4️⃣ 工具节点:真正执行工具
const toolNode = new ToolNode(tools);

// 5️⃣ 路由函数:判断是否需要调用工具
function shouldCallTools(state) {
  const lastMessage = state.messages[state.messages.length - 1];
  if (lastMessage.tool_calls?.length > 0) {
    return "tools";
  }
  return "end";
}

// 6️⃣ 构建图
const agentGraph = new StateGraph(AgentState)
  .addNode("agent", agentNode)
  .addNode("tools", toolNode)
  .addEdge(START, "agent")
  .addConditionalEdges("agent", shouldCallTools, {
    tools: "tools",
    end: END,
  })
  .addEdge("tools", "agent"); // 🔄 工具执行完回到 agent

// 7️⃣ 编译 & 执行
const agent = agentGraph.compile();
const result = await agent.invoke({
  messages: [new HumanMessage("北京天气怎么样?")],
});

9.4 执行流程演示

vbnet 复制代码
用户: "北京天气怎么样?"

Step 1: [agent]
  └─ LLM 思考: "需要调用 get_weather 工具"
  └─ 输出: tool_calls: [{name: "get_weather", args: {city: "北京"}}]

Step 2: [router] → 检测到 tool_calls → 去 [tools]

Step 3: [tools]
  └─ 执行: get_weather("北京")
  └─ 返回: "北京:晴天,15°C"

Step 4: [agent]
  └─ LLM 看到工具结果
  └─ 生成最终回答,无 tool_calls

Step 5: [router] → 无 tool_calls → 去 [END]

最终输出: "北京今天晴天,气温15°C,是个好天气!"

9.5 重要概念辨析

很多人混淆 llmWithToolstoolNode

概念 作用 类比
llmWithTools 让 LLM 知道有哪些工具 工具说明书
agentNode 调用 LLM 决策是否用工具 决策者
toolNode 真正执行工具调用 执行者
css 复制代码
┌──────────────────────────────────────────────────────────┐
│                                                          │
│  agentNode (内部用 llmWithTools)                         │
│  ┌────────────────────────────────────────────────────┐  │
│  │ LLM: "用户问天气,我需要调用 get_weather"           │  │
│  │      ↓                                             │  │
│  │ 输出: tool_calls: [{name: "get_weather", ...}]     │  │
│  │      (只是"说"要调用,没有真的调)                  │  │
│  └────────────────────────────────────────────────────┘  │
│                          │                               │
│                          ▼                               │
│  toolNode                                                │
│  ┌────────────────────────────────────────────────────┐  │
│  │ 执行: get_weather("北京")                          │  │
│  │ 返回: "北京:晴天,15°C"                           │  │
│  │      (真正调用工具,拿到结果)                      │  │
│  └────────────────────────────────────────────────────┘  │
│                                                          │
└──────────────────────────────────────────────────────────┘

💡 记忆口诀:llmWithTools 让 LLM「知道」工具,toolNode 让工具「执行」!


10. 流式输出

10.1 invoke vs stream

javascript 复制代码
// invoke: 等待全部完成
const result = await app.invoke(initialState);

// stream: 实时返回每个节点输出
for await (const event of app.stream(initialState)) {
  console.log(event);
  // { "agent": { messages: [...] } }
  // { "tools": { messages: [...] } }
}

10.2 优势

  • 用户体验:实时看到进度,减少等待焦虑
  • 调试方便:看到每步中间结果
  • 早期发现:某节点出错可尽早发现
  • 内存友好:边处理边输出

11. 最佳实践与踩坑指南

11.1 状态设计

javascript 复制代码
// ✅ 好的设计
const GoodState = Annotation.Root({
  userQuestion: Annotation({ ... }),      // 清晰命名
  chatHistory: Annotation({
    reducer: (p, n) => [...p, ...n],      // 合适的 reducer
  }),
  retryCount: Annotation({
    default: () => 0,                     // 明确默认值
  }),
});

// ❌ 不好的设计
const BadState = Annotation.Root({
  data: Annotation({ ... }),   // 命名模糊
  x: Annotation({ ... }),      // 含义不明
});

11.2 节点设计:单一职责

javascript 复制代码
// ✅ 好:每个节点只做一件事
async function translateNode(state) {
  const translated = await llm.invoke([...]);
  return { translatedText: translated.content };
}

async function analyzeNode(state) {
  const analysis = await llm.invoke([...]);
  return { analysisResult: analysis.content };
}

// ❌ 不好:一个节点做太多事
async function doEverythingNode(state) {
  // 翻译 + 分析 + 总结 + 生成报告...
}

11.3 错误处理

javascript 复制代码
async function safeNode(state) {
  try {
    const result = await riskyOperation(state);
    return { result, error: null };
  } catch (error) {
    console.error("节点执行失败:", error);
    return { result: null, error: error.message };
  }
}

// 在路由中处理错误
function router(state) {
  if (state.error) return "errorHandler";
  return "nextStep";
}

11.4 循环必须设置上限

javascript 复制代码
const MAX_ITERATIONS = 10; // 必须!

function loopRouter(state) {
  if (state.iterations >= MAX_ITERATIONS) {
    console.warn("⚠️ 达到最大迭代次数");
    return "end";
  }
  if (state.isComplete) return "end";
  return "continue";
}

12. 附录:LLM 参数配置

12.1 temperature 参数

效果 输出特点
0 确定性最高 每次几乎相同
0.1-0.3 低随机性 稳定、可预测
0.5-0.7 中等 平衡创意和一致性
0.8-1.0 高随机性 更有创意

12.2 推荐值

场景 推荐值
代码生成 / 数学 0 ~ 0.2
数据提取 / 分类 0
问答 / 知识检索 0 ~ 0.3
通用对话 0.5 ~ 0.7
创意写作 0.7 ~ 1.0

12.3 在 LangGraph 中使用

javascript 复制代码
// 不同节点用不同 temperature
async function codeNode(state) {
  const precisionLLM = new ChatDeepSeek({ temperature: 0.1 });
  // ...
}

async function creativeNode(state) {
  const creativeLLM = new ChatDeepSeek({ temperature: 0.9 });
  // ...
}

🎯 快速参考模板

javascript 复制代码
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
import { ToolNode } from "@langchain/langgraph/prebuilt";

// 1. 定义状态
const MyState = Annotation.Root({
  input: Annotation({ reducer: (_, x) => x, default: () => "" }),
  output: Annotation({ reducer: (_, x) => x, default: () => "" }),
});

// 2. 定义节点
async function myNode(state) {
  return { output: "processed" };
}

// 3. 定义路由
function myRouter(state) {
  return state.output ? "end" : "continue";
}

// 4. 构建图
const graph = new StateGraph(MyState)
  .addNode("myNode", myNode)
  .addEdge(START, "myNode")
  .addConditionalEdges("myNode", myRouter, {
    continue: "myNode",
    end: END,
  });

// 5. 编译执行
const app = graph.compile();
const result = await app.invoke({ input: "hello" });

📖 学习资源


写在最后

LangGraph 的核心就是用「画流程图」的思维来构建 AI 应用。掌握了 State、Node、Edge 这三个核心概念,你就能构建各种复杂的 AI 工作流。

学习建议

  1. 先从简单的串行流程开始
  2. 逐步添加条件分支
  3. 最后实现完整的 ReAct Agent

每一步都运行代码,观察状态的变化,你会很快上手!


如果这篇文章对你有帮助,欢迎点赞 👍 收藏 ⭐ 关注,后续还会分享更多 AI 开发干货!

有问题欢迎评论区交流~ 🎉

相关推荐
小胖霞1 小时前
彻底搞懂 JWT 登录认证与路由守卫(五)
前端·vue.js·node.js
程序员爱钓鱼2 小时前
Node.js 与前端 JavaScript 的区别:不仅仅是“运行环境不同”
后端·node.js
老前端的功夫3 小时前
Webpack 深度解析:从配置哲学到编译原理
前端·webpack·前端框架·node.js
良木林3 小时前
webpack:快速搭建环境
前端·webpack·node.js
天星烛4 小时前
Milvus 分词器学习笔记
langchain
再会呀4 小时前
[Ai Agent] 10 MCP基础:打破孤岛,让MCP连接万物
langchain·mcp
陈鋆4 小时前
Langchain-Chatchat[三、知识库管理的 RAG]
windows·langchain
casterQ4 小时前
4. Agent Quality ——【Google 5-Day AI Agents】
人工智能·llm·agent·langgraph·adk