1.会话记忆原理
大模型不具备记忆能力,每次会话都是独立的。要想让大模型产生记忆的效果,唯一的方法就是把之前聊天的内容和新的内容一起发送给大模型。
有了langchian4j了之后就不需要这么麻烦了,它能够帮我们记录聊天消息并自动发送!
请求:后端接收到消息后,会自动把消息存放到存储对象中,然后再获取存储对象中记录的所有会话消息,一块发送给大模型
响应:大模型根据接收到的消息,生成答案,比如说是的,再把答案响应给web后端,此时web后端会把得到的响应消息往存储对象中拷贝一份,然后再把响应消息发送给用户。
2.会话记忆基本实现
langchain4j提供了一个接口叫做ChatMemory,该接口中提供了
- add方法用于添加一条记录
- messages方法用于获取所有的会话记录
- clear方法用于清除所有的会话记录
这里还有一个id方法,它是用于唯一的标识一个存储对象,当然这个id暂时我们用不着,等会儿我们讲解会话记忆隔离的时候再给大家详细的讲解。
同时LangChain4j还提供了该接口的两个实现类,一个是TokenWindowChatMemory,另外一个是MessageWindowChatMemory, 咱们暂时先使用MessageWindowChatMemory来存储会话记录。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package dev.langchain4j.memory;
import dev.langchain4j.data.message.ChatMessage;
import java.util.List;
public interface ChatMemory {
//记忆存储对象的唯一标识
Object id();
//添加一条会话记忆
void add(ChatMessage var1);
//获取所有会话记忆
List<ChatMessage> messages();
//清除所有会话记忆
void clear();
}
2.1定义会话记忆对象
在CommonConfig类中,构建MessageWindowChatMemory对象,并注入到IOC容器中。构建的时候我们可以指定该对象中最大的会话存储数量,一般设置20就足够了。
package com.shenma.config;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author 军哥
* @version 1.0
* @description: 会话记录配置类
* @date 2026/4/7 14:56
*/
@Configuration
public class CommonConfig {
/**
* @description: 创建一个会话记录对象
* @params []
* @return dev.langchain4j.memory.ChatMemory
* @author 军哥
* @date 2026/4/7 14:57
*/
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(20)//最大保存的会话记录数量
.build();
}
}
2.2 配置会话记忆
package com.shenma.service;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
import reactor.core.publisher.Flux;
/**
* @author 军哥
* @version 1.0
* @description: 会话记录服务
* @date 2026/4/7 14:55
*/
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "openAiChatModel",
streamingChatModel = "openAiStreamingChatModel",
chatMemory = "chatMemory"//配置会话记忆对象
)
public interface ConsultantService {
@SystemMessage(fromResource = "system.txt")
public Flux<String> chat(String message);
}
2.3 测试会话记忆
package com.shenma.controller;
import com.shenma.service.ConsultantService;
import dev.langchain4j.memory.ChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
/**
* @author 军哥
* @version 1.0
* @description: 测试会话记忆
* @date 2026/4/7 14:59
*/
@RestController
public class ChatController {
@Autowired
private ConsultantService consultantService;
//produces:响应的数据编码类型
@PostMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chat(@RequestParam("message") String message) {
Flux<String> result = consultantService.chat(message);
return result;
}
}
2.4 运行效果
-
问题1

-
问题2

