跳到主要内容

对话记忆系统设计

和ChatGPT聊过天的人都知道,它能记住你之前说的话。问它"我刚才说了什么",它能准确回答。这种能力叫做对话记忆

对于AI应用来说,记忆能力至关重要。没有记忆,每次对话都是"新朋友",用户体验会很差。今天我们来深入聊聊Spring AI中对话记忆的设计与实现。

为什么需要对话记忆

先来理解一个基本事实:大模型本身是无状态的

每次你调用大模型API,对它来说都是一次全新的请求。它不知道一秒钟前你问过什么,更不知道昨天聊了什么话题。

那ChatGPT是怎么做到"记住"对话的?答案是:把历史对话一起发给模型

没有记忆与带记忆对话的差异
没有记忆与带记忆对话的差异

每次对话时,把之前的所有消息都带上,模型就能"理解"上下文。这就是对话记忆的本质。

对话记忆的本质

大模型本身是无状态的,每次 API 调用对它来说都是全新请求。实现"记忆"的方式是:把历史对话消息一起发给模型,模型从完整的上下文中"理解"之前说了什么。

短期记忆 vs 长期记忆

AI的记忆能力分两种层次:

短期记忆(Session Memory)

指在单次对话会话中的记忆能力,特点是:

  • 局限在当前对话窗口
  • 关闭页面或开始新对话就消失
  • 受限于模型的上下文窗口大小

比如你和AI聊天,说了10轮之后,第1轮的内容可能就被"遗忘"了——因为上下文窗口装不下了。

长期记忆(Persistent Memory)

跨对话的记忆能力,特点是:

  • 信息存储在外部(数据库、向量库等)
  • 不受单次对话限制
  • 需要主动管理(增删改)

比如你告诉AI"我对海鲜过敏",一周后再问它推荐餐厅,它能记得避开海鲜。

维度短期记忆长期记忆
生命周期单次会话持久存储
存储位置内存/请求体数据库/向量库
信息完整度完整上下文通常是摘要
适用场景连续对话用户偏好、历史信息

这篇文章主要聊短期记忆的实现,长期记忆涉及更复杂的RAG架构,后续专门讨论。

短期记忆 vs 长期记忆
维度短期记忆长期记忆
生命周期单次会话持久存储
存储位置内存/请求体数据库/向量库
信息完整度完整上下文通常是摘要
适用场景连续对话用户偏好、历史信息

Spring AI的记忆实现方案

Spring AI提供了两种实现对话记忆的方式。

方案一:手动维护Message列表

最直观的方式是自己管理对话历史:

@RestController
@RequestMapping("/memory")
public class ManualMemoryController {

private final ChatModel chatModel;

// 用ConcurrentHashMap存储不同会话的历史
private final Map<String, List<Message>> sessionHistory = new ConcurrentHashMap<>();

@GetMapping("/chat")
public String chat(@RequestParam String sessionId,
@RequestParam String message) {
// 1. 获取或创建该会话的历史记录
List<Message> history = sessionHistory.computeIfAbsent(
sessionId, k -> new ArrayList<>());

// 2. 添加系统提示词(如果是第一条消息)
if (history.isEmpty()) {
history.add(new SystemMessage("你是一个智能理财顾问"));
}

// 3. 添加用户本次输入
history.add(new UserMessage(message));

// 4. 调用模型
Prompt prompt = new Prompt(history);
ChatResponse response = chatModel.call(prompt);
String aiReply = response.getResult().getOutput().getText();

// 5. 把AI回复也加入历史
history.add(new AssistantMessage(aiReply));

// 6. 控制历史长度,防止无限增长
trimHistory(history, 20); // 最多保留20条

return aiReply;
}

private void trimHistory(List<Message> history, int maxSize) {
// 保留系统消息 + 最近的对话
while (history.size() > maxSize) {
// 找到第一条非系统消息,删除它
for (int i = 0; i < history.size(); i++) {
if (!(history.get(i) instanceof SystemMessage)) {
history.remove(i);
break;
}
}
}
}
}

优点:完全可控,想怎么管理记忆就怎么管理
缺点:代码繁琐,需要自己处理很多细节

方案二:使用ChatMemoryAdvisor(推荐)

