SpringAIAlibaba之短期记忆与长期记忆实现原理(十一)

本文深入剖析 Spring AI Alibaba 框架中短期记忆(Short-term Memory)和长期记忆(Long-term Memory)的实现机制,结合源码和测试用例讲解两者的协作模式。

一、记忆系统概述

在 AI Agent 应用中,记忆系统是维持对话连贯性和个性化体验的核心能力:

  • 短期记忆(Short-term Memory):保存当前会话的对话历史,类似人类的"工作记忆"
  • 长期记忆(Long-term Memory):持久化存储关键信息,类似人类的"长时记忆"

Spring AI Alibaba 通过 MemorySaverMemoryStore 两个组件实现了这两种记忆机制。


二、短期记忆实现原理

2.1 核心组件:MemorySaver

MemorySaver 是短期记忆的实现类,负责保存和恢复会话状态(Checkpoint)。其核心特点:

  • 存储结构ConcurrentHashMap<String, Map<String, Checkpoint>>
  • 隔离维度 :通过 threadId 隔离不同会话
  • 生命周期:应用重启后数据丢失(内存存储)

2.2 实现机制

保存检查点

当 Agent 执行完一轮对话后,会调用 MemorySaver.save() 保存状态:

java 复制代码
// MemorySaver.java
public class MemorySaver implements CheckpointSaver {
    private final ConcurrentHashMap<String, Map<String, Checkpoint>> storage = new ConcurrentHashMap<>();

    @Override
    public void save(Checkpoint checkpoint, RunnableConfig config) {
        String threadId = config.threadId().orElseThrow();
        String checkpointId = checkpoint.getId();

        // 按 threadId 分组存储
        storage.computeIfAbsent(threadId, k -> new ConcurrentHashMap<>())
               .put(checkpointId, checkpoint);
    }
}

关键要点

  • 每个 threadId 对应一个独立的 Map
  • Checkpoint 包含完整的对话历史(messages)和状态(state)
恢复检查点

当用户继续对话时,Agent 会加载上一次的 Checkpoint:

java 复制代码
@Override
public Optional<Checkpoint> load(RunnableConfig config) {
    String threadId = config.threadId().orElseThrow();
    Map<String, Checkpoint> threadCheckpoints = storage.get(threadId);

    if (threadCheckpoints == null || threadCheckpoints.isEmpty()) {
        return Optional.empty();
    }

    // 返回最新的 checkpoint
    return threadCheckpoints.values().stream()
        .max(Comparator.comparing(Checkpoint::getCreatedAt));
}

2.3 使用示例

来自测试文件 ShortTermMemoryTest.java:82-116

java 复制代码
@Test
void testBasicShortTermMemory() throws Exception {
    // 1. 创建带 MemorySaver 的 Agent
    ReactAgent agent = ReactAgent.builder()
        .name("memory_agent")
        .model(chatModel)
        .saver(new MemorySaver())  // ← 启用短期记忆
        .build();

    // 2. 创建带 threadId 的配置
    RunnableConfig config = RunnableConfig.builder()
        .threadId("conversation_1")  // ← 会话隔离标识
        .build();

    // 3. 第一轮对话:告诉名字
    agent.invoke("你好!我叫张三。", config);

    // 4. 第二轮对话:询问名字(依赖短期记忆)
    Optional<OverAllState> result = agent.invoke("我叫什么名字?", config);
    AssistantMessage response = extractLastMessage(result);

    // Agent 能记住之前的对话
    assertTrue(response.getText().contains("张三"));
}

执行流程

  1. 第一轮对话后,MemorySaver 保存 Checkpoint(包含用户消息 + 回复)
  2. 第二轮对话时,Agent 加载 Checkpoint 恢复对话历史
  3. 将新消息追加到历史中,发送给 LLM
  4. LLM 基于完整历史回答问题

三、长期记忆实现原理

3.1 核心组件:MemoryStore

MemoryStore 用于存储结构化的长期信息,支持命名空间(namespace)组织数据:

  • 存储结构namespace + key + value(类似 K-V 数据库)
  • 持久化:支持扩展到 Redis/MongoDB(当前版本为内存实现)
  • 跨会话:不依赖 threadId,可在不同会话间共享

