Spring AI Alibaba 1.x 系列【23】短期记忆

文章目录

  • [1. 概述](#1. 概述)
  • [2. 基于内存](#2. 基于内存)
  • [3. 生产环境 Mysql 持久化](#3. 生产环境 Mysql 持久化)
    • [3.1 引入依赖](#3.1 引入依赖)
    • [3.2 配置数据库](#3.2 配置数据库)
    • [3.3 配置 MysqlSaver](#3.3 配置 MysqlSaver)
      • [3.3.1 DataSource](#3.3.1 DataSource)
      • [3.3.2 CreateOption](#3.3.2 CreateOption)
      • [3.3.3 StateSerializer](#3.3.3 StateSerializer)
    • [3.4 构建 ReactAgent](#3.4 构建 ReactAgent)
    • [3.5 单元测试](#3.5 单元测试)
  • [4. 长上下文](#4. 长上下文)
    • [4.1 带来的问题](#4.1 带来的问题)
    • [4.2 长上下文优化方案(上下文工程)](#4.2 长上下文优化方案(上下文工程))
      • [4.2.1 消息修剪(保留关键消息)](#4.2.1 消息修剪(保留关键消息))
      • [4.2.2 消息删除(清理冗余历史)](#4.2.2 消息删除(清理冗余历史))
        • [4.2.2.1 删除旧消息](#4.2.2.1 删除旧消息)
        • [4.2.2.2 清空所有消息](#4.2.2.2 清空所有消息)
      • [4.2.3 消息总结(高级上下文压缩)](#4.2.3 消息总结(高级上下文压缩))
  • [5. 记忆读写与扩展](#5. 记忆读写与扩展)
    • [5.1 工具中读写短期记忆](#5.1 工具中读写短期记忆)
    • [5.2 Before Model Hook 预处理消息](#5.2 Before Model Hook 预处理消息)
    • [5.3 After Model Hook 校验与过滤](#5.3 After Model Hook 校验与过滤)
    • [5.4 拦截器实现动态提示](#5.4 拦截器实现动态提示)
  • [6. 总结](#6. 总结)

1. 概述

记忆是 Agent 具备持续交互能力的核心,它能让 Agent 记住历史对话、从交互中学习并适应用户偏好。在处理复杂任务与多轮对话时,记忆能力直接决定了 Agent 的执行效率与用户体验。

短期记忆的作用是让应用在单个线程/会话内保留历史交互记录,而 thread_id 则实现了同 Agent 下多对话的隔离管理,类似邮件按对话分组的逻辑。

Spring AI Alibaba 将短期记忆作为 Agent 状态的一部分统一管理(核心原理):

  1. 对话上下文存储在 Graph 状态中,不同会话通过 thread_id 隔离
  2. 状态通过 CheckpointSaver 持久化到内存/数据库,支持随时恢复会话
  3. 记忆在 Agent 调用、工具执行等步骤自动更新
  4. 每轮执行开始时读取状态,保证上下文一致性

短期记忆核心价值:

  • 单会话上下文留存
  • 多对话隔离互不干扰
  • 支持中断恢复与状态回溯
  • 结合 Checkpoint 实现持久化

Agent 启用短期记忆(会话级持久化)只需要在构建时指定 saver(checkpointer),并通过 threadId 区分会话。

状态持久化 Saver 对比表:

Saver 名称 存储介质 适用场景 核心特点
MemorySaver 内存 测试、演示、临时会话 速度极快、重启丢失、无持久化
MysqlSaver MySQL 生产环境、需要可靠持久化 关系型数据库、事务支持、成熟稳定
RedisSaver Redis 高并发、分布式会话 读写极快、支持分布式、过期自动清理
MongoSaver MongoDB 文档型状态、灵活 Schema NoSQL、结构灵活、易水平扩展
FileSystemSaver 文件系统 简单部署、本地单机场景 无数据库依赖、部署简单
PostgresSaver PostgreSQL 生产环境、复杂查询/JSON 场景 功能完备、原生 JSON 支持、强事务
OracleSaver Oracle 企业级项目、高可用要求 企业级特性、高可靠性、金融级稳定

2. 基于内存

配置内存版检查点存储器:

java 复制代码
MemorySaver memorySaver = new MemorySaver();

ReactAgent agent = ReactAgent.builder()
  .name("my_agent")
  .model(chatModel)
  .tools(getUserInfoTool)
  .saver(memorySaver)
  .build();

使用 thread_id 维护对话上下文:

java 复制代码
RunnableConfig config = RunnableConfig.builder()
  .threadId("user_123456") // 会话唯一标识
  .build();

多轮对话会自动保留上下文:

java 复制代码
String res1 = my_agent.call("你好!我叫 Bob。", config).toString();
String res2 = my_agent.call("我是谁", config).toString();

System.out.println(res1);
System.out.println(res2);

可以在内存中看到当前会话的所有 Checkpoint

Checkpoint 中包含了历史对话消息:

3. 生产环境 Mysql 持久化

生产环境推荐使用分布式存储实现会话高可用、共享与持久化。

Spring AI Alibaba 1.x 系列【13】 检查点 (Checkpoint) 机制及各类持久化实现 中介绍过支持的持久化方式:

  • RedisSaver
  • MongoSaver
  • MysqlSaver
  • OracleSaver
  • FileSystemSaver
  • PostgresSaver

提示 :如果想实现其他存储库,参考 MysqlSaver 即可。

3.1 引入依赖

引入 Spring Boot Starter JDBC 支持:

xml 复制代码
        <!-- Spring Boot Starter JDBC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- MySQL Driver -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

3.2 配置数据库

yml 复制代码
spring:
  application:
    name: spring-ai-alibaba-01
  # MySQL 数据库配置(用于对话记忆持久化)
  datasource:
    url: jdbc:mysql://192.168.1.630:3306/admin?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

3.3 配置 MysqlSaver

java 复制代码
	// Configuration
	private final DataSource dataSource;
	private final CreateOption createOption;
	private final StateSerializer stateSerializer;

当前配置示例:

java 复制代码
    @Bean
    public MysqlSaver mysqlSaver(DataSource dataSource) {  // ← Spring 自动注入
        return MysqlSaver.builder()
                .createOption(CreateOption.CREATE_IF_NOT_EXISTS)
                .dataSource(dataSource)  // ← 使用 Spring 配置的 DataSource
                .build();
    }

3.3.1 DataSource

MysqlSaver 依赖于 DataSource ,这里 Spring Boot 已经帮我们自动配置了,所以直接使用即可

3.3.2 CreateOption

数据库 schema 创建配置:

枚举常量 英文含义 中文释义 适用场景
CREATE_NONE No attempt is made to create the schema object 不创建 生产环境,表已手动创建,禁止自动操作
CREATE_IF_NOT_EXISTS Reuse existing, create if not exists 不存在则创建 初始化环境,兼容已有表,安全通用
CREATE_OR_REPLACE Drop existing and create new 删除并重建 开发/测试环境,重置表结构,不保留数据

CreateOption 枚举类:

java 复制代码
/**
 * 配置数据库架构对象(如表、索引)创建策略的选项
 */
public enum CreateOption {

	/** 不执行任何架构对象创建操作 */
	CREATE_NONE,

	/** 复用已存在的架构对象,若不存在则创建 */
	CREATE_IF_NOT_EXISTS,

	/** 删除已存在的架构对象,并重新创建新对象 */
	CREATE_OR_REPLACE
}

3.3.3 StateSerializer

StateSerializer用于将图状态转为 JSON 存储到 MySQL ,若未手动配置状态序列化器,则自动使用框架默认的 Jackson 序列化器:

java 复制代码
		/**
		 * Creates a new instance of MysqlSaver
		 *
		 * @return the new instance of MysqlSaver.
		 */
		public MysqlSaver build() {
			if(stateSerializer == null) {
                this.stateSerializer = StateGraph.DEFAULT_JACKSON_SERIALIZER;
            }
			return new MysqlSaver(this);
		}

默认使用的是 SpringAIJacksonStateSerializer

java 复制代码
	/**
	 * Default Jackson serializer instance.
	 */
	public static final StateSerializer DEFAULT_JACKSON_SERIALIZER = new SpringAIJacksonStateSerializer(OverAllState::new, new ObjectMapper());

3.4 构建 ReactAgent

构建 ReactAgent 时指定 MysqlSaver

java 复制代码
@Configuration
public class AgentConfig {


    @Bean
    public MysqlSaver mysqlSaver(DataSource dataSource) {  // ← Spring 自动注入
        return MysqlSaver.builder()
                .createOption(CreateOption.CREATE_IF_NOT_EXISTS)
                .dataSource(dataSource)  // ← 使用 Spring 配置的 DataSource
                .build();
    }

    @Bean
    public ReactAgent chatAgent(ChatModel zhiPuAiChatModel,MysqlSaver mysqlSaver) {
        ReactAgent my_agent = ReactAgent.builder()
                .name("my_agent")
                .model(zhiPuAiChatModel)
                .saver(mysqlSaver)
                .build();

        return chatAgent;
    }
}   

3.5 单元测试

执行多轮会话:

java 复制代码
        String res1 = my_agent.call("你好!我叫 Bob。", config).getText();
        String res2 = my_agent.call("我是谁", config).getText();

        System.out.println(res1);
        System.out.println(res2);

在数据库自动创建表并保存了相关数据:

4. 长上下文

4.1 带来的问题

保留全量对话历史是最常见的记忆实现方式,但会带来一系列问题:

  • LLM 上下文窗口超限,直接报错
  • 模型被冗余信息干扰,推理效果下降
  • Token 成本飙升、响应速度变慢

Spring AI Alibaba 中,ReactAgent 通过 messages 维护上下文,随着交互增多消息列表会持续膨胀。因此需要通过上下文工程裁剪、优化历史信息。

4.2 长上下文优化方案(上下文工程)

Spring AI Alibaba 提供四类标准策略解决上下文膨胀问题:

  • 消息修剪
  • 消息删除
  • 消息总结
  • 自定义策略

4.2.1 消息修剪(保留关键消息)

保留系统提示 + 最新 N 条消息,避免上下文超限。

java 复制代码
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook;
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import org.springframework.ai.chat.messages.Message;
import java.util.*;

@HookPositions({HookPosition.BEFORE_MODEL})
public class MessageTrimmingHook extends MessagesModelHook {

  private static final int MAX_MESSAGES = 3;

  @Override
  public String getName() {
      return "message_trimming";
  }

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

      // 保留第一条系统消息 + 最新几条对话
      Message firstMsg = previousMessages.get(0);
      List<Message> recentMessages = previousMessages.subList(
          previousMessages.size() - MAX_MESSAGES,
          previousMessages.size()
      );

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

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

4.2.2 消息删除(清理冗余历史)

4.2.2.1 删除旧消息
java 复制代码
@HookPositions({HookPosition.AFTER_MODEL})
public class DeleteOldMessagesHook extends MessagesModelHook {

  @Override
  public String getName() {
      return "delete_old_messages";
  }

  @Override
  public AgentCommand afterModel(List<Message> previousMessages, RunnableConfig config) {
      if (previousMessages.size() > 2) {
          // 删除最早两条消息
          List<Message> remaining = previousMessages.subList(2, previousMessages.size());
          return new AgentCommand(remaining, UpdatePolicy.REPLACE);
      }
      return new AgentCommand(previousMessages);
  }
}
4.2.2.2 清空所有消息
java 复制代码
@HookPositions({HookPosition.AFTER_MODEL})
public class ClearAllMessagesHook extends MessagesModelHook {

  @Override
  public String getName() {
      return "clear_all_messages";
  }

  @Override
  public AgentCommand afterModel(List<Message> previousMessages, RunnableConfig config) {
      // 直接返回空列表清空上下文
      return new AgentCommand(new ArrayList<>(), UpdatePolicy.REPLACE);
  }
}

4.2.3 消息总结(高级上下文压缩)

使用轻量模型对旧消息做摘要,既保留信息又控制长度:

java 复制代码
@HookPositions({HookPosition.BEFORE_MODEL})
public class MessageSummarizationHook extends MessagesModelHook {

  private final ChatModel summaryModel;
  private final int maxTokensBeforeSummary;
  private final int messagesToKeep;

  public MessageSummarizationHook(ChatModel summaryModel, int maxTokens, int keepCount) {
      this.summaryModel = summaryModel;
      this.maxTokensBeforeSummary = maxTokens;
      this.messagesToKeep = keepCount;
  }

  @Override
  public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
      // 简易 Token 估算
      int estimatedTokens = previousMessages.stream()
          .mapToInt(m -> m.getText().length() / 4).sum();

      if (estimatedTokens < maxTokensBeforeSummary) {
          return new AgentCommand(previousMessages);
      }

      // 拆分旧消息与最新消息
      int summarizeCount = previousMessages.size() - messagesToKeep;
      List<Message> oldMsgs = previousMessages.subList(0, summarizeCount);
      List<Message> recentMsgs = previousMessages.subList(summarizeCount, previousMessages.size());

      // 生成摘要
      String summary = generateSummary(oldMsgs);

      // 构造新消息列表:摘要 + 最新消息
      List<Message> newMessages = new ArrayList<>();
      newMessages.add(new SystemMessage("## 历史对话摘要:\n" + summary));
      newMessages.addAll(recentMsgs);

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

  private String generateSummary(List<Message> messages) {
      StringBuilder sb = new StringBuilder();
      messages.forEach(m -> sb.append(m.getMessageType()).append(": ").append(m.getText()).append("\n"));
      String prompt = "请简要总结以下对话:\n" + sb;
      return summaryModel.call(new Prompt(new UserMessage(prompt))).getResult().getOutput().getText();
  }

  @Override
  public String getName() {
      return "message_summarization";
  }
}

5. 记忆读写与扩展

Spring AI Alibaba 支持在工具、Hook、拦截器中自由读写 Agent 记忆(状态)。

5.1 工具中读写短期记忆

通过 ToolContext 访问会话配置与元数据:

java 复制代码
import com.alibaba.cloud.ai.graph.RunnableConfig;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.ai.chat.model.ToolContext;
import java.util.function.BiFunction;

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

  @Override
  public String apply(String query, ToolContext toolContext) {
      // 从上下文获取配置与用户信息
      RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config");
      String userId = (String) config.metadata("user_id").orElse("");

      return "user_123".equals(userId) ? "用户是 John Smith" : "未知用户";
  }
}

// 注册工具
ToolCallback getUserInfoTool = FunctionToolCallback
  .builder("get_user_info", new UserInfoTool())
  .description("获取用户信息")
  .inputType(String.class)
  .build();

5.2 Before Model Hook 预处理消息

在模型调用前裁剪/过滤消息:

java 复制代码
@HookPositions({HookPosition.BEFORE_MODEL})
public class TrimMessagesHook extends MessagesModelHook {

  @Override
  public AgentCommand beforeModel(List<Message> messages, RunnableConfig config) {
      if (messages.size() <= 3) return new AgentCommand(messages);
      
      Message first = messages.get(0);
      List<Message> recent = messages.subList(messages.size()-3, messages.size());
      List<Message> result = new ArrayList<>();
      result.add(first);
      result.addAll(recent);
      
      return new AgentCommand(result, UpdatePolicy.REPLACE);
  }
}

5.3 After Model Hook 校验与过滤

模型返回后过滤敏感信息:

java 复制代码
@HookPositions({HookPosition.AFTER_MODEL})
public class ValidateResponseHook extends MessagesModelHook {

  private static final List<String> STOP_WORDS = List.of("password", "secret", "api_key");

  @Override
  public AgentCommand afterModel(List<Message> messages, RunnableConfig config) {
      if (messages.isEmpty()) return new AgentCommand(messages);
      
      Message last = messages.get(messages.size()-1);
      boolean hasSensitive = STOP_WORDS.stream().anyMatch(w -> last.getText().toLowerCase().contains(w));

      if (hasSensitive) {
          List<Message> filtered = new ArrayList<>(messages.subList(0, messages.size()-1));
          filtered.add(new AssistantMessage("抱歉,我无法提供该信息。"));
          return new AgentCommand(filtered, UpdatePolicy.REPLACE);
      }
      return new AgentCommand(messages);
  }
}

5.4 拦截器实现动态提示

java 复制代码
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelRequest;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelCallHandler;
import org.springframework.ai.chat.messages.SystemMessage;

public class DynamicPromptInterceptor extends ModelInterceptor {

  @Override
  public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
      String userName = (String) request.getContext().get("user_name");
      String dynamicPrompt = "你是贴心助手,请称呼用户为:" + userName;

      SystemMessage newSys = new SystemMessage(request.getSystemMessage().getText() + "\n" + dynamicPrompt);
      ModelRequest newReq = ModelRequest.builder(request).systemMessage(newSys).build();
      
      return handler.call(newReq);
  }
}

6. 总结

  1. 短期记忆是 Spring AI Agent 实现多轮对话的核心,基于 Checkpoint 持久化
  2. thread_id 实现会话隔离,支持内存/Redis/MySQL/PostgreSQL/Oracle/文件等存储
  3. 长上下文问题可通过修剪、删除、总结三类标准方案解决
  4. 记忆可在工具、Hook、拦截器中自由读写,扩展性极强
  5. 生产环境建议使用 Redis/数据库实现分布式会话持久化
相关推荐
xiaotao1312 小时前
01-编程基础与数学基石:概率与统计
人工智能·python·numpy·pandas
竹之却2 小时前
【Agent-阿程】OpenClaw v2026.4.15 版本更新全解析
人工智能·ai·openclaw
嵌入式小企鹅2 小时前
DeepSeek-V4昇腾首发、国芯抗量子MCU突破、AI编程Agent抢班夺权
人工智能·学习·ai·程序员·算力·risc-v
摇滚侠2 小时前
帮我整理一份 IDEA 开发中常用快捷键
java·ide·intellij-idea
快乐非自愿2 小时前
抛弃传统AI:OpenClaw与Skill重构AI生产力,技术范式不可逆
大数据·人工智能
大模型最新论文速读2 小时前
合成数据的正确打开方式:格式比模型重要,小模型比大模型好用
论文阅读·人工智能·深度学习·机器学习·自然语言处理
网络研究员2 小时前
Claude身份认证后还是被封?三条稳定防封策略
大数据·人工智能
冬奇Lab2 小时前
一天一个开源项目(第76篇):Cangjie Skill —— 将书本知识炼金为 AI 智能体可执行的技能
人工智能·开源·资讯
金融Tech趋势派2 小时前
OpenClaw火了,AI Agent下一步走向哪里?
人工智能·github·企业微信·openclaw·企微管家claw