Spring AI提供了更优雅的方式——通过Advisor自动管理记忆:

@RestController
@RequestMapping("/memory")
public class AdvisorMemoryController {

private final ChatClient chatClient;

public AdvisorMemoryController(ChatModel chatModel) {
// 创建基于内存的记忆存储
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20) // 最多保留20条消息
.build();

// 构建带记忆能力的ChatClient
this.chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是一个智能理财顾问")
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}

@GetMapping("/chat")
public Flux<String> chat(@RequestParam String sessionId,
@RequestParam String message) {
return chatClient.prompt()
.user(message)
// 指定会话ID,相同ID的对话共享记忆
.advisors(spec -> spec.param(
ChatMemory.CONVERSATION_ID, sessionId))
.stream()
.content();
}
}

代码简洁多了!核心是两个组件:

  • MessageWindowChatMemory:记忆存储,采用滑动窗口机制
  • MessageChatMemoryAdvisor:负责自动将历史消息注入请求

MessageWindowChatMemory详解

MessageWindowChatMemory是Spring AI内置的记忆实现,它的特点是:

  • 为每个会话维护一个消息窗口
  • 当消息数量超过上限时,自动淘汰最早的消息
  • 默认存储在内存中
MessageWindowChatMemory 滑动窗口示意
MessageWindowChatMemory 滑动窗口示意

关键参数

MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
.maxMessages(20) // 最大消息数(包括所有类型)
.chatMemoryRepository( // 底层存储实现
new InMemoryChatMemoryRepository()
)
.build();

注意maxMessages是指所有消息的总数,包括UserMessage、AssistantMessage、SystemMessage,而不只是用户发送的消息。

maxMessages 的计数方式

maxMessages 计算的是所有类型消息的总条数,包括 SystemMessage、UserMessage 和 AssistantMessage。一轮对话(用户问+AI答)占用 2 条,加上系统消息后实际可用的对话轮数会比你以为的少。建议设置时留出余量。

两种MemoryAdvisor的区别

Spring AI提供了两种记忆Advisor:

MessageChatMemoryAdvisor

将历史消息添加到请求的messages列表中,这是标准做法:

{
"messages": [
{"role": "system", "content": "你是智能助手"},
{"role": "user", "content": "第一个问题"},
{"role": "assistant", "content": "第一个回答"},
{"role": "user", "content": "第二个问题"} // 当前问题
]
}

PromptChatMemoryAdvisor

将历史对话拼接到系统提示词中,适用于不支持多轮messages的模型:

{
"messages": [
{
"role": "system",
"content": "你是智能助手\n\n历史对话:\n用户:第一个问题\n助手:第一个回答\n"
},
{"role": "user", "content": "第二个问题"}
]
}

大多数情况用MessageChatMemoryAdvisor就行。

持久化记忆:保存到MySQL

内存记忆有个致命问题:应用重启后记忆就丢失了。生产环境必须要持久化存储。

生产环境必须持久化

InMemoryChatMemoryRepository(内存存储)在应用重启后会完全丢失所有对话记忆,不适合生产环境使用。生产环境必须切换到 JdbcChatMemoryRepository 或其他持久化存储方案。

Spring AI提供了基于JDBC的持久化方案。

引入依赖

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
<version>1.1.0</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>

配置数据源

spring:
datasource:
url: jdbc:mysql://localhost:3306/ai_memory_db?useUnicode=true&characterEncoding=UTF-8
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
ai:
chat:
memory:
repository:
jdbc:
platform: mysql

创建数据表

Spring AI官方提供的SQL可能有兼容性问题,建议用这个:

