SpringBoot整合Spring-AI并使用Redis实现自定义上下文记忆对话

背景

后端开发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,需谨慎使用。

相关推荐
郝学胜-神的一滴14 小时前
超越Spring的Summer(一): PackageScanner 类实现原理详解
java·服务器·开发语言·后端·spring·软件构建
小刘的大模型笔记14 小时前
大模型LoRA微调全实战:普通电脑落地,附避坑手册
人工智能·电脑
乾元14 小时前
身份与访问:行为生物识别(按键习惯、移动轨迹)的 AI 建模
运维·网络·人工智能·深度学习·安全·自动化·安全架构
happyprince14 小时前
2026年02月07日全球AI前沿动态
人工智能
啊阿狸不会拉杆14 小时前
《机器学习导论》第 7 章-聚类
数据结构·人工智能·python·算法·机器学习·数据挖掘·聚类
Java后端的Ai之路14 小时前
【AI大模型开发】-AI 大模型原理深度解析与 API 实战(建议收藏!!!)
人工智能·ai·科普·ai大模型·llm大模型
禁默14 小时前
从图像预处理到目标检测:Ops-CV 助力 CV 任务在昇腾 NPU 上高效运行
人工智能·目标检测·目标跟踪·cann
pp起床14 小时前
Gen_AI 第四课 模型评估
人工智能
zhangshuang-peta14 小时前
人工智能代理团队在软件开发中的协同机制
人工智能·ai agent·mcp·peta
love you joyfully14 小时前
告别“人多力量大”误区:看AI团队如何通过奖励设计实现协作韧性
人工智能·深度学习·神经网络·多智能体