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), 以便实现对话隔离功能。

重启项目进行测试。

相关推荐
前沿观讯几秒前
2025年医药行业AI排班系统测评:实验室与产线的精准调度
人工智能
这是程序猿3 分钟前
基于java的ssm框架经典电影推荐网站
java·开发语言·spring boot·spring·经典电影推荐网站
SYC_MORE5 分钟前
无需 OCR,多模态大模型如何“读懂” PDF?——基于 GLM-4V-Flash 的智能文档解析原理剖析
人工智能·pdf·ocr
Nan_Shu_6148 分钟前
学习:Java (1)
java·开发语言·学习
正运动技术8 分钟前
正运动技术喜获机器人应用典型案例奖!
人工智能·正运动技术·运动控制器·运动控制卡·正运动·机器视觉运动控制一体机
花花无缺12 分钟前
搞清‘’时区设置‘’以及Mysql的`DATETIME` 和 `TIMESTAMP`
java·mysql
曹牧13 分钟前
Java:String.startsWith 方法
java·开发语言
jiayong2315 分钟前
海外求职平台与策略指南
java·spring
互联网江湖19 分钟前
蚂蚁阿福引爆AI健康赛道,美年健康锚定AI健康智能体核心生态位
大数据·人工智能
青稞社区.23 分钟前
小米大模型 Plus 团队提出BTL-UI:基于直觉-思考-关联的GUI Agent推理
人工智能·ui