跳到主要内容

核心概念

引言

在前面的章节中,我们了解了什么是 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 应用的基础。在后续章节中,我们会深入每个主题,学习更高级的用法。


下一步

核心概念已经掌握!接下来,让我们进入《核心组件详解》,深入学习每个组件的高级用法。