【LangChain4j+Redis】会话记忆功能实现
一、介绍
接着上文继续实现会话记忆功能
前置后端内容: https://blog.csdn.net/2401_84926677/article/details/151962893
前置前端内容:https://blog.csdn.net/2401_84926677/article/details/152051570
Langchain4j 的会话记忆功能核心作用是为 AI 对话提供 “上下文感知能力”,解决大语言模型(LLM)默认 “无状态” 的问题 —— 即让 AI 能记住多轮交互中的历史消息、用户偏好和对话逻辑,从而实现更连贯、个性化且符合场景的对话体验。
二、步骤
1. 定义会话记忆对象
创建config包下的CommonConfig类
@Configuration
public class CommonConfig {@Bean public ChatMemory chatMemory() {MessageWindowChatMemory memory = MessageWindowChatMemory.builder().maxMessages(20).build();return memory;}}
@Bean表示加入到 IOC 容器里
2. 配置会话记忆对象
在@AiService注解里设置chatMemory,配置会话记忆对象
@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,chatModel = "openAiChatModel",streamingChatModel = "openAiStreamingChatModel",chatMemory = "chatMemory" //这里的chatMemory是配置类@Bean注解下的方法名
)
public interface ConsultantService {// @SystemMessage(fromResource = "system.txt")@UserMessage("你是一位厨师,{{love}}")public Flux<String> chat(@V("love")String message);
}
3. 效果测试
后端请求的日志输出,已经携带了之前的会话历史记录:
但是!如果另一个用户进行访问,这个会话记忆是什么样的?
我们打开另一个浏览器访问,并继续提问:
---------------发现它仍然和上一个用户的关联着,没有实现隔离!----------------
三、会话记忆隔离实现
1. 定义会话记忆对象提供者
在config包下的,CommonConfig类中加入ChatMemoryProvider的定义
//构建ChatMemoryProvider对象@Beanpublic ChatMemoryProvider chatMemoryProvider() {ChatMemoryProvider chatMemoryProvider=new ChatMemoryProvider(){@Overridepublic ChatMemory get(Object memoryId) {return MessageWindowChatMemory.builder().id(memoryId).maxMessages(20).build();}};return chatMemoryProvider;}
2. 配置会话记忆对象提供者
修改@AiService注解里的内容:加入chatMemoryProvider,去掉chatMemory
@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,chatModel = "openAiChatModel",streamingChatModel = "openAiStreamingChatModel",// chatMemory = "chatMemory",chatMemoryProvider = "chatMemoryProvider"
)
public interface ConsultantService {@SystemMessage(fromResource = "system.txt")//@UserMessage("你是一位厨师,{{love}}")public Flux<String> chat(String message);
}
3. ConsultantService接口方法中添加参数MemoryId
在接口方法的参数加入memoryId,并加上相关注解标明这两个参数
@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,chatModel = "openAiChatModel",streamingChatModel = "openAiStreamingChatModel",// chatMemory = "chatMemory",chatMemoryProvider = "chatMemoryProvider"
)
public interface ConsultantService {@SystemMessage(fromResource = "system.txt")//@UserMessage("你是一位厨师,{{love}}")public Flux<String> chat(@MemoryId String memoryId,@UserMessage String message);
}
4. Controller中chat接口接收memoryId
在控制器方法和返回值中加入memoryId参数
@RestController
@RequestMapping("/api")
@CrossOrigin
public class ChatController {@Autowiredprivate ConsultantService consultantService;@GetMapping(value = "/chat",produces = "text/html;charset=utf-8")public Flux<String> chat(@RequestParam String memoryId, @RequestParam String message) {return consultantService.chat(memoryId, message);}}
5. 前端页面请求时传递memoryId
新增会话按钮,设置点击事件:
<button @click="newSession" class="new-session-btn">+</button>
/*** 创建新会话*/
const newSession = () => {// 生成新的memoryIdmemoryId.value = Date.now().toString()// 清空消息messages.value = []
}
发送请求时加入memoryId参数:
// 调用后端API,发送用户消息,包含memoryId参数const response = await fetch(`http://localhost:8080/api/chat?memoryId=${encodeURIComponent(memoryId.value)}&message=${encodeURIComponent(messageToSend)}`)
6. 效果测试
第一个用户页面:
第二个用户页面:
可见并没有携带第一个用户的会话,效果达成!
但是!如果系统重启,岂不是就都没有了?
重启后接着问:
可见,系统或者服务器重启后,会话记忆历史也就丢失了,那么就需要再次改进。
四、会话记忆持久化
想要实现持久化,那么必须通过外部的存储工具,例如redis,mysql或者其他存储工具
这里使用redis实现会话记忆持久化:
1. redis环境准备
这里使用VMware虚拟机linux的CentOS系统,通过Docker容器化安装部署Redis
sudo docker run -d \--name redis \-p 6379:6379 \-v /usr/local/redis/data:/data \redis:latest \redis-server --appendonly yes
然后安装Redis的可视化工具,这里使用Redis Insight创建连接:
2. pom.xml文件引入redis起步依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
3. 配置redis连接信息
配置application.yaml文件
spring:data:redis:host: 192.168.1.128port: 6379
4. 提供ChatMemoryStore实现类
创建repository包,创建RedisChatMemoryStore类实现ChatMemoryStore接口
@Repository
public class RedisChatMemoryStore implements ChatMemoryStore {//注入redisTemplate@Autowiredprivate StringRedisTemplate redisTemplate;@Overridepublic List<ChatMessage> getMessages(Object memoryId) {// 获取会话消息String json = redisTemplate.opsForValue().get(memoryId);// 把json数据转成List<ChatMessage>List<ChatMessage> list = ChatMessageDeserializer.messagesFromJson(json);return list;}@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> list) {// 更新会话消息//1.把list转成json数据String json = ChatMessageSerializer.messagesToJson(list);//2.保存到redis中redisTemplate.opsForValue().set( memoryId.toString(), json, Duration.ofDays(1));}@Overridepublic void deleteMessages(Object memoryId) {redisTemplate.delete(memoryId.toString());}
}
5. 配置ChatMemoryStore
配置类配置ChatMemoryStore
@Configuration
public class CommonConfig {@Autowiredprivate RedisChatMemoryStore redisChatMemoryStore; // 注入RedisChatMemoryStore@Beanpublic ChatMemory chatMemory() {MessageWindowChatMemory memory = MessageWindowChatMemory.builder().maxMessages(20).build();return memory;}//构建ChatMemoryProvider对象@Beanpublic ChatMemoryProvider chatMemoryProvider() {ChatMemoryProvider chatMemoryProvider=new ChatMemoryProvider(){@Overridepublic ChatMemory get(Object memoryId) {return MessageWindowChatMemory.builder().id(memoryId).maxMessages(20).chatMemoryStore(redisChatMemoryStore) // 设置存储对象.build();}};return chatMemoryProvider;}}
6. 效果测试
重启项目,进行测试
------------会话记忆正常-------------
那么我们重启一下后端再试试:
------------依旧可以!我们来看看后端-------------
下面我们打开redis看一下
会话历史记录已成功存入Redis缓存,实现了会话历史的限时持久化存储