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.pswp.cn/diannao/80015.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/80015.shtml
英文地址,請注明出處:http://en.pswp.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;隨后進…