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

390 阅读11分钟

🔥 本文 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
  2. 核心概念速览
  3. StateGraph 状态图
  4. Annotation 状态定义
  5. Node 节点详解
  6. Edge 边与流程控制
  7. 条件分支实战
  8. 循环流程实现
  9. ReAct Agent 完整实战
  10. 流式输出
  11. 最佳实践与踩坑指南
  12. 附录:LLM 参数配置

1. 什么是 LangGraph

1.1 一句话定义

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

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

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

1.2 为什么需要 LangGraph?

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

1.3 安装

npm install @langchain/langgraph

2. 核心概念速览

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

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

3. StateGraph 状态图

3.1 基本用法

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「状态长什么样」+「状态怎么更新」

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

📦 Annotation 定义「快递单」格式

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

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

4.2 基本语法

import { Annotation } from "@langchain/langgraph";

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

4.3 Reducer 详解

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

// 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 实际案例

// 对话 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?

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

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

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

5. Node 节点详解

5.1 节点函数签名

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

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

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

5.2 常见节点类型

// 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 返回值规则

// ✅ 正确:只返回需要更新的字段
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 = 流程图里的「箭头」= 定义执行顺序

        Edge(边)
           ↓
[START] ────────→ [NodeA] ────────→ [END]
          ↑                  ↑
       这是边             这也是边

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

6.2 两种边的对比

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

6.3 普通边语法

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可分支、循环
条件分支❌ 不支持✅ 支持
循环❌ 不支持✅ 支持
pipe():      A ──→ B ──→ C ──→ 结束(只能往前)

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

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


7. 条件分支实战

7.1 语法

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

7.2 路由函数

// 路由函数:接收 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 完整示例:情感分析路由

// 路由函数
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);

流程图:

                    ┌─────────────┐
               ┌───→│  positive   │───┐
               │    └─────────────┘   │
[START][analyze]                     │→[END]
               │    ┌─────────────┐   │
               ├───→│  negative   │───┤
               │    └─────────────┘   │
               │    ┌─────────────┐   │
               └───→│  neutral    │───┘
                    └─────────────┘

8. 循环流程实现

8.1 核心思路

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

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,
  });

流程图:

[START] ──→ [process] ──→ [check]
                ↑            │
                │  continue  │
                └────────────┤
                             │ end
                             ↓
                          [END]

8.2 ⚠️ 防止无限循环

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

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 自主使用工具的模式:

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

9.2 流程图

[START] ──→ [agent] ──→ [router]
               ↑            │
               │   tools    │ has_tools
               │  ┌─────────┴─────────┐
               │  ↓                   ↓
           [tools]              no_tools
               │                      │
               └──────────────────────↓
                                   [END]

9.3 完整代码

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 执行流程演示

用户: "北京天气怎么样?"

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真正执行工具调用执行者
┌──────────────────────────────────────────────────────────┐
│                                                          │
│  agentNode (内部用 llmWithTools)                         │
│  ┌────────────────────────────────────────────────────┐  │
│  │ LLM: "用户问天气,我需要调用 get_weather"           │  │
│  │      ↓                                             │  │
│  │ 输出: tool_calls: [{name: "get_weather", ...}]     │  │
│  │      (只是"说"要调用,没有真的调)                  │  │
│  └────────────────────────────────────────────────────┘  │
│                          │                               │
│                          ▼                               │
│  toolNode                                                │
│  ┌────────────────────────────────────────────────────┐  │
│  │ 执行: get_weather("北京")                          │  │
│  │ 返回: "北京:晴天,15°C"                           │  │
│  │      (真正调用工具,拿到结果)                      │  │
│  └────────────────────────────────────────────────────┘  │
│                                                          │
└──────────────────────────────────────────────────────────┘

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


10. 流式输出

10.1 invoke vs stream

// 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 状态设计

// ✅ 好的设计
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 节点设计:单一职责

// ✅ 好:每个节点只做一件事
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 错误处理

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 循环必须设置上限

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 中使用

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

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

🎯 快速参考模板

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

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