LangChain4j 从入门到放弃,java可以使用的 LLM 大模型 AI 框架(4)聊天记忆

注意:本篇文章建立在官方文档的理解上,文章会围绕官方文档去书写,博主会根据对框架的理解去动态修改文章内容,由于该框架的官方文档写的并不是很详细,市面上也没有特别详细的教程,所以博主也不能很全面的理解整个框架,但是对于入门来说,已经足够了,如果有看不懂的地方建议把全部文章都阅读一遍后再回顾会有不同的理解。

聊天记忆(Chat Memory)

官网 Chat Memory 介绍

LangChain4J 提供了强大的聊天记忆(Chat Memory)功能,可以方便地管理和维护对话的上下文。手动管理 ChatMessage 可能会很麻烦,因此 LangChain4J 提供了 ChatMemory 抽象类及多个现成的实现。

ChatMemory 可以作为独立的底层组件使用,也可以作为高级组件(例如 AI 服务)的一部分。

ChatMemory 充当一个存储 ChatMessage 的容器(通常由 List 实现),并提供了额外的功能,例如:

  • Eviction policy(逐出策略)
    • 控制哪些消息应该从记忆中移除。逐出策略用于在达到令牌或消息数限制时,删除一些不再需要的消息。通常,最早的消息会被逐出,但也可以使用更复杂的算法来决定逐出策略。
  • Persistence(持久化)
    • 指将聊天记忆数据保存在外部存储(如数据库、文件系统等)中,以便在会话结束后保持对话状态。通过持久化,记忆数据可以在应用重启后恢复。
  • Special treatment of SystemMessage(系统消息的特殊处理)
    • 系统消息通常用于提供背景信息、设置对话的规则或配置一些参数。LangChain4J 提供了对系统消息的特殊处理,以便它们在记忆中发挥特定的作用或不被逐出。
  • Special treatment of tool messages(工具消息的特殊处理)
    • 工具消息通常与外部工具或API交互有关。LangChain4J 允许对工具消息进行特殊处理,比如确保它们在记忆中的处理方式与普通消息不同,或确保它们在记忆中的存储与其他消息的存储方式有所区分。

记忆与历史(Memory vs History)

需要注意的是,记忆(Memory)和历史记录(History)是两个不同的概念,尽管它们有些相似。

  • 历史记录:保留了用户和 AI 之间的所有消息内容,并且这些内容是不可变的。历史记录是用户在界面中看到的内容,代表了实际的对话内容。
  • 记忆:保留的是部分信息,这些信息被呈现给大型语言模型(LLM),让它看起来像是模型“记住”了之前的对话。记忆和历史记录的区别在于,记忆可能会通过不同的算法对历史记录进行修改,比如:逐出部分消息、汇总多条消息、删除不重要的细节、注入额外的信息(如用于 RAG 的信息)或指令(如用于生成结构化输出的指令)等。

LangChain4J 当前只提供“记忆”功能,不提供历史记录。如果你需要保留完整的历史记录,可能需要手动管理。

驱逐策略(Eviction Policy)

驱逐策略对于管理记忆至关重要,原因如下:

  1. 适应 LLM 的上下文窗口大小:LLM 对话一次只能处理有限数量的令牌。如果对话内容过长,可能会超出该限制。在这种情况下,必须逐出某些消息。通常情况下,最旧的消息会被逐出,但也可以根据需要实现更复杂的策略。
  2. 控制成本:每次调用 LLM 时,都会产生令牌消耗,每个令牌都有成本。随着对话的增加,调用成本也会逐渐增加。通过逐出不必要的消息,可以有效降低成本。
  3. 控制延迟:发送给 LLM 的消息越多,处理所需的时间就越长。通过减少消息数量,可以降低延迟。

目前,LangChain4J 提供了两种开箱即用的驱逐策略实现:

  1. MessageWindowChatMemory

    • 这是一个简单的实现,充当滑动窗口,保留最新的 N 条消息,并逐出不再适用的旧消息。由于每条消息可能包含不同数量的令牌,MessageWindowChatMemory 适用于快速原型开发,但它并不关注每条消息的令牌数量。
  2. TokenWindowChatMemory

    • 这是一个更复杂的实现,依然充当滑动窗口,但它主要关注保留最近 N<