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,需谨慎使用。

相关推荐
是Dream呀2 小时前
多样化算力使能:openFuyao引领算力池化与调度革命
人工智能·python·算力
vx_bisheyuange2 小时前
基于SpringBoot的在线互动学习网站设计
java·spring boot·spring·毕业设计
雨中飘荡的记忆2 小时前
Spring状态机深度解析:从入门到生产实战
java·spring
Kings902 小时前
线程池导致的 shutdown失败的完整排查过程
java·spring boot
在坚持一下我可没意见2 小时前
Spring 后端安全双剑(下篇):JWT 无状态认证 + 密码加盐加密实战
java·开发语言·spring boot·后端·安全·spring
快乐非自愿2 小时前
AI重构低代码开发:从“可视化编码”到“自然语言编程”(技术解析+实战案例)
人工智能·低代码·重构
程序媛青青2 小时前
spring boot 和 spring cloud 的区别
spring boot·后端·spring cloud
秋刀鱼 ..3 小时前
第五届机电一体化、自动化与智能控制国际学术会议(MAIC 2025)
运维·人工智能·python·机器人·自动化·制造·新人首发