核心概念
引言
在前面的章节中,我们了解了什么是 LangGraph 以及为什么需要它。现在,让我们深入探讨 LangGraph 的核心概念。理解这些概念就像学习 React 时需要理解组件、状态和生命周期一样重要。
LangGraph 的核心概念包括:
- 图(Graph):整个应用的结构框架
- 状态(State):应用的数据流和存储
- 节点(Nodes):执行具体逻辑的单元
- 边(Edges):控制执行流程的连接
- Reducers:管理状态更新的机制
掌握这些概念后,你就能构建出功能强大且灵活的智能代理应用。
图(Graph)结构
什么是图?
在 LangGraph 中,图是整个应用的骨架结构,类似于:
- React 应用中的组件树
- Express 应用中的路由系统
- 状态机中的状态转换图
图的类型
LangGraph 提供两种主要的图类型:
StateGraph(推荐)
StateGraph 是最常用的图类型,允许你定义自定义的状态结构。你可以自由定义状态包含哪些字段、如何更新。
import { StateGraph, Annotation } from '@langchain/langgraph';
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: messagesStateReducer,
default: () => [],
}),
userInfo: Annotation<string>(),
});
const graph = new StateGraph(StateAnnotation);
MessageGraph(简化版)
MessageGraph 是简化版本,状态只包含消息数组,适合简单的对话场景。
import { MessageGraph } from '@langchain/langgraph';
// 状态自动为 { messages: BaseMessage[] }
const graph = new MessageGraph();
对于大多数应用,推荐使用 StateGraph,因为它提供了更大的灵活性和类型安全性。
编译过程
图在使用前必须经过编译,这个过程类似于 TypeScript 编译或 Webpack 打包。编译会验证图的结构是否正确(比如是否有孤立节点、是否有正确的入口和出口)。
// 构建图结构
const workflow = new StateGraph(StateAnnotation)
.addNode('input', processInput)
.addNode('llm', callLLM)
.addEdge('input', 'llm')
.addEdge('llm', END);
// 编译图(必需步骤)
const app = workflow.compile();
// 现在可以执行
const result = await app.invoke({ messages: [...] });
状态(State)管理
状态的概念
状态是 LangGraph 应用的数据中心,类似于 Redux 中的 store 或 React 中的 component state。
状态具有以下特点:
- 共享性:所有节点都可以访问和修改
- 持久性:在整个执行过程中保持
- 类型安全:通过 TypeScript 提供类型检查
使用 Annotation 定义状态
Annotation 是 LangGraph 定义状态的方式。每个字段可以指定:
- 类型:字段的 TypeScript 类型
- reducer:定义如何合并新值和旧值
- default:字段的默认值
import { Annotation, messagesStateReducer } from '@langchain/langgraph';
import { BaseMessage } from '@langchain/core/messages';
const StateAnnotation = Annotation.Root({
// 消息数组 - 使用内置的消息 reducer(会追加新消息)
messages: Annotation<BaseMessage[]>({
reducer: messagesStateReducer,
default: () => [],
}),
// 简单字符串字段(直接覆盖)
userInput: Annotation<string>(),
// 数字字段,带默认值
step: Annotation<number>({
default: () => 0,
}),
// 自定义 reducer - 对象合并
metadata: Annotation<Record<string, any>>({
reducer: (current, update) => ({ ...current, ...update }),
default: () => ({}),
}),
});
Reducer 的作用
Reducer 决定了当节点返回状态更新时,如何将新值与旧值合并。
没有 reducer 时:新值直接覆盖旧值
// 如果 userInput 没有 reducer,新值直接替换旧值
// 旧: { userInput: "hello" }
// 节点返回: { userInput: "world" }
// 新: { userInput: "world" }
有 reducer 时:通过 reducer 函数计算新值
// messages 使用 messagesStateReducer,会追加新消息
// 旧: { messages: [msg1] }
// 节点返回: { messages: [msg2] }
// 新: { messages: [msg1, msg2] }
常见的 reducer 模式:
| 模式 | 用途 | 示例 |
|---|---|---|
| 累加 | 数组追加 | (a, b) => [...a, ...b] |
| 合并 | 对象合并 | (a, b) => ({...a, ...b}) |
| 计数 | 数值累加 | (a, b) => a + b |
| 覆盖 | 直接替换 | 不设置 reducer |
状态设计最佳实践
好的实践:
const StateAnnotation = Annotation.Root({
// 使用具体的类型
messages: Annotation<BaseMessage[]>({
reducer: messagesStateReducer,
default: () => [],
}),
// 可选字段用 undefined 联合类型
currentTool: Annotation<string | undefined>(),
// 提供默认值
retryCount: Annotation<number>({
default: () => 0,
}),
});
应该避免:
- 使用
any类型(类型不安全) - 过于复杂的嵌套结构(难以维护)
- 在状态中存储函数或不可序列化的对象(影响持久化)
节点(Nodes)
节点的本质
节点是 LangGraph 中的执行单元。每个节点就是一个 JavaScript/TypeScript 函数,接收当前状态,返回状态更新。
// 节点函数签名
const myNode = (state: State) => {
// 读取状态
const value = state.someField;
// 返回部分状态更新
return {
updatedField: newValue,
};
};
重要概念:节点返回的是部分状态更新,不需要返回完整状态。LangGraph 会自动将更新合并到完整状态中。
节点类型
同步节点
最简单的节点类型,直接返回状态更新:
const processInputNode = (state: State) => {
const processed = state.userInput.trim().toLowerCase();
return {
processedInput: processed,
step: state.step + 1,
};
};
异步节点
需要等待异步操作(如 API 调用)的节点:
const llmNode = async (state: State) => {
const model = new ChatOpenAI({ model: 'gpt-4' });
const response = await model.invoke(state.messages);
return {
messages: [response],
};
};
配置化节点
可以接收运行时配置的节点:
const configurableNode = (state: State, config?: RunnableConfig) => {
const temperature = config?.configurable?.temperature ?? 0.7;
// 使用配置...
return { /* 状态更新 */ };
};
调用时传入配置:
await graph.invoke(initialState, {
configurable: { temperature: 0.9 },
});
START 和 END
- START:虚拟的起始节点,表示图的入口
- END:虚拟的结束节点,表示图的出口
graph
.addEdge(START, 'firstNode') // 从入口连接到第一个节点
.addEdge('lastNode', END); // 从最后一个节点连接到出口
边(Edges)
边的作用
边定义了节点之间的执行流程,决定了「执行完这个节点后,下一步去哪里」。
边的类型
普通边(Normal Edge)
无条件跳转,执行完 A 必定执行 B:
graph.addEdge('nodeA', 'nodeB');
条件边(Conditional Edge)
根据状态动态决定下一个节点:
// 路由函数
const routeDecision = (state: State) => {
if (state.error) return 'errorHandler';
if (state.needsTools) return 'toolNode';
return 'answerNode';
};
// 添加条件边
graph.addConditionalEdges('decisionNode', routeDecision);
路由函数返回的字符串必须是已添加的节点名称,或者是 END。
带映射的条件边
当路由函数返回的值与节点名不同时,可以使用映射:
const classify = (state: State) => {
if (state.score > 0.8) return 'high';
if (state.score > 0.5) return 'medium';
return 'low';
};
graph.addConditionalEdges('classifier', classify, {
high: 'premiumHandler', // 'high' 映射到 premiumHandler 节点
medium: 'standardHandler',
low: 'basicHandler',
});
边的设计模式
顺序执行:A → B → C,适合流水线式处理
条件分支:根据条件走不同路径,适合分类处理
循环:满足条件时重复执行,适合 ReAct 模式的 Agent
完整示例
让我们把所有概念组合成一个完整的例子:
import { StateGraph, Annotation, START, END } from '@langchain/langgraph';
import { BaseMessage, HumanMessage, AIMessage } from '@langchain/core/messages';
import { messagesStateReducer } from '@langchain/langgraph';
// 1. 定义状态
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: messagesStateReducer,
default: () => [],
}),
intent: Annotation<'question' | 'chat' | 'unknown'>(),
});
// 2. 定义节点
const analyzeIntent = (state: typeof StateAnnotation.State) => {
const lastMessage = state.messages[state.messages.length - 1];
const content = lastMessage.content.toString().toLowerCase();
// 简单的意图分析
let intent: 'question' | 'chat' | 'unknown' = 'unknown';
if (content.includes('?') || content.includes('什么') || content.includes('如何')) {
intent = 'question';
} else if (content.includes('你好') || content.includes('嗨')) {
intent = 'chat';
}
return { intent };
};
const handleQuestion = (state: typeof StateAnnotation.State) => {
return {
messages: [new AIMessage({ content: '这是一个很好的问题,让我来回答...' })],
};
};
const handleChat = (state: typeof StateAnnotation.State) => {
return {
messages: [new AIMessage({ content: '你好!很高兴和你聊天。' })],
};
};
const handleUnknown = (state: typeof StateAnnotation.State) => {
return {
messages: [new AIMessage({ content: '我不太确定你的意思,能再说清楚一点吗?' })],
};
};
// 3. 定义路由
const routeIntent = (state: typeof StateAnnotation.State) => {
switch (state.intent) {
case 'question': return 'questionHandler';
case 'chat': return 'chatHandler';
default: return 'unknownHandler';
}
};
// 4. 构建图
const graph = new StateGraph(StateAnnotation)
.addNode('analyzer', analyzeIntent)
.addNode('questionHandler', handleQuestion)
.addNode('chatHandler', handleChat)
.addNode('unknownHandler', handleUnknown)
.addEdge(START, 'analyzer')
.addConditionalEdges('analyzer', routeIntent)
.addEdge('questionHandler', END)
.addEdge('chatHandler', END)
.addEdge('unknownHandler', END)
.compile();
// 5. 执行
const result = await graph.invoke({
messages: [new HumanMessage({ content: '什么是 LangGraph?' })],
});
小结
通过本节学习,你应该掌握了:
| 概念 | 作用 | 关键 API |
|---|---|---|
| 图 | 应用的整体结构 | StateGraph, compile() |
| 状态 | 数据的存储和流转 | Annotation.Root, reducer |
| 节点 | 执行具体逻辑 | addNode(), 返回部分状态 |
| 边 | 控制执行流程 | addEdge(), addConditionalEdges() |
这些概念是构建 LangGraph 应用的基础。在后续章节中,我们会深入每个主题,学习更高级的用法。
核心概念已经掌握!接下来,让我们进入《核心组件详解》,深入学习每个组件的高级用法。