LangChain4j会话记忆:从原理到Redis持久化实战
本文深入探讨了大模型应用开发中的会话记忆实现方案。首先分析了大模型无状态的本质,指出需要通过传递历史对话来实现记忆功能。接着详细介绍了LangChain4j的ChatMemory接口设计及其两种实现方式。针对多用户场景,提出了基于memoryId的会话隔离解决方案。最后重点讲解了Redis持久化实现,包括Redis配置、序列化处理和过期管理,确保服务重启后记忆不丢失。文章还提供了性能优化、内存管理
在大模型应用开发中,让AI“记住”对话历史是实现连贯对话体验的关键。本文将带你深入理解LangChain4j会话记忆机制,并实现完整的隔离与持久化方案。
引言:为什么大模型需要“记忆”?
大语言模型本身是无状态的,每次请求都是独立的。要让AI助手在对话中记住上下文,必须将历史对话与新问题一起发送给模型。这就像与一个记忆力只有7秒的鱼对话,需要不断提醒它“我们刚才说到哪儿了”。
一、会话记忆的核心原理
1.1 大模型的“记忆”本质
大模型本身不具备记忆能力。实现会话记忆的唯一方法 就是将历史对话内容与新提示词一起发送。
API请求示例如下:
{
"model": "qwen-plus",
"messages": [
{"role": "system", "content": "你是地理知识小能手"},
{"role": "user", "content": "北京城市大吗?"},
{"role": "assistant", "content": "北京是中国的首都,也是一座非常大的城市"},
{"role": "user", "content": "人口呢?"}
]
}
1.2 系统架构中的记忆流程

