掘金 人工智能 2024年07月08日
LangChain4j系列:LangChain4j ChatMemory聊天记忆详解与实战
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入分析了LangChain4j中的ChatMemory组件,介绍了其核心功能,包括容器管理、淘汰机制、持久化机制以及对SystemMessage和工具消息的特殊处理。文章还提供了共享ChatMemory和会话维度隔离的ChatMemory示例,并阐述了自定义存储机制的实现方法,最后通过实战代码示例展示了ChatMemory的实际应用场景。

🤔 **容器管理机制**:ChatMemory充当ChatMessage容器,负责管理聊天消息,确保用户所有的提问、大模型回答和产生的内容都能被有效地存储和检索。

🗑️ **淘汰机制**:为了防止聊天上下文过长导致超出大模型的上下文token限制,ChatMemory采用了淘汰机制,例如MessageWindowChatMemory,它会保留最新的N条消息,并驱逐不再适合的旧消息。TokenWindowChatMemory则专注于保留最新的token,根据需要驱逐较旧的消息。

💾 **持久化机制**:ChatMemory默认情况下将ChatMessage存储在内存中,为了防止聊天上下文丢失,可以自定义ChatMemoryStore,将ChatMessage存储在持久性存储中,例如数据库或文件系统。

🤖 **消息特殊处理机制**:ChatMemory对SystemMessage和工具消息进行了特殊处理,确保这些消息能够被正确地存储和检索,并满足不同的使用场景。

💻 **实战代码示例**:本文提供了共享ChatMemory和会话维度隔离的ChatMemory示例,以及自定义存储机制的实现方法,帮助读者更好地理解ChatMemory的使用场景和实现细节。


theme: vue-prohighlight: a11y-dark

前两篇文章是对LangChain4j比较全面的介绍,从本篇文章开始从某一个点进行分析。我们先从ChatMemory开始!

对于聊天记忆的场景、实现原理,在Spring AI 专栏中的# Spring AI 聊天上下文记忆源码分析以及实战文章有介绍,在这里就不多介绍直入主题。

为什么需要ChatMemory

实现聊天记忆实现起来非常简单,就是把用户所有的提问、大模型回答/产生的内容,放在一个List<ChatMessage>中,随着用户提问将List一并发送给大模型,让大模型具备了聊天记忆功能。实现虽然简单,大家想一想有没有问题呢?

手动维护和管理 ChatMessage 很麻烦。因此,LangChain4j 提供了一个 ChatMemory 抽象以及多个开箱即用的实现。ChatMemory 可以用作独立的低级组件,也可以用作高级组件(如 AI Services)的一部分。

LangChain4j 目前只提供“内存”,不提供“历史记录”

ChatMemory实现什么能力

淘汰机制(Eviction policy)

出于以下几个原因,数据淘汰机制是必要的:

ChatMemory源码分析

ChatMemory接口