CREATE TABLE `spring_ai_chat_memory` (
`conversation_id` varchar(36) NOT NULL COMMENT '会话ID',
`content` text NOT NULL COMMENT '消息内容',
`type` enum('USER','ASSISTANT','SYSTEM','TOOL') NOT NULL COMMENT '消息类型',
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
KEY `idx_conversation_timestamp` (`conversation_id`, `timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI对话记忆表';

配置JDBC记忆

@Configuration
public class PersistentMemoryConfig {

@Bean
public ChatMemory jdbcChatMemory(JdbcChatMemoryRepository repository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository) // 使用JDBC存储
.maxMessages(50)
.build();
}

@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory jdbcChatMemory) {
return ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(jdbcChatMemory).build(),
new SimpleLoggerAdvisor()
)
.build();
}
}

使用方式

使用方式和内存记忆完全一样,只是底层存储换成了MySQL:

@GetMapping("/chat")
public Flux<String> chat(@RequestParam String sessionId,
@RequestParam String message) {
return chatClient.prompt()
.user(message)
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, sessionId))
.stream()
.content();
}

现在对话记录会存到数据库里,重启应用后记忆依然保留。

ChatMemoryRepository体系

Spring AI的记忆存储是可扩展的,核心接口是ChatMemoryRepository:

ChatMemoryRepository 实现体系
ChatMemoryRepository 实现体系

Spring AI支持多种存储后端:

实现类适用场景
InMemoryChatMemoryRepository开发测试,不需要持久化
JdbcChatMemoryRepository关系型数据库(MySQL、PostgreSQL等)
CassandraChatMemoryRepository高并发、需要TTL自动过期
Neo4jChatMemoryRepository需要图关系分析
MongoChatMemoryRepository灵活的文档存储
CosmosDBChatMemoryRepositoryAzure云环境

记忆管理最佳实践

最佳实践
  1. 合理设置窗口大小:窗口太小上下文丢失,太大 token 消耗增加且可能超出模型限制,一般 20-50 条比较合理
  2. 重要信息放在 System 消息:系统消息通常不会被淘汰,用户偏好、用户身份等关键信息放在这里
  3. 定期清理过期会话:数据库存储会不断积累,建议设置定时任务清理超过 7 天的记录
  4. 敏感信息脱敏:对话记录存储前要对手机号、身份证号等进行脱敏处理

1. 合理设置窗口大小

窗口太小,上下文信息丢失;窗口太大,token消耗增加且可能超出模型限制。

// 根据模型能力和业务需求调整
MessageWindowChatMemory.builder()
.maxMessages(30) // 一般20-50条比较合理
.build();

2. 重要信息放在System消息

系统消息通常不会被淘汰,重要的设定放在这里:

chatClient.prompt()
.system("用户姓名:张三\n用户等级:VIP\n偏好:科技类产品")
.user(question)
.call()
.content();

3. 定期清理过期会话

数据库存储会不断积累数据,需要定期清理:

@Scheduled(cron = "0 0 2 * * ?")  // 每天凌晨2点
public void cleanExpiredSessions() {
// 删除7天前的会话记录
LocalDateTime threshold = LocalDateTime.now().minusDays(7);
jdbcTemplate.update(
"DELETE FROM spring_ai_chat_memory WHERE timestamp < ?",
threshold
);
}

4. 敏感信息脱敏

对话记录可能包含敏感信息,存储前考虑脱敏:

public class SensitiveDataAdvisor implements CallAdvisor {

@Override
public ChatClientResponse adviseCall(ChatClientRequest request,
CallAdvisorChain chain) {
// 在存储前对消息内容脱敏
ChatClientRequest sanitized = sanitizeRequest(request);
return chain.nextCall(sanitized);
}

private ChatClientRequest sanitizeRequest(ChatClientRequest request) {
// 手机号、身份证号等脱敏处理
// ...
}
}

小结

这篇文章系统介绍了Spring AI的对话记忆机制:

  • 记忆本质:把历史对话一起发给模型,让它理解上下文
  • 两种记忆:短期记忆(会话内)和长期记忆(跨会话)
  • 实现方式:手动维护Message列表 或 使用ChatMemoryAdvisor
  • 存储选择:内存、JDBC、Cassandra、MongoDB等
  • 持久化配置:引入JDBC依赖 + 配置数据源 + 创建表结构

对话记忆是AI应用的基础能力,掌握好它,你的AI助手就能真正"记住"用户了。


到这里,Spring AI详细解析系列就全部结束了。我们从入门实战开始,一路讲到核心架构、流式输出、提示词工程、结构化输出、Advisor机制和对话记忆。

这些知识足够你在实际项目中用好Spring AI了。当然,大模型应用开发还有很多进阶话题,比如RAG、工具调用、Agent编排等,后续会详细的讲解。

🎁优惠