Spring AI Alibaba ——记忆管理(Memory)

Spring AI Alibaba ------记忆管理(Memory)

💡 核心结论:一句话先记住 如果说 Short-term Memory(短期记忆) 是 Agent 的"系统内存(RAM)",聊完天、关掉当前对话(Thread)就清空;那么 Long-term Memory(长期记忆) 就是 Agent 的"专属硬盘(数据库)",哪怕你换台电脑重新登录,它依然记得你"不喝香菜、只写 Java"。

在 Spring AI Alibaba 中,想要打造一个拥有"超强记忆力"的专属 AI 助理,你必须把短期记忆(MemorySaver)长期记忆(MemoryStore)双剑合璧!


🛑 一、 核心概念:长期记忆是怎么存的? 大白话: 长期记忆在系统里是以 JSON 文档的形式存着的。为了不乱套,官方搞了两个概念:

  • Namespace(命名空间): 相当于文件夹 。比如你可以建一个叫 users 的文件夹,专门存用户画像。
  • Key(键): 相当于文件名 。比如叫 user_123

💻 1. 基础热身:MemoryStore 怎么用?(数据库操作)

Java 复制代码
import com.alibaba.cloud.ai.graph.store.stores.MemoryStore;
import com.alibaba.cloud.ai.graph.store.StoreItem;
import java.util.*;

// 1. 建一个存储库(生产环境请换成真实的数据库 Redis/MySQL 等实现)
MemoryStore store = new MemoryStore();
String userId = "my-user";
String applicationContext = "chitchat";
// ⭐ 定义文件夹路径:[用户ID, 业务场景]
List<String> namespace = List.of(userId, applicationContext);

// 2. 准备要存的数据
Map<String, Object> memoryData = new HashMap<>();
memoryData.put("rules", List.of("用户喜欢简短直接的语言", "用户只说中文和Java"));
memoryData.put("my-key", "my-value");

// 3. 存进去!(存入文件夹 namespace,文件名为 a-memory)
StoreItem item = StoreItem.of(namespace, "a-memory", memoryData);
store.putItem(item);

// 4. 精准读取
Optional<StoreItem> retrievedItem = store.getItem(namespace, "a-memory");

// 5. 模糊搜索(根据内容找记忆,甚至按向量相似度排序!)
List<StoreItem> items = store.searchItems(namespace, Map.of("my-key", "my-value"));

🛠️ 二、 玩法一:让 Agent 的"双手"去读写记忆(Tools 显式操作)

大白话: 这是最直接的办法------给大模型配两把"工具(Tools)",一把叫"查档案",一把叫"写档案"。当大模型觉得需要时,它会自己动手去翻。

💻 2. 让 Agent 自己去【读】长期记忆(抄作业区):

Java 复制代码
// 1. 定义工具的输入输出
public record GetMemoryRequest(List<String> namespace, String key) {}
public record MemoryResponse(String message, Map<String, Object> value) {}

// 2. 搞一个存储库,塞点假数据
MemoryStore store = new MemoryStore();
Map<String, Object> userData = Map.of("name", "张三", "language", "中文");
store.putItem(StoreItem.of(List.of("users"), "user_123", userData));

// 3. ⭐ 打造"查档案"工具
BiFunction<GetMemoryRequest, ToolContext, MemoryResponse> getUserInfoFunction = (request, context) -> {
    // 从上下文里掏出全局仓库
    RunnableConfig runnableConfig = (RunnableConfig) context.getContext().get("config");
    Optional<StoreItem> itemOpt = runnableConfig.store().getItem(request.namespace(), request.key());
    
    if (itemOpt.isPresent()) {
        return new MemoryResponse("找到用户信息", itemOpt.get().getValue());
    }
    return new MemoryResponse("未找到用户", Map.of());
};

ToolCallback getUserInfoTool = FunctionToolCallback.builder("getUserInfo", getUserInfoFunction)
        .description("查询用户信息").inputType(GetMemoryRequest.class).build();

// 4. 挂载给 Agent
ReactAgent agent = ReactAgent.builder()
        .name("memory_agent").model(chatModel)
        .tools(getUserInfoTool) // 装备工具
        .saver(new MemorySaver()) // 开启短期记忆
        .build();

// 5. 跑起来!让它去查
RunnableConfig config = RunnableConfig.builder()
        .threadId("session_001") // 当前聊天 ID
        .store(store) // ⭐ 关键:把仓库挂在运行时配置上
        .build();
agent.invoke("查询用户信息,namespace=['users'], key='user_123'", config);

💻 3. 让 Agent 自己去【写】长期记忆(抄作业区):

Java 复制代码
// 1. 定义写入工具的输入
public record SaveMemoryRequest(List<String> namespace, String key, Map<String, Object> value) {}

// 2. ⭐ 打造"写档案"工具
BiFunction<SaveMemoryRequest, ToolContext, MemoryResponse> saveUserInfoFunction = (request, context) -> {
    RunnableConfig runnableConfig = (RunnableConfig) context.getContext().get("config");
    Store store = runnableConfig.store();
    
    // 把 AI 传进来的数据直接怼进数据库
    store.putItem(StoreItem.of(request.namespace(), request.key(), request.value()));
    return new MemoryResponse("成功保存用户信息", request.value());
};

ToolCallback saveUserInfoTool = FunctionToolCallback.builder("saveUserInfo", saveUserInfoFunction)
        .description("保存用户信息").inputType(SaveMemoryRequest.class).build();