3.2 数据模型

java 复制代码
// StoreItem.java
public class StoreItem {
    private List<String> namespace;  // 命名空间:如 ["user_profiles"]
    private String key;               // 键:如 "user_001"
    private Map<String, Object> value; // 值:如 {"name": "张三", "age": 28}

    public static StoreItem of(List<String> namespace, String key, Map<String, Object> value) {
        return new StoreItem(namespace, key, value);
    }
}

3.3 实现机制

存储数据
java 复制代码
// MemoryStore.java
public class MemoryStore implements Store {
    private final Map<String, Map<String, StoreItem>> storage = new ConcurrentHashMap<>();

    @Override
    public void putItem(StoreItem item) {
        String namespaceKey = String.join("/", item.getNamespace());
        storage.computeIfAbsent(namespaceKey, k -> new ConcurrentHashMap<>())
               .put(item.getKey(), item);
    }
}
检索数据
java 复制代码
@Override
public Optional<StoreItem> getItem(List<String> namespace, String key) {
    String namespaceKey = String.join("/", namespace);
    Map<String, StoreItem> namespaceMap = storage.get(namespaceKey);

    if (namespaceMap == null) {
        return Optional.empty();
    }
    return Optional.ofNullable(namespaceMap.get(key));
}

3.4 使用示例

来自测试文件 LongTermMemoryTest.java:88-188

