一、大模型聊天系统中的记忆缓存

        在大模型对话系统中,记忆缓存(Memory Caching) 是专门针对 “多轮对话场景” 设计的核心优化技术 —— 它通过选择性存储、更新和复用 “历史对话信息”(如用户需求、上下文细节、交互结论),解决大模型原生的 “上下文窗口有限”“多轮交互易遗忘” 问题,最终让对话系统具备连贯、一致、个性化的交互能力,避免 “重复询问用户信息”“前后回答矛盾” 等体验问题。


        简单来说,对话系统的记忆缓存就像 “AI 的对话笔记本”:它会实时记录对话过程中的关键信息,当后续交互需要时,直接从 “笔记本” 中调取,无需让大模型重新 “回忆” 或重复计算,既提升响应效率,又保障对话逻辑的连续性。

核心价值解决对话系统的 3 个核心痛点大模型对话系统(如客服 AI、智能助手)若没有记忆缓存,会面临典型问题:

  1. 上下文遗忘:若对话轮次超过大模型的上下文窗口(如 GPT-4 标准版 8k Token),模型会 “忘记” 早期对话信息(比如用户前 3 轮说过 “自己是学生”,第 5 轮时模型又询问 “您的身份是?”);

  2. 重复计算浪费:多轮对话中若反复提及相同信息(如用户反复确认 “订单号 12345”),无缓存时模型需每次重新处理该信息,消耗额外算力;

  3. 个性化缺失:无法长期记住用户的固定偏好(如 “用户喜欢极简风格的回答”“对海鲜过敏”),导致每轮对话都像 “重新认识用户”。

        而记忆缓存的核心价值,就是针对性解决以上问题:用 “局部信息存储” 补全大模型的 “长期记忆能力”,让对话更连贯、高效、贴合用户需求。

        在实际开发中,无需从零搭建记忆缓存 —— 主流对话框架已封装好成熟的记忆缓存模块,开发者可直接调用,核心代表如下:

框架 / 工具

核心记忆缓存组件

特点与适用场景

LangChain/LangChain4j

ConversationBufferMemory(缓冲缓存)、ConversationSummaryMemory(摘要缓存)、ConversationEntityMemory(实体缓存)

灵活可配置,支持自定义缓存逻辑,适合开发者搭建个性化对话系统(如企业客服、垂直领域助手)

LangSmith

ChatMemory(集成缓冲 + 摘要功能)

自带缓存监控与调试功能,方便跟踪 “缓存是否生效”,适合需要 debug 的场景

大模型原生 API

OpenAI 的context_window扩展、Claude 3 的100k Token窗口

内置轻量记忆缓存逻辑,无需开发者额外配置,适合快速搭建简单对话系统(如个人助手)

二、Memory VS History

        大模型对话系统里 “记忆” 和 “历史” 两个概念。

2.1、核心概念区分

  • 历史(History):是「对话事实的完整记录」,严格保留用户和 AI 交互的每一条消息,相当于对话的 “原始档案”,和 UI 展示的实际内容一致,是客观发生过的对话全貌。

  • 记忆(Memory):是「为让大模型 “理解对话上下文” 而加工后的信息」,不会严格保留所有原始消息 。它会通过算法对历史做筛选、修改(比如删冗余内容、总结多条消息、补充额外信息),最终给大模型提供 “好像记得对话” 的素材,目的是让模型用更高效的方式理解对话逻辑,本质是「服务大模型推理的工具」。

2.2、记忆的 “加工手段”(理解记忆的关键)

        记忆不是简单存历史,而是会主动改造,常见做法:

  • 删减:删掉重复、无意义的消息(比如用户重复问相同问题的冗余表述 )。

  • 总结:把多条零散对话浓缩成摘要(比如 5 轮关于 “旅游规划” 的对话,总结成 “用户想 7 月去云南,偏好自然风景,预算 5000” )。

  • 补充信息:结合外部知识(RAG 场景)或规则,给对话注入额外内容(比如用户提 “订酒店”,记忆里补充 “本地酒店旺季价格规则” )。

2.3、LangChain4j 的现状

        LangChain4j 只提供 “记忆” 能力 ,不会自动存「完整对话历史」。如果你的场景需要保留每一条原始消息(比如合规要求、复盘需求),得自己额外手动实现存储逻辑。


