概述

前面我们学习了工具使用和Hook等。但是有一个问题还没有解决。

比如,我们和AI会话时,很可能出现,我前面告诉他我喜欢吃苹果,再问他我喜欢吃什么水果,他就不记得了。我们使用chatgpt或deepseek时,在同一个会话框里他好像也记得啊,那真是因为他有了“记忆”。也就是整体能否记住我之前说过的话。

那么有了记忆,智能体不再是个健忘的机器人了,他能:

  • 记住先前的交互:在多轮对话中保持上下文连贯性
  • 从反馈中学习:根据用户的纠正或补充信息调整后续行为
  • 适应用户偏好:随着时间的推移,越来越了解用户的习惯和需求

Spring AI Alibaba 将记忆分为两个层次,短期记忆和长期记忆:

维度 短期记忆(Short-term Memory) 长期记忆(Long-term Memory)
存储位置 Graph 状态(State) Store(持久化存储)
生命周期 单个会话(threadId)内有效 跨会话、跨时间持久化
存储内容 对话历史(Messages) 用户画像、偏好、知识
实现方式 CheckpointSaver
(如 MemorySaver、RedisSaver)
MemoryStore
更新时机 每次 Agent 调用或工具调用后自动更新 通过工具或 Hook 显式读写
典型用途 多轮对话上下文 跨会话的用户记忆

通俗点来讲:

  • 短期记忆就像“即时通讯软件的聊天记录”,只在这个会话窗口内有效,关掉窗口就没了(除非保存到服务器)。
  • 长期记忆就像“用户档案”,无论什么时候打开聊天,系统都能调出这个用户的历史偏好和信息。

短期记忆

短期记忆是指在单个线程或会话中还能记住先前的对话。会话可以隔离同一个 Agent 实例中的多个不同交互,类似于电子邮件在单个对话中分组消息的方式。

在Spring AI Alibaba 中,短期记忆是作为 Agent 状态(State)的一部分进行管理。前面我们学过状态是通过 Checkpointer(检查点器) 持久化到数据库(或内存)的,以便随时恢复线程。

短期记忆也一样,使用Checkpointer在调用 Agent 或完成步骤(如工具调用)时自动更新,并在每个步骤开始时自动读取状态。

使用方法

在 Spring AI Alibaba 中,要向 Agent 添加短期记忆(会话级持久化),你需要在创建 Agent 时指定 Checkpointer

下面看一个示例,使用 MemorySaver,MemorySaver 将状态保存在 JVM 内存中,适用于开发和测试场景:

import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.alibaba.cloud.ai.graph.RunnableConfig;

// 创建 Agent 并配置 checkpointer
ReactAgent agent = ReactAgent.builder()
    .name("my_agent")
    .model(chatModel)
    .tools(getUserInfoTool)
    .saver(new MemorySaver())  // 启用短期记忆
    .build();

// 使用 threadId 维护对话上下文
RunnableConfig config = RunnableConfig.builder()
    .threadId("1")  // threadId 指定会话 ID
    .build();

// 第一轮对话
agent.call("你好!我叫 Bob。", config);

// 第二轮对话 - Agent 还记得上一轮的内容
agent.call("你还记得我叫什么吗?", config);
// 输出:当然记得!你叫 Bob。

注意:

  • threadId 是会话的唯一标识,同一个 threadId 的多次调用共享同一个会话上下文
  • 不同的 threadId 之间的对话历史完全隔离

生产环境我们还可以使用数据库支持的Checkpointer,比如Redis Checkpointer:

import com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver;
import org.redisson.api.RedissonClient;

// 配置 Redis 连接
RedissonClient redissonClient = // ... 创建 Redisson 客户端

// 创建 Agent 并配置 Redis checkpointer
RedisSaver redisSaver = new RedisSaver(redissonClient);

ReactAgent agent = ReactAgent.builder()
    .name("my_agent")
    .model(chatModel)
    .tools(getUserInfoTool)
    .saver(redisSaver)  // 使用 Redis 持久化
    .build();

// 使用方式与 MemorySaver 完全相同
RunnableConfig config = RunnableConfig.builder()
    .threadId("user-session-123")
    .build();
