Spring AI:ChatMemory 实现聊天记忆功能

目前我们已经实现了和 DeepSeek 大模型进行聊天,但是存在一个问题!没有聊天记忆功能!

相关文章:
Spring AI:对接DeepSeek实战
Spring AI:对接官方 DeepSeek-R1 模型 ------ 实现推理效果
Spring AI:ChatClient实现对话效果
Spring AI:使用 Advisor 组件 - 打印请求大模型出入参日志

大模型具备记忆功能吗?

大模型是 "无状态" 的,每次请求(如 API 调用)都是独立的。模型在生成回复时,只会关注当前输入中包含的上下文信息,不会主动保留之前的对话记录。

如果要让模型 "记住" 之前的对话,需要开发者或用户显式地将历史对话内容附加到当前输入中。例如,在聊天机器人应用中,每次请求需附带完整的对话历史,模型才能基于完整上下文生成连贯回复。

动手实现记忆功能

所以,为了实现记忆功能,需要在调用 DeepSeek 时,主动将之前的历史会话再次发送给它。接下来,我们来看看,在 Spring AI 中,要如何实现记忆功能!

配置 ChatMemory

在项目中的 /config 包下,新增一个 ChatMemoryConfig 配置类:

java 复制代码
@Configuration
public class ChatMemoryConfig {

    /**
     * 记忆存储
     */
    @Resource
    private ChatMemoryRepository chatMemoryRepository;

    /**
     * 初始化一个 ChatMemory 实例,并注入到 Spring 容器中
     * @return
     */
    @Bean
    public ChatMemory chatMemory() {
        return MessageWindowChatMemory.builder()
                .maxMessages(50) // 最大消息窗口为 50,默认值为 20
                .chatMemoryRepository(chatMemoryRepository) // 记忆存储
                .build();
    }
}

解释一下上述代码:

  • 通过 @Resource 注解注入一个 ChatMemoryRepository 的实例。它用于存储聊天会话,当我们没有自定义外部存储时(如 Cassandra、MySQL),默认情况下,Spring AI 使用的是 InMemoryChatMemoryRepository 实现类,会将消息存储在内存中的 ConcurrentHashMap 中。
  • MessageWindowChatMemory:它是具体的 ChatMemory 实现类,使用 "滑动窗口" 策略管理聊天记录。
    滑动窗口策略:保留最近的 N 条消息(这里是50条,默认值20),超出数量限制时自动移除旧消息。因为在 AI 对话中,限制上下文长度,可以避免超出模型的 Token 限制。

与 ChatClient 整合

完成 ChatMemory 的初始化工作后,我们需要和 ChatClient 整合一下,依然是使用上小节的 Advisor 组件,来实现调用大模型时,将之前的聊天内容,设置到入参中,代码如下:

java 复制代码
@Configuration
public class ChatClientConfig {

    @Resource
    private ChatMemory chatMemory;

    /**
     * 初始化 ChatClient 客户端
     * @param chatModel
     * @return
     */
    @Bean
    public ChatClient chatClient(DeepSeekChatModel chatModel) {
        return ChatClient.builder(chatModel)
                .defaultSystem("请你扮演一个智能客服")
                .defaultAdvisors(new SimpleLoggerAdvisor(), // 添加 Spring AI 内置的日志记录功能
//                                 new MyLoggerAdvisor(), // 添加自定义的日志打印 Advisor
                                MessageChatMemoryAdvisor.builder(chatMemory).build()
                        )
                .build();
    }
}

重启项目,在第一个问题后,再问相关问题,会发现已经有了记忆功能。会携带上之前的聊天

会话隔离问题

看似完美了,但是依然存在问题,我们在浏览器中,重新打开一个页面,再次请求相关接口, 按道理这是开启了一个 "新对话",它应该忘记了之前的记忆。但是事实上还是被记住了。

如何解决会话隔离的问题呢? 我们可以为每个对话定义一个 ID 唯一标识, 后端在存储聊天内容到 ConcurrentHashMap 中时,可以使用这个 ID 来作为键,从而实现每个对话的隔离:

回到 ChatClientController 控制器中,为 /v2/ai/generateStream 接口,添加参数 chatId, 表示当前对话 ID, 代码如下:

java 复制代码
@RestController
@RequestMapping("/v2/ai")
public class ChatClientController {

    @Resource
    private ChatClient chatClient;

    // 省略...

    /**
     * 流式对话
     * @param message
     * @return
     */
    @GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
    public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message,
                                       @RequestParam(value = "chatId") String chatId) {

        // 流式输出
        return chatClient.prompt()
                .user(message) // 提示词
                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId))
                .stream()
                .content();

    }

}

并且在调用 DeepSeek 时,通过 .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId)) 为当前聊天请求绑定一个会话 ID(chatId), 以便实现对话隔离功能。

重启项目进行测试。

相关推荐
ziwu33 分钟前
【植物识别系统】Python+TensorFlow+Django+人工智能+深度学习+卷积神经网络算法
人工智能·深度学习·图像识别
Al leng36 分钟前
机器学习中偏差和方差的通俗理解
人工智能·机器学习
小许学java38 分钟前
数据结构-模拟实现顺序表和链表
java·数据结构·链表·arraylist·linkedlist·顺序表模拟实现·链表的模拟实现
Mxsoft6191 小时前
某次数据解析失败,发现IEC61850版本差异,手动校验报文结构救急!
人工智能
+VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue零食商城管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
智元视界1 小时前
农业AI化:如何让一台无人机懂得“看天种地”?
大数据·人工智能·prompt·无人机·数字化转型·产业升级
Query*1 小时前
杭州2024.08 Java开发岗面试题分类整理【附面试技巧】
java·开发语言·面试
WZTTMoon1 小时前
Spring Boot 4.0 迁移核心注意点总结
java·spring boot·后端
丝斯20111 小时前
AI学习笔记整理(26)—— 计算机视觉之目标追踪‌
人工智能·笔记·学习