redis相关准备
添加依赖
我利用redission来实现
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.37.0</version>
</dependency>
添加配置文件
spring:
redis:
database: 5
host: 127.0.0.1
port: 6379
password: 1234
创建RedisProperties文件
@Data
@Component
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private String host;
private int port;
private String password;
private int database;
}
创建redissionConfig配置文件
@Configuration
@RequiredArgsConstructor
public class RedisConfig {
private final RedisProperties redisProperties;
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort())
.setDatabase(redisProperties.getDatabase())
.setPassword(redisProperties.getPassword());
config.setCodec(new StringCodec(StandardCharsets.UTF_8));
return Redisson.create(config);
}
}
创建RedissionUtil工具类
package com.sde.util;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.StreamSupport;
@Component
public class RedissonUtil {
private static RedissonClient redissonClient;
@Autowired
public void setRedissonClient(RedissonClient client) {
redissonClient = client;
}
/**
* 设置数据的过期时间
* @param key 要设置的键
* @param seconds 过期时间(秒)
*/
public static boolean expire(String key, long seconds) {
return redissonClient.getKeys().expire(key, seconds, TimeUnit.SECONDS);
}
/**
* 设置数据的过期时间
* @param key 要设置的键
* @param duration 时长
* @param unit 时间单位
* @return
*/
public static boolean expire(String key, long duration, TimeUnit unit) {
return redissonClient.getKeys().expire(key, duration, unit);
}
/**
* 将字符串添加到 Redis List 的尾部
*
* @param key Redis 键
* @param value 要添加的值
*/
public static void addToList(String key, String value) {
RList<String> list = redissonClient.getList(key);
list.add(value);
}
/**
* 获取列表
* @param key
* @return
*/
public static RList<String> getList(String key) {
return redissonClient.getList(key);
}
/**
* 获取 RList 对象,用于后续操作如 trim、remove 等
*/
public static RList<String> getRList(String key) {
return redissonClient.getList(key);
}
/**
* 删除数据
* @param key 要删除的键
* @return
*/
public static boolean remove(final String key) {
return redissonClient.getKeys().delete(key) >= 0;
}
/**
* 检查键是否存在
* @param key 键
* @return 是否存在
*/
public static boolean exists(String key) {
RBucket<String> bucket = redissonClient.getBucket(key);
return bucket.isExists();
}
}
SpringAI相关准备
不用阿里云的maven仓库,用默认的maven中央仓库。
版本控制
<properties>
<java.version>21</java.version>
<spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version>
</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>
添加依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
因为我对接的阿里云百炼平台
添加配置
spring:
ai:
openai:
base-url: https://dashscope.aliyuncs.com/compatible-mode/
api-key: ${ALIYUN_AK}
chat:
options:
model: qwen-max
我的api-key 存储在我环境变量中
编写代码
创建保存到redis中的实体
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StoredMessage implements Serializable {
@JSONField(ordinal = 0)
private String role;
@JSONField(ordinal = 1)
private String content;
public static StoredMessage from(Message message) {
return new StoredMessage(message.getMessageType().name(), message.getText());
}
public Message toMessage() {
return switch (role) {
case "USER" -> new UserMessage(content);
case "ASSISTANT" -> new AssistantMessage(content);
default -> new SystemMessage(content);
};
}
}
创建RedisChatMemory
@Component
public class RedisChatMemory implements ChatMemory {
// redis key 的前缀
private static final String CHAT_MEMORY_KEY = "chat:conversationid:";
private static final long TTL_MILLIS = 2 * 24 * 60 * 60 * 1000; // 2天
private final int maxMessage;
public RedisChatMemory() {
this(10); // 默认保留 10 条消息
}
public RedisChatMemory(int maxMessages) {
this.maxMessage = maxMessages;
}
@Override
public void add(String conversationId, Message message) {
String key = getKey(conversationId);
StoredMessage storedMessage = StoredMessage.from(message);
String json = toJson(storedMessage);
RedissonUtil.addToList(key, json);
trimMessages(key);
//刷新过期时间为2天
RedissonUtil.expire(key, TTL_MILLIS, TimeUnit.MILLISECONDS);
}
@Override
public void add(String conversationId, List<Message> messages) {
messages.forEach(message -> add(conversationId, message));
}
@Override
public List<Message> get(String conversationId) {
String key = getKey(conversationId);
//检查key是否存在
if (!RedissonUtil.exists(key)) {
return Collections.emptyList();
}
//获取所有 json字符串格式的消息
List<String> messages = RedissonUtil.getList(key);
//转换成 Message 列表
return messages.stream()
.map(this::fromJson)
.filter(Objects::nonNull)
.map(StoredMessage::toMessage)
.toList();
}
@Override
public void clear(String conversationId) {
String key = getKey(conversationId);
RedissonUtil.remove(key);
}
/**
* 构建redis的key
* @param conversationId
* @return
*/
private String getKey(String conversationId){
return CHAT_MEMORY_KEY + conversationId;
}
/**
* 将消息转换为 JSON 字符串
*/
private String toJson(StoredMessage msg) {
SerializeConfig config = new SerializeConfig();
return JSON.toJSONString(msg,config, SerializerFeature.SortField);
}
/**
* 将 JSON 字符串转换为 StoredMessage 对象
*/
private StoredMessage fromJson(String json) {
return JSON.parseObject(json, StoredMessage.class);
}
/**
* 控制消息数量,保留最近 maxMessages 条
*/
private void trimMessages(String key) {
RList<String> list = RedissonUtil.getRList(key);
if (list.size() > maxMessage) {
int toRemove = list.size() - maxMessage;
for (int i = 0; i < toRemove; i++) {
list.remove(0);
}
}
}
}
创建Controller
@Slf4j
@RestController
@RequestMapping("/memory")
public class ChatMemoryController {
private final ChatClient chatClient;
public ChatMemoryController(ChatModel qwen) {
this.chatClient = ChatClient.builder(qwen).build();
}
@Autowired
private RedisChatMemory redisChatMemory;
@GetMapping("/redis")
public Flux<String> redis(String question, String conversationId){
MessageChatMemoryAdvisor advisor = MessageChatMemoryAdvisor.builder(redisChatMemory)
.conversationId(conversationId)
.order(5)
.build();
return chatClient.prompt()
.system("你是我的学习助手名字叫小菜包")
.user(question)
.advisors(advisor)
.stream()
.content();
}
}