agent.call("你好!", config);

上下文过长的问题

保留所有对话历史是实现短期记忆最常见的形式。但较长的对话历史也会带来很多问题。比如,我们和大模型对话的时候,发给大模型的消息长度也是有限制的,超过限制会导致上下文丢失或报错。不仅如此,太大的上下文也会影响模型的性能,分散注意力影响效果,同时Token 数量增加导致推理时间延长,成本上升。

常见的解决方案包括:

  • 修剪消息:在调用 LLM 之前移除前 N 条或后 N 条消息
  • 删除消息:从 Graph 状态中永久删除某些消息
  • 总结消息:总结历史中较早的消息并用摘要替换它们
  • 自定义策略:根据业务需求定制消息过滤或压缩逻辑

这些技术统称为 “上下文工程” ,将在第 17 章中详细展开。

长期记忆

长期记忆让 Agent 能够跨会话记住用户信息、偏好和知识。

Spring AI Alibaba 将长期记忆以 JSON 文档的形式存储在 Store 中。每个记忆都在自定义的 namespace(命名空间) 下组织,并使用唯一的 key 来标识。命名空间通常包含用户或组织 ID 或其他标签,以便更容易地组织信息。

存储模型的核心概念:

概念 说明 类比
Store 长期记忆的存储抽象接口 文件系统
Namespace 记忆的层级组织,通常包含用户 ID 或组织 ID 文件夹
Key 记忆的唯一标识符 文件名
StoreItem 一个完整的记忆条目,包含 namespace、key 和 value 一个文件

命名空间通常包含用户 ID 或其他标签,以便更容易地组织信息。这种结构支持记忆的层次化组织,并支持通过内容过滤器进行跨命名空间搜

基础使用

MemoryStore 为例。

MemoryStore 是将数据保存到内存字典中的实现,适用于开发和测试。生产环境请使用基于数据库的存储实现。

package com.cys.saa.chapter08;

import com.alibaba.cloud.ai.graph.store.StoreItem;
import com.alibaba.cloud.ai.graph.store.StoreSearchRequest;
import com.alibaba.cloud.ai.graph.store.StoreSearchResult;
import com.alibaba.cloud.ai.graph.store.stores.MemoryStore;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class MemoryStoreUse {

    public static void main(String[] args) {
        // MemoryStore 将数据保存到内存字典中。在生产环境中请使用基于数据库的存储实现
        MemoryStore store = new MemoryStore();

        String userId = "my-user";
        String applicationContext = "chitchat";
        List<String> namespace = List.of(userId, applicationContext);

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

        StoreItem item = StoreItem.of(namespace, "a-memory", memoryData);
        store.putItem(item);

        // 通过ID获取记忆
        Optional<StoreItem> retrievedItem = store.getItem(namespace, "a-memory");

        // 在此命名空间内搜索记忆,通过内容等价性过滤,按向量相似度排序
        StoreSearchRequest storeSearchRequest = new StoreSearchRequest();
        storeSearchRequest.setNamespace(namespace);
        storeSearchRequest.setFilter(Map.of("my-key", "my-value"));
        StoreSearchResult result = store.searchItems(storeSearchRequest);
        List<StoreItem> items = result.getItems();
    }
}

在工具中读取长期记忆

下面的示例展示了如何创建一个工具,让 Agent 能够查询用户信息

package com.cys.saa.chapter08;

import com.alibaba.cloud.ai.graph.store.Store;
import com.alibaba.cloud.ai.graph.store.StoreItem;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import org.springframework.ai.chat.model.ToolContext;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;

public class GetUserInfoTool implements BiFunction<String, ToolContext, String> {

    @Override
    public String apply(String userId, ToolContext context) {
        // 从上下文中获取 Store
        RunnableConfig config = (RunnableConfig) context.getContext().get("config");
        Store store = config.store();

        // 从长期记忆中读取用户信息
        List<String> namespace = List.of("users", userId);
        Optional<StoreItem> userItem = store.getItem(namespace, "profile");

        if (userItem.isEmpty()) {
            return "未找到用户 " + userId + " 的信息";
        }

        Map<String, Object> userData = userItem.get().getValue();
        return String.format("用户信息:姓名=%s, 偏好=%s",
                userData.get("name"),
                userData.get("preference")
        );
    }
}

