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

【AI大模型】Spring AI 基于Redis实现对话持久存储详解

目录

一、前言

二、Spring AI 会话记忆介绍

2.1 Spring AI 会话记忆概述

2.2 常用的会话记忆实现方式

2.2.1 集成数据库持久存储会话实现步骤

2.3 适用场景

三、Spring AI基于内存会话记忆存储

3.1 本地开发环境准备

3.2 工程搭建与集成

3.2.1 添加核心依赖

3.3.2 添加配置文件

3.3.3 添加测试接口

3.2 ChatMemory 介绍

3.2.1 ChatMemory 概述

3.2.2 InMemoryChatMemory

3.2.3 MessageWindowChatMemory 

3.2.4 MessageChatMemoryAdvisor

3.3 案例效果演示

3.3.1 自定义配置类

3.3.2 添加测试接口

四、Spring AI基于Redis记忆存储

4.1 前置准备

4.1.1 导入redis依赖

4.1.2 增加配置信息

4.1.4 增加redis配置类

4.1.5 启动redis服务

4.2 代码整合过程

4.2.1 增加会话实体对象

4.2.2 自定义ChatMemory

4.2.3 自定义模型配置类

4.2.4 添加测试接口

4.2.5 效果测试

五、写在文末


一、前言

在使用AI大模型产品进行多轮对话的时候发现,你可以在第一次输入问题并得到大模型的回复之后,只要是在一定的会话时间窗口期内,再次提问与第一次相关的问题,或者基于第一次的提问的衍生内容,大模型均可以再次回复与此相关的回答,这就是大模型的记忆功能。

默认情况下,我们向大模型每次发起的提问都是新的,大模型就无法把我们的每次对话形成记忆,也无法根据对话上下文给出人性化的答案,因为大模型已经失去了上一次的提问记忆。所以让智能体(如AI助手、机器人、虚拟角色等)拥有记忆功能不仅能提升交互体验,还能增强其功能性、适应性和长期价值。本篇以Spring AI为例,详细说明下基于Spring AI框架下的记忆功能的实现。

二、Spring AI 会话记忆介绍

2.1 Spring AI 会话记忆概述

Spring AI 的会话记忆功能是指让智能体(如AI助手、机器人、虚拟角色等)在多次交互中保持上下文或状态,从而提升交互体验和功能性。这种记忆功能使得智能体能够“记住”用户提供的信息,并在后续对话中参考和使用这些信息,从而提供更加个性化和精准的回复。官方文档:Chat Memory :: Spring AI Reference

2.2 常用的会话记忆实现方式

Spring AI通过 ChatMemory 接口来实现会话记忆功能。具体实现方式有下面几种:

  • 内存存储:

    • 使用 InMemoryChatMemory 来存储对话历史,这种方式适用于临时会话,但不适合长期存储;

  • 数据库存储:

    • 集成 MySQL 、 Mongo、Redis 或其他数据库来存储对话历史,以实现长期的会话记忆

2.2.1 集成数据库持久存储会话实现步骤

在实际应用中,一般是将会话数据进行持久化存储,通常的实现步骤如下:

  1. 集成数据库:

    1. 选择合适的数据存储组件(如MySQL、Mongo、Redis等),并导入相关依赖。

      1. 例如,使用MySQL存储对话历史需要导入 spring-ai-starter-model-chat-memory-repository-jdbc 依赖和 mysql-connector-j 依赖。

  2. 配置会话记忆:

    1. 在Spring配置中定义会话存储方式和会话记忆Advisor。

      1. 例如,使用 InMemoryChatMemory 并通过 MessageChatMemoryAdvisor 配置会话记忆。

  3. 添加会话ID:

    1. 每次会话时通过前端区分会话ID,确保同一个会话ID对应多次对话的消息列表,从而实现会话隔离。

2.3 适用场景

会话记忆功能适用于需要保持上下文一致性的场景,如多轮对话、用户偏好跟踪等,具体来说,使用会话记忆具有如下优势:

  • 用户信息的持久化:

    • 记住用户的背景信息或个人偏好,提供更加个性化的服务。

  • 上下文跟踪:

    • 在多轮对话中保持上下文一致性,避免用户重复说明。

  • 更好地回答追问:

    • 在复杂的多轮对话中,助手能更好地理解用户的意图,提供更详细的回复。

