当前位置: 首页 > news >正文

SpringAI实现AI应用-使用redis持久化聊天记忆

SpringAI实战链接

1.SpringAl实现AI应用-快速搭建-CSDN博客

2.SpringAI实现AI应用-搭建知识库-CSDN博客

3.SpringAI实现AI应用-内置顾问-CSDN博客

4.SpringAI实现AI应用-使用redis持久化聊天记忆-CSDN博客

5.SpringAI实现AI应用-自定义顾问(Advisor)-CSDN博客

概述

针对SpringAI的内置顾问,上篇帖子已经进行了说明,这里就不再赘述,之前使用SpringAI的内置的聊天记忆顾问时,都是使用内存的方式进行存储,当项目重启的时候,聊天记录就没有了。此篇就使用redis将聊天记录进行持久化

项目修改

通过前面几篇帖子,已经有了一个项目框架,这里不再说项目搭建所需的环境,只在原来的项目上进行修改

安装并启动redis

redis的安装,网上有很多方法,在此不再说明,安装完成之后,启动redis就可以了

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>SpringAI_Demo</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.5</version><relativePath/> <!-- lookup parent from repository --></parent><properties><java.version>17</java.version><spring-ai.version>1.0.0-M6</spring-ai.version><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>${spring-ai.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!--    常规jar--><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-actuator</artifactId></dependency><!--    springAI--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId></dependency><!--    向量存储引擎--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-transformers-spring-boot-starter</artifactId></dependency><!--   向量库--><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency><!--    文档解析器--><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-tika-document-reader</artifactId></dependency><!--    lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--    redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency></dependencies><build><resources><resource><directory>src/main/java</directory><!--所在的目录--><includes><!--包括目录下的.properties,.xml 文件都会被扫描到--><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>false</filtering></resource><resource><directory>src/main/resources</directory><includes><include>**/*.*</include></includes></resource></resources><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>3.2.5</version></plugin></plugins></build><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository></repositories>
</project>

与前文相比,只添加一个redis的依赖,别的都没动

Application.yml

server:port: 3210spring:#redisdata:redis:host: 127.0.0.1port: 6379datebase: 0#向量库datasource:url: jdbc:postgresql://localhost:5432/postgresusername: postgrespassword: pgsqldriver-class-name: org.postgresql.Driverai:#调用ai大模型(可使用本地化部署模型,也可以使用线上的)openai:base-url: https://api.siliconflow.cnapi-key: #你自己申请的keychat:options:model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B#调用矢量化模型embedding:transformer:onnx:modelUri: classpath:/text2vec-base-chinese/onnx/model.onnxtokenizer:uri: classpath:/text2vec-base-chinese/onnx/tokenizer.json#矢量化配置vectorstore:pgvector:index-type: HNSWdistance-type: COSINE_DISTANCEdimensions: 768

与之前代码相比,多配置了redis的相关东西

RedisConfig(redis配置文件)

添加redis依赖之后,首先对redis进行配置

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;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;/*** @Author majinzhong* @Date 2025/5/7 14:49* @Version 1.0*/
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> messageRedisTemplate(RedisConnectionFactory factory, Jackson2ObjectMapperBuilder builder) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 使用String序列化器作为key的序列化方式template.setKeySerializer(new StringRedisSerializer());// 对value进行序列化template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));// 设置hash类型的key和value序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));template.afterPropertiesSet();return template;}
}

ChatRedisMemory(重写ChatMemory)

通过前文可知,聊天记忆的内置顾问都有ChatMemory,想要将聊天记录持久化就需要将ChatMemory内的方法按照redis存储的方式进行重写

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.example.entity.ChatEntity;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;/*** @Author majinzhong* @Date 2025/5/7 14:51* @Version 1.0*/
@Slf4j
@Component
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;}@Overridepublic 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);}@Overridepublic 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;}@Overridepublic void clear(String conversationId) {redisTemplate.delete(KEY_PREFIX + conversationId);}
}

AiConfig

