🚀 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 开发干货!

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

相关推荐
2401_8772742415 小时前
LangChain聊天模型---工具调用
langchain
万事可爱^15 小时前
LangChain v1.0学习笔记(4)—— 核心组件Models
人工智能·笔记·学习·langchain·大模型
饭勺oO16 小时前
AI 编程配置太头疼?ACP 帮你一键搞定,再也不用反复折腾!
ai·prompt·agent·acp·mcp·skills·agent skill
AGI杂货铺16 小时前
零基础也能快速搭建的Deep Agents
ai·langchain·llm·agent·deepagent
AlienZHOU16 小时前
MCP 是最大骗局?Skills 才是救星?
agent·mcp·vibecoding
Glink17 小时前
从零开始编写自己的AI账单Agent
前端·agent·ai编程
Hilaku17 小时前
我是如何用一行 JS 代码,让你的浏览器内存瞬间崩溃的?
前端·javascript·node.js
进阶的鱼17 小时前
一文助你了解Langchain
python·langchain·agent
五仁火烧17 小时前
npm run build命令详解
前端·vue.js·npm·node.js
逸凡17 小时前
langchain核心组件 Model I/O(3)-OutputParser
langchain