三、Spring AI基于内存会话记忆存储

3.1 本地开发环境准备

为了后续案例代码能够正常运行,这里列举本次相关的基础环境版本,提供参考:

  • Jdk 17;

  • Springboot 3.3.3 ;

  • Spring AI 1.0.0-M6;

3.2 工程搭建与集成

3.2.1 添加核心依赖

在pom文件中添加下面的依赖

<properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring-ai.version>1.0.0-M6</spring-ai.version><spring-ai-alibaba.version>1.0.0-M6.1</spring-ai-alibaba.version>
</properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.3</version><relativePath/>
</parent><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><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter</artifactId><version>${spring-ai-alibaba.version}</version></dependency></dependencies><repositories><repository><name>Central Portal Snapshots</name><id>central-portal-snapshots</id><url>https://central.sonatype.com/repository/maven-snapshots/</url><releases><enabled>false</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository>
</repositories><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>

3.3.2 添加配置文件

在配置文件中增加下面的配置信息

  • 这里的apikey 用的是阿里云百炼平台上面的key

spring:ai:dashscope:api-key: 你的apikeychat:options:model: qwen-max

3.3.3 添加测试接口

添加一个测试接口用于效果测试

package com.congge.web;import jakarta.annotation.Resource;
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.memory.InMemoryChatMemory;
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("/memory")
public class ChatMemoryController {private ChatClient chatClient;public ChatMemoryController(ChatClient.Builder builder) {this.chatClient = builder.build();}//localhost:8082/memory/chatV2?message=你是谁@GetMapping("/chatV2")public String chatV2(String message){String content = chatClient.prompt().user(message).call().content();return content;}}

调用一下接口,看到下面的效果说明集成是没问题的

3.2 ChatMemory 介绍

3.2.1 ChatMemory 概述

ChatMemory 是 Spring AI 中定义聊天记忆行为的核心接口,通过源码点进去查看,可以看到这是一个顶级接口

public interface ChatMemory {default void add(String conversationId, Message message) {this.add(conversationId, List.of(message));}void add(String conversationId, List<Message> messages);List<Message> get(String conversationId, int lastN);void clear(String conversationId);
}

该接口定义了三个核心操作方法:

  • add()

    • 添加消息到记忆

  • get()

    • 获取对话历史

  • clear()

    • 清除对话记忆

3.2.2 InMemoryChatMemory

如果没有引入第三方的会话存储组件,ChatMemory 的默认实现类为InMemoryChatMemory,从源码中可以看到该类对ChatMemory 的几个接口方法进行了实现,开发者可以直接使用。

3.2.3 MessageWindowChatMemory 

下面是Spring AI官方给出的MessageWindowChatMemory 的解释:

MessageWindowChatMemory将消息的窗口保持在指定的最大大小。当消息数超过最大值时,在保存系统消息时会删除较旧的消息。默认窗口大小为20个消息。

MessageWindowChatMemory 是 ChatMemory 的主要实现类,具有以下特点:

  • 维护一个固定大小的消息窗口

  • 自动移除旧消息,保留最新消息

  • 默认保留20条消息(可配置)

  • 特殊处理系统消息(不会被自动移除)

下面是MessageWindowChatMemory 在项目集成中的创建方式:

@Bean
public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(20) // 可选,默认20.build();
}

参考如下完整示例配置代码:

@Configuration
public class ChatMemoryConfig {@Beanpublic ChatMemoryRepository chatMemoryRepository() {return new InMemoryChatMemoryRepository();}@Beanpublic ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(30) // 自定义消息窗口大小.build();}
}

3.2.4 MessageChatMemoryAdvisor

MessageChatMemoryAdvisor 是 Spring AI 中用于处理会话历史记录的核心类。我们可以创建一个自定义的 MemoryAdvisor,并使用它来存储对话数据。MessageWindowChatMemory 是 ChatMemory 的主要实现类,具有以下特点:

  • 维护一个固定大小的消息窗口

  • 自动移除旧消息,保留最新消息

  • 默认保留20条消息(可配置)

  • 特殊处理系统消息(不会被自动移除)

