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

重启项目进行测试。

相关推荐
骄阳如火4 分钟前
论文SKILLS实测系列一、nature-skills:把“中式论文腔“润色成 Nature 风格
人工智能
逻辑君6 分钟前
基于 PPO 的双足机器人行走控制
人工智能·深度学习·机器人
xiaoeshuo8 分钟前
产品介绍PPT模板哪家强?8个平台实测对比
人工智能
长不胖的路人甲23 分钟前
Serial 串行、Parallel 并行、CMS 并发收集器
java·开发语言·jvm
惊讶的猫24 分钟前
《动手学大模型智能体》(Hands-on AI Agent)
人工智能
找方案28 分钟前
北京砸1亿支持智能体:Agent创业迎来黄金窗口期
大数据·人工智能·microsoft
Revolution6136 分钟前
多个 Agent 同时工作时,主 Agent 怎样接收队友结果
人工智能·llm·claude
不加辣椒43 分钟前
第8章:工具调用与多模态上下文集成
人工智能
葡萄城技术团队44 分钟前
三大适配场景:释放AI Coding真实落地价值(四)
人工智能
MomentYY1 小时前
RAG 混合检索:关键词 + 语义
人工智能·agent·ai编程