📖目录
- 前言
- [1. 为什么需要记忆管理?------从"健忘"到"贴心"的转变](#1. 为什么需要记忆管理?——从"健忘"到"贴心"的转变)
- [2. Spring AI Alibaba的记忆架构:从短期到长期的智能存储](#2. Spring AI Alibaba的记忆架构:从短期到长期的智能存储)
-
- [2.1 短期记忆 vs 长期记忆](#2.1 短期记忆 vs 长期记忆)
- [2.2 记忆存储原理](#2.2 记忆存储原理)
- [3. 核心机制解析:如何实现"长期记忆"](#3. 核心机制解析:如何实现"长期记忆")
-
- [3.1 ModelHook 拦截器:记忆的"开关"](#3.1 ModelHook 拦截器:记忆的"开关")
- [3.2 记忆工具:显式控制记忆](#3.2 记忆工具:显式控制记忆)
- [4. 源码解析:记忆管理的实现流程](#4. 源码解析:记忆管理的实现流程)
-
- [4.1 整体执行流程](#4.1 整体执行流程)
- [4.2 核心类图](#4.2 核心类图)
- [4.3 关键源码解析](#4.3 关键源码解析)
- [5. 实战案例:电商客服Agent的长期记忆](#5. 实战案例:电商客服Agent的长期记忆)
-
- [5.1 业务场景](#5.1 业务场景)
- [5.2 代码实现](#5.2 代码实现)
- [5.3 执行结果](#5.3 执行结果)
- [6. 结语](#6. 结语)
- [7. 附录:推荐阅读](#7. 附录:推荐阅读)
前言
本文基于Spring AI Alibaba最新版本(2025年12月,兼容Spring Boot 3.5.7 / Spring Cloud Alibaba 2023.0.x),适合中高级Java开发者、AI应用架构师阅读
想象一下:你每次和客服机器人聊天,它都像第一次见面一样,完全不记得你上次提过什么需求。这就像你去便利店买东西,每次都要重新告诉店员你想要什么,店员却从不记得你上次的偏好。这种体验让人沮丧,而Spring AI Alibaba的"记忆管理"功能正是解决这个问题的利器。
在AI应用中,"记忆"不是简单的对话历史记录,而是能够理解、存储并利用用户偏好、行为习惯的智能机制。就像我们人类会记住朋友的生日、喜欢的食物一样,一个拥有"长期记忆"的AI Agent能提供更贴心、更个性化的服务。
今天,我们将深入Spring AI Alibaba的Memory框架,解析如何让Agent真正拥有"长期记忆",并展示如何在实际项目中应用。
1. 为什么需要记忆管理?------从"健忘"到"贴心"的转变
在AI应用中,如果没有记忆管理,每次对话都是"从零开始"。这就像你每次去餐厅点餐,服务员都问你"想吃什么",而不是根据你上次的点单建议"您上次点了宫保鸡丁,今天还要吗?"
记忆管理的核心价值:
- 个性化体验:记住用户偏好,提供定制化服务
- 上下文连贯:保持对话连贯性,避免重复提问
- 学习进化:随着交互次数增加,AI越来越了解用户
- 业务价值:提升用户满意度和转化率
💡 一个电商案例:如果AI客服记住用户"不喜欢辣味",那么在推荐菜品时,会自动过滤掉辣味选项,提升用户体验。
2. Spring AI Alibaba的记忆架构:从短期到长期的智能存储
Spring AI Alibaba的记忆管理不是简单的"保存对话",而是一个精心设计的分层架构,将记忆分为短期记忆和长期记忆两部分。
短期记忆
长期记忆
拦截器
工具回调
ReactAgent
+RunnableConfig shortTermMemory
+MemoryStore longTermMemory
+ModelHook modelHook
+Builder tools(ToolCallback... tools)
+invoke(String message, RunnableConfig config) : --ok
RunnableConfig
+String threadId
+Map metadata
MemoryStore
+Map storage
+putItem(StoreItem item)
+Optional getItem(List namespace, String key)
ModelHook
+String agentName
+beforeModel(OverAllState state, RunnableConfig config
+afterModel(OverAllState state, RunnableConfig config)
ToolCallback
+call(String toolInput, @Nullable ToolContext toolContext)
+String call(String toolInput)
+ToolMetadata getToolMetadata()
2.1 短期记忆 vs 长期记忆
| 维度 | 短期记忆 (Short-term) | 长期记忆 (Long-term) |
|---|---|---|
| 存储范围 | 单次对话会话 | 跨会话、跨时间 |
| 存储方式 | 内存中的MemorySaver | 持久化存储在MemoryStore |
| 数据类型 | 对话历史、消息状态 | 用户画像、偏好设置、持久化数据 |
| 生命周期 | 对话结束即清除 | 持久存储,直到显式删除 |
| 使用场景 | 保持当前对话上下文 | 个性化推荐、历史行为分析 |
2.2 记忆存储原理
Spring AI Alibaba将长期记忆以JSON文档的形式存储在Store中。这就像把用户的"数字画像"存放在一个结构化的数据库中。
json
{
"namespace": "user_profile",
"key": "user_12345",
"data": {
"name": "张三",
"preferred_cuisine": "川菜",
"dislikes": ["辣味", "海鲜"],
"purchase_history": [
{"product": "宫保鸡丁", "date": "2025-01-15"},
{"product": "麻婆豆腐", "date": "2025-01-20"}
]
}
}
关键点:这个JSON文档是结构化的,不是简单的文本。这意味着我们可以轻松地查询、更新和分析用户数据,而不仅仅是存储"记忆"。
💡 问题:我的偏好是存储在我的账号下面吗?
答案:是的!在Spring AI Alibaba中,记忆存储是与用户ID绑定的。通过
threadId和namespace/key,系统可以准确地将记忆关联到特定用户。这就像你每次登录账号,系统都能记住你的偏好,而不是所有用户共享同一个记忆。
3. 核心机制解析:如何实现"长期记忆"
3.1 ModelHook 拦截器:记忆的"开关"
Spring AI Alibaba通过ModelHook拦截器实现了记忆的自动化管理:
java
// ModelHook拦截器
// ModelHook:AI Agent的记忆管理拦截器核心接口
// 作用:在模型调用前后自动注入/保存记忆数据,实现"长期记忆"的自动化管理
public abstract class ModelHook implements Hook {
// 当前Agent的名称(用于日志和调试)
private String agentName;
// 【核心方法】:模型调用前执行
// 作用:从长期记忆加载用户偏好,注入到当前对话上下文中
// 返回值:需包含注入的记忆数据(如用户偏好),供模型使用
public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
// 实际实现中会调用memoryStore.get()加载用户偏好
// 示例:Map<String, Object> memoryData = memoryStore.get("user_preferences", state.getThreadId());
return CompletableFuture.completedFuture(Map.of()); // 空实现,实际需替换为具体逻辑
}
// 【核心方法】:模型调用后执行
// 作用:将本次交互内容保存到长期记忆,实现"学习"能力
// 返回值:保存结果状态(实际实现中通常无需返回,这里保持接口一致性)
public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
// 实际实现中会调用memoryStore.save()存储交互记录
// 示例:memoryStore.save("interaction_history", state.getThreadId(), new InteractionRecord(prompt, response));
return CompletableFuture.completedFuture(Map.of()); // 空实现,实际需替换为具体逻辑
}
// 设置Agent名称(用于日志和调试)
public void setAgentName(String agentName) {
this.agentName = agentName;
}
// 获取Agent名称(用于日志和调试)
public String getAgentName() {
return this.agentName;
}
}
大白话解释:这个拦截器就像AI的"记忆管理员",在AI思考前,它会从"记忆库"中取出用户的偏好信息,放进AI的思考中;在AI回答后,它会把这次对话的"新发现"存回"记忆库"。
3.2 记忆工具:显式控制记忆
除了自动管理,Spring AI Alibaba还提供了显式的记忆工具回调:
java
// 工具回调
// ToolCallback:AI Agent中工具调用的核心接口
// 作用:定义可被Agent调用的"记忆工具"(如saveMemory/getMemory)的规范
public interface ToolCallback {
// 日志记录器(用于跟踪工具调用过程)
Logger logger = LoggerFactory.getLogger(ToolCallback.class);
// 【核心方法】:获取工具的元定义(包含工具名称、描述、参数等)
// 作用:向Agent框架注册工具,使其能被识别和调用
// 示例:返回MemoryTools工具的定义(如"saveMemory"、"getMemory")
ToolDefinition getToolDefinition();
// 【默认方法】:获取工具的元数据(如版本、依赖等)
// 作用:为工具提供额外信息,便于框架管理
// 实际实现中通常不需要覆盖,框架会自动填充
default ToolMetadata getToolMetadata() {
return ToolMetadata.builder().build();
}
// 【核心方法】:执行工具调用(无上下文)
// 作用:执行工具的核心逻辑(如保存/检索记忆)
// 参数:toolInput - 工具调用的输入参数(如JSON格式的存储数据)
// 返回:工具执行结果(如"成功保存"或检索到的用户画像)
String call(String toolInput);
// 【默认方法】:执行工具调用(带上下文)
// 作用:提供更高级的工具调用支持(如使用工具上下文)
// 参数:toolInput - 工具输入;toolContext - 工具执行的上下文(如当前用户ID)
// 注意:默认实现不使用toolContext,需在具体工具类中覆盖
// 为什么需要这个?因为记忆管理需要关联当前用户(通过threadId)
default String call(String toolInput, @Nullable ToolContext toolContext) {
// 如果提供了toolContext,但未被实现,记录警告日志
if (toolContext != null && !toolContext.getContext().isEmpty()) {
logger.info("By default the tool context is not used, override the method 'call(String toolInput, ToolContext toolcontext)' to support the use of tool context.Review the ToolCallback implementation for {}", this.getToolDefinition().name());
}
// 默认行为:调用无上下文的版本
return this.call(toolInput);
}
}
实际应用:在用户明确表达偏好时,我们可以显式保存这些信息:
java
// 用户说:"我讨厌辣味"
Map<String, Object> userData = new HashMap<>();
userData.put("name", "张三");
userData.put("language", "中文");
userData.put("口味", "我讨厌辣味");
StoreItem userItem = StoreItem.of(List.of("users"), "user_123", userData);
store.putItem(userItem);
// 创建获取用户信息的工具
BiFunction<GetMemoryRequest, ToolContext, MemoryResponse> getUserInfoFunction =
(request, context) -> {
Optional<StoreItem> itemOpt = store.getItem(request.namespace(), request.key());
if (itemOpt.isPresent()) {
Map<String, Object> value = itemOpt.get().getValue();
return new MemoryResponse("找到用户信息", value);
}
return new MemoryResponse("未找到用户", Map.of());
};
ToolCallback getUserInfoTool = FunctionToolCallback.builder("getUserInfo", getUserInfoFunction)
.description("查询用户信息")
.inputType(GetMemoryRequest.class)
.build();
// 创建Agent
ReactAgent agent = ReactAgent.builder()
.name("memory_agent")
.model(chatModel)
.tools(getUserInfoTool)
.saver(new MemorySaver())
.build();
4. 源码解析:记忆管理的实现流程
4.1 整体执行流程
是
否
用户输入
Agent启动
是否需要记忆?
加载短期记忆
直接调用模型
注入用户偏好
调用模型
保存交互内容
更新长期记忆
返回结果
4.2 核心类图
短期记忆
长期记忆
拦截器
工具
ReactAgent
+RunnableConfig shortTermMemory
+MemoryStore longTermMemory
+ModelHook modelHook
+Builder tools(ToolCallback... tools)
+invoke(String message, RunnableConfig config)
RunnableConfig
+String threadId
+Map metadata
MemoryStore
+Map storage
+putItem(StoreItem item)
+Optional getItem(List namespace, String key)
ModelHook
+String agentName
+beforeModel(OverAllState state, RunnableConfig config
+afterModel(OverAllState state, RunnableConfig config)
ToolCallback
+call(String toolInput, @Nullable ToolContext toolContext)
+String call(String toolInput)
+ToolMetadata getToolMetadata()
4.3 关键源码解析
让我们深入短期记忆和长期记忆的核心实现:
RunnableConfig.java (短期记忆)
java
// RunnableConfig:AI Agent执行时的运行时配置核心类
// 作用:封装对话会话的上下文、状态和元数据,是记忆管理的关键载体
public final class RunnableConfig implements HasMetadata<Builder> {
// 【关键常量】:人类反馈元数据键(用于记录用户反馈)
public static final String HUMAN_FEEDBACK_METADATA_KEY = "HUMAN_FEEDBACK";
// 【关键常量】:状态更新元数据键(用于记录状态变化)
public static final String STATE_UPDATE_METADATA_KEY = "STATE_UPDATE";
// 【关键常量】:Agent名称元数据键(用于标识Agent)
public static final String AGENT_NAME = "AGENT_NAME";
// 【核心字段】:当前对话的唯一标识(关键!所有记忆操作必须关联threadId)
private final String threadId;
// 【核心字段】:检查点ID(用于断点续传,记忆恢复的关键)
private final String checkPointId;
// 【核心字段】:下一个执行节点(用于流程控制,与记忆状态关联)
private final String nextNode;
// 【核心字段】:流式响应模式(影响记忆保存时机)
private final CompiledGraph.StreamMode streamMode;
// 【核心字段】:元数据存储(存储临时状态,如HUMAN_FEEDBACK)
private final Map<String, Object> metadata;
// 【核心字段】:执行上下文(存储当前会话的上下文,如用户ID)
private final Map<String, Object> context;
// 【核心字段】:存储引擎(实际存储长期记忆的实现)
private Store store;
// 【核心字段】:被中断节点的映射(用于恢复中断的会话,与记忆恢复强关联)
private final Map<String, Object> interruptedNodes;
// 【构造函数】:通过Builder构建配置
// 作用:初始化所有字段,确保线程安全(使用ConcurrentHashMap存储interruptedNodes)
private RunnableConfig(Builder builder) {
this.threadId = builder.threadId;
this.checkPointId = builder.checkPointId;
this.nextNode = builder.nextNode;
this.streamMode = builder.streamMode;
// 安全复制元数据(避免外部修改)
this.metadata = (Map)Optional.ofNullable(builder.metadata()).map(Map::copyOf).orElse((Object)null);
// 初始化中断节点存储(线程安全)
this.interruptedNodes = new ConcurrentHashMap();
this.store = builder.store;
this.context = builder.context;
}
// 【核心方法】:获取存储引擎(用于记忆的持久化操作)
public Store store() {
return this.store;
}
// 【核心方法】:获取流式响应模式(影响记忆保存时机)
public CompiledGraph.StreamMode streamMode() {
return this.streamMode;
}
// 【核心方法】:获取threadId(关键!记忆关联的核心标识)
public Optional<String> threadId() {
return Optional.ofNullable(this.threadId);
}
// 【核心方法】:获取检查点ID(用于恢复记忆状态)
public Optional<String> checkPointId() {
return Optional.ofNullable(this.checkPointId);
}
// 【核心方法】:获取下一个执行节点(用于流程恢复)
public Optional<String> nextNode() {
return Optional.ofNullable(this.nextNode);
}
// 【核心方法】:检查节点是否被中断(用于恢复中断的会话)
public boolean isInterrupted(String nodeId) {
// 从中断节点映射中获取状态
return (Boolean)this.interruptData(HasMetadata.formatNodeId(nodeId)).map((value) -> Boolean.TRUE.equals(value)).orElse(false);
}
// 【核心方法】:标记节点为已恢复(用于恢复会话流程)
public void withNodeResumed(String nodeId) {
String formattedNodeId = HasMetadata.formatNodeId(nodeId);
// 标记为未中断(恢复状态)
this.interruptedNodes.put(formattedNodeId, false);
}
// 【核心方法】:移除中断节点记录(用于清理恢复后的状态)
public void removeInterrupted(String nodeId) {
String formattedNodeId = HasMetadata.formatNodeId(nodeId);
// 安全移除(避免NPE)
if (this.interruptedNodes != null && this.interruptedNodes.containsKey(formattedNodeId)) {
this.interruptedNodes.remove(formattedNodeId);
}
}
// 【核心方法】:标记节点为中断(用于保存中断状态)
public void markNodeAsInterrupted(String nodeId) {
// 标记为中断状态
this.interruptedNodes.put(HasMetadata.formatNodeId(nodeId), true);
}
// 【核心方法】:创建新配置(修改流式模式)
public RunnableConfig withStreamMode(CompiledGraph.StreamMode streamMode) {
// 避免不必要的重建
return this.streamMode == streamMode ? this : builder(this).streamMode(streamMode).build();
}
// 【核心方法】:创建新配置(修改检查点ID)
public RunnableConfig withCheckPointId(String checkPointId) {
// 避免不必要的重建
return Objects.equals(this.checkPointId, checkPointId) ? this : builder(this).checkPointId(checkPointId).build();
}
// 【核心方法】:获取中断数据(关键!用于恢复会话状态)
public Optional<Object> interruptData(String key) {
// 安全获取中断数据
return key == null ? Optional.empty() : Optional.ofNullable(this.interruptedNodes).map((m) -> m.get(key));
}
// 【核心方法】:获取元数据(用于访问HUMAN_FEEDBACK等)
public Optional<Map<String, Object>> metadata() {
return Optional.of(Collections.unmodifiableMap(this.metadata));
}
// 【核心方法】:获取执行上下文(存储当前会话的上下文)
public Map<String, Object> context() {
return this.context;
}
// 【核心方法】:获取指定元数据(用于快速访问)
public Optional<Object> metadata(String key) {
return key == null ? Optional.empty() : Optional.ofNullable(this.metadata).map((m) -> m.get(key));
}
// 【核心方法】:字符串表示(用于日志和调试)
public String toString() {
return String.format("RunnableConfig{ threadId=%s, checkPointId=%s, nextNode=%s, streamMode=%s }",
this.threadId, this.checkPointId, this.nextNode, this.streamMode);
}
// 【工厂方法】:创建Builder(用于构建配置)
public static Builder builder() {
return new Builder();
}
// 【工厂方法】:从现有配置创建Builder(用于修改配置)
public static Builder builder(RunnableConfig config) {
return new Builder(config);
}
// 【内部类】:Builder用于构建RunnableConfig
// 作用:提供安全、链式的配置构建方式
public static class Builder extends HasMetadata.Builder<Builder> {
// 【Builder字段】:threadId(会话唯一标识)
private String threadId;
// 【Builder字段】:checkPointId(检查点ID)
private String checkPointId;
// 【Builder字段】:nextNode(下一个执行节点)
private String nextNode;
// 【Builder字段】:Store(存储引擎)
private Store store;
// 【Builder字段】:context(执行上下文)
private Map<String, Object> context;
// 【Builder字段】:流式模式(默认值)
private CompiledGraph.StreamMode streamMode;
// 【Builder构造函数】:初始化默认值
Builder() {
this.streamMode = StreamMode.VALUES;
this.context = new ConcurrentHashMap();
}
// 【Builder构造函数】:从现有配置初始化
Builder(RunnableConfig config) {
super(((RunnableConfig)Objects.requireNonNull(config, "config cannot be null!")).metadata);
this.streamMode = StreamMode.VALUES;
this.threadId = config.threadId;
this.checkPointId = config.checkPointId;
this.nextNode = config.nextNode;
this.streamMode = config.streamMode;
this.store = config.store;
this.context = new ConcurrentHashMap(config.context);
}
// 【Builder方法】:设置threadId(关键!记忆关联的核心)
public Builder threadId(String threadId) {
this.threadId = threadId;
return this;
}
// 【Builder方法】:设置检查点ID(用于记忆恢复)
public Builder checkPointId(String checkPointId) {
this.checkPointId = checkPointId;
return this;
}
// 【Builder方法】:设置下一个执行节点(用于流程恢复)
public Builder nextNode(String nextNode) {
this.nextNode = nextNode;
return this;
}
// 【Builder方法】:设置流式模式(影响记忆保存时机)
public Builder streamMode(CompiledGraph.StreamMode streamMode) {
this.streamMode = streamMode;
return this;
}
// 【Builder方法】:添加人类反馈元数据(用于记录用户反馈)
public Builder addHumanFeedback(InterruptionMetadata humanFeedback) {
return (Builder)this.addMetadata("HUMAN_FEEDBACK", humanFeedback);
}
// 【Builder方法】:添加状态更新元数据(用于记录状态变化)
public Builder addStateUpdate(Map<String, Object> stateUpdate) {
return (Builder)this.addMetadata("STATE_UPDATE", stateUpdate);
}
// 【Builder方法】:添加并行节点执行器(用于多线程处理)
public Builder addParallelNodeExecutor(String nodeId, Executor executor) {
return (Builder)this.addMetadata(ParallelNode.formatNodeId(nodeId), Objects.requireNonNull(executor, "executor cannot be null!"));
}
// 【Builder方法】:清除上下文(用于重置会话状态)
public Builder clearContext() {
this.context.clear();
return this;
}
// 【Builder方法】:设置存储引擎(关键!记忆持久化的核心)
public Builder store(Store store) {
this.store = store;
return this;
}
// 【Builder方法】:构建RunnableConfig(最终创建配置对象)
public RunnableConfig build() {
return new RunnableConfig(this);
}
}
}
MemoryStore.java (长期记忆)
java
// MemoryStore:AI Agent长期记忆的核心存储实现
// 作用:提供线程安全的存储、检索和管理能力,是记忆系统的持久化核心
public class MemoryStore extends BaseStore {
// 【核心存储】:使用ConcurrentHashMap实现线程安全的内存存储(关键!)
private final Map<String, StoreItem> storage = new ConcurrentHashMap();
// 【线程控制】:读写锁确保并发安全(避免多线程操作导致数据不一致)
private final ReadWriteLock lock = new ReentrantReadWriteLock();
// 【核心方法】:保存记忆项(关键!所有记忆操作的入口)
// 作用:将用户偏好/对话历史等数据存入存储
// 参数:item - 包含命名空间、键、数据的记忆项
public void putItem(StoreItem item) {
// 验证输入(防止无效数据)
this.validatePutItem(item);
// 获取写锁(确保写操作互斥)
this.lock.writeLock().lock();
try {
// 创建唯一存储键(格式:namespace/key)
String storeKey = this.createStoreKey(item.getNamespace(), item.getKey());
// 存储到内存
this.storage.put(storeKey, item);
} finally {
// 释放写锁
this.lock.writeLock().unlock();
}
}
// 【核心方法】:获取记忆项(关键!检索用户记忆)
// 作用:根据命名空间和键获取存储的记忆
// 参数:namespace - 命名空间(如"user_preferences");key - 记忆键(如"user_123")
// 返回:Optional<StoreItem>(安全返回,避免NPE)
public Optional<StoreItem> getItem(List<String> namespace, String key) {
// 验证输入
this.validateGetItem(namespace, key);
// 获取读锁(允许多线程读)
this.lock.readLock().lock();
Optional var4;
try {
// 创建存储键
String storeKey = this.createStoreKey(namespace, key);
// 从存储中获取(可能为null)
var4 = Optional.ofNullable((StoreItem)this.storage.get(storeKey));
} finally {
// 释放读锁
this.lock.readLock().unlock();
}
return var4;
}
// 【核心方法】:删除记忆项(关键!清理过期记忆)
// 作用:删除指定命名空间和键的记忆
// 参数:namespace - 命名空间;key - 记忆键
// 返回:true(删除成功)或 false(未找到)
public boolean deleteItem(List<String> namespace, String key) {
// 验证输入
this.validateDeleteItem(namespace, key);
// 获取写锁
this.lock.writeLock().lock();
boolean var4;
try {
// 创建存储键
String storeKey = this.createStoreKey(namespace, key);
// 从存储中移除(返回是否成功)
var4 = this.storage.remove(storeKey) != null;
} finally {
// 释放写锁
this.lock.writeLock().unlock();
}
return var4;
}
// 【核心方法】:搜索记忆项(关键!高级查询能力)
// 作用:根据条件搜索记忆(如查找特定用户的所有偏好)
// 参数:searchRequest - 搜索请求(包含查询条件、排序等)
// 返回:StoreSearchResult(包含结果列表、总数等)
public StoreSearchResult searchItems(StoreSearchRequest searchRequest) {
// 验证输入
this.validateSearchItems(searchRequest);
// 获取读锁
this.lock.readLock().lock();
StoreSearchResult var8;
try {
// 获取所有存储项
List<StoreItem> allItems = new ArrayList(this.storage.values());
// 过滤匹配搜索条件的项
List<StoreItem> filteredItems = (List)allItems.stream()
.filter((item) -> this.matchesSearchCriteria(item, searchRequest))
.collect(Collectors.toList());
// 如果需要排序,应用排序规则
if (!searchRequest.getSortFields().isEmpty()) {
filteredItems.sort(this.createComparator(searchRequest));
}
// 计算总数
long totalCount = (long)filteredItems.size();
int offset = searchRequest.getOffset();
int limit = searchRequest.getLimit();
// 分页处理
if (offset < filteredItems.size()) {
int endIndex = Math.min(offset + limit, filteredItems.size());
List<StoreItem> resultItems = filteredItems.subList(offset, endIndex);
// 创建搜索结果
StoreSearchResult var10 = StoreSearchResult.of(resultItems, totalCount, offset, limit);
return var10;
}
// 无结果时返回空列表
var8 = StoreSearchResult.of(Collections.emptyList(), totalCount, offset, limit);
} finally {
// 释放读锁
this.lock.readLock().unlock();
}
return var8;
}
// 【核心方法】:列出命名空间(关键!管理记忆结构)
// 作用:获取所有命名空间(如"user_preferences"、"chat_history")
// 参数:namespaceRequest - 命名空间请求(包含过滤条件)
// 返回:命名空间列表(按路径排序)
public List<String> listNamespaces(NamespaceListRequest namespaceRequest) {
// 验证输入
this.validateListNamespaces(namespaceRequest);
// 获取读锁
this.lock.readLock().lock();
List endIndex;
try {
// 存储所有唯一命名空间路径
Set<String> namespaceSet = new HashSet();
// 获取命名空间前缀过滤
List<String> prefixFilter = namespaceRequest.getNamespace();
// 遍历所有存储项
for(StoreItem item : this.storage.values()) {
List<String> itemNamespace = item.getNamespace();
// 检查是否匹配前缀
if (prefixFilter.isEmpty() || this.startsWithPrefix(itemNamespace, prefixFilter)) {
// 确定最大深度
int maxDepth = namespaceRequest.getMaxDepth();
int depth = maxDepth == -1 ? itemNamespace.size() : Math.min(maxDepth, itemNamespace.size());
// 生成所有可能的命名空间路径
for(int i = 1; i <= depth; ++i) {
String namespacePath = String.join("/", itemNamespace.subList(0, i));
namespaceSet.add(namespacePath);
}
}
}
// 排序命名空间
List<String> namespaces = new ArrayList(namespaceSet);
Collections.sort(namespaces);
int offset = namespaceRequest.getOffset();
int limit = namespaceRequest.getLimit();
// 分页处理
if (offset < namespaces.size()) {
int endIndex = Math.min(offset + limit, namespaces.size());
List var19 = namespaces.subList(offset, endIndex);
return var19;
}
// 无结果返回空列表
endIndex = Collections.emptyList();
} finally {
// 释放读锁
this.lock.readLock().unlock();
}
return endIndex;
}
// 【辅助方法】:清除所有记忆(关键!用于重置会话)
// 作用:清空整个存储(用于新对话或测试)
public void clear() {
this.storage.clear();
}
// 【辅助方法】:获取存储大小(用于监控)
public long size() {
return (long)this.storage.size();
}
// 【辅助方法】:检查是否为空(用于条件判断)
public boolean isEmpty() {
return this.storage.isEmpty();
}
}
ModelHook.java (拦截器实现)
java
// ModelHook:AI Agent模型调用的核心钩子抽象类
// 作用:提供在模型调用前后执行自定义逻辑的扩展点(关键!)
public abstract class ModelHook implements Hook {
// 【核心标识】:当前Agent的名称(用于日志和上下文关联)
private String agentName;
// 【钩子核心】:模型调用前的预处理(关键!)
// 作用:在模型执行前修改状态或配置(如添加上下文、验证输入)
// 参数:state - 当前全局状态;config - 模型执行配置
// 返回:CompletableFuture<Map<String, Object>>(可异步修改状态)
// 注意:默认返回空Map(子类需覆盖实现)
public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
return CompletableFuture.completedFuture(Map.of());
}
// 【钩子核心】:模型调用后的后处理(关键!)
// 作用:在模型执行后处理结果(如结果验证、日志记录、数据转换)
// 参数:state - 模型执行后的状态;config - 模型执行配置
// 返回:CompletableFuture<Map<String, Object>>(可异步修改结果)
// 注意:默认返回空Map(子类需覆盖实现)
public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
return CompletableFuture.completedFuture(Map.of());
}
// 【核心配置】:设置Agent名称(关键!用于日志追踪和上下文关联)
// 作用:为钩子关联特定Agent实例(避免多Agent混淆)
// 参数:agentName - Agent的唯一标识(如"customer_service_agent")
public void setAgentName(String agentName) {
this.agentName = agentName;
}
// 【核心配置】:获取Agent名称(关键!用于日志和调试)
// 返回:当前关联的Agent名称
public String getAgentName() {
return this.agentName;
}
}
5. 实战案例:电商客服Agent的长期记忆
让我们通过一个电商客服Agent的案例,展示记忆管理如何提升用户体验。
5.1 业务场景
用户与AI客服的对话流程:
- 用户第一次咨询:
"我想要点一份宫保鸡丁" - 用户第二次咨询:
"上次点的宫保鸡丁太辣了,这次能不能不放辣椒?"
5.2 代码实现
java
// MemoryExample.java
/**
* MemoryExample:AI Agent记忆管理的完整示例类
* 作用:演示短期记忆、长期记忆、跨会话记忆等核心功能实现
*
* 演示如何在Agent中使用记忆管理功能,包括:
* 1. 在工具中读取长期记忆
* 2. 在工具中写入长期记忆
* 3. 使用ModelHook管理长期记忆
* 4. 结合短期和长期记忆
* 5. 跨会话记忆
* 6. 用户偏好学习
*
*/
public class MemoryExample {
// 【核心依赖】:ChatModel实例(AI模型的执行引擎)
// 作用:连接到实际AI服务(如DashScope)进行模型调用
private final ChatModel chatModel;
// 【构造函数】:初始化记忆示例
// 参数:chatModel - 已配置的AI模型实例
// 为什么关键?没有ChatModel,所有记忆操作都无法执行
public MemoryExample(ChatModel chatModel) {
this.chatModel = chatModel;
}
// 【主入口】:运行所有记忆管理示例
// 作用:配置AI服务并触发所有示例执行
// 注意:需要正确设置API密钥才能运行
public static void main(String[] args) {
// 创建DashScope API客户端(实际应用中应从环境变量获取密钥)
// 为什么关键?这是连接AI服务的唯一入口
DashScopeApi dashScopeApi = DashScopeApi.builder()
.apiKey("xxx") // 【安全警告】:示例密钥仅用于演示
.build();
// 创建ChatModel实例(绑定到DashScope API)
ChatModel chatModel = DashScopeChatModel.builder()
.dashScopeApi(dashScopeApi)
.build();
// 【安全校验】:确保ChatModel已正确配置
if (chatModel == null) {
System.err.println("错误:请先配置ChatModel实例");
System.err.println("请设置 AI_DASHSCOPE_API_KEY 环境变量");
return;
}
// 创建示例实例并运行所有示例
MemoryExample example = new MemoryExample(chatModel);
example.runAllExamples();
}
// 【示例1】:在工具中读取长期记忆
// 作用:演示如何通过工具查询持久化用户数据
// 为什么关键?这是实现用户画像的核心方式
public void example1_readMemoryInTool() throws GraphRunnerException {
// 定义工具请求结构(namespace和key用于定位数据)
record GetMemoryRequest(List<String> namespace, String key) { }
// 定义工具响应结构(包含消息和实际数据)
record MemoryResponse(String message, Map<String, Object> value) { }
// 创建内存存储(长期记忆的容器)
MemoryStore store = new MemoryStore();
// 【写入测试数据】:模拟用户数据
Map<String, Object> userData = new HashMap<>();
userData.put("name", "张三");
userData.put("language", "中文");
StoreItem userItem = StoreItem.of(List.of("users"), "user_123", userData);
store.putItem(userItem); // 将数据存入长期记忆
// 【创建工具函数】:从存储中读取用户信息
BiFunction<GetMemoryRequest, ToolContext, MemoryResponse> getUserInfoFunction =
(request, context) -> {
Optional<StoreItem> itemOpt = store.getItem(request.namespace(), request.key());
if (itemOpt.isPresent()) {
Map<String, Object> value = itemOpt.get().getValue();
return new MemoryResponse("找到用户信息", value);
}
return new MemoryResponse("未找到用户", Map.of());
};
// 【注册工具】:将工具暴露给Agent
ToolCallback getUserInfoTool = FunctionToolCallback.builder("getUserInfo", getUserInfoFunction)
.description("查询用户信息")
.inputType(GetMemoryRequest.class)
.build();
// 【创建Agent】:配置带工具的Agent
ReactAgent agent = ReactAgent.builder()
.name("memory_agent")
.model(chatModel)
.tools(getUserInfoTool) // 关键:注册记忆读取工具
.saver(new MemorySaver()) // 关键:启用短期记忆保存
.build();
// 【运行Agent】:触发记忆查询
RunnableConfig config = RunnableConfig.builder()
.threadId("session_001")
.addMetadata("user_id", "user_123") // 【关键】:绑定用户ID用于记忆检索
.build();
agent.invoke("查询用户信息,namespace=['users'], key='user_123'", config);
System.out.println("工具读取长期记忆示例执行完成");
}
// 【示例2】:在工具中写入长期记忆
// 作用:演示如何保存用户数据到持久化存储
public void example2_writeMemoryInTool() throws GraphRunnerException {
// 定义工具请求结构(包含要保存的数据)
record SaveMemoryRequest(List<String> namespace, String key, Map<String, Object> value) { }
record MemoryResponse(String message, Map<String, Object> value) { }
// 创建内存存储(长期记忆容器)
MemoryStore store = new MemoryStore();
// 【创建保存工具】:将数据写入存储
BiFunction<SaveMemoryRequest, ToolContext, MemoryResponse> saveUserInfoFunction =
(request, context) -> {
StoreItem item = StoreItem.of(request.namespace(), request.key(), request.value());
store.putItem(item);
return new MemoryResponse("成功保存用户信息", request.value());
};
// 【注册工具】:将保存工具暴露给Agent
ToolCallback saveUserInfoTool = FunctionToolCallback.builder("saveUserInfo", saveUserInfoFunction)
.description("保存用户信息")
.inputType(SaveMemoryRequest.class)
.build();
// 创建Agent(启用工具和短期记忆)
ReactAgent agent = ReactAgent.builder()
.name("save_memory_agent")
.model(chatModel)
.tools(saveUserInfoTool)
.saver(new MemorySaver())
.build();
// 【运行Agent】:触发数据保存
RunnableConfig config = RunnableConfig.builder()
.threadId("session_001")
.addMetadata("user_id", "user_123")
.build();
agent.invoke(
"我叫张三,请保存我的信息。使用 saveUserInfo 工具,namespace=['users'], key='user_123', value={'name': '张三'}",
config
);
// 【验证保存】:直接检查存储
Optional<StoreItem> savedItem = store.getItem(List.of("users"), "user_123");
if (savedItem.isPresent()) {
Map<String, Object> savedValue = savedItem.get().getValue();
System.out.println("保存的数据: " + savedValue);
}
System.out.println("工具写入长期记忆示例执行完成");
}
// 【示例3】:使用ModelHook管理长期记忆
// 作用:在模型调用前后自动加载/保存长期记忆
// 为什么关键?这是实现"记忆感知"的核心机制
public void example3_memoryWithModelHook() throws GraphRunnerException {
// 创建内存存储(长期记忆容器)
MemoryStore memoryStore = new MemoryStore();
// 【预填充用户画像】:模拟初始数据
Map<String, Object> profileData = new HashMap<>();
profileData.put("name", "王小明");
profileData.put("age", 28);
profileData.put("email", "wang@example.com");
profileData.put("preferences", List.of("喜欢咖啡", "喜欢阅读"));
StoreItem profileItem = StoreItem.of(List.of("user_profiles"), "user_001", profileData);
memoryStore.putItem(profileItem); // 写入长期记忆
// 【创建记忆拦截器】:实现ModelHook
ModelHook memoryInterceptor = new ModelHook() {
@Override
public String getName() {
return "memory_interceptor";
}
@Override
public HookPosition[] getHookPositions() {
// 【关键配置】:在模型调用前/后触发
return new HookPosition[] {HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL};
}
// 【Before钩子】:在模型调用前注入用户上下文
@Override
public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
// 从配置获取用户ID(关键!用于关联记忆)
String userId = (String) config.metadata("user_id").orElse(null);
if (userId == null) {
return CompletableFuture.completedFuture(Map.of());
}
// 从长期记忆加载用户画像
Optional<StoreItem> itemOpt = memoryStore.getItem(List.of("user_profiles"), userId);
if (itemOpt.isPresent()) {
Map<String, Object> profile = itemOpt.get().getValue();
// 生成上下文字符串
String userContext = String.format(
"用户信息:姓名=%s, 年龄=%s, 邮箱=%s, 偏好=%s",
profile.get("name"),
profile.get("age"),
profile.get("email"),
profile.get("preferences")
);
// 获取当前消息列表
List<Message> messages = (List<Message>) state.value("messages").orElse(new ArrayList<>());
List<Message> newMessages = new ArrayList<>();
// 【关键逻辑】:在SystemMessage中注入上下文
SystemMessage existingSystemMessage = null;
int systemMessageIndex = -1;
for (int i = 0; i < messages.size(); i++) {
Message msg = messages.get(i);
if (msg instanceof SystemMessage) {
existingSystemMessage = (SystemMessage) msg;
systemMessageIndex = i;
break;
}
}
// 更新或创建SystemMessage
SystemMessage enhancedSystemMessage;
if (existingSystemMessage != null) {
enhancedSystemMessage = new SystemMessage(
existingSystemMessage.getText() + "\n\n" + userContext
);
} else {
enhancedSystemMessage = new SystemMessage(userContext);
}
// 构建新消息列表(替换SystemMessage)
if (systemMessageIndex >= 0) {
for (int i = 0; i < messages.size(); i++) {
if (i == systemMessageIndex) {
newMessages.add(enhancedSystemMessage);
} else {
newMessages.add(messages.get(i));
}
}
} else {
newMessages.add(enhancedSystemMessage);
newMessages.addAll(messages);
}
// 返回更新后的消息列表(关键!影响模型输入)
return CompletableFuture.completedFuture(Map.of("messages", newMessages));
}
return CompletableFuture.completedFuture(Map.of());
}
// 【After钩子】:模型调用后可添加保存逻辑
@Override
public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
return CompletableFuture.completedFuture(Map.of());
}
};
// 【配置Agent】:绑定记忆拦截器
ReactAgent agent = ReactAgent.builder()
.name("memory_agent")
.model(chatModel)
.hooks(memoryInterceptor) // 【关键】:注册记忆拦截器
.saver(new MemorySaver())
.build();
// 【运行Agent】:触发自动记忆加载
RunnableConfig config = RunnableConfig.builder()
.threadId("session_001")
.addMetadata("user_id", "user_001") // 【关键】:绑定用户ID触发记忆加载
.build();
agent.invoke("请介绍一下我的信息。", config);
System.out.println("ModelHook管理长期记忆示例执行完成");
}
// 【示例4】:结合短期和长期记忆
// 作用:同时利用短期记忆(对话上下文)和长期记忆(用户画像)
public void example4_combinedMemory() throws GraphRunnerException {
// 创建长期记忆存储
MemoryStore memoryStore = new MemoryStore();
// 【预填充长期记忆】
Map<String, Object> userProfile = new HashMap<>();
userProfile.put("name", "李工程师");
userProfile.put("occupation", "软件工程师");
StoreItem profileItem = StoreItem.of(List.of("profiles"), "user_002", userProfile);
memoryStore.putItem(profileItem);
// 【创建组合Hook】:只在模型调用前注入长期记忆
ModelHook combinedMemoryHook = new ModelHook() {
@Override
public String getName() {
return "combined_memory";
}
@Override
public HookPosition[] getHookPositions() {
return new HookPosition[] {HookPosition.BEFORE_MODEL};
}
@Override
public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
// 从配置获取用户ID
Optional<Object> userIdOpt = config.metadata("user_id");
if (userIdOpt.isEmpty()) {
return CompletableFuture.completedFuture(Map.of());
}
String userId = (String) userIdOpt.get();
// 从长期记忆加载用户画像
Optional<StoreItem> profileOpt = memoryStore.getItem(List.of("profiles"), userId);
if (profileOpt.isEmpty()) {
return CompletableFuture.completedFuture(Map.of());
}
// 生成上下文字符串
Map<String, Object> profile = profileOpt.get().getValue();
String contextInfo = String.format("长期记忆:用户 %s, 职业: %s",
profile.get("name"), profile.get("occupation"));
// 获取消息列表
List<Message> messages = (List<Message>) state.value("messages").orElse(new ArrayList<>());
List<Message> newMessages = new ArrayList<>();
// 【关键】:在SystemMessage中注入长期上下文
SystemMessage existingSystemMessage = null;
int systemMessageIndex = -1;
for (int i = 0; i < messages.size(); i++) {
Message msg = messages.get(i);
if (msg instanceof SystemMessage) {
existingSystemMessage = (SystemMessage) msg;
systemMessageIndex = i;
break;
}
}
// 更新SystemMessage
SystemMessage enhancedSystemMessage;
if (existingSystemMessage != null) {
enhancedSystemMessage = new SystemMessage(
existingSystemMessage.getText() + "\n\n" + contextInfo
);
} else {
enhancedSystemMessage = new SystemMessage(contextInfo);
}
// 构建新消息列表
if (systemMessageIndex >= 0) {
for (int i = 0; i < messages.size(); i++) {
if (i == systemMessageIndex) {
newMessages.add(enhancedSystemMessage);
} else {
newMessages.add(messages.get(i));
}
}
} else {
newMessages.add(enhancedSystemMessage);
newMessages.addAll(messages);
}
return CompletableFuture.completedFuture(Map.of("messages", newMessages));
}
};
// 【创建Agent】:绑定组合Hook和短期记忆
ReactAgent agent = ReactAgent.builder()
.name("combined_memory_agent")
.model(chatModel)
.hooks(combinedMemoryHook)
.saver(new MemorySaver()) // 【关键】:启用短期记忆保存
.build();
// 【运行Agent】:先建立短期上下文,再触发长期记忆
RunnableConfig config = RunnableConfig.builder()
.threadId("combined_thread")
.addMetadata("user_id", "user_002")
.build();
// 短期记忆:记录当前对话上下文
agent.invoke("我今天在做一个 Spring 项目。", config);
// 长期记忆:结合职业信息给出建议
agent.invoke("根据我的职业和今天的工作,给我一些建议。", config);
System.out.println("结合短期和长期记忆示例执行完成");
}
// 【示例5】:跨会话记忆
// 作用:演示同一用户在不同会话中访问相同长期记忆
public void example5_crossSessionMemory() throws GraphRunnerException {
// 定义工具请求结构
record SaveMemoryRequest(List<String> namespace, String key, Map<String, Object> value) { }
record GetMemoryRequest(List<String> namespace, String key) { }
record MemoryResponse(String message, Map<String, Object> value) { }
// 创建内存存储(长期记忆容器)
MemoryStore memoryStore = new MemoryStore();
// 【创建保存工具】
ToolCallback saveMemoryTool = FunctionToolCallback.builder("saveMemory",
(BiFunction<SaveMemoryRequest, ToolContext, MemoryResponse>) (request, context) -> {
StoreItem item = StoreItem.of(request.namespace(), request.key(), request.value());
memoryStore.putItem(item);
return new MemoryResponse("已保存", request.value());
})
.description("保存到长期记忆")
.inputType(SaveMemoryRequest.class)
.build();
// 【创建获取工具】
ToolCallback getMemoryTool = FunctionToolCallback.builder("getMemory",
(BiFunction<GetMemoryRequest, ToolContext, MemoryResponse>) (request, context) -> {
Optional<StoreItem> itemOpt = memoryStore.getItem(request.namespace(), request.key());
return new MemoryResponse(
itemOpt.isPresent() ? "找到" : "未找到",
itemOpt.map(StoreItem::getValue).orElse(Map.of())
);
})
.description("从长期记忆获取")
.inputType(GetMemoryRequest.class)
.build();
// 创建Agent(启用工具和短期记忆)
ReactAgent agent = ReactAgent.builder()
.name("session_agent")
.model(chatModel)
.tools(saveMemoryTool, getMemoryTool)
.saver(new MemorySaver())
.build();
// 【会话1】:保存信息
RunnableConfig session1 = RunnableConfig.builder()
.threadId("session_morning")
.addMetadata("user_id", "user_003")
.build();
agent.invoke(
"记住我的密码是 secret123。用 saveMemory 保存,namespace=['credentials'], key='user_003_password', value={'password': 'secret123'}。",
session1
);
// 【会话2】:在不同线程中检索(跨会话验证)
RunnableConfig session2 = RunnableConfig.builder()
.threadId("session_afternoon")
.addMetadata("user_id", "user_003")
.build();
agent.invoke(
"我的密码是什么?用 getMemory 获取,namespace=['credentials'], key='user_003_password'。",
session2
);
System.out.println("跨会话记忆示例执行完成");
}
// 【示例6】:用户偏好学习
// 作用:Agent随对话学习并存储用户偏好
public void example6_preferLearning() throws GraphRunnerException {
MemoryStore memoryStore = new MemoryStore();
// 【创建偏好学习Hook】:在模型调用后触发
ModelHook preferenceLearningHook = new ModelHook() {
@Override
public String getName() {
return "preference_learning";
}
@Override
public HookPosition[] getHookPositions() {
return new HookPosition[] {HookPosition.AFTER_MODEL};
}
@Override
public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
// 从配置获取用户ID
String userId = (String) config.metadata("user_id").orElse(null);
if (userId == null) {
return CompletableFuture.completedFuture(Map.of());
}
// 获取对话消息
List<Message> messages = (List<Message>) state.value("messages").orElse(new ArrayList<>());
if (messages.isEmpty()) {
return CompletableFuture.completedFuture(Map.of());
}
// 【加载现有偏好】
Optional<StoreItem> prefsOpt = memoryStore.getItem(List.of("user_data"), userId + "_preferences");
List<String> prefs = new ArrayList<>();
if (prefsOpt.isPresent()) {
Map<String, Object> prefsData = prefsOpt.get().getValue();
prefs = (List<String>) prefsData.getOrDefault("items", new ArrayList<>());
}
// 【简单偏好提取】(实际应用中使用NLP)
for (Message msg : messages) {
String content = msg.getText().toLowerCase();
if (content.contains("喜欢") || content.contains("偏好")) {
prefs.add(msg.getText());
// 保存更新后的偏好
Map<String, Object> prefsData = new HashMap<>();
prefsData.put("items", prefs);
StoreItem item = StoreItem.of(List.of("user_data"), userId + "_preferences", prefsData);
memoryStore.putItem(item);
System.out.println("学习到用户偏好 " + userId + ": " + msg.getText());
}
}
return CompletableFuture.completedFuture(Map.of());
}
};
// 创建Agent(绑定偏好学习Hook)
ReactAgent agent = ReactAgent.builder()
.name("learning_agent")
.model(chatModel)
.hooks(preferenceLearningHook)
.saver(new MemorySaver())
.build();
// 【运行Agent】:用户表达偏好
RunnableConfig config = RunnableConfig.builder()
.threadId("learning_thread")
.addMetadata("user_id", "user_004")
.build();
agent.invoke("我喜欢喝绿茶。", config);
agent.invoke("我偏好早上运动。", config);
// 【验证学习】:检查存储的偏好
Optional<StoreItem> savedPrefs = memoryStore.getItem(List.of("user_data"), "user_004_preferences");
if (savedPrefs.isPresent()) {
System.out.println("已保存的偏好: " + savedPrefs.get().getValue());
}
System.out.println("用户偏好学习示例执行完成");
}
// 【运行所有示例】
public void runAllExamples() {
System.out.println("=== 记忆管理(Memory)示例 ===\n");
try {
System.out.println("示例1: 在工具中读取长期记忆");
example1_readMemoryInTool();
System.out.println();
System.out.println("示例2: 在工具中写入长期记忆");
example2_writeMemoryInTool();
System.out.println();
System.out.println("示例3: 使用ModelHook管理长期记忆");
example3_memoryWithModelHook();
System.out.println();
System.out.println("示例4: 结合短期和长期记忆");
example4_combinedMemory();
System.out.println();
System.out.println("示例5: 跨会话记忆");
example5_crossSessionMemory();
System.out.println();
System.out.println("示例6: 用户偏好学习");
example6_preferLearning();
System.out.println();
} catch (Exception e) {
System.err.println("执行示例时出错: " + e.getMessage());
e.printStackTrace();
}
}
}
5.3 执行结果
=== 记忆管理(Memory)示例 ===
示例1: 在工具中读取长期记忆
工具读取长期记忆示例执行完成
示例2: 在工具中写入长期记忆
保存的数据: {name=张三}
工具写入长期记忆示例执行完成
示例3: 使用ModelHook管理长期记忆
ModelHook管理长期记忆示例执行完成
示例4: 结合短期和长期记忆
19:25:27.281 [main] ERROR com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy -- Multiple SystemMessage instances detected (count: 2). This may cause unexpected behavior.
结合短期和长期记忆示例执行完成
示例5: 跨会话记忆
跨会话记忆示例执行完成
示例6: 用户偏好学习
学习到用户偏好 user_004: 我喜欢喝绿茶。
学习到用户偏好 user_004: 很高兴听到你喜欢喝绿茶!🍵 绿茶不仅清香怡人,还富含抗氧化物质,对健康有很多好处呢。你平时最喜欢哪种绿茶呀?是龙井、碧螺春,还是毛峰?我也可以分享一些泡茶的小技巧,或者推荐几款适合不同季节饮用的绿茶哦~ 😊
学习到用户偏好 user_004: 我喜欢喝绿茶。
学习到用户偏好 user_004: 很高兴听到你喜欢喝绿茶!🍵 绿茶不仅清香怡人,还富含抗氧化物质,对健康有很多好处呢。你平时最喜欢哪种绿茶呀?是龙井、碧螺春,还是毛峰?我也可以分享一些泡茶的小技巧,或者推荐几款适合不同季节饮用的绿茶哦~ 😊
学习到用户偏好 user_004: 我偏好早上运动。
学习到用户偏好 user_004: 太棒了!早上运动是个非常健康的生活方式,搭配你喜欢的绿茶,简直是开启一天的完美组合呢!🌞💪
晨间运动能帮助提升新陈代谢、增强专注力,还能让你一整天都充满活力。如果你在运动后喝上一杯温热的绿茶,不仅可以补充水分,茶中的儿茶素和少量咖啡因还能帮助抗氧化、提神醒脑,又不会像咖啡那样刺激。
小建议:
- 运动后等心率平稳后再喝茶,避免空腹饮用浓茶,以免引起不适。
- 可以尝试在运动后30分钟内喝一杯淡绿茶,搭配一点健康早餐,比如全麦面包或香蕉,效果更佳哦!
你平时早上都做哪些运动呢?跑步、瑜伽,还是力量训练?我可以帮你搭配一个"晨练+绿茶"的理想早晨流程 😊
已保存的偏好: {items=[我喜欢喝绿茶。, 很高兴听到你喜欢喝绿茶!🍵 绿茶不仅清香怡人,还富含抗氧化物质,对健康有很多好处呢。你平时最喜欢哪种绿茶呀?是龙井、碧螺春,还是毛峰?我也可以分享一些泡茶的小技巧,或者推荐几款适合不同季节饮用的绿茶哦~ 😊, 我喜欢喝绿茶。, 很高兴听到你喜欢喝绿茶!🍵 绿茶不仅清香怡人,还富含抗氧化物质,对健康有很多好处呢。你平时最喜欢哪种绿茶呀?是龙井、碧螺春,还是毛峰?我也可以分享一些泡茶的小技巧,或者推荐几款适合不同季节饮用的绿茶哦~ 😊, 我偏好早上运动。, 太棒了!早上运动是个非常健康的生活方式,搭配你喜欢的绿茶,简直是开启一天的完美组合呢!🌞💪
晨间运动能帮助提升新陈代谢、增强专注力,还能让你一整天都充满活力。如果你在运动后喝上一杯温热的绿茶,不仅可以补充水分,茶中的儿茶素和少量咖啡因还能帮助抗氧化、提神醒脑,又不会像咖啡那样刺激。
小建议:
- 运动后等心率平稳后再喝茶,避免空腹饮用浓茶,以免引起不适。
- 可以尝试在运动后30分钟内喝一杯淡绿茶,搭配一点健康早餐,比如全麦面包或香蕉,效果更佳哦!
你平时早上都做哪些运动呢?跑步、瑜伽,还是力量训练?我可以帮你搭配一个"晨练+绿茶"的理想早晨流程 😊]}
用户偏好学习示例执行完成
关键点:第二次对话中,AI客服能识别"上次点的宫保鸡丁太辣了",并自动调整推荐,因为系统已经通过记忆管理存储了用户的偏好。
6. 结语
在AI应用中,"记忆"不是简单的对话历史,而是让AI真正理解用户、提供个性化服务的关键。Spring AI Alibaba的Memory框架提供了一套完整、灵活的记忆管理机制,让Agent能够像人类一样记住用户的偏好、习惯,提供更贴心的服务。
正如《AI与人类:2025年的协作新范式》中所说:"在AI的世界里,最强大的力量不是算法,而是人与AI的智慧融合。" 记忆管理正是实现这种融合的关键技术之一。
通过合理使用Spring AI Alibaba的记忆管理功能,我们可以构建出真正理解用户、能够持续学习和进化的AI应用,为用户提供前所未有的个性化体验。
💡 提示:在实际项目中,建议从简单的用户偏好存储开始,逐步扩展到更复杂的记忆管理场景。不要一开始就追求"大而全",而是从解决具体业务问题出发。
7. 附录:推荐阅读
《Designing Data-Intensive Applications》(第二版) - Martin Kleppmann
这本书虽然不是专门讲AI的,但它深入探讨了数据存储、持久化和记忆管理的核心原理,对理解Spring AI Alibaba的Memory框架非常有帮助。特别是第15章"Data Models and Query Languages"和第16章"Storage and Databases",提供了关于如何设计和实现高效、可靠的数据存储系统的宝贵见解。
本书是2025年最新版,包含了对现代AI应用中数据管理的最新思考,是AI架构师的必读书籍。
参考链接:
- Spring AI Alibaba官方文档 - Memory Management
- Spring AI Alibaba官方文档 - Agent Framework
- Spring AI Alibaba GitHub仓库
本文所有代码和示例均基于Spring AI Alibaba最新版本(2025年12月),如需完整代码示例,请访问官方GitHub仓库。