Spring AI Alibaba 对话记忆丢失问题:Redis 缓存过期后如何恢复 AI 上下文
🤔 问题背景
在开发基于 Spring AI Alibaba 的聊天应用时,遇到了一个令人困惑的问题:
当 Redis 缓存过期后,虽然能从数据库重新加载历史对话记录,但 AI 却"失忆"了------它完全不记得之前的对话内容!
用户访问历史对话时能看到完整的聊天记录,但当继续提问时,AI 的回答就像第一次见面一样,完全没有上下文。这是怎么回事呢?
🔍 问题分析
经过代码审查,我发现了问题的根源:系统中存在两套独立的存储机制,它们之间没有同步!
第一套:消息缓存系统
java
// 在 AiChatMessageServiceImpl 中
private static final String CHAT_HISTORY_KEY = "chat:history:";
public List<AiChatMessage> getMessagesFromRedis(Long sessionId) {
String key = CHAT_HISTORY_KEY + sessionId;
String json = stringRedisTemplate.opsForValue().get(key);
// ... 用于前端展示的聊天记录
}
作用:缓存聊天记录,供前端页面展示历史消息。
第二套:AI 记忆系统
java
// Spring AI 框架内部使用
RedisChatMemoryRepository chatMemoryRepository;
ChatMemory chatMemory;
作用:为 ChatClient 提供对话上下文,让 AI 能记住之前的对话。
问题本质
这两套系统完全独立:
┌─────────────────────────┐ ┌──────────────────────────┐
│ 消息缓存系统 │ │ AI 记忆系统 │
│ │ │ │
│ chat:history:{id} │ ✗ │ Spring AI Memory Store │
│ (供前端展示) │ │ (供 AI 推理使用) │
└─────────────────────────┘ └──────────────────────────┘
↑ ↑
│ │
从数据库恢复 未恢复!
当 Redis 过期后,我们只恢复了消息缓存系统 ,但 AI 记忆系统仍然是空的,所以 AI 失去了记忆!
💡 解决方案
核心思路:在从数据库加载历史消息时,同时恢复两套系统的数据。
方案架构
数据库历史消息
│
├──> 恢复到消息缓存系统 (chat:history:{id})
│
└──> 恢复到 AI 记忆系统 (ChatMemory)
🛠️ 完整实现
步骤 1:配置 ChatMemory Bean
首先需要将 ChatMemory 暴露为 Spring Bean,以便其他服务可以注入使用。
java
@Configuration
public class AiConfig {
/**
* 配置 ChatMemory Bean,供其他服务注入
*/
@Bean
public ChatMemory chatMemory(RedisChatMemoryRepository chatMemoryRepository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.build();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
return builder
.defaultSystem(AiPrompts.GENERAL_ASSISTANT)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
}
步骤 2:修改消息服务,添加记忆恢复逻辑
java
@Service
@Slf4j
public class AiChatMessageServiceImpl extends ServiceImpl<AiChatMessageMapper, AiChatMessage>
implements IAiChatMessageService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private ObjectMapper objectMapper;
// ✅ 注入 ChatMemory
@Resource
private ChatMemory chatMemory;
/**
* 获取会话消息(核心方法)
*/
public List<AiChatMessage> getSessionMessages(Long sessionId) {
// 1. 先尝试从 Redis 获取
List<AiChatMessage> messages = getMessagesFromRedis(sessionId);
// 2. 如果 Redis 没有,从数据库加载
if (messages == null) {
messages = getMessagesFromDB(sessionId);
if (!messages.isEmpty()) {
// 3. 同时恢复两套系统
cacheToRedis(sessionId, messages); // 恢复消息缓存
restoreAiMemory(sessionId, messages); // 恢复 AI 记忆
}
}
return messages != null ? messages : new ArrayList<>();
}
/**
* 恢复 AI 记忆系统
*/
private void restoreAiMemory(Long sessionId, List<AiChatMessage> messages) {
try {
String conversationId = String.valueOf(sessionId);
// 将数据库消息转换为 Spring AI 的 Message 格式
List<Message> aiMessages = messages.stream()
.map(msg -> {
if (msg.getMessageType() == 1) {
return new UserMessage(msg.getContent());
} else {
return new AssistantMessage(msg.getContent());
}
})
.collect(Collectors.toList());
// 清空旧记忆
chatMemory.clear(conversationId);
// 添加历史消息到 AI 记忆
chatMemory.add(conversationId, aiMessages);
log.info("✅ 恢复会话 {} 的AI记忆,共 {} 条消息", sessionId, aiMessages.size());
} catch (Exception e) {
log.error("❌ 恢复AI记忆失败 - 会话ID: {}", sessionId, e);
}
}
/**
* 删除会话时,同时清理两套系统
*/
public boolean deleteSessionMessages(Long sessionId, Long userId) {
boolean removed = remove(/* 数据库删除逻辑 */);
if (removed) {
// 删除消息缓存
stringRedisTemplate.delete(CHAT_HISTORY_KEY + sessionId);
// 清空 AI 记忆
clearAiMemory(sessionId);
}
return removed;
}
private void clearAiMemory(Long sessionId) {
try {
chatMemory.clear(String.valueOf(sessionId));
log.info("✅ 清空会话 {} 的AI记忆", sessionId);
} catch (Exception e) {
log.error("❌ 清空AI记忆失败", e);
}
}
}
🎯 关键要点
1. 消息格式转换
数据库的消息需要正确转换为 Spring AI 的格式:
java
// 用户消息
new UserMessage(content)
// AI 助手消息
new AssistantMessage(content)
2. 会话 ID 一致性
确保两套系统使用相同的会话标识:
java
String conversationId = String.valueOf(sessionId);
3. 异常处理
AI 记忆恢复失败不应影响主流程:
java
try {
restoreAiMemory(sessionId, messages);
} catch (Exception e) {
log.error("恢复失败,但不影响消息加载", e);
}
🧪 测试验证
测试场景 1:正常对话流程
bash
# 发起对话
curl "http://localhost:8080/ai/chatStream?message=我叫张三&sessionId=123"
# AI: 你好张三,很高兴认识你!
# 继续对话
curl "http://localhost:8080/ai/chatStream?message=我叫什么名字&sessionId=123"
# AI: 你叫张三
测试场景 2:Redis 过期后恢复
bash
# 1. 模拟 Redis 过期
redis-cli DEL "chat:history:123"
# 2. 获取历史消息(触发恢复)
curl "http://localhost:8080/ai/messages/123"
# 3. 继续对话,AI 应该记得之前的内容
curl "http://localhost:8080/ai/chatStream?message=我叫什么名字&sessionId=123"
# AI: 你叫张三(记忆恢复成功!)
📊 效果对比
修复前
用户: 我叫张三
AI: 你好张三!
[Redis 过期]
用户: 我叫什么名字?
AI: 抱歉,我不知道你的名字 ❌
修复后
用户: 我叫张三
AI: 你好张三!
[Redis 过期 + 自动恢复]
用户: 我叫什么名字?
AI: 你叫张三 ✅

🎓 经验总结
1. 理解框架的存储机制
Spring AI 有自己的记忆管理系统,不能简单地认为"消息在数据库就够了"。
2. 统一数据同步点
在数据恢复的关键节点(如从数据库加载),同时处理所有相关系统的状态恢复。
3. 保持状态一致性
java
// 保存时
saveToDatabase();
syncToRedis();
syncToAiMemory();
// 删除时
deleteFromDatabase();
deleteFromRedis();
deleteFromAiMemory();
4. 优雅降级
即使 AI 记忆恢复失败,也不影响用户查看历史消息的基本功能。
如果觉得有帮助,欢迎点赞收藏!有问题欢迎评论区交流~