ReactAgent agent = ReactAgent.builder()
        .name("save_memory_agent").model(chatModel)
        .tools(saveUserInfoTool).saver(new MemorySaver()).build();

// 3. 跑起来!命令它记住你
RunnableConfig config = RunnableConfig.builder().threadId("session_001").store(new MemoryStore()).build();
agent.invoke("我叫张三,请保存我的信息。使用 saveUserInfo 工具,namespace=['users'], key='user_123', value={'name': '张三'}", config);

🧠 三、 玩法二:大管家偷偷喂料(ModelHook 自动加载,最优雅 ⭐⭐⭐⭐⭐)

大白话: 用 Tool 让大模型自己查,费 Token 也费脑子。最高级的做法是:每次大模型开口前,系统管家(Hook)先去查档案,查完后悄悄拼接在系统提示词(System Prompt)里塞给它。大模型会觉得:"哎?我怎么天然就知道这哥们的喜好?"

💻 4. 使用 ModelHook 全自动管理记忆(抄作业区):

Java 复制代码
// 1. 创建拦截器/钩子
ModelHook memoryInterceptor = new ModelHook() {
    @Override public String getName() { return "memory_interceptor"; }
    @Override public HookPosition[] getHookPositions() { 
        return new HookPosition[]{HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL}; 
    }

    // ⭐ 在大模型思考前,硬核塞记忆
    @Override
    public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
        String userId = (String) config.metadata("user_id").orElse(null);
        if (userId == null) return CompletableFuture.completedFuture(Map.of());

        // 去长期记忆里扒出这个人的画像
        Optional<StoreItem> itemOpt = config.store().getItem(List.of("user_profiles"), userId);
        if (itemOpt.isPresent()) {
            Map<String, Object> profile = itemOpt.get().getValue();
            String userContext = String.format("用户信息:姓名=%s, 年龄=%s, 偏好=%s", 
                    profile.get("name"), profile.get("age"), profile.get("preferences"));

            // 把这堆信息,偷偷加进原有的聊天记录(Messages)的系统人设里
            List<Message> messages = (List<Message>) state.value("messages").orElse(new ArrayList<>());
            List<Message> newMessages = new ArrayList<>();
            // (此处省略遍历找原 SystemMessage 的细节逻辑,总之就是把 userContext 拼接到 SystemMessage 里面去,或者新建一个放在最前面)
            newMessages.add(new SystemMessage(userContext));
            newMessages.addAll(messages);
            
            // 狸猫换太子,把加了记忆的记录传给大模型
            return CompletableFuture.completedFuture(Map.of("messages", newMessages));
        }
        return CompletableFuture.completedFuture(Map.of());
    }
    
    // (可选) afterModel 里还可以写逻辑:对话结束后,自动总结并存入长期记忆
};

// 2. 挂载 Hook
ReactAgent agent = ReactAgent.builder().name("memory_agent").model(chatModel)
        .hooks(memoryInterceptor).saver(new MemorySaver()).build();

// 3. 准备数据并运行
MemoryStore memoryStore = new MemoryStore();
memoryStore.putItem(StoreItem.of(List.of("user_profiles"), "user_001", Map.of("name", "王小明", "age", 28)));

RunnableConfig config = RunnableConfig.builder().threadId("session_001").addMetadata("user_id", "user_001").store(memoryStore).build();
// AI 虽然没见过你,但 Hook 已经告诉它了!
agent.invoke("请介绍一下我的信息。", config);

🤝 四、 终极体:长短期记忆双管齐下 (跨会话不迷路)

大白话: 如果你想做一个生产级的完美 Agent。你必须:

  1. MemorySaver + 相同的 threadId 来保证用户在这一次聊天框里上下文不丢(短期)。
  2. MemoryStore + 相同的 user_id 来保证用户就算第二天新开了一个聊天框,它还记得用户的身份和职业(长期)。

💻 5. 长短期记忆双拼代码:

Java 复制代码
// (Hook 的定义与上文类似,主要是在 beforeModel 中拼接长期记忆)
ModelHook combinedMemoryHook = new ModelHook() { /*...*/ };

// 核心配置:同时注入 hook(管长期) 和 saver(管短期)
ReactAgent agent = ReactAgent.builder()
        .name("combined_memory_agent")
        .model(chatModel)
        .hooks(combinedMemoryHook) // ⭐ 长期记忆的搬运工
        .saver(new MemorySaver())  // ⭐ 短期记忆的保管箱
        .build();

// 提前在库里存好老底(长期)
MemoryStore memoryStore = new MemoryStore();
memoryStore.putItem(StoreItem.of(List.of("profiles"), "user_002", Map.of("name", "李工程师", "occupation", "软件工程师")));

RunnableConfig config = RunnableConfig.builder()
        .threadId("combined_thread") // 短期记忆靠这个认人
        .addMetadata("user_id", "user_002") // 长期记忆靠这个认人
        .store(memoryStore).build();

// 聊第一句(此时它用了长期记忆"李工程师",并把当前聊天放进短期记忆)
agent.invoke("我今天在做一个 Spring 项目。", config);
// 聊第二句(此时它既知道你是谁(长期),又知道你在干嘛(短期)!)
agent.invoke("根据我的职业和今天的工作,给我一些建议。", config);

🎯 终极秒记口诀 一次聊天短期记,Saver 搞定省力气; 要想档案永不丢,Store 上阵做登记。 Tools 读写随手用,手动存取很给力; 想要优雅显神奇,Hook 拦截偷喂你; 长短结合跨会话,最强助理就是你!