简单说就是:

  • 「历史」是 “对话录像”,客观完整;

  • 「记忆」是 “给大模型看的对话解说”,经过剪辑加工;

  • LangChain4j 目前只做 “解说”,想要 “录像” 得自己存 。

三、Eviction polocy

         LangChain4j 里的 “对话消息清除策略”,核心是解决大模型对话时的 “上下文窗口限制、成本、延迟” 问题。

3.1、“清除策略必要” 的 3 大原因

        对话不能无限制存下去,必须删,因为:

  1. 适配大模型上下文窗口:大模型能处理的 Token(对话基本单位)有限(比如 GPT-4 早期是 8k Token),对话长了会超限制,必须删旧消息(通常删最老的,也能自定义复杂逻辑)。

  2. 控制调用成本:大模型按 Token 收费 / 消耗算力,删冗余消息能少花钱、少占资源。

  3. 控制响应延迟:传给大模型的 Token 越多,模型处理越慢,删消息能让回复更快。

3.2、LangChain4j 提供的 2 种 “清除策略实现”

        为了让你不用自己写删消息逻辑,LangChain4j 直接给了两个工具,按需求选:

1. 简单版:MessageWindowChatMemory

  • 逻辑:把对话当 “滑动窗口”,只保留最近 N 条完整消息,超了就删最老的。

  • 缺点:没考虑 “每条消息的 Token 数量”(比如一条长消息可能占几百 Token,短消息占几个),所以精准度弱,适合快速做 Demo、验证想法(原型设计阶段)。

2. 复杂版:TokenWindowChatMemory

  • 逻辑:更精准!把对话当 “Token 滑动窗口”,只保留最近 N 个 Token,超了就删最老的消息(但消息是整体,一条消息里的 Token 不够窗口会直接删整条)。

  • 依赖:需要额外的 TokenCountEstimator 工具,帮你算每条消息的 Token 数量,适合正式项目、对成本 / 窗口控制严格的场景(比如商用客服、复杂对话)。


简单说就是:

  • 大模型对话不能存太多消息,必须删(窗口、成本、延迟逼的);

  • LangChain4j 给了两种删法:简单版看 “消息条数”,复杂版看 “Token 数量”;

  • 做小项目用 MessageWindowChatMemory 省事,正式场景用 TokenWindowChatMemory 更精准。

四、撸代码

4.1、记忆缓存

step1

在之前工程的基础上换一个模型,这次我们改换qwen-long来做实验,在LLMConfig中该换模型

package com.xxx.demo.config;


import com.bbchat.demo.service.ChatAssistant;
import com.bbchat.demo.service.ChatMemoryAssistant;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
import dev.langchain4j.model.TokenCountEstimator;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiTokenCountEstimator;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class LLMConfig {
    @Bean
    public ChatModel chatModel()
    {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliqwen-apikey"))
                .modelName("qwen-long")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }


    @Bean(name = "chat")
    public ChatAssistant chatAssistant(ChatModel chatModel)
    {
        return AiServices.create(ChatAssistant.class, chatModel);
    }



    @Bean(name = "chatMessageWindowChatMemory")
    public ChatMemoryAssistant chatMessageWindowChatMemory(ChatModel chatModel)
    {
        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                // 注意每个memoryId对应创建一个ChatMemory
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(100))
                .build();
    }

    @Bean(name = "chatTokenWindowChatMemory")
    public ChatMemoryAssistant chatTokenWindowChatMemory(ChatModel chatModel)
    {
        // TokenCountEstimator默认的token分词器,需要结合Tokenizer计算ChatMessage的token数量
        TokenCountEstimator openAiTokenizer = new OpenAiTokenCountEstimator("gpt-4");

        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                .chatMemoryProvider(memoryId -> TokenWindowChatMemory.withMaxTokens(1000,openAiTokenizer))
                .build();
    }
}

step2

新建两个接口--javascripttypescriptshellbashsqljsonhtmlcssccppjavarubypythongorustmarkdown

package com.bbchat.demo.service;

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;

/*
一个带缓存的对话接口
 */
public interface ChatMemoryAssistant {
    /**
     * 聊天带记忆缓存功能
     *
     * @param userId  用户 ID
     * @param prompt 消息
     * @return {@link String }
     */
    String chatWithChatMemory(@MemoryId Long userId, @UserMessage String prompt);
}

