Spring AI Alibaba-02-多轮对话记忆、持久化消息记录

Spring AI Alibaba-02-多轮对话记忆、持久化消息记录

Lison <dreamlison@163.com>, v1.0.0, 2025.04.19

文章目录

  • Spring AI Alibaba-02-多轮对话记忆、持久化消息记录
    • 多轮对话
    • 对话持久-Redis

本次主要聚焦于多轮对话功能的实现,后续会逐步增加更多实用内容,也欢迎大家提出宝贵意见,共同完善。

依赖:

  • 开发工具:IntelliJ IDEA(推荐使用最新版本,以获得更好的兼容性和功能支持)
  • JDK:17 及以上版本(可利用 IDEA 自带的 JDK,安装便捷且配置简单)
  • 阿里云百炼平台:阿里百炼平台(提供强大的 AI 模型支持和训练服务)

多轮对话

1、配置ChatMemory

@Configuration
public class SpringAiChatConfig {/*** 创建一个基于内存的聊天模型*/@Beanpublic ChatMemory chatMemory() {return new InMemoryChatMemory();}
}

2、创建ChatMemoryController

这里面注意这里可以设置用户ID等信息,咱们也就可以根据这个ID设置用户ID,以及会话ID,确保上下文连贯啦

// 调用 chatClient.prompt() 方法开始构建聊天请求
ChatClient.CallResponseSpec response = chatClient.prompt()// 调用 .user(input) 方法,将用户输入作为聊天请求的内容.user(input)// 调用 .advisors 方法,传入一个 Lambda 表达式,配置聊天顾问的参数.advisors(spec -> spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, userId)// 继续在 Lambda 表达式中调用 .param 方法,设置聊天记忆的检索大小为 100.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))// 调用 .call() 方法执行聊天请求并获取响应规格.call();

全文:

package com.lison.ai.spring_ai_alibaba_demo.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor;
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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RestController
@RequestMapping("/ai/v1")
public class ChatMemoryController {private final ChatClient chatClient;// 构造器中注入 ChatModel(底层与 AI 模型交互)和 ChatMemory(对话记忆实现)public ChatMemoryController(ChatModel chatModel, ChatMemory chatMemory) {// 使用 ChatClient.Builder 构建 ChatClient,同时加入对话记忆 Advisorthis.chatClient = ChatClient.builder(chatModel).defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)).build();}/*** 多轮对话接口* 每次调用时自动加载和更新该会话的历史记录。*/@GetMapping("/multi/chat")public String chat(@RequestParam(value = "userId",defaultValue = "10001") String userId,@RequestParam("input") String input) {log.info("/multi/chat   input:  [{}]", input);// 调用 chatClient.prompt() 方法开始构建聊天请求ChatClient.CallResponseSpec response = chatClient.prompt()// 调用 .user(input) 方法,将用户输入作为聊天请求的内容.user(input)// 调用 .advisors 方法,传入一个 Lambda 表达式,配置聊天顾问的参数.advisors(spec -> spec.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, userId)// 继续在 Lambda 表达式中调用 .param 方法,设置聊天记忆的检索大小为 100.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))// 调用 .call() 方法执行聊天请求并获取响应规格.call();return response.content();}
}

通过以上代码,我们成功创建了一个对外接口:http://127.0.0.1:8080/ai/v1/multi/chat。该接口接受两个参数:

userId:代表用户 ID,在实际项目中,建议将其设置为用户 ID 与会话 ID 的组合,以便更精准地区分不同用户的对话。
input:用户输入的问题或消息。

在这里插入图片描述

测试连续对话,继续问他们出生在什么地方
在这里插入图片描述

修改用户ID

修改用户ID 继续询问问题 lison002,看是否能够继续作答
在这里插入图片描述

结果显示,AI 并未出现“串线”现象,对于新用户的提问,它无法获取之前用户的对话信息,只能要求我们提供更详细的资料,这证明了我们的多轮对话系统在不同用户间是相互独立且安全的。

对话持久-Redis

增加依赖

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

Redis配置类(RedisConfig.java)

package com.lison.ai.spring_ai_alibaba_demo.config;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 {@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;}
}

配置了 RedisTemplate,使用 JSON 序列化器将对象存储为 JSON 格式,方便后续的存储和读取。

创建消息实体(ChatEntity.java)

package com.lison.ai.spring_ai_alibaba_demo.config;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@NoArgsConstructor
@AllArgsConstructor
@Data
public class ChatEntity implements Serializable {String chatId;String type;String text;
}

定义了消息实体类,用于存储对话的 ID、类型和内容,实现了序列化接口以便在 Redis 中存储。

实现 Redis 聊天记忆模型(ChatStorageMemory.java)

package com.lison.ai.spring_ai_alibaba_demo.config.chat;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;public ChatStorageMemory(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);}
}

实现了 Redis 中的对话记忆功能,包括添加对话、获取对话历史和清除对话记录。

SpringAiChatConfig 注入类