在工具中写入长期记忆

同样,Agent 也可以通过工具将信息写入长期记忆:

package com.cys.saa.chapter08;

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.store.Store;
import com.alibaba.cloud.ai.graph.store.StoreItem;
import org.springframework.ai.chat.model.ToolContext;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

public class SaveUserInfoTool implements BiFunction<Map<String, Object>, ToolContext, String> {

    @Override
    public String apply(Map<String, Object> request, ToolContext context) {
        RunnableConfig config = (RunnableConfig) context.getContext().get("config");
        Store store = config.store();

        String userId = (String) request.get("userId");
        String name = (String) request.get("name");
        String preference = (String) request.get("preference");

        // 构建记忆数据
        Map<String, Object> userData = new HashMap<>();
        userData.put("name", name);
        userData.put("preference", preference);
        userData.put("updatedAt", System.currentTimeMillis());

        // 保存到长期记忆
        List<String> namespace = List.of("users", userId);
        StoreItem item = StoreItem.of(namespace, "profile", userData);
        store.putItem(item);

        return "用户信息已保存成功!";
    }
}

ModelHook 管理长期记忆

结合上一张我们学的Hook,可以使用用 ModelHook 在模型调用前后自动加载和保存长期记忆。

package com.cys.saa.chapter08;

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.ModelHook;
import com.alibaba.cloud.ai.graph.store.Store;
import com.alibaba.cloud.ai.graph.store.StoreItem;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

public class memoryInterceptor extends 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) {
        // 从配置中获取用户ID
        String userId = (String) config.metadata("user_id").orElse(null);
        if (userId == null) {
            return CompletableFuture.completedFuture(Map.of());
        }

        Store store = config.store();
        // 从记忆存储中加载用户画像
        Optional<StoreItem> itemOpt = store.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) {
                // 更新现有的 SystemMessage
                enhancedSystemMessage = new SystemMessage(
                        existingSystemMessage.getText() + " " + userContext
                );
            } else {
                // 创建新的 SystemMessage
                enhancedSystemMessage = new SystemMessage(userContext);
            }

            // 构建新的消息列表
            if (systemMessageIndex >= 0) {
                // 如果找到了 SystemMessage,替换它
                for (int i = 0; i < messages.size(); i++) {
                    if (i == systemMessageIndex) {
                        newMessages.add(enhancedSystemMessage);
                    } else {
                        newMessages.add(messages.get(i));
                    }
                }
            } else {
                // 如果没有找到 SystemMessage,在开头添加新的
                newMessages.add(enhancedSystemMessage);
                newMessages.addAll(messages);
            }

            return CompletableFuture.completedFuture(Map.of("messages", newMessages));
        }

        return CompletableFuture.completedFuture(Map.of());
    }

    @Override
    public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
        // 可以在这里实现对话后的记忆保存逻辑
        return CompletableFuture.completedFuture(Map.of());
    }
}

记忆管理在Agent中流程

短期记忆和长期记忆在 Spring AI Alibaba 中是如何协同工作的?下图展示了完整的记忆架构:

┌─────────────────────────────────────────────────────────┐
│                      ReactAgent                         │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌──────────────────┐  ┌────────────────────┐          │
│  │    短期记忆        │  │    长期记忆         │          │
│  │  (MemorySaver)    │  │  (MemoryStore)     │          │
│  └──────────────────┘  └────────────────────┘          │
│         │                      │                        │
│    threadId              namespace/key                  │
│         ↓                      ↓                        │
│  ┌──────────────────┐  ┌────────────────────┐          │
│  │   对话历史        │  │   用户画像          │          │
│  │   Conversation   │  │   User Profiles    │          │
│  │   History        │  │   Preferences      │          │
│  └──────────────────┘  └────────────────────┘          │
│                                                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │        ModelHook / 拦截器                        │    │
│  │  - beforeModel: 加载并注入记忆                   │    │
│  │  - afterModel: 保存并学习交互内容               │    │
│  └─────────────────────────────────────────────────┘    │
│                                                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │        记忆工具 Tools                            │    │
│  │  - saveMemory: 显式保存到长期记忆               │    │
│  │  - getMemory: 显式从长期记忆检索                │    │
│  └─────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘

