📁langchain4j基于内存的聊天记忆实现
创建子模块langchain4j-08chat-memory
📦1.pom.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.yumeko.stduy</groupId>
<artifactId>langchain4j</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>langchain4j-08chat-memory</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--langchain4j-open-ai + langchain4j + langchain4j-reactor-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
⚙️2.application.properties配置文件
server.port=9008
spring.application.name=langchain4j-08chat-memory
# 设置响应的字符编码
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
🔧3.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);
}
🤖4.AI模型配置类
import com.yumeko.study.service.ChatAssistant;
import com.yumeko.study.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("qwen-api"))
.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);
}
/**
* @Description: 按照MessageWindowChatMemory方式配置聊天记忆
* 知识出处,https://docs.langchain4j.dev/tutorials/chat-memory/#eviction-policy
* <p>
* 该Bean提供基于消息数量限制的聊天记忆功能:
* 1. 使用MessageWindowChatMemory实现聊天历史记录的存储和管理
* 2. 限制最大保存100条消息记录(包括用户和AI的消息)
* 3. 当消息超过限制时,会自动移除最旧的消息以保持窗口大小
* 4. 通过memoryId参数区分不同用户的聊天上下文
* <p>
* MessageWindowChatMemory特点:
* - 基于消息条数进行驱逐策略(eviction policy)
* - 简单直接,适合对token数量控制要求不严格的场景
* - 每个memoryId维护独立的聊天上下文
*/
@Bean(name = "chatMessageWindowChatMemory")
public ChatMemoryAssistant chatMessageWindowChatMemory(ChatModel chatModel) {
return AiServices.builder(ChatMemoryAssistant.class)
.chatModel(chatModel)
// 按照memoryId对应创建了一个chatMemory,每个memoryId代表一个独立的聊天上下文
// MessageWindowChatMemory.withMaxMessages(100) 设置最大保存100条消息
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(100))
.build();
}
/**
* @Description: 按照TokenWindowChatMemory方式配置聊天记忆
* <p>
* 该Bean提供基于Token数量限制的聊天记忆功能:
* 1. 使用TokenWindowChatMemory实现聊天历史记录的存储和管理
* 2. 限制聊天历史记录的总Token数量不超过1000个Token
* 3. 当Token数量超过限制时,会自动移除最旧的消息以保持Token窗口大小
* 4. 通过memoryId参数区分不同用户的聊天上下文
* <p>
* TokenWindowChatMemory特点:
* - 基于Token数量进行驱逐策略(eviction policy),更精确地控制内存使用
* - 适合需要精确控制token消耗的场景,避免超出模型的token限制
* - 每个memoryId维护独立的聊天上下文
* <p>
* 注意事项:
* - 需要TokenCountEstimator来计算每条消息的token数量
* - 这里使用OpenAiTokenCountEstimator作为token计算器
*/
@Bean(name = "chatTokenWindowChatMemory")
public ChatMemoryAssistant chatTokenWindowChatMemory(ChatModel chatModel) {
// 创建TokenCountEstimator,默认使用OpenAI的token分词器
// TokenCountEstimator用于计算ChatMessage的token数量
// 这里使用"gpt-4"模型的分词器进行token计算
TokenCountEstimator openAiTokenCountEstimator = new OpenAiTokenCountEstimator("gpt-5");
return AiServices.builder(ChatMemoryAssistant.class)
.chatModel(chatModel)
// 配置chatMemoryProvider,使用TokenWindowChatMemory
// TokenWindowChatMemory.withMaxTokens(1000, openAiTokenCountEstimator)
// 第一个参数:最大token数量限制为1000
// 第二个参数:token计数器,用于计算每条消息的token数量
.chatMemoryProvider(memoryId -> TokenWindowChatMemory.withMaxTokens(1000, openAiTokenCountEstimator))
.build();
}
}
🌐5.Web控制器
import cn.hutool.core.date.DateUtil;
import com.yumeko.study.service.ChatAssistant;
import com.yumeko.study.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 = "chatMessageWindowChatMemory")
private ChatMemoryAssistant chatMessageWindowChatMemory;
@Resource(name = "chatTokenWindowChatMemory")
private ChatMemoryAssistant chatTokenWindowChatMemory;
/**
* @Description: MessageWindowChatMemory实现聊天功能
*/
@GetMapping(value = "/chatmemory/test2")
public String chatMessageWindowChatMemory() {
chatMessageWindowChatMemory.chatWithChatMemory(1L, "你好!我的名字是Java.");
String answer01 = chatMessageWindowChatMemory.chatWithChatMemory(1L, "我的名字是什么");
System.out.println("answer01返回结果:" + answer01);
chatMessageWindowChatMemory.chatWithChatMemory(3L, "你好!我的名字是python");
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;
}
/**
* @Description: 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, "你好!我的名字是redis");
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;
}
}
🧪6.测试接口
http://localhost:9008/chatmemory/test2
http://localhost:9008/chatmemory/test3
📁langchain4j持久化聊天记忆
创建子模块langchain4j-10chat-persistence
📦1.pom.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.yumeko.stduy</groupId>
<artifactId>langchain4j</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>langchain4j-10chat-persistence</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--langchain4j-open-ai + langchain4j + langchain4j-reactor-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
</dependency>
<!--spring-boot-starter-data-redis
https://docs.langchain4j.dev/tutorials/chat-memory#persistence
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
⚙️2.application.properties配置文件
server.port=9010
spring.application.name=langchain4j-10chat-persistence
# 设置响应的字符编码
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
# ==========config redis===============
spring.data.redis.host=192.168.88.129
spring.data.redis.port=6379
spring.data.redis.database=0
spring.data.redis.password=yumeko
spring.data.redis.connect-timeout=3s
spring.data.redis.timeout=2s
🔧3.service服务
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
public interface ChatPersistenceAssistant {
/**
* 聊天
*
* @param userId 用户 ID
* @param message 消息
* @return {@link String }
*/
String chat(@MemoryId Long userId, @UserMessage String message);
}
🤖4.AI模型配置类
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 {
@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;
}
}
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;
/**
* Redis聊天记忆存储实现类
*
* 该类实现了ChatMemoryStore接口,使用Redis作为聊天历史记录的持久化存储方案。
* 它负责将用户的聊天记录序列化后存储到Redis中,并在需要时反序列化取出,
* 实现了聊天上下文的持久化保存,即使应用重启也不会丢失历史记录。
*
* 主要功能:
* 1. 将聊天消息序列化并存储到Redis
* 2. 从Redis中获取并反序列化聊天消息
* 3. 删除指定用户的聊天记录
*/
@Component
public class RedisChatMemoryStore implements ChatMemoryStore {
/**
* Redis中聊天记录的键前缀
* 所有聊天记录将以 "CHAT_MEMORY:{memoryId}" 的格式存储在Redis中
*/
public static final String CHAT_MEMORY_PREFIX = "CHAT_MEMORY:";
/**
* Redis操作模板,用于执行各种Redis操作
* 通过@Resource注解自动注入Spring容器中的RedisTemplate实例
*/
@Resource
private RedisTemplate<String, String> redisTemplate;
/**
* 根据memoryId获取存储的聊天消息列表
*
* @param memoryId 用户会话标识符,用于区分不同用户的聊天记录
* @return 该用户的所有聊天消息列表,如果不存在则返回空列表
*
* 实现原理:
* 1. 构造Redis键名:CHAT_MEMORY_PREFIX + memoryId
* 2. 从Redis中获取对应的值(JSON字符串)
* 3. 使用ChatMessageDeserializer将JSON字符串反序列化为ChatMessage列表
*/
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String retValue = redisTemplate.opsForValue().get(CHAT_MEMORY_PREFIX + memoryId);
return ChatMessageDeserializer.messagesFromJson(retValue);
}
/**
* 更新指定memoryId的聊天消息列表
*
* @param memoryId 用户会话标识符,用于区分不同用户的聊天记录
* @param messages 需要更新的聊天消息列表
*
* 实现原理:
* 1. 使用ChatMessageSerializer将ChatMessage列表序列化为JSON字符串
* 2. 构造Redis键名:CHAT_MEMORY_PREFIX + memoryId
* 3. 将序列化后的JSON字符串存储到Redis中
*
* 持久化特点:
* - 数据以字符串形式存储在Redis中,便于查看和调试
* - 每个用户的聊天记录独立存储,通过memoryId进行隔离
* - Redis的高性能保证了聊天记录读写的效率
*/
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
redisTemplate.opsForValue()
.set(CHAT_MEMORY_PREFIX + memoryId, ChatMessageSerializer.messagesToJson(messages));
}
/**
* 删除指定memoryId的聊天消息
*
* @param memoryId 用户会话标识符,用于定位需要删除的聊天记录
*
* 使用场景:
* - 用户主动清除聊天历史
* - 系统定期清理过期聊天记录
* - 隐私保护需求下的数据删除
*/
@Override
public void deleteMessages(Object memoryId) {
redisTemplate.delete(CHAT_MEMORY_PREFIX + memoryId);
}
}
import com.yumeko.study.service.ChatPersistenceAssistant;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
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("qwen-api"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
/**
* @Description: 配置持久化聊天记忆助手
*
* 该Bean提供具备持久化能力的聊天记忆功能:
* 1. 使用Redis作为聊天记录的持久化存储
* 2. 结合MessageWindowChatMemory实现聊天历史记录的管理
* 3. 限制最大保存1000条消息记录
* 4. 通过memoryId参数区分不同用户的聊天上下文
*
* 实现原理:
* 1. 创建ChatMemoryProvider,用于根据memoryId提供独立的聊天记忆实例
* 2. 使用MessageWindowChatMemory作为聊天记忆的实现方式
* 3. 通过redisChatMemoryStore实现聊天记录的持久化存储
* 4. 构建AiServices服务,将chatModel和chatMemoryProvider整合
*
* 持久化特点:
* - 聊天记录存储在Redis中,即使应用重启也不会丢失
* - 每个用户(通过memoryId区分)拥有独立的聊天上下文
* - 支持大规模并发用户访问,通过Redis实现高性能读写
*
* 相关组件:
* - redisChatMemoryStore: Redis存储实现
* - MessageWindowChatMemory: 基于消息窗口的聊天记忆管理
* - ChatPersistenceAssistant: 持久化聊天助手接口
*/
@Bean
public ChatPersistenceAssistant chatMemoryAssistant(ChatModel chatModel) {
// 创建聊天记忆提供者,用于根据memoryId提供独立的聊天记忆实例
// MessageWindowChatMemory.builder() 构建基于消息窗口的聊天记忆管理器
// .id(memoryId) 设置记忆实例的唯一标识
// .maxMessages(1000) 设置最大保存1000条消息
// .chatMemoryStore(redisChatMemoryStore) 设置使用Redis作为持久化存储
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(1000)
.chatMemoryStore(redisChatMemoryStore)
.build();
// 构建AiServices服务
// 指定ChatPersistenceAssistant接口作为服务接口
// .chatModel(chatModel) 设置使用的聊天模型
// .chatMemoryProvider(chatMemoryProvider) 设置聊天记忆提供者
return AiServices.builder(ChatPersistenceAssistant.class)
.chatModel(chatModel)
.chatMemoryProvider(chatMemoryProvider)
.build();
}
}
🌐5.Web控制器
import cn.hutool.core.date.DateUtil;
import com.yumeko.study.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();
}
}