一、ChatMemory 的核心作用与功能解析
Spring AI 中的的ChatMemory(聊天记忆)提供了维护 AI 聊天应用程序的对话上下文和历史的机制。聊天记忆使 AI 应用程序能够:维护对话历史、提供上下文感知的响应、实现不同的记忆策略、管理对话状态。
1. 核心功能:对话上下文管理
-
历史对话存储:记录用户与AI模型之间的完整交互消息(包括用户提问、模型回应及工具调用记录),确保多轮对话的连贯性。
-
上下文关联:通过分析历史对话内容,帮助模型理解当前问题的背景和意图(例如:「刚才提到的合同条款是指哪一条?」)[[历史对话]]。
2. 多轮对话支持
-
会话ID绑定:通过
chatId
(如用户ID或UUID)区分不同对话会话,实现对话数据的隔离与管理[[历史对话]]。 -
动态上下文加载:每次请求自动附加相关历史消息到模型输入中,无需手动拼接上下文。
3. 性能与资源优化
-
滑动窗口机制:通过
MessageWindowChatMemory
等策略限制存储的消息数量(如仅保留最近5条),避免无限增长导致内存压力。 -
Token控制:减少冗余历史信息的传输,降低模型处理的Token消耗及API成本。
4. 数据隔离与安全
-
多用户支持:在分布式系统中,结合
chatMemoryProvider
为每个用户创建独立的对话存储空间,防止数据混淆。 -
敏感信息过滤:可扩展实现消息内容的脱敏处理(如自动屏蔽电话号码或隐私关键词)。
5. 持久化与扩展
-
存储适配:支持内存、Redis、关系型数据库等多种存储方式,满足不同场景的持久化需求。
-
长期记忆增强:结合向量数据库实现语义检索,支持长期对话记忆(例如数月前的历史咨询)
1.1、典型应用场景
场景 | 作用 |
---|---|
智能客服 | 保持用户咨询的连续性,自动关联历史工单或解决方案 |
教育助手 | 跟踪学习进度,基于过往提问推荐相关知识模块 |
医疗咨询 | 记录患者病史,确保诊断建议的连贯 |
1.2、配置建议
# Spring AI 示例配置
spring:ai:chat:memory:type: redis # 可选内存/redis/jdbcwindow-size: 5 # 滑动窗口保留消息数retrieval-size: 3 # 每次检索的历史消息条数
该组件已在多个实际场景中验证,例如某电商客服系统通过集成 ChatMemory
+ Redis 实现了日均百万级对话的上下文管理]。开发者可根据需求选择适配存储方案,并通过扩展接口实现自定义逻辑(如消息加密或归档)。
二、ChatMemory 存储方式分类
2.1 内存存储(In-Memory)
-
实现方式
使用 Java 堆内存存储对话记录,会话数据保存在ConcurrentHashMap
等结构中,以chatId
作为键。 -
特点
-
读写速度极快(微秒级响应)
-
服务器重启后数据丢失,仅适用于开发和临时会话场景
-
- 配置示例
spring.ai.chat.memory.type=memory
2. 2 文件系统存储(File-Based)
-
实现方式
将会话历史序列化为 JSON 或 XML 文件,存储到本地磁盘14。 -
特点
-
数据持久化,适合离线场景1
-
读写性能受磁盘 I/O 限制(毫秒级响应)4
-
需注意文件锁机制避免并发冲突6
-
2.3 关系型数据库(RDBMS)
-
典型方案
MySQL、PostgreSQL 等,通过JdbcTemplate
或 ORM 框架存储。 -
表结构示例
CREATE TABLE chat_memory ( session_id VARCHAR(64) PRIMARY KEY, messages JSON, last_accessed TIMESTAMP )
-
特点
-
支持事务和复杂查询
-
数据安全性高,但延迟较高(10-100ms)
-
2.4 分布式存储(Redis/MongoDB)
-
Redis 实现
使用RedisTemplate
存储序列化会话数据,支持过期时间管理。redisTemplate.opsForValue().set(chatId, messages, 24, TimeUnit.HOURS);
-
MongoDB 实现
利用文档数据库特性存储多轮对话的非结构化数据。 -
特点
-
高并发支持(10万+ QPS)
-
天然支持分布式会话同步
-
2.5 向量数据库增强存储
-
技术方案
结合 Qdrant/Pinecone 等向量数据库,将对话内容编码为向量存储。 -
优势
-
支持语义检索历史对话
-
可实现长期记忆和上下文关联分析
-
选型建议
存储方式 | 适用场景 | 性能指标 | 数据持久性 |
---|---|---|---|
内存存储 | 开发测试/短时交互 | ≤1ms | 无 |
Redis | 高并发生产环境 | 1-5ms | 可选 |
关系型数据库 | 审计/复杂分析需求 | 10-100ms | 强 |
向量数据库 | 智能客服/上下文关联场景 | 5-20ms | 强 |
扩展优化
-
混合存储:热数据存 Redis,冷数据存 MySQL
-
压缩策略:对历史消息使用 GZIP 压缩减少存储开销
-
TTL 机制:通过
@Scheduled
任务自动清理过期会话
以上方案已在多个实际项目中验证,例如某金融客服系统通过「Redis + 向量数据库」架构实现了 10万级并发对话管理。开发者可根据场景需求选择或组合存储方式。
三、ChatMemory 实现
3.1 基本配置
@Configuration
public class ChatMemoryConfig {@Beanpublic ChatMemory chatMemory() {return new InMemoryChatMemory();}
}
3.2 使用聊天记忆
@Service
public class ChatService {private final ChatClient chatClient;private final ChatMemory chatMemory;public ChatService(ChatClient chatClient, ChatMemory chatMemory) {this.chatClient = chatClient;this.chatMemory = chatMemory;}public String chat(String message, String sessionId) {// 将消息添加到记忆中chatMemory.addMessage(sessionId, message);// 获取对话历史List<Message> history = chatMemory.getMessages(sessionId);// 使用上下文生成响应String response = chatClient.generate(history);// 将响应存储在记忆中chatMemory.addMessage(sessionId, response);return response;}
}
3.3 记忆策略
(1) 固定窗口记忆
@Bean
public ChatMemory fixedWindowMemory() {return new FixedWindowChatMemory(10); // 保留最后10条消息
}
(2)基于令牌的记忆
@Bean
public ChatMemory tokenBasedMemory() {return new TokenBasedChatMemory(1000); // 在令牌限制内保留消息
}
(3)摘要记忆
@Bean
public ChatMemory summaryMemory() {return new SummaryChatMemory(summarizer);
}
3.4 配置属性
spring:ai:chat:memory:max-messages: 100max-tokens: 1000ttl: 3600type: in-memory
3.5 最佳实践
在实现聊天记忆时,请考虑以下最佳实践:
-
记忆大小:根据您的用例选择适当的记忆大小
-
持久化:在生产环境中使用持久化存储
-
清理:为旧对话实现适当的清理机制
-
安全性:确保适当的数据保护和隐私措施
-
可扩展性:考虑为高规模应用程序使用分布式记忆
3.6 高级功能
自定义记忆实现
@Component
public class CustomChatMemory implements ChatMemory {@Overridepublic void addMessage(String sessionId, String message) {// 自定义实现}@Overridepublic List<Message> getMessages(String sessionId) {// 自定义实现return messages;}
}
记忆监控
监控记忆使用情况和性能:
management.endpoints.web.exposure.include=chat-memory
management.endpoint.chat-memory.enabled=true
3.7 故障排除
常见问题和解决方案:
内存泄漏
实现适当的清理
设置适当的 TTL
监控内存使用情况
性能问题
使用适当的记忆类型
实现缓存
优化存储
可扩展性
使用分布式记忆
实现适当的分区
考虑缓存策略
四、关于代码段 advisors(a-> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
的代码作用解析
/*** 会话记忆 已通过* @param message* @param chatId* @return** 此次新增chatId字段,目的是为一次会话添加唯一标识,基于该标识存储会话记忆,目前使用的内存记忆功能,可以尝试使用向量数据库,永久有效。** 问题1:共12个苹果,分给12个人,怎么分* 问题2:分给3个人呢*/@PostMapping(value = "/memoryChat",produces = "text/html;charset=utf-8")public Flux<String> memoryChat(@RequestParam("message") String message, String chatId) {return chatClient.prompt().user(message).advisors(a-> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)).stream().content();}
这是一个典型的 Spring AI 框架中 Advisor 参数绑定代码,主要用于管理多轮对话的上下文记忆。
其核心功能如下:
1. 参数定义
-
CHAT_MEMORY_CONVERSATION_ID_KEY
预定义常量,标识对话的唯一会话ID键(如spring.ai.chat.memory.conversation-id
) -
chatId
动态生成的会话唯一标识符(通常为UUID或用户ID),用于追踪特定对话的完整上下文
2. 功能实现
-
对话内存绑定
将chatId
与会话管理器(如ChatMemory
)关联,确保每次请求能正确检索历史对话记录 -
数据隔离
通过唯一ID隔离不同用户的对话数据,避免内存混用问题
3. 运行机制
// 伪代码流程
1. 用户发起新对话 → 生成 chatId (UUID/用户ID)
2. 调用 advisors() 配置链 → 绑定 chatId 到当前请求上下文
3. Advisor 链处理请求时 → 通过 chatId 加载历史消息
4. 模型响应后 → 根据 chatId 保存最新对话记录到内存/数据库
关联技术组件
组件 | 作用 |
---|---|
ChatMemory | 存储对话历史记录的接口,支持内存/Redis等存储方式 |
VectorStoreChatMemoryAdvisor | 将对话记录存储到向量数据库的增强组件,依赖 chatId 实现长期记忆 |
MessageChatMemoryAdvisor | 基础上下文管理组件,自动附加历史消息到模型请求中 |
典型应用场景
-
多轮对话服务
用户连续提问时,通过chatId
保持上下文连贯性 -
用户个性化服务
结合用户ID作为chatId
,实现基于用户画像的定制应答 -
分布式会话
将会话ID与Redis等分布式存储结合,支持集群环境下的状态同步
配置建议
# 需配合的配置项示例(application.yaml )
spring:ai:chat:memory:type: redis # 可选内存/数据库等 retrieval-size: 5 # 历史消息检索条数
该模式已在客服系统、智能助手等场景广泛应用2,需注意及时清理过期 chatId
防止内存泄漏
五、Spring AI实现ChatMemory接口,实现MySQL持久化
Spring AI官方提供的InMemoryChatMemory是存储在内存中的,电脑关机后和AI的聊天记录就无了,将聊天记录存储在MySQL数据库中就能持久保存聊天记录了。
在使用InMemoryChatMemory基础上改造成将聊天记录存储到MySQL可参照下面代码:
5.1、数据库表的设计
CREATE TABLE chat_messages (id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID,自增',conversation_id VARCHAR(255) NOT NULL COMMENT '会话ID,用于区分不同的会话',message_type ENUM('USER', 'ASSISTANT') NOT NULL COMMENT '消息类型:USER表示用户,ASSISTANT表示AI助手',content TEXT NOT NULL COMMENT '消息内容',created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '消息创建时间',KEY idx_conversation_id (conversation_id),KEY idx_conversation_time (conversation_id, created_at)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='聊天消息记录表';
5.2 创建数据表实体类
import lombok.Data;
import java.time.LocalDateTime;@Data
public class ChatMessageEntity {private Long id;private String conversationId;private MessageType messageType;private String content;private LocalDateTime createdAt;public enum MessageType {USER, ASSISTANT}
}
5.3 创建Mapper接口
import com.zry.ai.entity.ChatMemoryEntity.ChatMessageEntity;
import org.apache.ibatis.annotations.*;
import java.util.List;@Mapper
public interface MessageMapper {@Insert({"<script>","INSERT INTO chat_messages (conversation_id, message_type, content, created_at)","VALUES ","<foreach collection='messages' item='msg' separator=','>","(#{msg.conversationId}, #{msg.messageType}, #{msg.content}, #{msg.createdAt})","</foreach>","</script>"})void insertMessages(@Param("messages") List<ChatMessageEntity> messages);@Select("SELECT * FROM chat_messages " +"WHERE conversation_id = #{conversationId} " +"ORDER BY created_at DESC " +"LIMIT #{lastN}")List<ChatMessageEntity> findLastNMessages(@Param("conversationId") String conversationId, @Param("lastN") int lastN);@Delete("DELETE FROM chat_messages WHERE conversation_id = #{conversationId}")void deleteByConversationId(String conversationId);
}
5.4 实现SpringAI的ChatMemery接口
ChatMemery接口:
实现ChatMemery接口:
import com.zry.ai.entity.ChatMemoryEntity.ChatMessageEntity;
import com.zry.ai.mapper.MessageMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;@Component
@RequiredArgsConstructor
public class MysqlChatMemory implements ChatMemory {private final MessageMapper messageMapper;@Overridepublic void add(String conversationId, List<Message> messages) {List<ChatMessageEntity> entities = messages.stream().map(msg -> {ChatMessageEntity entity = new ChatMessageEntity();entity.setConversationId(conversationId);entity.setContent(msg.getText());if (msg instanceof UserMessage) {entity.setMessageType(ChatMessageEntity.MessageType.USER);} else if (msg instanceof AssistantMessage) {entity.setMessageType(ChatMessageEntity.MessageType.ASSISTANT);}entity.setCreatedAt(LocalDateTime.now());return entity;}).collect(Collectors.toList());messageMapper.insertMessages(entities);}@Overridepublic List<Message> get(String conversationId, int lastN) {List<ChatMessageEntity> entities = messageMapper.findLastNMessages(conversationId, lastN);Collections.reverse(entities);return entities.stream().map(entity -> {switch (entity.getMessageType()) {case USER:return new UserMessage(entity.getContent());case ASSISTANT:return new AssistantMessage(entity.getContent());default:throw new IllegalArgumentException("未知的消息类型");}}).collect(Collectors.toList());}@Overridepublic void clear(String conversationId) {messageMapper.deleteByConversationId(conversationId);}
}
5.5 在配置ChatClient时将MysqlChatMemery加入环绕增强
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ChatConfig {@Beanpublic ChatMemory chatMemory(MessageMapper messageMapper) {return new MysqlChatMemory(messageMapper);}@Beanpublic ChatClient chatClient(OllamaChatModel model, ChatMemory chatMemory) {return ChatClient.builder(model).defaultSystem(SystemConstants.CHAT_AI_SYSTEM_PROMPT).defaultAdvisors(new SimpleLoggerAdvisor(),new MessageChatMemoryAdvisor(chatMemory)).build();}
}
参考链接:
Spring AI实现ChatMemory接口,实现MySQL持久化_springai inmemorychatmemory-CSDN博客