哈喽各位后端小伙伴👋,承接上一篇百炼大模型流式返回博客,今天继续更新Langchain4j系列干货!
很多小伙伴接入大模型后都会遇到同一个致命问题:AI没有记忆,多轮对话完全上下文断层。
举个真实业务场景:
-
你问AI:我的名字是张三
-
再追问:我叫什么名字?
-
AI一脸茫然:我不知道你的名字
本质原因:大模型本身是无状态的,每一次请求都是独立的,默认不会保存对话历史。想要实现连贯对话,必须手动管理上下文记忆。
Langchain4j内置开箱即用的ChatMemory对话记忆组件,无需自己拼接上下文、无需手动维护聊天记录,一行配置即可实现多轮对话。
本篇博客覆盖企业开发全部刚需内容:
-
✅ Langchain4j四大ChatMemory底层原理与适用场景
-
✅ SpringBoot全局统一配置类(直接复制可用)
-
✅ 单用户固定会话记忆完整代码演示
-
✅ 核心重点:多用户会话隔离(千万人对话互不干扰)
-
✅ 内存记忆隐患 + 持久化记忆拓展方案
-
✅ 线上业务踩坑总结与最佳实践
一、前置认知:为什么原生大模型没有记忆?
1. 无记忆原生调用(反面案例)
我们先看没有配置ChatMemory时,原生调用的效果,直观感受对话失忆问题:
java
// 初始化大模型
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.apiKey("xxx")
.baseUrl("xxx")
.build();
// 第一轮对话
System.out.println(chatModel.chat("我的名字是李四"));
// 第二轮对话,无任何上下文记忆
System.out.println(chatModel.chat("我叫什么名字"));
**输出结果:**AI无法回答你的名字,完全忘记上文内容。
2. ChatMemory核心作用
自动收集用户提问、AI回答,自动拼接上下文传入大模型,开发者无需手动维护消息列表,框架底层自动完成上下文管理。
二、Langchain4j四大内置ChatMemory详解
Langchain4j官方提供4种记忆实现类,覆盖全部业务场景,不用盲目选型,看完直接对号入座:
| 记忆类型 | 原理说明 | 适用业务场景 |
|---|---|---|
| MessageWindowChatMemory(最常用) | 消息窗口记忆,限制最大对话条数,超出自动删除最早记录,控制上下文长度 | 绝大多数在线AI聊天、智能客服(生产首选) |
| TokenWindowChatMemory | Token窗口记忆,限制上下文Token数量,贴合大模型token上限限制 | 长对话场景,严格控制请求token开销 |
| PersistentChatMemory | 持久化记忆,对接数据库,服务重启不丢失对话记录 | 需要保存历史聊天记录,会话跨服务重启有效 |
| EmptyChatMemory | 空记忆,不保存任何上下文,等同于原生无记忆调用 | 一次性问答、不需要上下文的AI接口 |
生产推荐选型:MessageWindowChatMemory,兼顾性能与上下文完整性,也是本文全程实战使用的记忆方案
三、项目依赖准备
沿用SpringBoot+Langchain4j环境,引入官方启动器:
XML
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.14.0</version>
</dependency>
<!-- 对接阿里百炼/OpenAI通用模型依赖 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.14.0</version>
</dependency>
四、SpringBoot全局ChatMemory配置类(企业级直接复制)
不要在业务代码中硬编码记忆参数,统一抽离配置类,统一管理记忆窗口大小、内存存储、会话生成规则,解耦业务与底层AI配置。
完整ChatMemoryConfig配置类
java
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.store.memory.chat.InMemoryChatMemoryStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Langchain4j 对话记忆全局配置类
* 支持:单会话记忆 + 多用户会话隔离
*/
@Configuration
public class LangChain4jChatMemoryConfig {
/**
* 单会话全局记忆Bean
* 固定一个记忆实例,所有请求共用一套上下文(仅适合单用户测试,禁止线上多用户使用)
*/
@Bean
public MessageWindowChatMemory singleChatMemory() {
// 设置保留最近10轮对话,兼顾上下文与token开销
return MessageWindowChatMemory.builder()
.maxMessages(10)
// 内存存储:服务重启记忆丢失,适合临时会话
.chatMemoryStore(new InMemoryChatMemoryStore())
.build();
}
/**
* 【核心】多用户会话隔离记忆提供者
* 根据sessionId/memoryId生成独立记忆空间,不同用户对话完全隔离
* 线上生产环境必须使用该Bean
*/
@Bean
public ChatMemoryProvider chatMemoryProvider() {
// 根据传入的会话ID,返回独立的对话记忆
return memoryId -> MessageWindowChatMemory.builder()
// 绑定会话唯一ID,实现对话隔离
.id(memoryId)
// 最大保存10轮问答
.maxMessages(10)
.chatMemoryStore(new InMemoryChatMemoryStore())
.build();
}
}
配置类核心说明
-
singleChatMemory:单例记忆,全局共用,多用户会互相串话,仅本地测试用
-
chatMemoryProvider:动态记忆提供者,根据唯一会话ID生成独立记忆,生产环境必备
-
maxMessages(10):保存最近10次问答,防止上下文过长导致token溢出、接口变慢
五、实战一:单用户固定会话记忆(基础版)
适合单用户后台、个人AI助手,全程只有一个对话上下文,直接注入全局记忆Bean即可。
1. 定义AI服务接口(无实现类,动态代理)
java
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.UserMessage;
/**
* 基础AI对话助手
*/
public interface ChatAssistant {
/**
* 普通多轮对话
*/
String chat(@UserMessage String message);
}
2. Service层业务代码
java
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.service.AiServices;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class SingleChatService {
// 注入全局大模型
private final ChatLanguageModel chatLanguageModel;
// 注入单会话记忆
private final MessageWindowChatMemory singleChatMemory;
// 创建带记忆的AI助手
private final ChatAssistant chatAssistant = AiServices.builder(ChatAssistant.class)
.chatLanguageModel(chatLanguageModel)
.chatMemory(singleChatMemory)
.build();
/**
* 单用户带记忆对话
*/
public String chat(String msg) {
return chatAssistant.chat(msg);
}
}
3. 接口测试效果
java
// 调用接口
chat("我叫王五");
// AI回答:你好王五,很高兴认识你
chat("我叫什么名字");
// AI回答:你叫王五(成功记住上下文)
致命缺陷 :全局共用同一个memory实例,多用户同时访问会对话串线,A用户的对话会被B用户看到,线上严禁使用!
六、实战二:多用户会话隔离(生产核心方案)
线上项目最核心的需求:每个用户/每个会话拥有独立记忆,用户之间对话完全隔离,互不干扰。
实现原理:通过唯一sessionId区分会话,ChatMemoryProvider根据sessionId生成独立记忆空间,彻底解决串话问题。
1. 支持会话ID的AI接口
java
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
public interface IsolationChatAssistant {
/**
* @param memoryId 会话唯一标识:用户ID / 前端生成的sessionId
* @param message 用户提问内容
* @return AI回答
*/
String chat(@MemoryId String memoryId, @UserMessage String message);
}
关键注解:@MemoryId,Langchain4j通过该注解识别会话标识,自动绑定独立记忆。
2. 会话隔离Service层完整代码
java
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.service.AiServices;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class IsolationChatService {
private final ChatLanguageModel chatLanguageModel;
// 注入会话隔离记忆提供者
private final ChatMemoryProvider chatMemoryProvider;
// 构建支持会话隔离的AI助手(全局单例,自动根据memoryId分发记忆)
private final IsolationChatAssistant assistant = AiServices.builder(IsolationChatAssistant.class)
.chatLanguageModel(chatLanguageModel)
.chatMemoryProvider(chatMemoryProvider)
.build();
/**
* 带会话隔离的AI对话
* @param sessionId 会话唯一ID
* @param message 用户消息
* @return AI回答
*/
public String chat(String sessionId, String message) {
return assistant.chat(sessionId, message);
}
/**
* 清空指定会话记忆
*/
public void clearChatMemory(String sessionId) {
chatMemoryProvider.get(sessionId).clear();
}
}
3. Controller层接口(对接前端)
java
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.service.AiServices;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class IsolationChatService {
private final ChatLanguageModel chatLanguageModel;
// 注入会话隔离记忆提供者
private final ChatMemoryProvider chatMemoryProvider;
// 构建支持会话隔离的AI助手(全局单例,自动根据memoryId分发记忆)
private final IsolationChatAssistant assistant = AiServices.builder(IsolationChatAssistant.class)
.chatLanguageModel(chatLanguageModel)
.chatMemoryProvider(chatMemoryProvider)
.build();
/**
* 带会话隔离的AI对话
* @param sessionId 会话唯一ID
* @param message 用户消息
* @return AI回答
*/
public String chat(String sessionId, String message) {
return assistant.chat(sessionId, message);
}
/**
* 清空指定会话记忆
*/
public void clearChatMemory(String sessionId) {
chatMemoryProvider.get(sessionId).clear();
}
}
4. 会话隔离效果测试
java
// 会话A:session_001
chat("session_001", "我是用户张三");
chat("session_001", "我是谁"); // 输出:你是张三
// 会话B:session_002
chat("session_002", "我是用户李四");
chat("session_002", "我是谁"); // 输出:你是李四
// 两个会话完全隔离,互相看不到对方对话上下文,无任何串话
七、流式接口适配ChatMemory(衔接上一篇流式代码)
很多同学同时需要流式打字机返回 + 对话记忆 + 会话隔离,这里直接补上适配代码,和之前百炼流式代码无缝衔接。
java
/**
* 带会话记忆隔离的流式对话接口
*/
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(@RequestParam String sessionId, @RequestParam String message) {
// 流式返回同时携带上下文记忆,不同sessionId对话隔离
return Flux.from(assistant.streamChat(sessionId, message));
}
完美兼容上篇SSE流式接口,实现:打字机流式输出 + 多轮上下文记忆 + 多用户会话隔离三位一体。
八、内存记忆痛点 + 持久化解决方案
1. InMemoryChatMemoryStore内存存储缺陷
-
对话记录存储在JVM内存中,服务重启后所有记忆全部丢失
-
集群部署时,不同节点记忆不互通,用户切换节点记忆消失
-
长时间对话堆积,造成JVM内存溢出
2. 生产持久化优化方案(拓展)
Langchain4j支持自定义ChatMemoryStore,我们可以对接Redis实现分布式持久化记忆:
-
自定义RedisChatMemoryStore,将对话历史存入Redis
-
设置Redis过期时间,自动清理过期闲置会话
-
集群环境所有节点共享Redis记忆,会话永不丢失
九、线上开发避坑总结(8个高频问题)
-
禁止全局单例ChatMemory用于多用户,一定会出现对话串线
-
必须搭配@MemoryId注解,否则无法实现会话隔离
-
合理设置maxMessages,不要设置过大,避免上下文token超标、请求超时
-
闲置会话务必清理记忆,调用clear()释放内存,防止内存泄漏
-
流式接口和记忆可以无缝叠加,记忆逻辑不需要改动,直接复用
-
token受限场景使用TokenWindowChatMemory,按token而非消息条数截断上下文
-
集群环境不要使用默认内存存储,必须Redis持久化记忆
-
前端务必生成唯一sessionId,推荐使用uuid,保证会话唯一
十、博主总结
1、大模型本身无状态,ChatMemory是多轮对话的核心,不用自己手动拼接上下文消息;
2、开发区分两个场景:本地测试用单会话记忆,线上生产必须用ChatMemoryProvider + @MemoryId实现会话隔离;
3、全局统一配置类,不要业务代码硬编码记忆参数,方便后续统一调整上下文窗口大小;
4、默认内存记忆仅适合单机临时会话,生产集群务必做Redis持久化改造。
本篇代码全部经过项目实测,直接复制即可运行,无缝对接之前的流式大模型调用代码。