LangChain4J Chat Memory(聊天记忆)

LangChain4J 聊天记忆

手动维护和管理聊天消息非常繁琐。因此,LangChain4j 提供了一个 ChatMemory 抽象,以及多种现成的实现。

ChatMemory 可以作为独立的低级组件使用,或者作为 AI Services(AI 服务)等高级组件的一部分。

ChatMemory 作为 ChatMessages 的容器(由 List 支持),并具有以下附加功能:

  • 驱逐策略
  • 持久化
  • 特殊处理 SystemMessage
  • 特殊处理工具消息
记忆与历史

注意:“记忆”和“历史”是相似但不同的概念。

历史( History )保存了用户和 AI 之间的所有消息。历史( History )就是用户在 UI 中看到的内容,代表了实际的对话内容。
记忆( Memory)保存了一些信息,这些信息呈现给 LLM,使其表现得像是“记得”了对话。记忆( Memory)与历史( History )有很大不同。根据使用的记忆算法,它可以以各种方式修改历史:驱逐某些消息、总结多条消息、总结独立消息、从消息中删除不重要的细节、向消息中注入额外的信息(例如,RAG)或指令(例如,结构化输出)等。
LangChain4j 当前只提供“记忆”,而不提供“历史”。如果您需要保存完整的历史记录,请手动完成。

驱逐策略

驱逐策略是必要的,原因有以下几点:

  • 适应 LLM 的上下文窗口:LLM 能够同时处理的 token 数量是有限的。在某些情况下,对话可能会超过此限制。在这种情况下,应该驱逐一些消息。通常,最旧的消息会被驱逐,但如果需要,可以实现更复杂的算法。
  • 控制成本:每个 token 都有成本,这使得每次调用 LLM 时,成本逐渐增加。驱逐不必要的消息可以降低成本。
  • 控制延迟:发送给 LLM 的 token 越多,处理这些 token 所需的时间就越长。

目前,LangChain4j 提供了两种现成的实现:

  • 更简单的实现,MessageWindowChatMemory,它作为滑动窗口,保留最近的 N 条消息,驱逐那些不再适合的旧消息。然而,由于每条消息可能包含不同数量的 token,MessageWindowChatMemory 主要适用于快速原型开发。
  • 更复杂的选项,TokenWindowChatMemory,也作为滑动窗口工作,但它专注于保留最近的 N 个 token,必要时驱逐旧消息。消息是不可分割的。如果一条消息无法适配,它会被完全驱逐。TokenWindowChatMemory 需要一个 Tokenizer 来计算每条 ChatMessage 中的 token 数量。
持久化

默认情况下,ChatMemory 实现将 ChatMessages 存储在内存中。

如果需要持久化,可以实现一个自定义的 ChatMemoryStore,将 ChatMessages 存储在任何您选择的持久化存储中:

class PersistentChatMemoryStore implements ChatMemoryStore {

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        // TODO: 实现通过 memoryId 从持久化存储中获取所有消息。
        // 可以使用 ChatMessageDeserializer.messageFromJson(String) 和 
        // ChatMessageDeserializer.messagesFromJson(String) 辅助方法来轻松地从 JSON 反序列化消息。
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        // TODO: 实现通过 memoryId 更新持久化存储中的所有消息。
        // 可以使用 ChatMessageSerializer.messageToJson(ChatMessage) 和 
        // ChatMessageSerializer.messagesToJson(List<ChatMessage>) 辅助方法来
        // 轻松地将消息序列化为 JSON。
    }

    @Override
    public void deleteMessages(Object memoryId) {
        // TODO: 实现通过 memoryId 删除持久化存储中的所有消息。
    }
}

ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .id("12345")
        .maxMessages(10)
        .chatMemoryStore(new PersistentChatMemoryStore())
        .build();

updateMessages() 方法会在每次向 ChatMemory 添加新的 ChatMessage 时调用。通常在与 LLM 的每次交互中会调用两次:一次是添加新的 UserMessage,另一次是添加新的 AiMessage。updateMessages() 方法的作用是更新与给定 memoryId 关联的所有消息。ChatMessages 可以单独存储(例如每条消息一个记录/行/对象),也可以一起存储(例如将整个 ChatMemory 存储为一个记录/行/对象)。

注意:从 ChatMemory 中驱逐的消息也会从 ChatMemoryStore 中被驱逐。当一条消息被驱逐时,updateMessages() 方法会被调用,传入的消息列表中不包括已被驱逐的消息。

getMessages() 方法会在每次 ChatMemory 的用户请求获取所有消息时调用。通常在与 LLM 的每次交互中调用一次。Object memoryId 参数的值对应于创建 ChatMemory 时指定的 id。它可以用来区分多个用户和/或对话。getMessages() 方法的作用是返回与给定 memoryId 关联的所有消息。

deleteMessages() 方法会在调用 ChatMemory.clear() 时被调用。如果您不使用此功能,可以将该方法留空。

SystemMessage 的特殊处理

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

  • 一旦添加,SystemMessage 会始终保留。
  • 同时只能保留一个 SystemMessage。
  • 如果添加一个内容相同的新 SystemMessage,则会被忽略。
  • 如果添加一个内容不同的新 SystemMessage,则会替换掉之前的 SystemMessage。
工具消息的特殊处理

如果包含 ToolExecutionRequests 的 AiMessage 被驱逐,以下孤立的 ToolExecutionResultMessage(s) 也会被自动驱逐,以避免与某些 LLM 提供商(如 OpenAI)相关的问题,这些提供商禁止在请求中发送孤立的 ToolExecutionResultMessage(s)。

示例

与 AiServices 一起使用:

与传统链一起使用:

相关教程

使用 LangChain4j ChatMemory 进行生成性 AI 对话,作者:Siva

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值