step3

新建一个controller做测试使用

package com.xxx.demo.controller;

import cn.hutool.core.date.DateUtil;
import com.bbchat.demo.service.ChatAssistant;
import com.bbchat.demo.service.ChatMemoryAssistant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@Slf4j
public class ChatMemoryController {
    @Resource(name = "chat")
    private ChatAssistant chatAssistant;
    @Resource(name = "chatMessageWindowChatMemory")
    private ChatMemoryAssistant chatMessageWindowChatMemory;
    @Resource(name = "chatTokenWindowChatMemory")
    private ChatMemoryAssistant chatTokenWindowChatMemory;

    /*
        没有记忆缓存的功能接口
        http://localhost:9008/chatmemory/test1
     */
    @GetMapping(value = "/chatmemory/test1")
    public String chat()
    {

        String answer01 = chatAssistant.chat("你好,我的名字叫张三");
        System.out.println("answer01返回结果:"+answer01);

        String answer02 = chatAssistant.chat("我的名字是什么");
        System.out.println("answer02返回结果:"+answer02);

        return "success : "+ DateUtil.now()+"<br> \n\n answer01: "+answer01+"<br> \n\n answer02: "+answer02;
    }

    /*
         MessageWindowChatMemory实现聊天功能
     */
    @GetMapping(value = "/chatmemory/test2")
    public String chatMessageWindowChatMemory()
    {
        chatMessageWindowChatMemory.chatWithChatMemory(1L, "你好!我的名字是李四.");
        String answer01 = chatMessageWindowChatMemory.chatWithChatMemory(1L, "我的名字是什么");
        System.out.println("answer01返回结果:"+answer01);

        chatMessageWindowChatMemory.chatWithChatMemory(3L, "你好!我的名字是王五");
        String answer02 = chatMessageWindowChatMemory.chatWithChatMemory(3L, "我的名字是什么");
        System.out.println("answer02返回结果:"+answer02);

        return "chatMessageWindowChatMemory success : "
                + DateUtil.now()+"<br> \n\n answer01: "+answer01+"<br> \n\n answer02: "+answer02;

    }

    /*
     TokenWindowChatMemory实现聊天功能
    */
    @GetMapping(value = "/chatmemory/test3")
    public String chatTokenWindowChatMemory()
    {
        chatTokenWindowChatMemory.chatWithChatMemory(1L, "你好!我的名字是mysql");
        String answer01 = chatTokenWindowChatMemory.chatWithChatMemory(1L, "我的名字是什么");
        System.out.println("answer01返回结果:"+answer01);

        chatTokenWindowChatMemory.chatWithChatMemory(3L, "你好!我的名字是oracle");
        String answer02 = chatTokenWindowChatMemory.chatWithChatMemory(3L, "我的名字是什么");
        System.out.println("answer02返回结果:"+answer02);

        return "chatTokenWindowChatMemory success : "
                + DateUtil.now()+"<br> \n\n answer01: "+answer01+"<br> \n\n answer02: "+answer02;
    }
}

step4

分别查看结果:

访问接口test1

访问接口test2

访问接口test3

(模型不止记住了我的名字,还自我发挥了一段)

4.2、持久化

        将对话结果保存进Redis进行持久化记忆留存,我们继续使用上述工程

step1

在module的pom文件中添加以下依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

step2

在application.properties中添加redis配置

# ==========config redis===============
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.database=0
spring.data.redis.connect-timeout=3s
spring.data.redis.timeout=2s

step3

新建高阶接口 ChatPersistenceAssistant

package com.xxx.demo.service;

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;

/**
 * packageName com.bbchat.demo.service
 *
 * @author Zting
 * @version JDK 17
 * @InterfaceName ChatPersistenceAssistant
 * @date 2025/9/15
 * @description TODO
 */
public interface ChatPersistenceAssistant {
    /**
     * 聊天
     *
     * @param userId  用户 ID
     * @param message 消息
     * @return {@link String }
     */
    String chat(@MemoryId Long userId, @UserMessage String message);
}

step4

编写redis配置

