背景
后端开发AI交互的对话,要求带有上下文对话,使用原生SpringAI+Redis实现。实现Demo笔记。
依赖
项目基础依赖:
Java17+SpringBoot3+SpringAI 1.0.0-M6
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.4.5</version>
</dependency>
配置
yaml
spring:
ai:
deepseek:
base-url: https://xxxxxxxxxx
api-key: xxxxxxxxxxx
model: deepseek-ai/DeepSeek-V3
##对话记忆方式
chat:
memory:
store-type: redis
persistent: true
data:
redis:
database: 1
host: xxxxxxx
prot: 6379
password: xxxxxx
定义Redis配置
只是用于简单定义一个RedisTemplate
java
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}
自定义对话记忆实现
上下文对话功能具体实现,可以自行定义最大消息数,token最大数等配置。
java
@Service
public class CustomChatMemory {
// 最大消息数
private final int MAX_MESSAGES = 20;
// 最大token数(估算)
private final int MAX_TOKENS = 4000;
private final RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
public CustomChatMemory(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 获取会话历史消息
*/
public List<ChatMessage> getHistory(String sessionId) {
String key = "chat:" + sessionId;
String json = redisTemplate.opsForValue().get(key);
if (json == null) return new ArrayList<>();
try {
return objectMapper.readValue(
json,
new TypeReference<List<ChatMessage>>() {}
);
} catch (Exception e) {
return new ArrayList<>();
}
}
/**
* 添加消息并维护窗口
*/
public void addMessage(String sessionId, ChatMessage message) {
String key = "chat:" + sessionId;
List<ChatMessage> messages = getHistory(sessionId);
messages.add(message);
trimMessages(messages);
try {
String json = objectMapper.writeValueAsString(messages);
redisTemplate.opsForValue().set(key, json, Duration.ofHours(24));
} catch (Exception e) {
// 记录日志
}
}
/**
* 裁剪消息:限制数量和token
*/
private void trimMessages(List<ChatMessage> messages) {
// 限制消息数量
if (messages.size() > MAX_MESSAGES) {
messages.subList(0, messages.size() - MAX_MESSAGES).clear();
}
// 估算token并裁剪(简化版)
int totalTokens = estimateTokens(messages);
while (totalTokens > MAX_TOKENS && messages.size() > 1) {
messages.remove(0);
totalTokens = estimateTokens(messages);
}
}
/**
* 简单token估算(1个中文字符≈2tokens,英文单词≈1token)
* // 每条消息预留100token
*/
private int estimateTokens(List<ChatMessage> messages) {
return messages.stream()
.mapToInt(msg -> msg.getContent().length() / 2 + 100)
.sum();
}
/**
* 清空会话记忆
*/
public void clear(String sessionId) {
String key = "chat:" + sessionId;
redisTemplate.delete(key);
}
}
大模型配置
java
@Configuration
public class MultiLlmConfig {
@Bean(name = "deepSeekAiChatModel")
public OpenAiChatModel deepSeekAiChatModel(
@Value("${spring.ai.deepseek.api-key}") String apiKey,
@Value("${spring.ai.deepseek.base-url}") String baseUrl,
@Value("${spring.ai.deepseek.model}") String model) {
OpenAiApi api = new OpenAiApi(baseUrl, apiKey);
OpenAiChatOptions options = OpenAiChatOptions.builder()
.model(model)
.temperature(0.7)
.maxTokens(8192)
.build();
return new OpenAiChatModel(api, options);
}
}
具体实现方法
仅以实现类为例:
java
@Override
public String chatWithMemory(String sessionId, String userMessage) {
// 1. 获取历史消息
List<ChatMessage> history = chatMemory.getHistory(sessionId);
// 2. 构建prompt(历史+当前消息)
List<ChatMessage> promptMessages = new ArrayList<>(history);
promptMessages.add(ChatMessage.user(userMessage));
// 3. 调用OpenAI
ChatResponse response = deepSeekAiChatModel.call(
new Prompt(Lists.newArrayList(
promptMessages.stream()
.map(msg -> new org.springframework.ai.chat.messages.UserMessage(msg.getContent()))
.collect(Collectors.toList())
))
);
String aiResponse = response.getResult().getOutput().getText();
// 4. 保存对话记录
chatMemory.addMessage(sessionId, ChatMessage.user(userMessage));
chatMemory.addMessage(sessionId, ChatMessage.assistant(aiResponse));
log.info("Session {}: User: {}, AI: {}", sessionId, userMessage, aiResponse);
return aiResponse;
}
验证测试
定义测试接口:
java
@Tag(name = "Demo 测试")
@RestController
@Validated
@RequestMapping("/api/v1/demo")
public class DemoController {
@Autowired
private ChatService chatService;
@GetMapping("/chatWithMemory")
public String chatWithMemory(@RequestParam String sessionId,@RequestParam String prompt) {
return chatService.chatWithMemory(sessionId,prompt);
}
验证结果:
第一次对话 :
浏览器输入:http://localhost:8080/ai-service/api/v1/demo/chatWithMemory?sessionId=123\&prompt=你好啊你是谁
AI回复:你好呀!😊我是DeepSeek
Chat,一个由深度求索公司打造的AI助手。我的使命是回答你的问题、提供帮助,或者陪你聊聊天~无论是学习、工作,还是生活中的各种疑惑,你都可以问我!✨
第二次对话 :浏览器输入: http://localhost:8080/ai-service/api/v1/demo/chatWithMemory?sessionId=123\&prompt=你能做什么
AI回复:我的能力可多了,随时为你效劳~💡 以下是一些我能帮你的方向: 📚 学习助手 - 解答各学科问题(数学、编程、语言、文史哲等) - 解析论文/文献概念,整理学习笔记 - 生成考试真题、写作范文或解题思路 💼 工作效率 - 写邮件、报告、PPT大纲、策划案 - 数据分析(需上传Excel/CSV)、代码调试 - 中英/多语言互译,润色文本 🛠️ 实用工具 - 查天气、菜谱、旅行攻略 - 生成合同、协议等模板(需自行核对法律效力) - 解谜题、算数、单位换算等小工具 💡 创意与娱乐 - 写小说、诗歌、剧本,甚至帮你起名字! - 推荐书影音/游戏,分析剧情 - 陪你闲聊、讲笑话、玩文字游戏 🔍 其他 - 解读专业术语(法律、医学等,但非专业替代) - 提供心理支持(非诊疗),助你梳理情绪 ⚠️ 注意:我不能访问实时网络(除非你手动上传文件),但知识截止到2024年7月~ 现在,你想先试试哪一项? 😄
第三次对话 :浏览器输入: http://localhost:8080/ai-service/api/v1/demo/chatWithMemory?sessionId=123\&prompt=我的第一个问题是什么
AI回复:你的第一个问题是:"你好啊你是谁",而我的回答是向你介绍自己是DeepSeek Chat,一个AI助手~ 😊 (我们之前的完整对话记录还在,如果你需要回溯某个问题或让我补充回答,随时告诉我!) 现在想继续聊点什么呢?✨
至此,上下文记忆功能已实现。
以上仅为个人开发测试的demo样例,可能存在bug,需谨慎使用。