java 复制代码
@Test
void testLongTermMemoryWithInterceptor() throws Exception {
    // 1. 创建记忆拦截器
    ModelHook memoryInterceptor = new ModelHook() {
        @Override
        public CompletableFuture<Map<String, Object>> beforeModel(
                OverAllState state, RunnableConfig config) {

            // 从配置获取用户 ID
            String userId = (String) config.metadata("user_id").get();

            // 从长期记忆加载用户资料
            Optional<StoreItem> profile = memoryStore.getItem(
                List.of("user_profiles"), userId);

            if (profile.isPresent()) {
                Map<String, Object> data = profile.get().getValue();

                // 注入到系统消息
                String context = String.format(
                    "用户信息:姓名=%s, 年龄=%s, 邮箱=%s",
                    data.get("name"), data.get("age"), data.get("email"));

                List<Message> messages = state.value("messages").orElse(List.of());
                List<Message> newMessages = new ArrayList<>(messages);
                newMessages.add(0, new SystemMessage(context));

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

    // 2. 预先存储用户资料到长期记忆
    Map<String, Object> profileData = Map.of(
        "name", "王小明",
        "age", 28,
        "email", "wang@example.com"
    );
    StoreItem item = StoreItem.of(List.of("user_profiles"), "user_001", profileData);
    memoryStore.putItem(item);

    // 3. 创建 Agent
    ReactAgent agent = ReactAgent.builder()
        .name("memory_agent")
        .model(chatModel)
        .hooks(memoryInterceptor)  // ← 注入拦截器
        .saver(new MemorySaver())
        .build();

    // 4. 询问个人信息
    RunnableConfig config = RunnableConfig.builder()
        .threadId("session_001")
        .addMetadata("user_id", "user_001")
        .build();

    Optional<OverAllState> result = agent.invoke("请介绍一下我的信息。", config);
    AssistantMessage response = extractLastMessage(result);

    // Agent 能使用长期记忆中的资料回答
    assertTrue(response.getText().contains("王小明") || response.getText().contains("28"));
}

执行流程

  1. Hook 在 beforeModel 位置从 MemoryStore 加载用户资料
  2. 将资料注入到 SystemMessage 中
  3. MemorySaver 保存包含注入内容的完整对话
  4. LLM 基于注入的用户资料生成回复

四、短期记忆与长期记忆的协作

4.1 协作模式

Spring AI Alibaba 提供三种协作模式:

模式1:Hook 自动注入

通过 ModelHook 在每次调用模型前自动注入长期记忆:

java 复制代码
@HookPositions(HookPosition.BEFORE_MODEL)
public class MemoryInjectionHook extends ModelHook {
    private final MemoryStore memoryStore;

    @Override
    public CompletableFuture<Map<String, Object>> beforeModel(
            OverAllState state, RunnableConfig config) {

        String userId = config.metadata("user_id").orElse(null);
        if (userId == null) {
            return CompletableFuture.completedFuture(Map.of());
        }

        // 加载用户偏好
        Optional<StoreItem> prefs = memoryStore.getItem(
            List.of("user_preferences"), userId);

        if (prefs.isPresent()) {
            // 注入到对话历史
            String context = buildContextFromPreferences(prefs.get().getValue());
            List<Message> messages = state.value("messages").orElse(List.of());
            List<Message> newMessages = new ArrayList<>();
            newMessages.add(new SystemMessage(context));
            newMessages.addAll(messages);

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

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

优点:透明化,业务代码无需关心记忆加载

模式2:Tool 主动存取

通过工具(Tool)让 Agent 自主决定何时读写长期记忆:

java 复制代码
// 保存记忆工具
BiFunction<SaveMemoryRequest, ToolContext, MemoryResponse> saveMemoryFunction =
    (request, context) -> {
        StoreItem item = StoreItem.of(
            request.namespace(),
            request.key(),
            request.value()
        );
        memoryStore.putItem(item);
        return new MemoryResponse("成功保存", request.value());
    };

ToolCallback saveMemoryTool = FunctionToolCallback.builder("saveMemory", saveMemoryFunction)
    .description("保存信息到长期记忆")
    .inputType(SaveMemoryRequest.class)
    .build();

// 获取记忆工具
BiFunction<GetMemoryRequest, ToolContext, MemoryResponse> getMemoryFunction =
    (request, context) -> {
        Optional<StoreItem> item = memoryStore.getItem(
            request.namespace(),
            request.key()
        );
        return item.isPresent()
            ? new MemoryResponse("找到记忆", item.get().getValue())
            : new MemoryResponse("未找到", Map.of());
    };

ToolCallback getMemoryTool = FunctionToolCallback.builder("getMemory", getMemoryFunction)
    .description("从长期记忆获取信息")
    .inputType(GetMemoryRequest.class)
    .build();

// 创建 Agent
ReactAgent agent = ReactAgent.builder()
    .name("memory_tool_agent")
    .model(chatModel)
    .tools(saveMemoryTool, getMemoryTool)  // ← 注册工具
    .saver(new MemorySaver())
    .build();

使用示例 (来自 LongTermMemoryTest.java:244-265):

java 复制代码
// 第一轮:保存信息
agent.invoke(
    "请帮我记住:我最喜欢的颜色是蓝色。使用 saveMemory 工具保存。",
    config
);

// 第二轮:检索信息
Optional<OverAllState> result = agent.invoke(
    "我最喜欢的颜色是什么?使用 getMemory 工具。",
    config
);
// Agent 调用 getMemory 工具获取答案

优点:灵活性高,Agent 可根据上下文决定是否使用记忆

模式3:跨会话共享

长期记忆不绑定 threadId,可在不同会话间共享:

java 复制代码
@Test
void testMemoryAcrossSessions() throws Exception {
    // Session 1: 保存密码
    RunnableConfig session1 = RunnableConfig.builder()
        .threadId("session_morning")
        .addMetadata("user_id", "user_003")
        .build();
    agent.invoke("记住我的密码是 secret123", session1);

    // Session 2: 读取密码(不同 threadId,相同 user_id)
    RunnableConfig session2 = RunnableConfig.builder()
        .threadId("session_afternoon")  // ← 不同会话
        .addMetadata("user_id", "user_003")  // ← 相同用户
        .build();

    Optional<OverAllState> result = agent.invoke("我的密码是什么?", session2);
    // 能够跨会话获取到密码
}

4.2 记忆转换:短期 → 长期

通过 Hook 自动提取短期记忆中的关键信息存入长期记忆(来自 LongTermMemoryTest.java:436-490):

java 复制代码
@HookPositions(HookPosition.AFTER_MODEL)
public class PreferenceLearningHook extends ModelHook {

    @Override
    public CompletableFuture<Map<String, Object>> afterModel(
            OverAllState state, RunnableConfig config) {

        String userId = config.metadata("user_id").orElse(null);
        if (userId == null) {
            return CompletableFuture.completedFuture(Map.of());
        }

        // 1. 从短期记忆读取对话
        List<Message> messages = state.value("messages").orElse(List.of());

        // 2. 加载已有的长期记忆
        Optional<StoreItem> prefsOpt = memoryStore.getItem(
            List.of("user_data"),
            userId + "_preferences"
        );
        List<String> prefs = prefsOpt.isPresent()
            ? (List<String>) prefsOpt.get().getValue().getOrDefault("items", new ArrayList<>())
            : new ArrayList<>();

        // 3. 提取偏好信息(简单示例:检测关键词)
        for (Message msg : messages) {
            String content = msg.getText().toLowerCase();
            if (content.contains("喜欢") || content.contains("偏好")) {
                prefs.add(msg.getText());

                // 4. 写入长期记忆
                Map<String, Object> prefsData = Map.of("items", prefs);
                StoreItem item = StoreItem.of(
                    List.of("user_data"),
                    userId + "_preferences",
                    prefsData
                );
                memoryStore.putItem(item);

                System.out.println("学习到用户偏好: " + msg.getText());
            }
        }

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

使用示例

java 复制代码
ReactAgent agent = ReactAgent.builder()
    .name("learning_agent")
    .model(chatModel)
    .hooks(new PreferenceLearningHook())
    .saver(new MemorySaver())
    .build();

// 用户表达偏好
agent.invoke("我喜欢喝绿茶。", config);
agent.invoke("我偏好早上运动。", config);

// 偏好已自动存入长期记忆
Optional<StoreItem> savedPrefs = memoryStore.getItem(
    List.of("user_data"),
    "user_004_preferences"
);
// savedPrefs.get().getValue().get("items") = ["我喜欢喝绿茶。", "我偏好早上运动。"]

五、短期记忆的上下文窗口管理

5.1 问题背景

短期记忆保存完整对话历史,但 LLM 有 token 限制,需要管理上下文窗口大小。

5.2 解决方案:MessagesModelHook

方案1:消息裁剪(Trimming)

保留系统消息 + 最近 N 条消息(来自 ShortTermMemoryTest.java:267-297):

java 复制代码
@HookPositions(HookPosition.BEFORE_MODEL)
public class MessageTrimmingHook extends MessagesModelHook {
    private static final int MAX_MESSAGES = 5;
    private static final int KEEP_LAST = 4;

    @Override
    public AgentCommand beforeModel(List<Message> messages, RunnableConfig config) {
        if (messages.size() <= MAX_MESSAGES) {
            return new AgentCommand(messages);  // 不需要裁剪
        }

        // 保留第一条(系统消息)+ 最后4条
        Message firstMsg = messages.get(0);
        List<Message> recentMessages = messages.subList(
            messages.size() - KEEP_LAST,
            messages.size()
        );

        List<Message> trimmed = new ArrayList<>();
        trimmed.add(firstMsg);
        trimmed.addAll(recentMessages);

        return new AgentCommand(trimmed, UpdatePolicy.REPLACE);
    }
}

效果

  • 对话超过5条时,自动裁剪中间部分
  • LLM 只看到系统消息 + 最近4轮对话
方案2:消息删除(Deletion)

定期清理旧消息(来自 ShortTermMemoryTest.java:238-261):

java 复制代码
@HookPositions(HookPosition.AFTER_MODEL)
public class MessageDeletionHook extends MessagesModelHook {
    private static final int KEEP_LAST = 4;

    @Override
    public AgentCommand afterModel(List<Message> messages, RunnableConfig config) {
        if (messages.size() > KEEP_LAST) {
            // 只保留最近4条
            List<Message> recentMessages = new ArrayList<>(
                messages.subList(messages.size() - KEEP_LAST, messages.size())
            );
            return new AgentCommand(recentMessages, UpdatePolicy.REPLACE);
        }
        return new AgentCommand(messages);
    }
}

区别

  • Trimming(裁剪) :在 beforeModel 执行,只影响本次调用,不修改 Checkpoint
  • Deletion(删除) :在 afterModel 执行,修改 Checkpoint,永久删除旧消息

六、短期记忆失效机制

6.1 失效时机

短期记忆(MemorySaver)在以下情况失效:

  1. 应用重启:内存数据全部丢失
  2. threadId 不再使用:无法访问对应的 Checkpoint
  3. 主动删除 :通过 Hook 清理旧消息(如上文的 MessageDeletionHook

6.2 会话隔离

不同 threadId 之间完全隔离:

java 复制代码
@Test
void testMemoryIsolationBetweenThreads() throws Exception {
    ReactAgent agent = ReactAgent.builder()
        .name("memory_agent")
        .model(chatModel)
        .saver(new MemorySaver())
        .build();

    // Thread 1: Alice
    RunnableConfig config1 = RunnableConfig.builder()
        .threadId("thread_alice")
        .build();
    agent.invoke("你好,我叫 Alice。", config1);

    // Thread 2: Bob
    RunnableConfig config2 = RunnableConfig.builder()
        .threadId("thread_bob")
        .build();
    agent.invoke("你好,我叫 Bob。", config2);

    // Thread 1 只记得 Alice
    Optional<OverAllState> result1 = agent.invoke("我叫什么名字?", config1);
    assertTrue(extractLastMessage(result1).getText().contains("Alice"));

    // Thread 2 只记得 Bob
    Optional<OverAllState> result2 = agent.invoke("我叫什么名字?", config2);
    assertTrue(extractLastMessage(result2).getText().contains("Bob"));
}

七、实战案例:电商客服 Agent

7.1 需求分析

构建一个电商客服 Agent,需要:

  • 短期记忆:记住本次会话的商品咨询、订单查询
  • 长期记忆:记住用户的收货地址、购买偏好

7.2 实现代码

java 复制代码
@Configuration
public class CustomerServiceAgentConfig {

    @Bean
    public MemoryStore memoryStore() {
        return new MemoryStore();
    }

    @Bean
    public ModelHook userProfileHook(MemoryStore memoryStore) {
        return new ModelHook() {
            @Override
            public HookPosition[] getHookPositions() {
                return new HookPosition[]{HookPosition.BEFORE_MODEL};
            }

            @Override
            public CompletableFuture<Map<String, Object>> beforeModel(
                    OverAllState state, RunnableConfig config) {

                String userId = config.metadata("user_id").orElse(null);
                if (userId == null) {
                    return CompletableFuture.completedFuture(Map.of());
                }

                // 加载用户资料
                Optional<StoreItem> profile = memoryStore.getItem(
                    List.of("user_profiles"), userId);

                // 加载购买偏好
                Optional<StoreItem> preferences = memoryStore.getItem(
                    List.of("user_preferences"), userId);

                StringBuilder context = new StringBuilder("用户档案:\n");

                if (profile.isPresent()) {
                    Map<String, Object> data = profile.get().getValue();
                    context.append(String.format(
                        "- 姓名:%s\n- 会员等级:%s\n- 默认地址:%s\n",
                        data.get("name"), data.get("vipLevel"), data.get("address")
                    ));
                }

                if (preferences.isPresent()) {
                    Map<String, Object> prefs = preferences.get().getValue();
                    context.append(String.format("- 偏好品类:%s\n", prefs.get("categories")));
                }

                // 注入系统消息
                List<Message> messages = state.value("messages").orElse(List.of());
                List<Message> newMessages = new ArrayList<>();
                newMessages.add(new SystemMessage(context.toString()));
                newMessages.addAll(messages);

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

    @Bean
    public ReactAgent customerServiceAgent(
            ChatModel chatModel,
            MemoryStore memoryStore,
            ModelHook userProfileHook) {

        return ReactAgent.builder()
            .name("customer_service_agent")
            .model(chatModel)
            .saver(new MemorySaver())  // 短期记忆
            .hooks(userProfileHook)     // 长期记忆注入
            .tools(
                createOrderQueryTool(),
                createAddressUpdateTool(memoryStore)
            )
            .build();
    }

    private ToolCallback createAddressUpdateTool(MemoryStore memoryStore) {
        return FunctionToolCallback.builder("updateAddress",
            (UpdateAddressRequest req, ToolContext ctx) -> {
                String userId = ctx.getContext("user_id");

                // 更新长期记忆中的地址
                Optional<StoreItem> profile = memoryStore.getItem(
                    List.of("user_profiles"), userId);

                if (profile.isPresent()) {
                    Map<String, Object> data = new HashMap<>(profile.get().getValue());
                    data.put("address", req.newAddress);

                    StoreItem updated = StoreItem.of(
                        List.of("user_profiles"), userId, data);
                    memoryStore.putItem(updated);

                    return "地址已更新为:" + req.newAddress;
                }
                return "用户不存在";
            })
            .description("更新用户收货地址")
            .inputType(UpdateAddressRequest.class)
            .build();
    }
}

7.3 使用示例

java 复制代码
@Service
public class CustomerServiceController {

    @Autowired
    private ReactAgent customerServiceAgent;

    @Autowired
    private MemoryStore memoryStore;

    public String chat(String userId, String sessionId, String message) {
        // 确保用户资料存在
        initUserProfileIfNotExists(userId);

        RunnableConfig config = RunnableConfig.builder()
            .threadId(sessionId)  // 会话隔离
            .addMetadata("user_id", userId)  // 用户标识
            .build();

        Optional<OverAllState> result = customerServiceAgent.invoke(message, config);
        return extractLastMessage(result).getText();
    }

    private void initUserProfileIfNotExists(String userId) {
        if (memoryStore.getItem(List.of("user_profiles"), userId).isEmpty()) {
            // 从数据库加载用户资料
            UserProfile profile = userRepository.findById(userId);

            Map<String, Object> profileData = Map.of(
                "name", profile.getName(),
                "vipLevel", profile.getVipLevel(),
                "address", profile.getDefaultAddress()
            );

            StoreItem item = StoreItem.of(List.of("user_profiles"), userId, profileData);
            memoryStore.putItem(item);
        }
    }
}

7.4 对话示例

复制代码
用户: 你好,我想买一个手机
Agent: 您好王先生(黄金会员)!根据您之前的购买记录,您偏好高端手机。我推荐...

用户: 送到哪里?
Agent: 默认发送到您的地址:北京市朝阳区xxx(从长期记忆获取)

用户: 我换地址了,送到上海市浦东新区yyy
Agent: [调用 updateAddress 工具更新长期记忆] 好的,地址已更新!

--- 第二天,新会话 ---

用户: 订单发货了吗?
Agent: 正在为您查询...您的订单将发往:上海市浦东新区yyy(从长期记忆读取)

八、架构设计总结

8.1 分层架构

复制代码
┌─────────────────────────────────────────────┐
│            Agent Application Layer          │
│  (业务逻辑、对话流程、工具调用)              │
└─────────────────────────────────────────────┘
                      ↓
┌─────────────────────────────────────────────┐
│          Memory Coordination Layer          │
│  ModelHook: 注入长期记忆到短期记忆           │
│  MessagesModelHook: 管理上下文窗口           │
└─────────────────────────────────────────────┘
                      ↓
┌──────────────────────┬──────────────────────┐
│  Short-term Memory   │  Long-term Memory    │
│  (MemorySaver)       │  (MemoryStore)       │
│  - threadId 隔离     │  - namespace 组织    │
│  - 完整对话历史       │  - 结构化数据        │
│  - 内存存储          │  - 可持久化扩展       │
└──────────────────────┴──────────────────────┘

8.2 关键设计原则

  1. 职责分离

    • MemorySaver 负责对话连贯性
    • MemoryStore 负责知识持久化
  2. 灵活注入

    • Hook 模式:自动注入(透明)
    • Tool 模式:主动调用(灵活)
  3. 上下文管理

    • MessagesModelHook 控制窗口大小
    • UpdatePolicy 控制更新策略(APPEND/REPLACE)
  4. 可扩展性

    • Store 接口支持扩展到 Redis/MongoDB
    • CheckpointSaver 接口支持自定义存储

九、最佳实践建议

9.1 选择合适的记忆类型

数据类型 推荐方案 示例
对话历史 MemorySaver "我刚才问了什么?"
用户资料 MemoryStore 姓名、地址、会员等级
业务状态 MemoryStore 订单号、物流信息
临时变量 State (不持久化) 当前选中的商品

9.2 上下文窗口优化

java 复制代码
// 推荐配置
@Bean
public MessagesModelHook contextWindowHook() {
    return new MessagesModelHook() {
        private static final int MAX_MESSAGES = 20;
        private static final int KEEP_SYSTEM = 1;
        private static final int KEEP_RECENT = 10;

        @Override
        public AgentCommand beforeModel(List<Message> messages, RunnableConfig config) {
            if (messages.size() <= MAX_MESSAGES) {
                return new AgentCommand(messages);
            }

            // 保留 System 消息 + 最近 10 条
            List<Message> trimmed = new ArrayList<>();
            trimmed.addAll(messages.subList(0, KEEP_SYSTEM));
            trimmed.addAll(messages.subList(messages.size() - KEEP_RECENT, messages.size()));

            return new AgentCommand(trimmed, UpdatePolicy.REPLACE);
        }
    };
}

9.3 长期记忆索引设计

java 复制代码
// 推荐的 namespace 组织方式
memoryStore.putItem(StoreItem.of(
    List.of("users", userId, "profile"),  // 用户资料
    "basic_info",
    profileData
));

memoryStore.putItem(StoreItem.of(
    List.of("users", userId, "preferences"),  // 用户偏好
    "shopping_habits",
    prefsData
));

memoryStore.putItem(StoreItem.of(
    List.of("sessions", sessionId, "context"),  // 会话上下文
    "selected_products",
    productsData
));

9.4 错误处理

java 复制代码
@Override
public CompletableFuture<Map<String, Object>> beforeModel(
        OverAllState state, RunnableConfig config) {
    try {
        String userId = config.metadata("user_id").orElseThrow();
        Optional<StoreItem> profile = memoryStore.getItem(
            List.of("user_profiles"), userId);

        // 处理逻辑...

    } catch (Exception e) {
        logger.error("Failed to load long-term memory", e);
        // 降级处理:不注入长期记忆,继续执行
        return CompletableFuture.completedFuture(Map.of());
    }
}

十、源码位置参考

如果您想深入研究源码,以下是关键文件位置:

  • 短期记忆测试spring-ai-alibaba-agent-framework/src/test/java/com/alibaba/cloud/ai/graph/agent/memory/ShortTermMemoryTest.java
  • 长期记忆测试spring-ai-alibaba-agent-framework/src/test/java/com/alibaba/cloud/ai/graph/agent/memory/LongTermMemoryTest.java
  • MemorySaverspring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/MemorySaver.java
  • MemoryStorespring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/store/stores/MemoryStore.java
  • ReactAgentspring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/ReactAgent.java

十一、总结

Spring AI Alibaba 通过 MemorySaverMemoryStore 提供了完善的记忆管理能力:

  • 短期记忆:维持对话连贯性,按 threadId 隔离,易失性
  • 长期记忆:存储持久化知识,跨会话共享,可扩展
  • 协作机制:Hook 自动注入、Tool 主动调用、记忆转换

这套设计模式既保证了对话的流畅性,又实现了知识的积累,为构建智能化的 AI Agent 应用奠定了坚实基础。
欢迎关注、一起学习、一起进步~

相关推荐
独自破碎E2 小时前
【BISHI11】变幻莫测
android·java·开发语言
qq_12498707532 小时前
基于Javaweb的《战舰世界》游戏百科信息系统(源码+论文+部署+安装)
java·vue.js·人工智能·spring boot·游戏·毕业设计·计算机毕业设计
colus_SEU2 小时前
【论文精读】Instance-Dependent Partial Label Learning
人工智能·深度学习·机器学习·pll·部分标签学习
m0_706653232 小时前
C++中的解释器模式
开发语言·c++·算法
小飞象—木兮2 小时前
《电商运营分析手册》:定义、价值、产品规划与定价策略、指标体系与公式详解、电商运营框架、运营思维与经营复盘···(附相关材料下载)
大数据·人工智能·产品运营
Lethehong2 小时前
一次 GLM-4.7 的 MaaS 接入实践:Dify 工作流搭建笔记
人工智能·蓝耘元生代·蓝耘maas
Cx330❀2 小时前
深入理解 Linux 基础 IO:从 C 库到系统调用的完整剖析
linux·运维·服务器·c语言·数据库·人工智能·科技
jkyy20142 小时前
赋能TOB端|以智能科技,筑牢糖尿病慢病精细化管理防线
大数据·人工智能·健康医疗
lsx2024062 小时前
命令模式:深入理解与实战应用
开发语言