文章目录
- [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 状态的一部分统一管理(核心原理):
- 对话上下文存储在
Graph状态中,不同会话通过thread_id隔离 - 状态通过
CheckpointSaver持久化到内存/数据库,支持随时恢复会话 - 记忆在
Agent调用、工具执行等步骤自动更新 - 每轮执行开始时读取状态,保证上下文一致性
短期记忆核心价值:
- 单会话上下文留存
- 多对话隔离互不干扰
- 支持中断恢复与状态回溯
- 结合
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) 机制及各类持久化实现 中介绍过支持的持久化方式:
RedisSaverMongoSaverMysqlSaverOracleSaverFileSystemSaverPostgresSaver
提示 :如果想实现其他存储库,参考 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. 总结
- 短期记忆是
Spring AI Agent实现多轮对话的核心,基于Checkpoint持久化 thread_id实现会话隔离,支持内存/Redis/MySQL/PostgreSQL/Oracle/文件等存储- 长上下文问题可通过修剪、删除、总结三类标准方案解决
- 记忆可在工具、
Hook、拦截器中自由读写,扩展性极强 - 生产环境建议使用
Redis/数据库实现分布式会话持久化