【Spring AI Alibaba】⑥ 记忆管理(Memory):让Agent拥有“长期记忆“的智能方法

📖目录

  • 前言
  • [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绑定的。通过threadIdnamespace/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客服的对话流程:

  1. 用户第一次咨询:"我想要点一份宫保鸡丁"
  2. 用户第二次咨询:"上次点的宫保鸡丁太辣了,这次能不能不放辣椒?"

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最新版本(2025年12月),如需完整代码示例,请访问官方GitHub仓库。

相关推荐
kevin_kang2 小时前
06-Next.js 13构建现代化AI聊天界面
人工智能
Codebee2 小时前
实战|Ooder 钩子机制全解析:AI 协同开发与权限框架集成实战
人工智能·后端
Coder_Boy_2 小时前
基于SpringAI企业级智能教学考试平台视频辅助学习模块全业务闭环方案
人工智能·spring cloud
kevin_kang2 小时前
09-JWT认证在Next.js中的最佳实践
人工智能
AI街潜水的八角2 小时前
基于Opencv的二维码识别与创建
人工智能·opencv·计算机视觉
helloworld也报错?2 小时前
目标检测系列之YOLOv11——v8模型的继续改进
人工智能·python·目标检测·目标跟踪
微光闪现2 小时前
国际航班动态提醒与延误预测优选平台指南
大数据·人工智能·算法
iiiiii112 小时前
TD(λ),资格迹(Eligibility Traces)与时序差分学习的统一
人工智能·学习·机器学习·强化学习·rl