package com.lison.ai.spring_ai_alibaba_demo.config.chat;import org.springframework.ai.chat.client.ChatClient;
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;
import org.springframework.data.redis.core.RedisTemplate;@Configuration
public class SpringAiChatConfig {@Autowiredprivate ChatModel chatModel;@Beanpublic ChatClient chatClient(ChatMemory chatMemory) {return ChatClient.builder(chatModel).build();}@Beanpublic ChatMemory chatMemory(RedisTemplate<String, Object> redisTemplate) {return new ChatStorageMemory(redisTemplate);}
}

通过 Spring 的依赖注入机制,将 Redis 聊天记忆模型与 ChatClient 进行绑定,确保对话记忆功能能够正常工作。

编写核心控制器(ChatStorageMemoryController.java)

package com.lison.ai.spring_ai_alibaba_demo.controller;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;@Slf4j
@RestController
@RequestMapping("/ai/v1")
public class ChatStorageMemoryController {@Autowiredprivate ChatClient chatClient;@Autowiredprivate ChatMemory chatMemory;// 对话记忆长度private final Integer CHAT_HISTORY_SIZE = 10;@GetMapping(value = "/storage/chat")public String chat(@RequestParam String userId, @RequestParam String inputMsg) {log.info("/redis/chat  userId: [{}],  input:  [{}]", userId, inputMsg);String text = chatClient.prompt().user(inputMsg).advisors(new MessageChatMemoryAdvisor(chatMemory, userId, CHAT_HISTORY_SIZE)).call().content();log.info("text --> [{}]", text);return text;}
}

application.yml

server:port: 8080
spring:application:name: AI Demodata:redis:host: 127.0.0.1port: 6379password: xxxxdatabase: 0ai:dashscope:api-key: sk-xxxx

验证测试

http://localhost:8080/ai/v1/storage/chat?userId=lison001&inputMsg=中国近代3个名人

**第一轮对话:**中国近代3个名人
在这里插入图片描述

第二轮对话:他们的出生地在哪

在这里插入图片描述

第三轮对话:这些地方曾经出过哪些大的事件
在这里插入图片描述

Redis 的存储
在这里插入图片描述

本次分享“可持久化的多轮对话”,以 Redis 为示例,实现对话记录的长期保存。当然,这一功能也可以拓展至数据库等其他存储方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/80015.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分别配置Github,Gitee的SSH链接

文章目录 前言一、为第二个账号生成新的密钥对二、 配置 SSH config 文件1.引入库使用 Host 别名进行 clone/push/pull注意扩展 前言 之前已经在电脑配置过Github一个仓库ssh链接&#xff0c;今天想配一个Gitee仓库的ssh链接。运行 ssh-keygen -t rsa提示已经存在&#xff0c…

Python 获取淘宝买家订单详情(buyer_order_detail)接口的详细指南

在电商运营中&#xff0c;订单详情数据是商家进行数据分析、客户服务和营销策略制定的重要依据。淘宝提供了 buyer_order_detail 接口&#xff0c;允许开发者获取买家的订单详情数据。本文将详细介绍如何使用 Python 调用该接口获取订单详情&#xff0c;并解析返回的数据。 一、…

C语言实战:用Pygame打造高难度水果消消乐游戏

水果消消乐 - 困难模式 以下是一个基于Python和Pygame的水果消消乐游戏实现&#xff0c;包含困难模式的特点&#xff1a; import pygame import random import sys from pygame.locals import *# 初始化 pygame.init() pygame.mixer.init()# 游戏常量 FPS 60 WINDOW_WIDTH …

Doris-BrokerLoad任务监控

BrokeLoad监控 #!/bin/bash target_label$1 user$2 password$3looptrue echo "开始循环了----------------------" while ${loop} dolabel$(mysql -h FE_IP -P9030 -urealtime -ppassword -D offline -e "show load where label${target_label}")if [ -z &…

企业微信私域运营,基于http协议实现SCRM+AI完整解决方案

1、方案介绍 基于企业微信原生功能已实现全场景的能力覆盖&#xff0c;并提供标准化可直接调用的API接口&#xff0c;可以帮助企业轻松实现上层应用的开发及落地&#xff0c;方案采用模拟通信技术可实现PC&#xff0c;手机&#xff0c;ipad三端的同时在线&#xff0c;单服务器…

Oracle Linux8 安装 MySQL 8.4.3,搭建一主一从

文章目录 安装依赖获取安装包解压准备相关目录设置配置文件启动数据库连接数据库socket 文件优化同样方法准备 3307 数据库实例设置配置文件启动 3307 实例数据库连接并查看 3307 数据库实例基于 bin log 搭建主从模式 安装依赖 yum install -y numactl libaio ncurses-compat…

Dataway在Spring Boot中的引入以及使用教程

Dataway是Hasor生态中的接口配置工具&#xff0c;能帮助开发者快速配置数据接口。它支持DataQL和SQL两种语言模式&#xff0c;可将SQL转换为DataQL执行&#xff0c;简化数据查询与交互&#xff0c;无需编写大量代码。接口配置完成后&#xff0c;可进行自测、冒烟测试&#xff0…

进程互斥的软件实现方法