官方文档地址:Chat Memory :: Spring AI Reference

在实际开发中,可以通过下面配置bean的方式创建

    @Beanpublic ChatClient chatClient(ChatMemory chatMemory) {return ChatClient.builder(chatModel)//.defaultSystem("你是一个数据库专家,接下来请以这个身份进行回答").defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)).build();}

3.3 案例效果演示

基于上述的理论分享,下面通过两个接口进行操作演示说明。

3.3.1 自定义配置类

增加一个自定义配置类,配置全局的chatMemory

package com.congge.config;import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
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.memory.InMemoryChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ChatMemoryConfig {@Autowiredprivate DashScopeChatModel chatModel;@Beanpublic InMemoryChatMemory chatMemory(){return new InMemoryChatMemory();}@Beanpublic ChatClient chatClient(ChatMemory chatMemory) {return ChatClient.builder(chatModel)//.defaultSystem("你是一个数据库专家,接下来请以这个身份进行回答").defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)).build();}}

3.3.2 添加测试接口

如下增加一个测试接口

@RestController
@RequestMapping("/memory")
public class ChatMemoryController {@Resourceprivate ChatClient chatClient;//localhost:8082/memory/chat?message=我叫小王@GetMapping("/chat")public String chat(String message){String content = chatClient.prompt().user(message).call().content();System.out.println(content);String content2 = chatClient.prompt().user("请问我是谁").call().content();return content2;}}

假如上面的会话基于配置不生效,先注释掉,如下:

此时项目启动后,调用下该接口进行验证,不难发现,由于大模型没有上下文记忆,这里是无法记住上一个问题的相关内容的

如果将注释放开再次测试,可以看到,此时会话记忆就生效了

如果不想配置全局的bean ,也可以直接在接口类中参照下面的方式编写

@RestController
@RequestMapping("/memory")
public class ChatMemoryController {private ChatClient chatClient;private ChatMemory chatMemory = new InMemoryChatMemory();public ChatMemoryController(ChatClient.Builder builder) {this.chatClient = builder.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory, UUID.randomUUID().toString(), 10)).build();}//localhost:8082/memory/chatV2?message=我叫小王@GetMapping("/chatV2")public String chatV2(String message){String content = chatClient.prompt().user(message).call().content();return content;}}

四、Spring AI基于Redis记忆存储

Redis是一种高效的数据存取组件,使用Redis可以用于存储大模型的会话记忆,接下来演示在Spring AI 如何基于Redis实现会话存储。

4.1 前置准备

4.1.1 导入redis依赖

pom中导入redis的核心依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4.1.2 增加配置信息

在工程的配置文件中添加如下配置

spring:ai:dashscope:api-key: 你的apikeychat:options:model: qwen-maxdata:redis:host: localhostport: 6379

4.1.4 增加redis配置类

自定义一个redis的配置类,配置序列化等信息

@Configuration
public class RedisConfig {@Beanpublic 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;}}

4.1.5 启动redis服务

本地启动redis服务

4.2 代码整合过程

4.2.1 增加会话实体对象

自定义一个消息实体类,方便后续解析和传递消息

@NoArgsConstructor
@AllArgsConstructor
@Data
public class ChatMessageInfo {String chatId;String type;String text;
}

4.2.2 自定义ChatMemory

在上文提到,ChatMemory是一个顶级接口,可以自定义一个类实现该接口,重写里面的方法

package com.congge.config;import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.*;
import org.springframework.stereotype.Component;
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
@Component
public class ChatStorageMemory implements ChatMemory {private static final String KEY_PREFIX = "chat:history:";private final RedisTemplate<String, Object> redisTemplate;private static final Integer TIME_OUT = 60;public ChatStorageMemory(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}@Overridepublic void add(String conversationId, List<Message> messages) {String key = KEY_PREFIX + conversationId;List<ChatMessageInfo> listIn = new ArrayList<>();for (Message msg : messages) {String[] strs = msg.getText().split("</think>");String text = strs.length == 2 ? strs[1] : strs[0];ChatMessageInfo ent = new ChatMessageInfo();ent.setChatId(conversationId);ent.setType(msg.getMessageType().getValue());ent.setText(text);listIn.add(ent);}redisTemplate.opsForList().rightPushAll(key, listIn.toArray());redisTemplate.expire(key, TIME_OUT, 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) {ChatMessageInfo chat = objectMapper.convertValue(obj, ChatMessageInfo.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);}
}

4.2.3 自定义模型配置类

增加一个自定义类,配置 ChatClient,ChatMemory,主要是在chatMemory这个配置bean中,注入redisTemplate

package com.congge.config;import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;@Configuration
public class SpringAiChatConfig {@Autowiredprivate DashScopeChatModel chatModel;@Beanpublic ChatClient chatClient() {return ChatClient.builder(chatModel).build();}@Beanpublic ChatMemory chatMemory(RedisTemplate<String, Object> redisTemplate) {return new ChatStorageMemory(redisTemplate);}
}

4.2.4 添加测试接口

增加一个测试接口,该接口接收两个参数,一个是会话的唯一ID,另一个是用户输入的问题

package com.congge.web;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.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RequestMapping("/memory/store")
@RestController
@Slf4j
public class ChatStoreController {@Autowiredprivate ChatClient chatClient;@Autowiredprivate ChatMemory chatMemory;private final Integer CHAT_HISTORY_SIZE = 10;//localhost:8082/memory/store/chat?userId=1&inputMsg=我叫小王//localhost:8082/memory/store/chat?userId=1&inputMsg=你知道我是谁吗@GetMapping(value = "/chat")public String chat(@RequestParam String userId, @RequestParam String inputMsg) {String response = chatClient.prompt().user(inputMsg).advisors(new MessageChatMemoryAdvisor(chatMemory, userId, CHAT_HISTORY_SIZE)).call().content();return response;}}

4.2.5 效果测试

工程运行起来之后,分别调用两次接口。

1)第一次调用

2)第二次调用

通过2次接口调用,可以看到大模型将会话信息存储到redis了,借助redis完成了会话的持久化存储,在redis客户端工具中,也能清楚看到对话的信息,由于做了持久化存储,即便短期服务挂掉,再次启动后仍然有效,同时由于UserId不同,会话也根据不同的用户ID进行了隔离。

五、写在文末

本文详细介绍了Spring AI 的会话持久化存储技术,并通过案例操作演示详细说明了如何基于Redis实现会话的持久化存储,希望对看到的同学有用,本篇到此结束,感谢观看。

相关文章:

  • 网站建设服务商城湘潭seo快速排名
  • 网站建设案例市场西安网络推广外包公司
  • 如何让自己的网站被百度收录邵阳做网站的公司
  • 手机网站建设是什么百度官网电话
  • 商务卫士包括网站建设北京seo招聘
  • 网站备案查询官网大数据精准客户
  • 华为云对象存储OBS 支持安卓/iOS/鸿蒙UTS组件
  • SQL Server 查询数据库及数据文件大小
  • 工作流会使用到Webhook是什么
  • 爬取小红书相关数据导入到excel
  • C++ 第二阶段:运算符重载 - 第二节:重载与 const 成员函数
  • Linux 文件 I/O 与标准 I/O 缓冲机制详解
  • 【JavaEE】(4) 文件操作和IO
  • Ribbon负载均衡的具体实现原理
  • MyBatis Plus与P6Spy日志配置
  • OpenSIPS 邂逅 Kafka:构建高效 VoIP 消息处理架构
  • UAVAI-YOLO:无人机航拍图像的小目标检测模型
  • 深度优化OSS上传性能:多线程分片上传 vs 断点续传实战对比
  • ntext 数据类型不能选为 DISTINCT,因为它不可比
  • 解析云计算虚拟化基石:KVM、QEMU与Libvirt的协同
  • ✨从零搭建 Ubuntu22.04 + Python3.11 + PyTorch2.5.1 GPU Docker 镜像并上传 Docker Hub
  • C# WinForm跨平台串口通讯实现
  • RFID馆员工作站DW312-A|全国已经规模化应用
  • linux实时同步工具sersync
  • 利用 Python 脚本批量查找并删除指定 IP 的 AWS Lightsail 实例
  • FunASR搭建语音识别服务和VAD检测