ChatMemory
ChatMemory 是 Spring AI 的对话记忆管理组件,自动记录每次对话的用户消息和模型回复,并在后续请求中注入历史上下文,让模型"记住"之前的交流内容。
1. 架构定位
ChatMemory 管理对话历史,通过 Advisor 自动注入上下文,支持内存、JDBC、Cassandra、Neo4j 等多种存储后端。
2. 对话记忆接口
ChatMemory 核心方法为 add(conversationId, messages) 和 get(conversationId, lastN),实际开发中通过 Advisor 自动调用,通常无需手动操作。
3. 滑动窗口记忆
MessageWindowChatMemory 是默认实现,通过滑动窗口控制记忆条数,超出上限时自动淘汰最早的消息。
3.1 窗口淘汰规则
- 已有的
SystemMessage在新增SystemMessage时会被全部替换。 - 普通消息按先进先出淘汰,始终保留最新的
maxMessages条。 SystemMessage在淘汰过程中被保留,不会被移出窗口。
3.2 创建方式
完整示例:WindowMemoryDemo.java
@Component
public class WindowMemoryDemo implements CommandLineRunner {
@Override
public void run(String... args) {
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(10)
.build();
chatMemory.add("user-001", List.of(
new UserMessage("我最喜欢的颜色是蓝色"),
new AssistantMessage("好的,我记住了,你最喜欢的颜色是蓝色")
));
List<Message> history = chatMemory.get("user-001");
System.out.println("历史消息数: " + history.size());
for (Message msg : history) {
System.out.println(" [" + msg.getMessageType() + "] " + msg.getText());
}
}
}
4. 两种 Advisor 对比
Spring AI 提供两种 Advisor 将记忆注入对话,区别在于注入方式:
| 维度 | PromptChatMemoryAdvisor | MessageChatMemoryAdvisor |
|---|---|---|
| 注入方式 | 格式化为文本追加到系统提示词 | 作为 Message 对象插入消息列表 |
| 系统提示模板 | 支持,可自定义格式 | 不需要,直接保留原始消息结构 |
| 记忆格式 | 类型:内容 纯文本 | 完整 Message 对象,保留角色信息 |
| 适用场景 | 需要显式引导模型关注记忆 | 模型原生理解多轮对话格式 |
5. 系统提示词记忆
将历史对话格式化为文本,追加到系统提示词中,适合需要显式指示模型参考记忆的场景。
5.1 基础用法
完整示例:PromptMemoryDemo.java
@Component
public class PromptMemoryDemo implements CommandLineRunner {
private final ChatClient chatClient;
public PromptMemoryDemo(ChatClient.Builder builder) {
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(10)
.build();
this.chatClient = builder
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
@Override
public void run(String... args) {
String r1 = chatClient.prompt()
.user("我的名字叫小明,我是一名 Java 开发者")
.call()
.content();
System.out.println("第一轮: " + r1);
String r2 = chatClient.prompt()
.user("你还记得我的名字和职业吗?")
.call()
.content();
System.out.println("第二轮: " + r2);
}
}
5.2 自定义系统提示模板
完整示例:CustomPromptTemplateDemo.java
@Component
public class CustomPromptTemplateDemo implements CommandLineRunner {
private final ChatClient chatClient;
public CustomPromptTemplateDemo(ChatClient.Builder builder) {
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
PromptTemplate customTemplate = new PromptTemplate("""
{instructions}
以下是与用户的对话历史,请据此回答问题:
---
{memory}
---
""");
this.chatClient = builder
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(chatMemory)
.systemPromptTemplate(customTemplate)
.build()
)
.build();
}
@Override
public void run(String... args) {
String r1 = chatClient.prompt()
.system("你是一个贴心的个人助手")
.user("我们公司使用 Spring Cloud 微服务架构")
.call()
.content();
System.out.println("第一轮: " + r1);
String r2 = chatClient.prompt()
.user("基于我之前告诉你的技术栈,推荐一套监控方案")
.call()
.content();
System.out.println("第二轮: " + r2);
}
}
5.3 自定义会话 ID
完整示例:CustomConversationIdDemo.java
@Component
public class CustomConversationIdDemo implements CommandLineRunner {
private final ChatClient chatClient;
public CustomConversationIdDemo(ChatClient.Builder builder) {
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
this.chatClient = builder
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(chatMemory)
.conversationId("default")
.build()
)
.build();
}
@Override
public void run(String... args) {
String r1 = chatClient.prompt()
.user("我是用户 A,负责前端开发")
.advisors(advisor -> advisor.param(
ChatMemory.CONVERSATION_ID, "user-a"))
.call()
.content();
String r2 = chatClient.prompt()
.user("我是用户 B,负责后端开发")
.advisors(advisor -> advisor.param(
ChatMemory.CONVERSATION_ID, "user-b"))
.call()
.content();
String r3 = chatClient.prompt()
.user("我刚才说我是做什么的?")
.advisors(advisor -> advisor.param(
ChatMemory.CONVERSATION_ID, "user-a"))
.call()
.content();
System.out.println("用户 A 的记忆: " + r3);
}
}
6. 消息列表记忆
将历史消息作为 Message 对象直接插入消息列表,保留完整的角色和结构信息。
完整示例:MessageMemoryAdvisorDemo.java
@Component
public class MessageMemoryAdvisorDemo implements CommandLineRunner {
private final ChatClient chatClient;
public MessageMemoryAdvisorDemo(ChatClient.Builder builder) {
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
this.chatClient = builder
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
@Override
public void run(String... args) {
String r1 = chatClient.prompt()
.user("请用一句话介绍 Spring Boot")
.call()
.content();
System.out.println("第一轮: " + r1);
String r2 = chatClient.prompt()
.user("请详细展开说明刚才提到的那一点")
.call()
.content();
System.out.println("第二轮: " + r2);
}
}
7. 多会话隔离
通过在每次请求中传递不同的 chat_memory_conversation_id,同一 ChatMemory 实例可以为不同用户或不同场景维护独立记忆。
8. 持久化存储
Spring AI 提供四种 ChatMemoryRepository 实现,默认使用内存存储,生产环境可切换为数据库持久化。
8.1 存储实现一览
| 实现 | Starter 依赖 | 存储模型 | 适用场景 |
|---|---|---|---|
InMemoryChatMemoryRepository | 无需额外依赖 | ConcurrentHashMap | 开发测试 |
JdbcChatMemoryRepository | spring-ai-starter-model-chat-memory-repository-jdbc | 关系表 | PostgreSQL / MySQL / HSQLDB / SQL Server |
CassandraChatMemoryRepository | spring-ai-starter-model-chat-memory-repository-cassandra | UDT + 冻结列表 | 分布式高可用 |
Neo4jChatMemoryRepository | spring-ai-starter-model-chat-memory-repository-neo4j | 图节点 + 关系边 | 复杂对话关系追踪 |
Spring Boot 自动配置会检测 classpath 中唯一的 ChatMemoryRepository Bean:引入任一持久化 Starter 后,其 Repository 自动替换默认的内存实现。
8.2 JDBC 持久化
JdbcChatMemoryRepository 通过 JDBC 方言自动适配不同数据库,方言由 DataSource 的 JDBC URL 自动检测。
完整示例:JdbcMemoryConfig.java
@Configuration
public class JdbcMemoryConfig {
@Bean
public ChatMemory chatMemory(DataSource dataSource) {
JdbcChatMemoryRepository repository = JdbcChatMemoryRepository.builder()
.dataSource(dataSource)
.build();
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository)
.maxMessages(20)
.build();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
return builder
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
}
通过配置控制建表行为:
spring:
ai:
chat:
memory:
repository:
jdbc:
initialize-schema: embedded # embedded | always | never
8.3 Cassandra 持久化
CassandraChatMemoryRepository 使用 UDT(用户自定义类型)存储每条消息,以冻结列表聚合到单行,适合分布式大规模部署。
完整示例:CassandraMemoryConfig.java
@Configuration
public class CassandraMemoryConfig {
@Bean
public ChatMemory chatMemory(CqlSession cqlSession) {
CassandraChatMemoryRepositoryConfig config =
CassandraChatMemoryRepositoryConfig.builder()
.withCqlSession(cqlSession)
.withKeyspaceName("springframework")
.withTimeToLive(Duration.ofDays(30))
.build();
CassandraChatMemoryRepository repository =
CassandraChatMemoryRepository.create(config);
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository)
.build();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
return builder
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
}
Cassandra 配置项:
spring:
ai:
chat:
memory:
repository:
cassandra:
keyspace: springframework
table: ai_chat_memory
messages-column: messages
time-to-live: 30d
initialize-schema: true
8.4 Neo4j 持久化
Neo4jChatMemoryRepository 以图模型存储对话,消息和元数据作为节点、关系作为边,完整保留 ToolCall、Media、Metadata 等嵌套结构。
完整示例:Neo4jMemoryConfig.java
@Configuration
public class Neo4jMemoryConfig {
@Bean
public ChatMemory chatMemory(Driver driver) {
Neo4jChatMemoryRepositoryConfig config =
Neo4jChatMemoryRepositoryConfig.builder()
.withDriver(driver)
.withSessionLabel("Session")
.withMessageLabel("Message")
.build();
Neo4jChatMemoryRepository repository =
new Neo4jChatMemoryRepository(config);
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository)
.build();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
return builder
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
}
Neo4j 配置项:
spring:
ai:
chat:
memory:
repository:
neo4j:
session-label: Session
message-label: Message
metadata-label: Metadata
media-label: Media
tool-call-label: ToolCall
tool-response-label: ToolResponse
9. 完整综合示例
完整示例:ChatMemoryCompleteExample.java
@Component
public class ChatMemoryCompleteExample implements CommandLineRunner {
private final ChatClient chatClient;
private final ChatMemory chatMemory;
public ChatMemoryCompleteExample(ChatClient.Builder builder) {
this.chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
this.chatClient = builder
.defaultSystem("你是一个智能编程助手,请根据对话历史提供连贯的回答")
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(chatMemory)
.conversationId("programming-chat")
.build()
)
.build();
}
@Override
public void run(String... args) {
String r1 = chatClient.prompt()
.user("什么是 Java 中的 Stream API?")
.call()
.content();
System.out.println("第一轮: " + r1);
String r2 = chatClient.prompt()
.user("它和传统的 for 循环相比有什么优势?")
.call()
.content();
System.out.println("第二轮: " + r2);
String r3 = chatClient.prompt()
.user("能否给出一个具体的代码示例来展示你刚才提到的优势?")
.call()
.content();
System.out.println("第三轮: " + r3);
List<Message> history = chatMemory.get("programming-chat");
System.out.println("记忆中共保存了 " + history.size() + " 条消息");
}
}