因为重写了ChatMemory,所以需要重新对内置顾问进行重新配置

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.*;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;import java.util.List;/*** @Author majinzhong* @Date 2025/4/28 10:34* @Version 1.0*/
@Configuration
public class AiConfig {@BeanChatClient chatClient(ChatClient.Builder builder,VectorStore vectorStore) {return builder// 它定义了聊天机器人在回答问题时应当遵循的风格和角色定位。.defaultSystem("你是一个智能机器人,你的名字叫 Spring AI智能机器人")//这里可以添加多个顾问 order(优先级)越小,越先执行// 注意:顾问添加到链中的顺序至关重要,因为它决定了其执行的顺序。每个顾问都会以某种方式修改提示或上下文,一个顾问所做的更改会传递给链中的下一个顾问。// 在此配置中,将首先执行MessageChatMemoryAdvisor,将对话历史记录添加到提示中。然后,问答顾问将根据用户的问题和添加的对话历史进行搜索,从而可能提供更相关的结果。.defaultAdvisors(//内存存储对话记忆new MessageChatMemoryAdvisor(inMemoryChatMemory()),
//                        new PromptChatMemoryAdvisor(inMemoryChatMemory()),// QuestionAnswerAdvisor 此顾问使用矢量存储提供问答功能,实现RAG(检索增强生成)模式
//                        QuestionAnswerAdvisor.builder(vectorStore).order(1).build(),// SafeGuardAdvisor是一个安全防护顾问,它确保生成的内容符合道德和法律标准。SafeGuardAdvisor.builder().sensitiveWords(List.of("色情", "暴力")) // 敏感词列表.order(2) // 设置优先级.failureResponse("抱歉,我无法回答这个问题。").build(), // 敏感词过滤失败时的响应// SimpleLoggerAdvisor是一个记录ChatClient的请求和响应数据的顾问。这对于调试和监控您的AI交互非常有用,建议将其添加到链的末尾。new SimpleLoggerAdvisor()).defaultOptions(ChatOptions.builder().topP(0.7) // 取值越大,生成的随机性越高;取值越低,生成的随机性越低。默认值为0.8.build()).build();}@BeanChatMemory inMemoryChatMemory() {return new InMemoryChatMemory();}@Beanpublic ChatMemory chatMemory(RedisTemplate<String, Object> redisTemplate) {return new ChatRedisMemory(redisTemplate);}
}

与前文相比,多配置了ChatRedisMemory

注意:为了测试要将问答顾问注释掉

RedisAiController(新建测试接口类)

import org.example.config.ChatRedisMemory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.VectorStoreChatMemoryAdvisor;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @Author majinzhong* @Date 2025/5/7 15:03* @Version 1.0*/
@CrossOrigin
@RestController
public class RedisAiController {@AutowiredChatClient chatClient;@AutowiredChatRedisMemory chatRedisMemory;/*** 持久化聊天记录* @param message* @param sessionId* @return*/@GetMapping("/ai/redisCall")public String redisCall(@RequestParam(value = "message", defaultValue = "讲个笑话") String message, @RequestParam String sessionId) {return chatClient.prompt().user(message).advisors(new MessageChatMemoryAdvisor(chatRedisMemory, sessionId, 10)).call().content().trim();}
}

代码中注入了重写的ChatMemory(ChatRedisMemory)

也在方法中重新配置了聊天记忆的内置顾问

测试

还是使用postman进行测试

查看redis储存结果

经过测试,聊天记录已经存到redis,重启项目之后,再测试

补充

VectorStoreChatMemoryAdvisor

上篇中没有对VectorStoreChatMemoryAdvisor进行测试,现在想对它进行测试时,才发现它已经被弃用了

也来简单测试一下吧,使用之前搭建知识库时创建的向量库

    @AutowiredVectorStore vectorStore;/*** 检索聊天记录向量数据库* @param message* @param sessionId* @return*/@GetMapping("/ai/vectorCall")public String vectorCall(@RequestParam(value = "message", defaultValue = "讲个笑话") String message, @RequestParam String sessionId) {VectorStoreChatMemoryAdvisor vectorStoreChatMemoryAdvisor = new VectorStoreChatMemoryAdvisor(vectorStore);return chatClient.prompt().user(message).advisors(vectorStoreChatMemoryAdvisor).call().content().trim();}

经过测试可以看出VectorStoreChatMemoryAdvisor先检索之前的对话记录,然后再生成回答

问题

调用接口时,控制台报了503,(System is too busy now.  Please try again later.)这是因为AI大模型被使用的人太多了,所以才出现的错误(毕竟使用的是免费的,本地部署的AI大模型不会出现这种问题)

解决方法:换一个模型就行了,直接再配置文件里修改

相关文章:

  • 如何在Vue-Cli中使用Element-UI和Echarts和swiper插件(低版本)
  • 【Linux系列】目录大小查看
  • 《企业级前端部署方案:Jenkins+MinIO+SSH+Gitee+Jenkinsfile自动化实践》
  • 2025年渗透测试面试题总结-某服面试经验分享(附回答)(题目+回答)
  • 用uniapp在微信小程序实现画板(电子签名)功能,使用canvas实现功能
  • 聊一聊接口的压力测试如何进行的?
  • Algolia - Docsearch的申请配置安装【以踩坑解决版】
  • Java线程安全问题深度解析与解决方案
  • Vue2 中 el-dialog 封装组件属性不生效的深度解析(附 $attrs、inheritAttrs 原理)
  • 【记录】HunyuanVideo 文生视频工作流
  • 用于构建安全AI代理的开源防护系统
  • 辰鳗科技朱越洋:紧扣时代契机,全力投身能源转型战略赛道
  • 射频前端模组芯片(PA)三伍微电子GSR2337 兼容替代SKY85337, RTC7646, KCT8247HE
  • 2025年小程序DDoS与CC攻击防御全指南:构建智能安全生态
  • Linux下的c/c++开发之操作Sqlite3数据库
  • 使用 Vite 创建 Vue 3 项目并手动配置路由的完整步骤
  • 蓝桥杯青少 图形化编程(Scratch)每日一练——校门外的树
  • 基于vueflow可拖拽元素的示例(基于官网示例的单文件示例)
  • 面试实践AND面经热点题目总结
  • 探索 C++23 的 views::cartesian_product
  • 眉山“笑气”迷局:草莓熊瓶背后的隐秘与危机
  • 迪拜金融市场CEO:2024年市场表现出色,超八成新投资者来自海外
  • 首家股份行旗下AIC来了,兴银金融资产投资有限公司获批筹建
  • “半世纪来对无争议边界最深入袭击”:印巴冲突何以至此又如何收场?
  • 丁薛祥在学习《习近平经济文选》第一卷专题研讨班上强调:深入学习贯彻习近平经济思想,加强党中央对经济工作的集中统一领导
  • 澎湃读报丨央媒头版五四青年节集中刊文:以青春之我,赴时代之约