跳到主要内容
版本:1.0.4

ChatMemory

ChatMemory 是 Spring AI 的对话记忆管理组件,自动记录每次对话的用户消息和模型回复,并在后续请求中注入历史上下文,让模型"记住"之前的交流内容。


1. 架构定位

ChatMemory 管理对话历史,通过 Advisor 自动注入上下文,支持内存、JDBC、Cassandra、Neo4j 等多种存储后端。


2. 对话记忆接口

ChatMemory 核心方法为 add(conversationId, messages)get(conversationId, lastN),实际开发中通过 Advisor 自动调用,通常无需手动操作。


3. 滑动窗口记忆

MessageWindowChatMemory 是默认实现,通过滑动窗口控制记忆条数,超出上限时自动淘汰最早的消息。

3.1 窗口淘汰规则

  1. 已有的 SystemMessage 在新增 SystemMessage 时会被全部替换。
  2. 普通消息按先进先出淘汰,始终保留最新的 maxMessages 条。
  3. SystemMessage 在淘汰过程中被保留,不会被移出窗口。

3.2 创建方式

完整示例:WindowMemoryDemo.java
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 将记忆注入对话,区别在于注入方式:

维度PromptChatMemoryAdvisorMessageChatMemoryAdvisor
注入方式格式化为文本追加到系统提示词作为 Message 对象插入消息列表
系统提示模板支持,可自定义格式不需要,直接保留原始消息结构
记忆格式类型:内容 纯文本完整 Message 对象,保留角色信息
适用场景需要显式引导模型关注记忆模型原生理解多轮对话格式

5. 系统提示词记忆

将历史对话格式化为文本,追加到系统提示词中,适合需要显式指示模型参考记忆的场景。

5.1 基础用法

完整示例:PromptMemoryDemo.java
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
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
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
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开发测试
JdbcChatMemoryRepositoryspring-ai-starter-model-chat-memory-repository-jdbc关系表PostgreSQL / MySQL / HSQLDB / SQL Server
CassandraChatMemoryRepositoryspring-ai-starter-model-chat-memory-repository-cassandraUDT + 冻结列表分布式高可用
Neo4jChatMemoryRepositoryspring-ai-starter-model-chat-memory-repository-neo4j图节点 + 关系边复杂对话关系追踪

Spring Boot 自动配置会检测 classpath 中唯一的 ChatMemoryRepository Bean:引入任一持久化 Starter 后,其 Repository 自动替换默认的内存实现。

8.2 JDBC 持久化

JdbcChatMemoryRepository 通过 JDBC 方言自动适配不同数据库,方言由 DataSource 的 JDBC URL 自动检测。

完整示例:JdbcMemoryConfig.java
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
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 以图模型存储对话,消息和元数据作为节点、关系作为边,完整保留 ToolCallMediaMetadata 等嵌套结构。

完整示例:Neo4jMemoryConfig.java
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
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() + " 条消息");
}
}