SpringAI Alibaba 集成 Redis 让 AI 记住上下文
pom 依赖
xml
<dependencies>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>添加配置文件
yaml
spring:
# DashScope 配置
ai:
# Redis 配置(用于内存管理)
memory:
redis:
host: localhost
port: 6379
password: ""
timeout: 5000添加配置类
java
import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Redis 记忆配置类 - 用于 Spring AI Alibaba 的会话记忆功能
*
* @author 朔风
* @date 2026-03-31 17:02
*/
@Configuration
public class RedisMemoryConfig {
@Value("${spring.ai.memory.redis.host:localhost}")
private String redisHost;
@Value("${spring.ai.memory.redis.port:6379}")
private int redisPort;
@Value("${spring.ai.memory.redis.password:}")
private String redisPassword;
@Value("${spring.ai.memory.redis.timeout:5000}")
private int redisTimeout;
/**
* Redis 聊天记忆仓库配置
*/
@Bean
public RedisChatMemoryRepository redisChatMemoryRepository() {
return RedisChatMemoryRepository.builder()
.host(redisHost)
.port(redisPort)
// .password(redisPassword)
.timeout(redisTimeout)
.build();
}
}修改 ChatConfig.java 配置类
java
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* ChatClient 配置类
*
* @author 朔风
* @date 2026-03-31 15:53
*/
@Configuration
public class ChatConfig {
private static final String DEFAULT_PROMPT = """
你是一个专业、耐心且富有启发性的 AI 学习助手,名字叫朔风。
你的目标是帮助用户深入理解知识点,而不仅仅是提供标准答案。
请遵循以下原则进行回复:
1. 角色设定:你是用户的导师,用鼓励性的语气交流。
2. 循序渐进:如果用户问一个复杂问题,先解释基础概念,再逐步深入。
3. 举例说明:尽量使用生活中的例子或代码示例来解释抽象概念。
4. 启发思考:在给出答案后,提出一个相关的问题,引导用户进一步思考。
5. 结构清晰:使用 Markdown 格式(如标题、列表、加粗)使内容易于阅读。
6. 准确严谨:确保提供的信息是最新且准确的,如果遇到不确定的领域,请诚实告知。
""";
/**
* 配置本地 Ollama 的 ChatClient(带日志记录)
*/
@Bean(name = "ollamaChatClient")
public ChatClient ollamaChatClient(OllamaChatModel ollamaChatModel) {
return ChatClient.builder(ollamaChatModel)
.defaultSystem("你是一个博学的本地大模型,名字叫朔风")
.defaultAdvisors(new SimpleLoggerAdvisor()) // 自动记录请求和响应日志
.build();
}
/**
* 配置云端 DashScope 的 ChatClient(带日志记录)
*/
@Bean(name = "dashscopeChatClient")
@Primary // 设置为默认ChatClient
public ChatClient dashscopeChatClient(DashScopeChatModel dashscopeChatModel,
RedisChatMemoryRepository redisChatMemoryRepository) {
return ChatClient.builder(dashscopeChatModel)
//覆盖配置文件配置
.defaultSystem(DEFAULT_PROMPT)
.defaultOptions(DashScopeChatOptions.builder()
//指定模型 优先级高于 配置文件
.withModel("qwen-plus")
//指定温度 优先级高于 配置文件
.withTemperature(0.7)
.build())
.defaultAdvisors(
// 添加日志顾问
new SimpleLoggerAdvisor(),
// 添加聊天记忆顾问
MessageChatMemoryAdvisor.builder(
// 指定聊天记忆
MessageWindowChatMemory.builder()
//指定聊天记忆仓库
.chatMemoryRepository(redisChatMemoryRepository)
//指定最大消息数
.maxMessages(50)
.build())
.build()
)
.build();
}
}Redis 会话记忆控制器
java
import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.List;
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
/**
* Redis 会话记忆控制器
*
* @author 朔风
* @date 2026-03-31 17:06
*/
@RestController
@RequestMapping("/api/redis-session")
@RequiredArgsConstructor
public class RedisSessionController {
private final ChatClient dashscopeChatClient;
private final RedisChatMemoryRepository redisChatMemoryRepository;
private final RedisTemplate redisTemplate;
/**
* 带会话记忆的聊天接口
*/
@GetMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chatWithMemory(
@RequestParam("question") String question,
@RequestParam(value = "sessionId", defaultValue = "student_session") String sessionId,
@RequestParam(value = "userId", defaultValue = "default_userId") String userId) {
// 将 userId 和 sessionId 拼接作为 Redis key
String redisKey = userId + ":" + sessionId;
MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(redisChatMemoryRepository)
.maxMessages(Integer.MAX_VALUE)
.build();
// 如果是新的会话 将 sessionId 存入 Redis list类型 userId为key sessionId为值
if (messageWindowChatMemory.get(redisKey).size() < 1) {
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
BoundListOperations boundListOperations = redisTemplate.boundListOps("history:" + userId);
boundListOperations.leftPush(sessionId);
}
// 使用已配置的 dashscopeChatClient,Redis 记忆已在配置中启用
return dashscopeChatClient.prompt(question)
.advisors(
a -> a.param(CONVERSATION_ID, redisKey)
)
.stream().content();
}
/**
* 获取指定会话的历史消息
*/
@GetMapping("/history/{userId}/{sessionId}")
public List<Message> getSessionHistory(
@PathVariable String userId,
@PathVariable String sessionId) {
String redisKey = userId + ":" + sessionId;
MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(redisChatMemoryRepository)
.maxMessages(Integer.MAX_VALUE)
.build();
return messageWindowChatMemory.get(redisKey);
}
/**
* 清除指定会话的记忆
*/
@DeleteMapping("/clear/{userId}/{sessionId}")
public String clearSession(@PathVariable String userId, @PathVariable String sessionId) {
String redisKey = userId + ":" + sessionId;
MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(redisChatMemoryRepository)
.maxMessages(Integer.MAX_VALUE)
.build();
messageWindowChatMemory.clear(redisKey);
// 从 Redis 会话列表中删除对应的 sessionId
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
BoundListOperations<String, String> boundListOperations = redisTemplate.boundListOps("history:" + userId);
boundListOperations.remove(0, sessionId);
return "用户 " + userId + " 的会话 " + sessionId + " 记忆已清除";
}
/**
* 获取指定用户的所有会话ID
*/
@GetMapping("/sessions/{userId}")
public List<String> getUserSessions(@PathVariable String userId) {
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
BoundListOperations<String, String> boundListOperations = redisTemplate.boundListOps("history:"+userId);
return boundListOperations.range(0, -1);
}
}访问接口
带会话记忆的聊天接口
shell
http://localhost:8080/api/redis-session/chat?question=你是谁&userId=1&sessionId=1获取指定会话的历史消息
shell
http://localhost:8080/api/redis-session/history/1/1清除指定会话的记忆
shell
http://localhost:8080/api/redis-session/clear/1/1获取指定用户的所有会话ID
shell
http://localhost:8080/api/redis-session/sessions/1
朔风