【LangChain4j】2-ChatMemory与数据库处理
本文介绍了LangChain4j提供的ChatMemory接口类,用于简化与AI的多次聊天交互。主要包含两种删除旧信息的策略:MessageWindowChatMemory(按消息条数限制)和TokenWindowChatMemory(按token数量限制)。文章通过代码示例展示了这两种策略的实现方式,并指出TokenWindowChatMemory需要配合OpenAI使用。此外,还讨论了将聊天信
文章目录
前言
在第一章chatModel介绍了如何与Ai进行多次聊天,但手动维护ChatMessage过于麻烦,LangChain4j提供ChatMemory接口类解决这个问题
1. 删除旧信息策略
我们知道调用Ai进行聊天如果传输的聊天信息越多,则Ai处理的时间则越长,消耗的tokens也随之更多,代价则变得更高。所以不能无限制的传输所有的历史聊天信息,这时需要提供一些策略去删除旧的聊天信息。langChain4j提供两种策略,分别是MessageWindowChatMemory和TokenWindowChatMemory。
1.1 MessageWindowChatMemory
消息窗口聊天缓存保存的最新maxMessages(指定数量)条信息,不论每条信息的tokens长度。常用于测试和快速验证功能。
/**
* @Author: liuxia
* @CreateTime: 2025/9/13 下午10:06
* @Description: 消息窗口聊天缓存测试
*/
public class MessageWindowChatMemoryTest {
public static void main(String[] args) {
ChatModel model = ChatModelInit.initOllama();
MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
.id("1234") // id,通常用于区分多个用户或多个对话
.maxMessages(2) // 最大存储消息数量
.chatMemoryStore(new InMemoryChatMemoryStore()) //内存缓存存储类
.build();
UserMessage oneMsg = UserMessage.from("你好,你现在是我的朋友koi");
memory.add(oneMsg);
AiMessage oneAiMsg = model.chat(memory.messages()).aiMessage();
System.out.println("ai回答1: " + oneAiMsg.text());
UserMessage twoMsg = UserMessage.from("你是谁?");
memory.add(twoMsg);
AiMessage twoAiMsg = model.chat(memory.messages()).aiMessage();
System.out.println("ai回答2: " + twoAiMsg.text());
UserMessage threeMsg = UserMessage.from("你是叫koi吗");
memory.add(threeMsg);
for (int i = 0; i < memory.messages().size(); i++) { // 只会打印最新的两条信息,删除了第一条
System.out.println("第" + i + "条信息: "+ memory.messages().get(i).toString());
}
AiMessage threeAiMsg = model.chat(memory.messages()).aiMessage();
System.out.println("ai回答3: " + threeAiMsg.text());
}
}
1.2 TokenWindowChatMemory
token窗口聊天缓存保存的是最新的maxTokens(指定数量)的数量,但Ollama不支持该类型缓存,需要使用OpenAi。
/**
* @Author: liuxia
* @CreateTime: 2025/9/13 下午10:43
* @Description: tokens窗口聊天缓存test
*/
public class TokenWindowChatMemoryTest {
public static void main(String[] args) {
ChatModel model = ChatModelInit.initOpenAi();
OpenAiTokenCountEstimator estimator = new OpenAiTokenCountEstimator(OpenAiChatModelName.GPT_4);
TokenWindowChatMemory memory = TokenWindowChatMemory.builder()
.maxTokens(1000, estimator)
.chatMemoryStore(new InMemoryChatMemoryStore()) //内存缓存存储类
.build();
UserMessage oneMsg = UserMessage.from("你好,你现在是我的朋友koi");
memory.add(oneMsg);
AiMessage oneAiMsg = model.chat(memory.messages()).aiMessage();
System.out.println("ai回答1: " + oneAiMsg.text());
UserMessage twoMsg = UserMessage.from("你是谁?");
memory.add(twoMsg);
AiMessage twoAiMsg = model.chat(memory.messages()).aiMessage();
System.out.println("ai回答2: " + twoAiMsg.text());
UserMessage threeMsg = UserMessage.from("你是叫koi吗");
memory.add(threeMsg);
AiMessage threeAiMsg = model.chat(memory.messages()).aiMessage();
System.out.println("ai回答3: " + threeAiMsg.text());
}
}
2.持久化
通过用户与Ai聊天的信息需要持久化,而不是一直放在内存(langChain4j默认实现InMemoryChatMemoryStore)中,所以我们需要实现ChatMemoryStore接口类,用来持久化用户聊天信息。否则生产环境承受不住全部聊天信息,而且重启项目就丢失所有聊天信息。
2.1 InMemoryChatMemoryStore默认实现类见下:
public class InMemoryChatMemoryStore implements ChatMemoryStore {
private final Map<Object, List<ChatMessage>> messagesByMemoryId = new ConcurrentHashMap(); // 保存memoryId和聊天集合的map缓存
public InMemoryChatMemoryStore() {
}
public List<ChatMessage> getMessages(Object memoryId) {
return (List)this.messagesByMemoryId.computeIfAbsent(memoryId, (ignored) -> {
return new ArrayList();
});
}
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
this.messagesByMemoryId.put(memoryId, messages);
}
public void deleteMessages(Object memoryId) {
this.messagesByMemoryId.remove(memoryId);
}
}
2.2 PersistentChatMemoryStore实现可参考如下所示:
/**
* @Author: liuxia
* @CreateTime: 2025/9/13 下午11:11
* @Description: 持久化聊天缓存test
*/
public class PersistentChatMemoryStoreTest {
@Resource
PersistentChatMemoryStore store;
@Test
public void test() {
ChatModel model = ChatModelInit.initOllama();
MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
.id("1234") // id,通常用于区分多个用户或多个对话
.maxMessages(4) // 最大存储消息数量
.chatMemoryStore(store) // 持久化缓存存储类
.build();
UserMessage oneMsg = UserMessage.from("你好,你现在是我的朋友koi");
memory.add(oneMsg);
AiMessage oneAiMsg = model.chat(memory.messages()).aiMessage();
memory.add(oneAiMsg);
System.out.println("ai回答1: " + oneAiMsg.text());
UserMessage twoMsg = UserMessage.from("你是我的朋友koi吗");
memory.add(twoMsg);
AiMessage twoAiMsg = model.chat(memory.messages()).aiMessage();
memory.add(twoAiMsg);
System.out.println("ai回答2: " + twoAiMsg.text());
System.out.println("数据库已有对话数:" + memory.messages().size()); // 输出的是4
}
}
PersistentChatMemoryStore类相关代码如下:
/**
* @Author: liuxia
* @CreateTime: 2025/9/14 下午3:16
* @Description:
* 持久化聊天缓存存储
* 实际场景应该结合redis做用户聊天信息处理
*/
@Component
public class PersistentChatMemoryStore implements ChatMemoryStore {
private ChatMessageService chatMessageService;
PersistentChatMemoryStore(ChatMessageService chatMessageService) {
this.chatMessageService = chatMessageService;
}
@Override
public List<ChatMessage> getMessages(Object memoryId) {
List<ChatMessage> messages = new ArrayList<>();
List<CustomChatMessage> msgs = chatMessageService.getMessageListById(Integer.valueOf(memoryId.toString()));
for (CustomChatMessage msg : msgs) {
if (msg.getType().equals(1)) {
UserMessage userMessage = UserMessage.from(msg.getMessage());
messages.add(userMessage);
} else if (msg.getType().equals(2)){
AiMessage aiMessage = AiMessage.from(msg.getMessage());
messages.add(aiMessage);
}
}
return messages;
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> list) {
// list 保存的是最新的全部信息,包含用户这次的聊天信息,只需要获取最新一条保存的数据库
ChatMessage latestMsg = list.get(list.size() - 1);
ChatMessageType typeEnum = latestMsg.type();
Integer type = 1;
String message = "";
if (typeEnum.equals(ChatMessageType.AI)) {
type = 2;
AiMessage aiMessage = (AiMessage) latestMsg;
message = aiMessage.text();
} else if (typeEnum.equals(ChatMessageType.USER)) {
type = 1;
UserMessage userMessage = (UserMessage) latestMsg;
message = userMessage.singleText();
}
chatMessageService.save(Integer.valueOf(memoryId.toString()), type, message);
}
@Override
public void deleteMessages(Object memoryId) {
chatMessageService.delByUserId(Integer.valueOf(memoryId.toString()));
}
}
ChatMessageService类相关代码如下:
/**
* @Author: liuxia
* @CreateTime: 2025/9/13 下午11:14
* @Description: 聊天信息service
*/
public interface ChatMessageService {
/**
* 获取用户的历史聊天信息
*
* @param userId
* @return
*/
List<CustomChatMessage> getMessageListById(Integer userId);
/**
* 保存聊天信息
*
* @param userId
* @param type
* @param message
* @return
*/
boolean save(Integer userId, Integer type, String message);
/**
* 删除用户聊天信息
*
* @param userId
* @return
*/
boolean delByUserId(Integer userId);
}
/**
1. @Author: liuxia
2. @CreateTime: 2025/9/14 下午3:09
3. @Description: 聊天信息实现类
*/
@Service
public class ChatMessageServiceImpl implements ChatMessageService {
/**
* 假设这是数据库
*/
private List<CustomChatMessage> messages = new ArrayList<>();
@Override
public List<CustomChatMessage> getMessageListById(Integer userId) {
return messages.stream().filter(f -> f.getUserId().equals(userId)).toList();
}
@Override
public boolean save(Integer userId, Integer type, String message) {
CustomChatMessage msg = CustomChatMessage.builder()
.userId(userId)
.type(type)
.message(message)
.build();
messages.add(msg);
return true;
}
@Override
public boolean delByUserId(Integer userId) {
messages.removeIf(f -> f.getUserId().equals(userId));
return true;
}
}
3. SystemMessage的特殊处理
在上一篇文章简单提到了SystemMessage,SystemMessage在ChatMemoryStore有以下约束:
1)一旦添加,始终保留。意味着删除策略不会删除该条。
2)只能有一条该类型消息;重复添加相同内容会忽略;重复添加不同内容会覆盖;SystemMessage测试代码如下:
public class SystemMessageTest {
@Resource
PersistentChatMemoryStore store;
/**
* 测试SystemMessage 不会删除
*/
@Test
public void noDelTest() {
MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
.id("1234") // id,通常用于区分多个用户或多个对话
.maxMessages(2) // 最大存储消息数量
.chatMemoryStore(store) // 持久化缓存存储类
.build();
SystemMessage systemMessage = SystemMessage.from("你是写诗小能手,能够帮助用户提供优美的七言等类型诗词。");
memory.add(systemMessage);
memory.add(UserMessage.from("测试数量1"));
memory.add(UserMessage.from("测试数量2"));
for (ChatMessage message : memory.messages()) {
/**
* 只会展示system信息和测试数量2,如下:
* SYSTEM : SystemMessage { text = "你是写诗小能手,能够帮助用户提供优美的七言等类型诗词。" }
* USER : UserMessage { name = null contents = [TextContent { text = "测试数量2" }] }
*/
System.out.println(message.type() + " : " + message);
}
System.out.println(memory.messages().size());
}
/**
* 测试SystemMessage 重复添加相同内容
*/
@Test
public void multipleAddSameTest() {
MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
.id("1234") // id,通常用于区分多个用户或多个对话
.maxMessages(4) // 最大存储消息数量
.chatMemoryStore(store) // 持久化缓存存储类
.build();
SystemMessage systemMessage = SystemMessage.from("你是写诗小能手,能够帮助用户提供优美的七言等类型诗词。");
memory.add(systemMessage);
memory.add(systemMessage);
System.out.println("总数:" + memory.messages().size());
System.out.println("数据:" + memory.messages().get(0));
/**
* 打印数据如下:
* 总数:1
* 数据:SystemMessage { text = "你是写诗小能手,能够帮助用户提供优美的七言等类型诗词。" }
*/
}
/**
* 测试SystemMessage 重复添加不相同内容
*/
@Test
public void multipleAddNoSameTest() {
MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
.id("1234") // id,通常用于区分多个用户或多个对话
.maxMessages(4) // 最大存储消息数量
.chatMemoryStore(store) // 持久化缓存存储类
.build();
SystemMessage systemMessage1 = SystemMessage.from("你是写诗小能手,能够帮助用户提供优美的七言等类型诗词。");
memory.add(systemMessage1);
SystemMessage systemMessage2 = SystemMessage.from("你是画画小能手,能够帮助用户提供画画思路。");
memory.add(systemMessage2);
System.out.println("总数:" + memory.messages().size());
System.out.println("数据:" + memory.messages().get(0));
/**
* 打印数据如下:
* 总数:1
* 数据:SystemMessage { text = "你是画画小能手,能够帮助用户提供画画思路。" }
*/
}
}
总结
ChatMemory在实际生产环境中是必须要实现,可能有人会觉得这样用还是不太方便,目前讲的毒是LangChain4j的低级API,后续会使用AIService类高级API关联ChatMemory,会隐藏添加,更新的细节,更关注业务实现,但如果不理解低级API,在解决bug时会面临无从下手的困难。
以上完整代码在我的仓库,地址:https://gitee.com/liutao-lx/langchain4j-agent-dev/tree/master/agent-example/src/main/java/com/koicarp/agent/example/chatmemery
更多推荐
所有评论(0)