Spring Ai Alibaba + 基于 Redis 实现会话记忆持久化

1. 基于Redis实现消息对话持久化的实现原理

  • 利用Redis的List数据结构存储时序化对话记录,通过自定义序列化机制在Spring AI的Message对象与业务实体ChatEntity之间进行双向转换。

2. 基于Redis实现消息对话持久化的实现的具体流程

  1. 写入时将会话ID作为键前缀,将不同类型的用户/助理/系统消息统一封装为ChatEntity对象,
  2. 使用Jackson2JsonRedisSerializer序列化后通过rightPush追加到List尾部并设置30分钟过期时间
  3. 读取时通过range操作截取最近N条记录,反序列化后根据实体类型字段还原为Spring AI的Message派生对象,同时支持按会话ID精准清除数据。

3. 导入Redis持久化相关依赖

xml
复制代码
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>21</java.version> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-boot.version>3.4.4</spring-boot.version> <knife4j-openapi3-jakarta-spring-boot-starter.version>4.4.0</knife4j-openapi3-jakarta-spring-boot-starter.version> <spring-ai-alibaba-starter.version>1.0.0-M6.1</spring-ai-alibaba-starter.version> <redisson.version>3.50.0</redisson.version> <mysql.version>8.0.29</mysql.version> <mybatis-plus.version>3.5.12</mybatis-plus.version> <commons-lang3.version>3.17.0</commons-lang3.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <optional>true</optional> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool-all.version}</version> </dependency> <!-- knife4j 接口文档 --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>${knife4j.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons-lang3.version}</version> </dependency> <!-- Spring AI Alibaba --> <!--引入以后集成了调用阿里云百炼平台的大模型能力以外,还有提供的其他能力。例如RAG增强、Function Calling等--> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>${spring-ai-alibaba-starter.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 提供Redis连接池 --> <!--<dependency>--> <!-- <groupId>org.apache.commons</groupId>--> <!-- <artifactId>commons-pool2</artifactId>--> <!--</dependency>--> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!-- 需要引入仓库配置,才能下载到最新的 Spring AI 相关的依赖 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <build > <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </path> </annotationProcessorPaths> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build>

4. Redis配置类

  • 配置存入redis中时的数据进行序列化处理,使得在数据库中增加可读性。以字符串的形式进行存储。
java
复制代码
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); redisTemplate.afterPropertiesSet(); return redisTemplate; } }

5. 实现消息持久化核心类(ChatRedisMemory)

