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

相关推荐
昨日之日20061 分钟前
FLUX 3 Image Klein:一键搞定文生图和智能编辑,支持精准布局,黑森林新开源
人工智能·计算机视觉·ai作画
Chengbei115 分钟前
JsOwl|AI 赋能 JS 敏感信息扫描器,AST + 正则、source‑map 源码还原、登录态爬虫输出交互式 HTML 报告
开发语言·javascript·网络·人工智能·爬虫·网络安全·自动化
leoZ2319 分钟前
第 31 篇 金融与医疗:高合规行业的 AI 产品方法论
大数据·人工智能·金融·大模型·agent
杨运交11 分钟前
[079][消息模块]基于 Redis Stream 的高可靠消息队列实现——Spring Data Redis 实战解析
数据库·redis·spring
细节处有神明13 分钟前
零样本时序预测:Mistral Timestral 本地实测
人工智能·机器学习·时间序列
阿明副业观察16 分钟前
AI短剧分镜秘籍:从零到电影级视觉冲击
大数据·人工智能·aigc·音视频·ai写作
艾克斯观察18 分钟前
做产品正在失去稀缺性,功能即护城河的时代结束了?
人工智能·产品运营·逆向工程·rea·渠道分发
邓工说电20 分钟前
智慧用电常见问题全解答:概念辨析、技术构成、落地路径与选型判断
人工智能
迪飞特科技21 分钟前
业务是否适合 AI 改造,3 个可落地的判断评估标准
人工智能
阿文和她的Key24 分钟前
国产AI芯片涨价20%-50%:企业算力采购的四层成本架构重估
人工智能·架构