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/数据库实现分布式会话持久化
相关推荐
你不是我我7 小时前
【Java 开发日记】HTTP3 性能更好,为什么内网微服务依然多用 HTTP2?HTTP2 内网优势是什么?
java·开发语言·微服务
agicall.com8 小时前
座机通话双方语音分离技术解决方案详解
人工智能·语音识别·信创电话助手·座机语音转文字·固话座机录音转文字
AI机器学习算法8 小时前
《动手学深度学习PyTorch版》笔记
人工智能·学习·机器学习
雪碧聊技术8 小时前
大模型爆火!Java后端如何抓住Agent全栈开发的风口
java·大模型·agent·全栈开发
Goboy8 小时前
「我的第一次移动端 AI 办公」TRAE SOLO 三端联动, 通勤路上就把活干了,这设计,老罗看了都想当场退役
人工智能·ai编程·trae
qq_452396238 小时前
第二十篇:《UI自动化测试的未来:AI驱动的智能测试与低代码平台》
人工智能·低代码·ui
视觉&物联智能8 小时前
【杂谈】-人工智能风险文化对组织决策的深远影响
人工智能·安全·ai·agi
β添砖java9 小时前
深度学习(12)Kaggle房价竞赛
人工智能·深度学习
冬奇Lab9 小时前
RAG 系列(十):混合检索——让召回更全面
人工智能·llm
冬奇Lab9 小时前
一天一个开源项目(第95篇):Claude for Financial Services - Anthropic 官方金融行业 AI 代理套件
人工智能·开源·资讯