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 為示例,實現對話記錄的長期保存。當然,這一功能也可以拓展至數據庫等其他存儲方式。