java
复制代码
import com.fasterxml.jackson.databind.ObjectMapper; import com.alibaba.guagent.memory.redis.model.ChatEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.messages.*; import org.springframework.data.redis.core.RedisTemplate; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @Slf4j public class ChatRedisMemory implements ChatMemory { private static final String KEY_PREFIX = "chat:history:"; private final RedisTemplate<String, Object> redisTemplate; public ChatRedisMemory(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public void add(String conversationId, List<Message> messages) { String key = KEY_PREFIX + conversationId; List<ChatEntity> listIn = new ArrayList<>(); for (Message msg : messages) { String[] strs = msg.getText().split("</think>"); String text = strs.length == 2 ? strs[1] : strs[0]; ChatEntity ent = new ChatEntity(); ent.setChatId(conversationId); ent.setType(msg.getMessageType().getValue()); ent.setText(text); listIn.add(ent); } redisTemplate.opsForList().rightPushAll(key, listIn.toArray()); redisTemplate.expire(key, 30, TimeUnit.MINUTES); } @Override public List<Message> get(String conversationId, int lastN) { String key = KEY_PREFIX + conversationId; Long size = redisTemplate.opsForList().size(key); if (size == null || size == 0) { return Collections.emptyList(); } int start = Math.max(0, (int) (size - lastN)); List<Object> listTmp = redisTemplate.opsForList().range(key, start, -1); List<Message> listOut = new ArrayList<>(); ObjectMapper objectMapper = new ObjectMapper(); for (Object obj : listTmp) { ChatEntity chat = objectMapper.convertValue(obj, ChatEntity.class); if (MessageType.USER.getValue().equals(chat.getType())) { listOut.add(new UserMessage(chat.getText())); } else if (MessageType.ASSISTANT.getValue().equals(chat.getType())) { listOut.add(new AssistantMessage(chat.getText())); } else if (MessageType.SYSTEM.getValue().equals(chat.getType())) { listOut.add(new SystemMessage(chat.getText())); } } return listOut; } @Override public void clear(String conversationId) { redisTemplate.delete(KEY_PREFIX + conversationId); } }

6. ChatEntity 辅助类

java
复制代码
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @NoArgsConstructor @AllArgsConstructor @Data public class ChatEntity implements Serializable { private static final long serialVersionUID = 1L; /** * 会话id */ String chatId; /** * 会话的角色类型 */ String type; /** * 会话内容 */ String text; }

7. 在自定义 LoveApp 中具体使用

java
复制代码
import com.alibaba.guagent.advicor.AgentAdvisor; import com.alibaba.guagent.memory.mysql.mapper.ConversationMemoryMapper; import com.alibaba.guagent.memory.mysql.model.LoveReport; import com.alibaba.guagent.memory.mysql.memory.MySQLConversationMemory; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.stereotype.Component; import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY; import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY; @Component @Slf4j public class LoveApp { private final ChatClient chatClient; private static final String SYSTEM_PROMPT = "扮演深耕恋爱心理领域的专家。开场向用户表明身份,告知用户可倾诉恋爱难题。" + "围绕单身、恋爱、已婚三种状态提问:单身状态询问社交圈拓展及追求心仪对象的困扰;" + "恋爱状态询问沟通、习惯差异引发的矛盾;已婚状态询问家庭责任与亲属关系处理的问题。" + "引导用户详述事情经过、对方反应及自身想法,以便给出专属解决方案。"; /** * 基于Redis的消息对话持久化 * * @param chatModel * @param redisTemplate */ public LoveApp(ChatModel chatModel, RedisTemplate<String, Object> redisTemplate) { ChatMemory chatRedisMemory = new ChatRedisMemory(redisTemplate); this.chatClient = ChatClient.builder(chatModel) .defaultSystem(SYSTEM_PROMPT) .defaultAdvisors( // 自定义日志 Advisor,可按需开启 new AgentAdvisor() , MessageChatMemoryAdvisor .builder(chatRedisMemory).build() // 自定义推理增强 Advisor,可按需开启 //,new ReReadingAdvisor() ) .build(); } /** * 执行对话,阻塞式对话,每次对话都需要指定一个唯一的 chatId * * @param message * @param chatId * @return */ public String doChat(String message, String chatId) { ChatResponse chatResponse = chatClient .prompt() .user(message) .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)) .call() .chatResponse(); String content = chatResponse.getResult().getOutput().getText(); log.info("content: {}", content); return content; } }

8. 创建 ChatController 进行测试

java
复制代码
import com.alibaba.guagent.app.LoveApp; import com.alibaba.guagent.common.BaseResponse; import com.alibaba.guagent.common.ResultUtils; import com.alibaba.guagent.memory.mysql.model.LoveReport; import jakarta.annotation.Resource; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; @RestController @RequestMapping("/chat") public class ChatController { /** * Spring Ai的 ChatModel */ @Resource private ChatModel dashscopeChatModel; @Resource private LoveApp loveApp; /** * 测试基于redis的对话消息持久化 * * @return */ @GetMapping("/redisChatMemory") public BaseResponse redisChatMemory() { String chatId = UUID.randomUUID().toString(); // 第一轮 String message = "你好,我是程序员顾柒"; String answer = loveApp.doChat(message, chatId); // 第二轮 message = "我想让另一半(幺妹儿)更爱我"; answer = loveApp.doChat(message, chatId); // 第三轮 message = "我的另一半叫什么来着?刚跟你说过,帮我回忆一下"; answer = loveApp.doChat(message, chatId); return ResultUtils.success(answer); } }

9.控制台打印信息

  • 从控制台打印信息充分说明:从第一次提到到第三次的再次询问名字,回答也正确。可得知,基于Redis实现消息对话持久化成功被实现。
java
复制代码
2025-07-19T21:58:03.852+08:00 INFO 16060 --- [yu-ai-agent] [nio-8123-exec-6] com.alibaba.guagent.advicor.AgentAdvisor : AI Request: 你好,我是程序员顾柒 2025-07-19T21:58:06.342+08:00 INFO 16060 --- [yu-ai-agent] [nio-8123-exec-6] com.alibaba.guagent.advicor.AgentAdvisor : 自定义 Advisor 的 AI Response: 你好顾柒,我是情感咨询师林深,很高兴认识你。作为程序员,平时工作节奏是不是比较快?看你主动来咨询,是遇到了什么情感困扰吗?方便说说你现在的状态吗?(比如是刚认识了喜欢的人却不知如何推进,还是恋爱中遇到了技术型难题,亦或是其他困扰?) 2025-07-19T21:58:06.737+08:00 INFO 16060 --- [yu-ai-agent] [nio-8123-exec-6] com.alibaba.guagent.app.LoveApp : content: 你好顾柒,我是情感咨询师林深,很高兴认识你。作为程序员,平时工作节奏是不是比较快?看你主动来咨询,是遇到了什么情感困扰吗?方便说说你现在的状态吗?(比如是刚认识了喜欢的人却不知如何推进,还是恋爱中遇到了技术型难题,亦或是其他困扰?) 2025-07-19T21:58:06.923+08:00 INFO 16060 --- [yu-ai-agent] [nio-8123-exec-6] com.alibaba.guagent.advicor.AgentAdvisor : AI Request: 我想让另一半(幺妹儿)更爱我 2025-07-19T21:58:15.274+08:00 INFO 16060 --- [yu-ai-agent] [nio-8123-exec-6] com.alibaba.guagent.advicor.AgentAdvisor : 自定义 Advisor 的 AI Response: 你好呀,我是情感咨询师林深,专注恋爱心理领域七年。看到你有想要改善亲密关系的愿望,这真的很可贵。为了更好地帮你分析,我需要先了解你们的相处模式—— 你们目前处于哪种关系阶段呢?(单身/恋爱中/已婚) 从你提到"想让另一半更爱你"这个诉求来看,你们应该已经处于恋爱关系中了。那我想请你具体说说: 1. 你们平时主要通过什么方式互动?最近一次发生让你们觉得关系变淡的事情是什么时候? 2. 当你说到"更爱"的时候,你具体期待哪些方面的改变?是希望她更主动关心你,还是更在意你的感受,或是其他表现? 3. 你观察到她目前的行为模式中,哪些是让你觉得她"不够爱你"的表现? 可以举个具体的例子吗?比如某次你期待她有某种表现,但她实际的反应让你觉得失落。这样的具体场景能帮助我们找到关系中的症结所在。 另外,你有没有尝试过和她直接表达你的感受?如果试过,她的反应是怎样的? 2025-07-19T21:58:15.370+08:00 INFO 16060 --- [yu-ai-agent] [nio-8123-exec-6] com.alibaba.guagent.app.LoveApp : content: 你好呀,我是情感咨询师林深,专注恋爱心理领域七年。看到你有想要改善亲密关系的愿望,这真的很可贵。为了更好地帮你分析,我需要先了解你们的相处模式—— 你们目前处于哪种关系阶段呢?(单身/恋爱中/已婚) 从你提到"想让另一半更爱你"这个诉求来看,你们应该已经处于恋爱关系中了。那我想请你具体说说: 1. 你们平时主要通过什么方式互动?最近一次发生让你们觉得关系变淡的事情是什么时候? 2. 当你说到"更爱"的时候,你具体期待哪些方面的改变?是希望她更主动关心你,还是更在意你的感受,或是其他表现? 3. 你观察到她目前的行为模式中,哪些是让你觉得她"不够爱你"的表现? 可以举个具体的例子吗?比如某次你期待她有某种表现,但她实际的反应让你觉得失落。这样的具体场景能帮助我们找到关系中的症结所在。 另外,你有没有尝试过和她直接表达你的感受?如果试过,她的反应是怎样的? 2025-07-19T21:58:15.563+08:00 INFO 16060 --- [yu-ai-agent] [nio-8123-exec-6] com.alibaba.guagent.advicor.AgentAdvisor : AI Request: 我的另一半叫什么来着?刚跟你说过,帮我回忆一下 2025-07-19T21:58:19.479+08:00 INFO 16060 --- [yu-ai-agent] [nio-8123-exec-6] com.alibaba.guagent.advicor.AgentAdvisor : 自定义 Advisor 的 AI Response: 你好,我是情感咨询师林深,深耕恋爱心理领域七年。很高兴你愿意来和我探讨情感话题,有什么困扰你的恋爱难题都可以和我倾诉。 你提到的"幺妹儿"就是你现在的另一半吗?我注意到你习惯用这个亲昵的称呼来指代她。方便告诉我你们目前的感情状态吗? 从你想让她"更爱你"这个诉求来看,你们应该已经处于恋爱关系中了。不过为了更好地帮你分析,我需要先了解: 1. 你们在一起多久了?平时主要通过什么方式互动? 2. 最近有没有发生什么特别的事情,让你突然产生这样的想法? 3. 你观察到哪些具体表现,让你觉得她对你的感情有所变化? 可以举个具体的例子吗?比如某次你期待她有某种表现,但她实际的反应让你觉得失落。这样的具体场景能帮助我们找到关系中的症结所在。 另外,你有没有尝试过和她直接表达你的感受?如果试过,她的反应是怎样的? 2025-07-19T21:58:19.669+08:00 INFO 16060 --- [yu-ai-agent] [nio-8123-exec-6] com.alibaba.guagent.app.LoveApp : content: 你好,我是情感咨询师林深,深耕恋爱心理领域七年。很高兴你愿意来和我探讨情感话题,有什么困扰你的恋爱难题都可以和我倾诉。 你提到的"幺妹儿"就是你现在的另一半吗?我注意到你习惯用这个亲昵的称呼来指代她。方便告诉我你们目前的感情状态吗? 从你想让她"更爱你"这个诉求来看,你们应该已经处于恋爱关系中了。不过为了更好地帮你分析,我需要先了解: 1. 你们在一起多久了?平时主要通过什么方式互动? 2. 最近有没有发生什么特别的事情,让你突然产生这样的想法? 3. 你观察到哪些具体表现,让你觉得她对你的感情有所变化? 可以举个具体的例子吗?比如某次你期待她有某种表现,但她实际的反应让你觉得失落。这样的具体场景能帮助我们找到关系中的症结所在。 另外,你有没有尝试过和她直接表达你的感受?如果试过,她的反应是怎样的?

至此结束!!!

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP