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

相关推荐
物联网软硬件开发-轨物科技21 小时前
【轨物方案】新能源的下半场:构筑光伏场站全生命周期智慧运维新范式
大数据·人工智能·物联网
2501_9361460421 小时前
【目标检测】钙钛矿晶体YOLO11-GhostDynamicConv模型改进与实现_1
人工智能·目标检测·计算机视觉
Deepoch21 小时前
Deepoc具身模型:电厂巡检机械狗的智能核心
人工智能·科技·机器狗·具身模型·deepoc·机械狗·巡检机械狗
sunnyday04261 天前
Spring Cloud Alibaba Sentinel 流量控制与熔断降级实战指南
spring boot·sentinel·springcloud
好奇龙猫1 天前
【人工智能学习-AI入试相关题目练习-第七次】
人工智能·学习
Mao.O1 天前
开源项目“AI思维圆桌”的介绍和对于当前AI编程的思考
人工智能
jake don1 天前
AI 深度学习路线
人工智能·深度学习
信创天地1 天前
信创场景软件兼容性测试实战:适配国产软硬件生态,破解运行故障难题
人工智能·开源·dubbo·运维开发·risc-v
幻云20101 天前
Python深度学习:从筑基到登仙
前端·javascript·vue.js·人工智能·python
无风听海1 天前
CBOW 模型中的输出层
人工智能·机器学习