🔥 本文 1.2w 字,从入门到实战,带你彻底搞懂 LangGraph!适合有一定 LangChain 基础的开发者。
📌 前言
你是否遇到过这些问题?
- 用 LangChain 的
pipe()写复杂流程时,代码变得一团乱麻? - 想让 AI Agent 自动循环调用工具,却不知道怎么实现?
- 需要根据条件走不同分支,却发现 Chain 不支持?
如果你有以上困扰,LangGraph 就是你的答案!
LangGraph 是 LangChain 团队推出的工作流编排框架 ,专门用于构建复杂的 AI Agent。它用图(Graph) 的方式来定义工作流,原生支持分支、循环、状态管理,是构建生产级 AI 应用的利器。
阅读本文你将收获:
- ✅ 理解 LangGraph 的核心概念(StateGraph、Annotation、Node、Edge)
- ✅ 掌握状态管理和流程控制
- ✅ 学会实现条件分支和循环流程
- ✅ 能够独立构建 ReAct Agent
📚 目录
- [什么是 LangGraph](#什么是 LangGraph "#1-%E4%BB%80%E4%B9%88%E6%98%AF-langgraph")
- 核心概念速览
- [StateGraph 状态图](#StateGraph 状态图 "#3-stategraph-%E7%8A%B6%E6%80%81%E5%9B%BE")
- [Annotation 状态定义](#Annotation 状态定义 "#4-annotation-%E7%8A%B6%E6%80%81%E5%AE%9A%E4%B9%89")
- [Node 节点详解](#Node 节点详解 "#5-node-%E8%8A%82%E7%82%B9%E8%AF%A6%E8%A7%A3")
- [Edge 边与流程控制](#Edge 边与流程控制 "#6-edge-%E8%BE%B9%E4%B8%8E%E6%B5%81%E7%A8%8B%E6%8E%A7%E5%88%B6")
- 条件分支实战
- 循环流程实现
- [ReAct Agent 完整实战](#ReAct Agent 完整实战 "#9-react-agent-%E5%AE%8C%E6%95%B4%E5%AE%9E%E6%88%98")
- 流式输出
- 最佳实践与踩坑指南
- [附录: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 重要概念辨析
很多人混淆 llmWithTools 和 toolNode:
| 概念 | 作用 | 类比 |
|---|---|---|
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 工作流。
学习建议:
- 先从简单的串行流程开始
- 逐步添加条件分支
- 最后实现完整的 ReAct Agent
每一步都运行代码,观察状态的变化,你会很快上手!
如果这篇文章对你有帮助,欢迎点赞 👍 收藏 ⭐ 关注,后续还会分享更多 AI 开发干货!
有问题欢迎评论区交流~ 🎉