3.会话记忆隔离
刚才我们借助于MessageWindowChatMemory实现了会话记忆的效果,看起来还不错, 但是还是有一些小问题的。当不同的用户访问我们的程序时,无法区分不同用户的会话记录,因为刚才实现的会话记忆,所有用户存储会话记录都是用的是同一个会话记忆对象,所以会话记忆并没有做到隔离。
在LangChain4j中可以准备一个容器,专门用于存储当前程序中所有的会话记忆对象。假设有一个用户访问我们的程序,此时它除了要把用户问题message携带给后端,还需要携带一个memoryId,我们只需要把对应memoryId的会话记忆对象的内容传给大模型。
3.1定义会话记忆对象提供者
LangChain4j中提供了一个类ChatMemoryProvider,将来LangChain4j如果从容器中没有找到指定id的ChatMemory对象,就会调用ChatMemoryProvider对象的get方法获取一个新的ChatMemory对象使用,因此我们需要提供这个ChatMemoryProvider对象,实现get方法。这里的get方法,会接收一个参数,这个参数就是memoryId,返回一个结果就是ChatMemory对象。
/**
* @description: 创建一个会话记录提供者对象
* @params []
* @return dev.langchain4j.memory.chat.ChatMemoryProvider
* @author 军哥
* @date 2026/4/7 14:57
*/
@Bean
public ChatMemoryProvider chatMemoryProvider() {
ChatMemoryProvider chatMemoryProvider = new ChatMemoryProvider() {
@Override
public ChatMemory get(Object memoryId) {
return MessageWindowChatMemory.builder()
.id(memoryId)//id值
.maxMessages(20)//最大会话记录数量
.build();
}
};
return chatMemoryProvider;
}
3.2配置会话记忆对象提供者
既然提供了ChatMemoryProvider,之前提供的这个公有的ChatMemory就没有必要了,可以把它注释掉。
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "openAiChatModel",
streamingChatModel = "openAiStreamingChatModel",
// chatMemory = "chatMemory"
chatMemoryProvider = "chatMemoryProvider"//配置会话记忆对象提供者
)
3.3ConsultantService接口的方法中添加参数memoryId
package com.shenma.service;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
import reactor.core.publisher.Flux;
/**
* @author 军哥
* @version 1.0
* @description: 会话记录服务
* @date 2026/4/7 14:55
*/
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "openAiChatModel",
streamingChatModel = "openAiStreamingChatModel",
// chatMemory = "chatMemory"
chatMemoryProvider = "chatMemoryProvider"//配置会话记忆对象提供者
)
public interface ConsultantService {
@SystemMessage(fromResource = "system.txt")
public Flux<String> chat(String message);
@SystemMessage(fromResource = "system.txt")
public Flux<String> chat(@MemoryId String memoryId, @UserMessage String message);
}
3.4ChatController中chat接口接收前端传递的memoryId
package com.shenma.controller;
import com.shenma.service.ConsultantService;
import dev.langchain4j.memory.ChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
/**
* @author 军哥
* @version 1.0
* @description: 测试会话记忆
* @date 2026/4/7 14:59
*/
@RestController
public class ChatController {
@Autowired
private ConsultantService consultantService;
//produces:响应的数据编码类型
@PostMapping(value = "/chat1", produces = "text/html;charset=utf-8")
public Flux<String> chat(@RequestParam("message") String message) {
Flux<String> result = consultantService.chat(message);
return result;
}
@PostMapping(value = "/chat2",produces = "text/html;charset=utf-8")
public Flux<String> chat(@RequestParam("memoryId") String memoryId, @RequestParam("message") String message){
Flux<String> result = consultantService.chat(memoryId, message);
return result;
}
}
3.5前端访问/chat接口是提交memoryId参数
我们使用的是Date.now.toString()来充当memoryId,每次初始化页面会生成一个新的memoryId,当新建对话的时候会重置memoryId

4.会话记忆持久化
4.1MessageWindowChatMemory不持久化的原理
刚才我们完成了会话记忆隔离,其实我们的会话记忆还是有一些瑕疵的,只要后端重启,会话记忆就没有了,丢失了。先分析一下为什么会存在这种问题,之前我们一直构建的用于存储会话记录的对象是MessageWindowChatMemory,而这个对象内部维护了一个成员变量ChatMemoryStore,其实我们使用MessageWindowChatMemory对象的add方法添加会话记录的时候,真正用于存储的对象是这个ChatMemoryStore,所以要分析为什么重启之后会话记录会丢失,我们得分析ChatMemoryStore是如何存储会话记录的。
public class MessageWindowChatMemory implements ChatMemory {
private final String id;
private final ChatMemoryStore store;//这个对象用于存储会话记录
public void add(ChatMessage message) {
this.store.updateMessages(this.id, messages);
}
public List<ChatMessage> messages() {
return this.store.getMessages(this.id);
}
public void clear() {
this.store.deleteMessages(this.id);
}
}
ChatMemoryStore是一个接口,它里面提供了getMessages、updateMessages、deleteMessages方法分别用于根据memoryId获取会话记录,根据memoryId更新会话记录以及根据memoryId删除会话记录。
public interface ChatMemoryStore {
List<ChatMessage> getMessages(Object memoryId);
void updateMessages(Object memoryId,List<ChatMessage> messages);
void deleteMessages(Object memoryId);
}
LangChain4j为该接口提供了两个实现类,分别是InMemoryChatMemoryStore和SingleSlotMemoryStore。而我们MessageWindowChatMemory中默认使用的Store对象就是这个SingleChatMemoryStore。

接下来我们重点分析它里面又是如何存储会话记录的。
class SingleSlotChatMemoryStore implements ChatMemoryStore {
private List<ChatMessage> messages = new ArrayList();//用于存储会话记录
public List<ChatMessage> getMessages(Object memoryId) {
return this.messages;
}
public void updateMessages(Object memoryId,
List<ChatMessage> messages) {
this.messages = messages;
}
public void deleteMessages(Object memoryId) {
this.messages = new ArrayList();
}
}
在SingleSlotChatMemoryStore中维护了一个集合对象messages,它就是使用这个集合存储会话消息的,所以很明显这是内存存储,一旦当服务器重启后这些消息必然会丢失!
4.2会话记录持久化——Redis
(1)准备Redis环境
下载 docker和redis
(2)引入redis起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(3) 配置redis连接信息
spring:
data:
redis:
host: localhost
port: 6379