集成的关键点:

  1. 短期记忆通过 CheckpointSaver(如 MemorySaverRedisSaver)实现,绑定到 threadId
  2. 长期记忆通过 Store(如 MemoryStore)实现,通过 namespace/key 组织
  3. ModelHook 可以在模型调用前后自动加载和保存长期记忆
  4. 工具(Tools) 可以通过 ToolContext 访问 Store,实现显式的记忆读写

结合短期和长期记忆

在实际应用中,短期记忆和长期记忆通常配合使用:

  • 短期记忆存储当前的对话上下文,让 Agent 在多轮对话中保持连贯
  • 长期记忆存储用户的持久化信息,让 Agent 跨会话记住用户偏好

完整示例:

package com.cys.saa.chapter08;

import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import com.alibaba.cloud.ai.graph.store.StoreItem;
import com.alibaba.cloud.ai.graph.store.stores.MemoryStore;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.beans.factory.annotation.Value;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ShortAndLongMemory {

    public static void main(String[] args) throws GraphRunnerException {
        // 1. 创建短期记忆(会话级)
        MemorySaver memorySaver = new MemorySaver();

        // 2. 创建长期记忆(跨会话)
        MemoryStore memoryStore = new MemoryStore();

        // 3. 预先写入一些示例数据
        Map<String, Object> userData = new HashMap<>();
        userData.put("name", "张三");
        userData.put("language", "中文");
        userData.put("preference", "喜欢技术类内容");
        StoreItem userItem = StoreItem.of(List.of("users", "user_123"), "profile", userData);
        memoryStore.putItem(userItem);

        // 4. 创建工具回调
        ToolCallback getUserInfoTool = FunctionToolCallback
        .builder("getUserInfo", new GetUserInfoTool())
        .description("根据用户 ID 获取用户信息,返回用户的姓名和偏好")
        .inputType(String.class)  // 输入类型为 String
        .build();

        ToolCallback saveUserInfoTool = FunctionToolCallback
        .builder("saveUserInfo", new SaveUserInfoTool())
        .description("保存或更新用户信息,包括姓名和偏好")
        .inputType(Map.class)  // 输入类型为 Map
        .build();

        // 5. 构建 Agent,同时配置短期和长期记忆
        String apiKey = System.getenv("SPACE_API_KEY");
        DashScopeChatModel chatModel = DashScopeChatModel.builder()
        .dashScopeApi(DashScopeApi.builder().apiKey(apiKey).build())
        .defaultOptions(DashScopeChatOptions.builder()
                        .withModel(DashScopeChatModel.DEFAULT_MODEL_NAME)
                        .withTemperature(0.7)
                        .withMaxToken(2048)
                        .build())
        .build();
        ReactAgent agent = ReactAgent.builder()
        .name("memory_agent")
        .model(chatModel)
        .tools(getUserInfoTool)  // 工具可以从长期记忆读取
        .tools(saveUserInfoTool) // 工具可以写入长期记忆
        .saver(memorySaver)            // 短期记忆
        .build();

        // 6. 执行:配置中包含 Store
        RunnableConfig config = RunnableConfig.builder()
        .threadId("session_001")
        .store(memoryStore)            // 长期记忆
        .addMetadata("user_id", "user_123")
                .build();

        // 7. 第一轮对话 - Agent 可以从长期记忆中读取用户信息
        agent.invoke("你好!", config);

        // 7. 第二轮对话 - 使用不同的 threadId,但相同的 Store
        RunnableConfig configB = RunnableConfig.builder()
                .threadId("session_002")
                .store(memoryStore)            // 同一个 Store
                .addMetadata("user_id", "user_123")
                .build();
        agent.invoke("你还记得我是谁吗?", configB);
    }
}

总结:这一章学习了短期以及和长期记忆,以及如何使用他们,他们的作用是什么,怎么影响我整个Agent流程的。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