public interface ChatMemory {    // ChatMemory的ID    Object id();       // 将message添加到ChatMemory中    void add(ChatMessage message);    // 从ChatMemory中获取消息,怎么取取决于实现    List<ChatMessage> messages();        // 清空ChatMemory中的消息    void clear();}

ChatMemory实现类

持久化机制(Persistence)

默认情况下,ChatMemory 实现是将 ChatMessage 存储在内存中的,如果需要持久性,可以实现自定义 ChatMemoryStore ,将ChatMessage 存储在您选择的任何持久性存储中。

可以自定义持久化策略!!!!

ChatMemoryStore 接口

public interface ChatMemoryStore {    // 根据memoryId从指定的ChatMemoryStore中获取消息    List<ChatMessage> getMessages(Object memoryId);    // 根据memoryId,更新存储的消息    void updateMessages(Object memoryId, List<ChatMessage> messages);        // 根据memoryId删除存储的消息    void deleteMessages(Object memoryId);}

ChatMemoryStore 实现类

public class InMemoryChatMemoryStore implements ChatMemoryStore {    private final Map<Object, List<ChatMessage>> messagesByMemoryId = new ConcurrentHashMap<>();    /*      Constructs a new {@link InMemoryChatMemoryStore}.     */    public InMemoryChatMemoryStore() {}    @Override    public List<ChatMessage> getMessages(Object memoryId) {        return messagesByMemoryId.computeIfAbsent(memoryId, ignored -> new ArrayList<>());    }    @Override    public void updateMessages(Object memoryId, List<ChatMessage> messages) {        messagesByMemoryId.put(memoryId, messages);    }    @Override    public void deleteMessages(Object memoryId) {        messagesByMemoryId.remove(memoryId);    }}

仅有一个基于内存实现的存储,如果有特殊需求,我们可以实现ChatMemoryStore接口,自定义逻辑。

SystemMessage 特殊处理

SystemMessage 是一种特殊类型的消息,因此它与其他消息类型的处理方式不同:

工具消息的特殊处理

如果包含ToolExecutionRequestAiMessage 被淘汰,则与其关联的ToolExecutionResultMessage也需要一同淘汰。

ChatMemory 代码实践

引入依赖包

<dependency>    <groupId>dev.langchain4j</groupId>    <artifactId>langchain4j-spring-boot-starter</artifactId>    <version>${langchain4j.version}</version></dependency><dependency>    <groupId>dev.langchain4j</groupId>    <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>    <version>${langchain4j.version}</version></dependency>

共享ChatMemory实现

package org.ivy.chatmemory;import dev.langchain4j.memory.ChatMemory;import dev.langchain4j.memory.chat.MessageWindowChatMemory;import dev.langchain4j.model.openai.OpenAiChatModel;import dev.langchain4j.service.AiServices;import org.ivy.chatmemory.service.Assistant;public class ChatMemoryJavaExample {    public static void main(String[] args) {        ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);        Assistant assistant = AiServices.builder(Assistant.class)                .chatLanguageModel(OpenAiChatModel.builder()                        .baseUrl("xxxx")                        .apiKey("xxxx")                        .build()                )                .chatMemory(chatMemory)                .build();        String answer = assistant.chat("Hello! My name is Klaus.");        System.out.println(answer); // Hello Klaus! How can I assist you today?        String answerWithName = assistant.chat("What is my name?");        System.out.println(answerWithName); // Your name is Klaus.    }}

独享ChatMemory实现

package org.ivy.chatmemory;import dev.langchain4j.memory.chat.MessageWindowChatMemory;import dev.langchain4j.model.openai.OpenAiChatModel;import dev.langchain4j.service.AiServices;import org.ivy.chatmemory.service.EachUserAssistant;public class ChatMemoryEachUserExample {    public static void main(String[] args) {        EachUserAssistant assistant = AiServices.builder(EachUserAssistant.class)                .chatLanguageModel(                        OpenAiChatModel.builder()                                .baseUrl("xxx")                                .apiKey("xxx")                                .build())                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))                .build();        System.out.println(assistant.chat(1, "Hello, my name is Klaus"));        // Hi Klaus! How can I assist you today?        System.out.println(assistant.chat(2, "Hello, my name is Francine"));        // Hello Francine! How can I assist you today?        System.out.println(assistant.chat(1, "What is my name?"));        // Your name is Klaus.        System.out.println(assistant.chat(2, "What is my name?"));    }}

定义memoryId,根据memoryId来获取是否是同一组上下文信息。

示例代码

在示例代码中,除上java版本的实现外,还有Spring Boot 版本的实现,这个来的更实际,使用更多,大家可以查看Github中的代码。

总结

对LangChain4j聊天记忆进行了分析,并介绍了ChatMemory四个特性。提供了使用示例,包括共享ChatMemory、会话维度隔离的ChatMemory,自定义存储机制等。实战代码可以参考Github。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

LangChain4j ChatMemory 聊天记忆 大模型 上下文管理
相关文章