package com.xxx.demo.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
@Slf4j
public class RedisConfig {
    /**
     * RedisTemplate配置
     * redis序列化的工具配置类,下面这个请一定开启配置
     * 127.0.0.1:6379> keys *
     * 1) "ord:102"  序列化过
     * 2) "\xac\xed\x00\x05t\x00\aord:102"   野生,没有序列化过
     * this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法
     * this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法
     * this.redisTemplate.opsForSet(); //提供了操作set的所有方法
     * this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法
     * this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法
     * @param redisConnectionFactor
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor)
    {
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(redisConnectionFactor);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

step5

自定义RedisChatMemoryStore类实现ChatMemoryStore

package com.xxx.demo.config;

import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;


@Component
public class RedisChatMemoryStore implements ChatMemoryStore {
    public static final String CHAT_MEMORY_PREFIX = "CHAT_MEMORY:";

    @Resource
    private RedisTemplate<String,String> redisTemplate;


    @Override
    public List<ChatMessage> getMessages(Object memoryId)
    {
        String retValue = redisTemplate.opsForValue().get(CHAT_MEMORY_PREFIX + memoryId);

        return  ChatMessageDeserializer.messagesFromJson(retValue);
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages)
    {
        redisTemplate.opsForValue().set(CHAT_MEMORY_PREFIX + memoryId, ChatMessageSerializer.messagesToJson(messages));
    }

    @Override
    public void deleteMessages(Object memoryId)
    {
        redisTemplate.delete(CHAT_MEMORY_PREFIX + memoryId);
    }
}

step6

拓展LLMConfig类

package com.xxxx.demo.config;


import com.bbchat.demo.service.ChatAssistant;
import com.bbchat.demo.service.ChatMemoryAssistant;
import com.bbchat.demo.service.ChatPersistenceAssistant;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
import dev.langchain4j.model.TokenCountEstimator;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiTokenCountEstimator;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class LLMConfig {

    @Resource
    private RedisChatMemoryStore redisChatMemoryStore;

    @Bean
    public ChatModel chatModel()
    {
        return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliqwen-apikey"))
                .modelName("qwen-long")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean
    public ChatPersistenceAssistant chatMemoryAssistant(ChatModel chatModel)
    {

        ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(1000)
                .chatMemoryStore(redisChatMemoryStore)
                .build();

        return AiServices.builder(ChatPersistenceAssistant.class)
                .chatModel(chatModel)
                .chatMemoryProvider(chatMemoryProvider)
                .build();
    }


    @Bean(name = "chat")
    public ChatAssistant chatAssistant(ChatModel chatModel)
    {
        return AiServices.create(ChatAssistant.class, chatModel);
    }



    @Bean(name = "chatMessageWindowChatMemory")
    public ChatMemoryAssistant chatMessageWindowChatMemory(ChatModel chatModel)
    {
        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                // 注意每个memoryId对应创建一个ChatMemory
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(100))
                .build();
    }

    @Bean(name = "chatTokenWindowChatMemory")
    public ChatMemoryAssistant chatTokenWindowChatMemory(ChatModel chatModel)
    {
        // TokenCountEstimator默认的token分词器,需要结合Tokenizer计算ChatMessage的token数量
        TokenCountEstimator openAiTokenizer = new OpenAiTokenCountEstimator("gpt-4");

        return AiServices.builder(ChatMemoryAssistant.class)
                .chatModel(chatModel)
                .chatMemoryProvider(memoryId -> TokenWindowChatMemory.withMaxTokens(1000,openAiTokenizer))
                .build();
    }
}

step7

编写一个用来测试的controller

package com.xxx.demo.controller;

import cn.hutool.core.date.DateUtil;
import com.bbchat.demo.service.ChatPersistenceAssistant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@Slf4j
public class ChatPersistenceController {
    @Resource
    private ChatPersistenceAssistant chatPersistenceAssistant;

    // http://localhost:9010/chatpersistence/redis
    @GetMapping(value = "/chatpersistence/redis")
    public String testChatPersistence()
    {
        chatPersistenceAssistant.chat(1L, "你好!我的名字是redis");
        chatPersistenceAssistant.chat(2L, "你好!我的名字是nacos");

        String chat = chatPersistenceAssistant.chat(1L, "我的名字是什么");
        System.out.println(chat);

        chat = chatPersistenceAssistant.chat(2L, "我的名字是什么");
        System.out.println(chat);

        return "testChatPersistence success : "+ DateUtil.now();
    }
}

step8

查看redis中存储的结果

Logo

更多推荐