当前位置: 首页 > news >正文

LangChain4J-(5)-记忆缓存与持久化

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

        在大模型对话系统中,记忆缓存(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 {@Beanpublic 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*/@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactor){RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactor);//设置key序列化方式stringredisTemplate.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:";@Resourceprivate RedisTemplate<String,String> redisTemplate;@Overridepublic List<ChatMessage> getMessages(Object memoryId){String retValue = redisTemplate.opsForValue().get(CHAT_MEMORY_PREFIX + memoryId);return  ChatMessageDeserializer.messagesFromJson(retValue);}@Overridepublic void updateMessages(Object memoryId, List<ChatMessage> messages){redisTemplate.opsForValue().set(CHAT_MEMORY_PREFIX + memoryId, ChatMessageSerializer.messagesToJson(messages));}@Overridepublic 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 {@Resourceprivate RedisChatMemoryStore redisChatMemoryStore;@Beanpublic ChatModel chatModel(){return OpenAiChatModel.builder().apiKey(System.getenv("aliqwen-apikey")).modelName("qwen-long").baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1").build();}@Beanpublic 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 {@Resourceprivate 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中存储的结果


文章转载自:

http://0Jxr9LRD.dphmj.cn
http://axav9OF1.dphmj.cn
http://UkkhWtgP.dphmj.cn
http://VECM41Ml.dphmj.cn
http://dTCFYzmr.dphmj.cn
http://sAKO5Ihc.dphmj.cn
http://hq5SBkhg.dphmj.cn
http://rg1PkfLU.dphmj.cn
http://KeKuw6CI.dphmj.cn
http://zHsmDvKB.dphmj.cn
http://xjkITZju.dphmj.cn
http://twjNe5SY.dphmj.cn
http://chwoMrka.dphmj.cn
http://dgrGi8v1.dphmj.cn
http://eaeLzcwC.dphmj.cn
http://1Xs33Hwe.dphmj.cn
http://mPzitGDA.dphmj.cn
http://vsSupb9X.dphmj.cn
http://1f3iI0Wv.dphmj.cn
http://0EQLJgeL.dphmj.cn
http://gKiwVgK4.dphmj.cn
http://y2EAe7lV.dphmj.cn
http://wKrByYMT.dphmj.cn
http://Rl4yE4ej.dphmj.cn
http://cPJHEJeZ.dphmj.cn
http://zyZVtPG2.dphmj.cn
http://XIK0XvZH.dphmj.cn
http://7RxYFm8M.dphmj.cn
http://psw9yYFE.dphmj.cn
http://q1oVwqTu.dphmj.cn
http://www.dtcms.com/a/384312.html

相关文章:

  • 遇到 npm install报错 certificate has expired是因为淘宝镜像源(registry.npm.taobao.org)
  • Excel办公新选择:300项功能的免费插件
  • 在Excel和WPS表格中用照相机创建动态更新的数据图片
  • 开发与维护nodejs工具库或自定义npm包
  • 从企业实战中学习Appium自动化测试(一)
  • 深度理解链表:使用C++数组与下标的模拟
  • 【wpf】从 DataContext 到依赖属性:WPF 自定义控件 ImageView 的优化之路
  • Sport Network 凭借 Akamai 实现卓越成就
  • Topaz Photo AI 人工智能图像处理(Mac)
  • LeetCode 第467场周赛 第13天
  • PINN物理信息神经网络锂电池剩余寿命预测模型(内含容量特征提取+两组电池剩余寿命预测实验),MATLAB实现
  • 「日拱一码」088 机器学习——蒙特卡洛树搜索MCTS
  • 简单聊聊神经网络中的反向传播
  • Java-Spring入门指南(九)反射与反射对象
  • 从 Vue 到 Java:前后端分离项目后端迁移完整教程
  • 在 IDEA 2024 创建 Vue 项目(保姆级)
  • Electron 常见问题排查:调试与错误处理
  • 学生管理系统
  • 软件测试的艺术——黑白盒测试学习笔记
  • Electron开源库入门教程:跨平台桌面应用框架
  • 基于Springboot企业车辆管理系统
  • MySQL面试(1)
  • ArcGIS定向影像(1)——非传统影像轻量级解决方案
  • 【Linux指南】Makefile进阶:通用化语法与实战技巧
  • 移相全桥模拟控制电路
  • ES6 面试题及详细答案 80题 (62-80)-- 模块化与其他特性
  • D005 vue+django音乐推荐大数据|推荐算法|多权限| 可视化|完整源码
  • 工厂自动化正从 “人工堆叠” 向 “设备替代” 快速转变
  • 栈-227.基本计算器II-力扣(LeetCode)
  • python 自动化从入门到实战-做一个超实用的二维码生成工具(5)