单标志法 算法思想&#xff1a;两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予 int turn 0; //turn 表示当前允许进入临界区的进程号P0 进程&#xff1a; while (turn ! 0); ① //进入区 critical …

力扣150题-- 汇总区间和合并区间

Day 27 题目描述 思路 做法&#xff1a; 特殊处理空数组和数组只有一个元素的情况设置beg&#xff0c;end标记范围的起始和结束&#xff0c;x用来比较元素是否有序&#xff08;初始end和beg都指向nums[0[,x为nums[0]1&#xff09;遍历数组如果当前元素等于x&#xff0c;说明…

【c++深入系列】:万字string详解(附有sso优化版本的string模拟实现源码)

&#x1f525; 本文专栏&#xff1a;c &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; 当你想放弃时&#xff0c;想想为什么当初坚持走到了这里 ★★★ 本文前置知识&#xff1a; 类和对象&#xff08;上&#xff09; 类和对…

Spark-Streaming简介和核心编程

Spark-Streaming简介 概述&#xff1a;用于流式数据处理&#xff0c;支持Kafka、Flume等多种数据输入源&#xff0c;可使用Spark原语运算&#xff0c;结果能保存到HDFS、数据库等。它以DStream&#xff08;离散化流&#xff09;为抽象表示&#xff0c;是RDD在实时场景的封装&am…

verilog中的约束信息

1、保持约束 keep&#xff1a;当编译器在对FPGA设计进行映射时&#xff0c;一些线网将会被吸收到逻辑块中。 (* KEEP "{TRUE | FALSE}" *) keep_hierarchy:vivado默认会把设计变成一级一级模块化的调用转换为一个没有子模块的超大模块。这个约束会保留部分层级关系…

Missashe考研日记-day24

Missashe考研日记-day24 1 专业课408 学习时间&#xff1a;2h30min学习内容&#xff1a; 今天把剩下的两个经典同步问题和管程部分的课看了&#xff0c;然后做课后习题。这部分的重点在PV大题&#xff0c;很多很经典&#xff0c;不过第一轮不打算做大题&#xff0c;把选择题做…

力扣每日打卡17 49. 字母异位词分组 (中等)

力扣 49. 字母异位词分组 中等 前言一、题目内容二、解题方法1. 哈希函数2.官方题解2.1 前言2.2 方法一&#xff1a;排序2.2 方法二&#xff1a;计数 前言 这是刷算法题的第十七天&#xff0c;用到的语言是JS 题目&#xff1a;力扣 49. 字母异位词分组 (中等) 一、题目内容 给…

C#抽象类和虚方法的作用是什么?

抽象类 (abstract class)&#xff1a; 不能直接实例化&#xff0c;只能被继承。 用来定义一套基础框架和规范&#xff0c;强制子类必须实现某些方法&#xff08;抽象方法&#xff09;。 可用来封装一些共通的逻辑&#xff0c;减少代码重复。 虚方法 (virtual)&#xff1a; …

PowerBi中ALLEXCEPT怎么使用?

在 Power BI 的 DAX 中&#xff0c;ALLEXCEPT() 是一个非常重要的函数&#xff0c;用来实现**“在保留部分筛选条件的前提下&#xff0c;移除其他所有筛选器”**&#xff0c;它常用于 同比、占比、累计汇总 等分析中。 ✅ 一、ALLEXCEPT 是什么意思&#xff1f; 函数全称&…

IQ信号和实信号的关系与转换的matlab实现

IQ信号 IQ信号通常是指两路正交的信号(I路和Q路),在实际信号采样中,通常会进行IQ采样,将实信号转换为复基带信号进行存储。 IQ信号转实信号 IQ信号转为实信号,其实就是将IQ两路正交信号通过上变频合并为一个实数的带通信号,这通常在通信系统中用于将基带信号调制到载…

【锂电池剩余寿命预测】LSTM长短期记忆神经网络锂电池剩余寿命预测(Matlab源码)

目录 效果一览程序获取程序内容代码分享研究内容基于LSTM长短期记忆神经网络的锂电池剩余寿命预测摘要关键词1. 引言1.1 研究背景1.2 研究现状与问题1.3 研究目的与意义2. 文献综述2.1 锂电池剩余寿命预测方法概述2.2 传统预测方法的优势与不足2.3 LSTM在锂电池寿命预测中的应用…

具身智能的理论基础

引言 在人工智能与认知科学快速发展的背景下&#xff0c;“具身智能”&#xff08;Embodied Intelligence&#xff09;这一概念日益受到重视。具身智能是指智能体的认知能力不仅源于其大脑&#xff08;或中央处理单元&#xff09;&#xff0c;更根植于其身体的结构、感官与其所…

【数据结构】励志大厂版·初级(二刷复习)双链表

前引&#xff1a;今天学习的双链表属于链表结构中最复杂的一种&#xff08;带头双向循环链表&#xff09;&#xff0c;按照安排&#xff0c;我们会先进行复习&#xff0c;如何实现双链表&#xff0c;如基本的头插、头删、尾删、尾插&#xff0c;掌握每个细节&#xff0c;随后进…