如图清晰地展示了实现原理:
-
用户 在浏览器提问
-
Web后端 从存储对象中读取历史对话
-
后端 将历史对话+新问题发送给大模型
-
大模型 基于完整上下文生成回答
-
后端 将新对话存入存储对象,返回回答给用户
这种架构确保了每次对话都在完整上下文中进行。
二、LangChain4j会话记忆基础实现
2.1 ChatMemory接口设计
LangChain4j的核心接口设计:
public interface ChatMemory {
Object id(); // 获取唯一标识
void add(ChatMessage message); // 添加消息
List<ChatMessage> messages(); // 获取所有消息
void clear(); // 清除记忆
}
2.2 主要实现类
LangChain4j提供了两种主要的记忆窗口实现:
-
TokenWindowChatMemory - 基于Token数量限制
-
MessageWindowChatMemory - 基于消息条数限制
2.3 基础配置
配置方式:
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
}
@AiService(
chatMemory = "chatMemory"
)
public interface ConsultantService {
// 服务方法
}
三、会话记忆隔离:多用户支持
3.1 问题识别
一个关键问题:默认配置下,所有会话共享同一个记忆存储对象。这会导致:
-
用户A的对话被用户B看到
-
不同会话的上下文混淆
-
安全隐患和数据混乱
3.2 隔离解决方案
完整的会话隔离实现方案:
步骤1:定义ChatMemoryProvider
@Bean
public ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.build();
}
步骤2:服务层参数配置
@AiService(
chatMemoryProvider = "chatMemoryProvider"
)
public interface ConsultantService {
@SystemMessage(fromResource = "system.txt")
Flux<String> chat(@MemoryId String memoryId,
@UserMessage String message);
}
步骤3:Controller层接收memoryId
@PostMapping("/chat")
public Flux<String> chat(@RequestParam String memoryId,
@RequestParam String message) {
return consultantService.chat(memoryId, message);
}
步骤4:前端传递memoryId
前端在请求时需要传递唯一的memoryId参数,通常可以使用用户ID、会话ID或设备ID。
四、会话记忆持久化:Redis实战
4.1 问题:服务重启记忆丢失
另一个关键问题:默认的会话记忆存储在内存中,服务重启后记忆丢失。
4.2 持久化架构设计
LangChain4j通过ChatMemoryStore接口提供存储抽象:
public interface ChatMemoryStore {
List<ChatMessage> getMessages(Object memoryId);
void updateMessages(Object memoryId, List<ChatMessage> messages);
void deleteMessages(Object memoryId);
}
4.3 Redis持久化完整实现
以下是完整的Redis持久化实现:
4.3.1 环境准备
# 启动Redis容器
docker run -d --name redis -p 6379:6379 redis:alpine
4.3.2 Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4.3.3 配置文件
spring:
data:
redis:
host: localhost
port: 6379
4.3.4 RedisChatMemoryStore实现
以下是完整的实现代码:
package com.qcby.consultant.repository;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Repository
public class RedisChatMemoryStore implements ChatMemoryStore {
private static final String REDIS_KEY_PREFIX = "chat:memory:";
@Autowired
private RedisTemplate<String, String> redisTemplate;
private String getKey(Object memoryId) {
return REDIS_KEY_PREFIX + memoryId.toString();
}
@Override
public List<ChatMessage> getMessages(Object memoryId) {
try {
String key = getKey(memoryId);
String json = redisTemplate.opsForValue().get(key);
if (json == null || json.trim().isEmpty()) {
log.debug("No chat memory found for memoryId: {}", memoryId);
return new ArrayList<>();
}
List<ChatMessage> messages = ChatMessageDeserializer.messagesFromJson(json);
log.debug("Retrieved {} messages for memoryId: {}", messages.size(), memoryId);
return messages;
} catch (Exception e) {
log.error("Failed to get messages from Redis for memoryId: {}", memoryId, e);
return new ArrayList<>();
}
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
try {
String key = getKey(memoryId);
String json = ChatMessageSerializer.messagesToJson(messages);
// 设置1天过期时间,避免内存泄漏
redisTemplate.opsForValue().set(key, json, Duration.ofDays(1));
log.debug("Updated {} messages for memoryId: {}", messages.size(), memoryId);
} catch (Exception e) {
log.error("Failed to update messages in Redis for memoryId: {}", memoryId, e);
}
}
@Override
public void deleteMessages(Object memoryId) {
try {
String key = getKey(memoryId);
Boolean deleted = redisTemplate.delete(key);
if (Boolean.TRUE.equals(deleted)) {
log.debug("Deleted chat memory for memoryId: {}", memoryId);
} else {
log.debug("No chat memory found to delete for memoryId: {}", memoryId);
}
} catch (Exception e) {
log.error("Failed to delete messages from Redis for memoryId: {}", memoryId, e);
}
}
/**
* 批量删除过期的会话记忆(可选)
*/
public void cleanupExpiredMemories() {
// 可以使用Redis的过期策略自动清理,或定时扫描清理
log.info("Cleanup expired chat memories completed");
}
}
4.3.5 配置ChatMemoryStore
@Configuration
public class ChatMemoryConfig {
@Bean
public ChatMemoryStore chatMemoryStore() {
return new RedisChatMemoryStore();
}
@Bean
public ChatMemoryProvider chatMemoryProvider(ChatMemoryStore chatMemoryStore) {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20) // 可根据需求调整
.chatMemoryStore(chatMemoryStore)
.build();
}
}
五、高级优化与最佳实践
5.1 性能优化
-
序列化优化:使用更高效的序列化方式(如MessagePack)
-
缓存策略:在Redis前增加本地缓存
-
分批加载:对于长对话,分批加载历史记录
5.2 内存管理
-
设置合理过期时间:根据业务场景设置Redis key的TTL
-
定期清理:实现清理任务删除无用会话
-
内存限制:监控Redis内存使用,设置淘汰策略
5.3 监控与调试
// 添加监控指标
@Bean
public MeterBinder chatMemoryMetrics(ChatMemoryStore store) {
return registry -> Gauge.builder("chat.memory.size",
() -> ((RedisChatMemoryStore) store).getEstimatedSize())
.description("Estimated size of chat memory storage")
.register(registry);
}
通过本文的完整实现,我们解决了大模型应用中的三个核心问题:
-
记忆传递:通过历史对话上下文传递实现连贯对话
-
会话隔离:通过ChatMemoryProvider实现多用户隔离
-
持久化存储:通过Redis实现记忆持久化,服务重启不丢失
会话记忆是大模型应用的基础设施,良好的记忆实现能够显著提升用户体验。希望本文的实现方案能为你的AI应用开发提供有力支持。
更多推荐

所有评论(0)