ARTICLE · 946440
Spring ai如何实现带短期记忆的聊天助手(下)
Spring ai如何实现带短期记忆的聊天助手(下) 上篇介绍了实现短期记忆的核心类和交互流程,这篇用一个基于redis的例子,来打造一个spring ai短期记忆的聊天助手。 1.marven依赖 1)注意这里依赖的是阿里云百炼spring-ai-alibaba-starter-dashscope 2)我们这里还需要引入spring-ai-alibaba-starter-memory-redis,是因为阿里云百炼提供了一个名为JedisRedisChatMemoryRepository的类,它其实就等于上一篇文章里介绍的RedisChatMemoryRepository. 3)另外因为我们需要使用到redis,JedisRedisChatMemoryRepository依赖于jedis,所以需要引入spring-boot-starter-data-redis和jedis. 2.AppConfig 1)注入JedisRedisChatMemoryRepository Bean,配置redis的ip,port: 这里要注意.keyPrefix("redischatmemoryrepository:test"),这个是用来指定持久化到redis中历史消息的键名前缀,意思是:如果我的sessionId为user-007,那么redis中的键名就是redischatmemoryrepository:testuser-007,另外需要注意,持久化到redis中历史消息的数据结构为队列,这个可从JedisRedisChatMemoryRepository的saveAll方法看得出来: 其中connection.listCommands().rPush(key.getBytes(), values)正是将messages插入到redis列表的尾部. 2)注入ChatMemory Bean 这里注意保存的最大历史消息数目是10个. 3)注入ChatClient bean 注意这里我们需要挂载MessageChatMemoryAdvisor进行请求前后的拦截. 3.Controller 4.测试 第一轮:http://localhost:8080/chat?message=请问什么是大模型?&sessionId=user-007 返回: 
在redis-cli中查看键的长度: 返回:2 第二轮:http://localhost:8080/chat?message=那么有哪些分类?&sessionId=user-007 
这里很明显,大模型记住了上下文,在我没有明确指明是有哪些分类下,它仍然能够正确作答. 在redis-cli中查看键的长度: 返回:4 第三轮:http://localhost:8080/chat?message=我如何学习?&sessionId=user-001 
同样大模型记住了上下文. 在redis-cli中查看键的长度: 返回:6 所以,从上面的两篇文章,我们掌握了spring ai实现短期记忆助手的原理和实践,在这个基础上,我们还要做进一步的优化,例如对于历史消息,我们如何压缩其大小?又如,如何实现长期记忆?
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId><version>1.1.2.3</version><scope>compile</scope></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-memory-redis</artifactId><version>1.1.2.2</version> <!-- 建议与你 dashscope starter 的版本保持一致或相近 --></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies>
@Configurationpublic class AppConfig {@Beanpublic JedisRedisChatMemoryRepository jedisRedisChatMemoryRepository() {return JedisRedisChatMemoryRepository.builder().host("192.168.xx.xxx").port(6379).keyPrefix("redischatmemoryrepository:test").build();}@Beanpublic ChatMemory chatMemory(JedisRedisChatMemoryRepository repository) {return MessageWindowChatMemory.builder().chatMemoryRepository(repository).maxMessages(10).build();}@Beanpublic ChatClient chatClient(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) {MessageChatMemoryAdvisor memoryAdvisor=MessageChatMemoryAdvisor.builder(chatMemory).build();return chatClientBuilder.defaultAdvisors(memoryAdvisor // 挂载记忆顾问).defaultOptions(DashScopeChatOptions.builder().withMultiModel(true).build()).build();}}
@Beanpublic JedisRedisChatMemoryRepository jedisRedisChatMemoryRepository(RedisConnectionFactory connectionFactory) {return JedisRedisChatMemoryRepository.builder().host("xxx.xxx.xxx.xxx").port(6379).keyPrefix("redischatmemoryrepository:test").build();}
@Overridepublic void saveAll(String conversationId, List<Message> messages) {Assert.hasText(conversationId, "conversationId cannot be null or empty");Assert.notNull(messages, "messages cannot be null");Assert.noNullElements(messages, "messages cannot contain null elements");String key = getKeyPrefix() + conversationId;List<String> messageJsons = messages.stream().map(this::serializeMessage).toList();try (RedisConnection connection = redisTemplate.getConnectionFactory().getConnection()) {connection.keyCommands().del(key.getBytes());if (!messageJsons.isEmpty()) {byte[][] values = new byte[messageJsons.size()][];for (int i = 0; i < messageJsons.size(); i++) {values[i] = messageJsons.get(i).getBytes();}connection.listCommands().rPush(key.getBytes(), values);}}}
@Beanpublic ChatMemory chatMemory(JedisRedisChatMemoryRepository repository) {return MessageWindowChatMemory.builder().chatMemoryRepository(repository).maxMessages(10).build();}
@Beanpublic ChatClient chatClient(ChatClient.Builder chatClientBuilder, ChatMemory chatMemory) {MessageChatMemoryAdvisor memoryAdvisor=MessageChatMemoryAdvisor.builder(chatMemory).build();return chatClientBuilder.defaultAdvisors(memoryAdvisor // 挂载记忆顾问).defaultOptions(DashScopeChatOptions.builder().withMultiModel(true).build()).build();}
@RestControllerpublic class ChatController {private final ChatClient chatClient;public ChatController(ChatClient chatClient) {this.chatClient = chatClient;}@GetMapping("/chat")public String chat(@RequestParamString message,@RequestParam(defaultValue = "default-user") String sessionId) {System.out.println("========== 收到 sessionId: " + sessionId + " ==========");// 使用 ChatMemory.CONVERSATION_ID 常量,更安全//必须用ChatMemory.CONVERSATION_ID,否则报错String response = chatClient.prompt().user(message).advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, sessionId) // 使用常量).call().content();return response;}}

llen redischatmemoryrepository:testuser-007
llen redischatmemoryrepository:testuser-007
llen redischatmemoryrepository:testuser-007