LangChain4j从入门到精通-4-聊天记忆
LangChain4j从入门到精通-4-聊天记忆
本文深入解析了LangChain4j的核心组件ChatMemory,揭秘如何让AI应用具备“记忆”能力,实现连贯的多轮对话。文章清晰区分了“记忆”与“历史”的本质差异,详细讲解了两种主流的记忆剔除策略:基于消息数量的MessageWindowChatMemory(适合快速原型)和基于令牌数量的TokenWindowChatMemory(推荐生产环境)。通过代码实例演示了如何实现记忆持久化(如集成Redis、PostgreSQL),并重点剖析了框架对SystemMessage和工具消息的特殊处理机制,帮助开发者避开常见陷阱。掌握ChatMemory,是构建能真正理解上下文的高智能对话应用的关键一步。
#Java #人工智能 #LangChain4j #大模型应用开发 #聊天记忆
ChatMemory可以作为独立的底层组件使用, 也可以作为高级组件(如 AI 服务)的一部分使用。
ChatMemory 充当 ChatMessage的容器(由 List支持),并具有以下附加功能:
- 剔除策略
- 持久化
- 对 SystemMessage的特殊处理
- 对工具消息的特殊处理
记忆与历史
请注意,“记忆”和“历史记录”是相似但不同的概念。 历史记录会完整保留用户与AI之间的所有消息。历史记录是用户在界面中看到的内容,它代表实际发生的对话。
记忆则保留部分信息,这些信息会被提供给大语言模型(LLM),使其表现得像是“记得”对话内容。 记忆与历史记录截然不同。根据所使用的记忆算法,它可能以多种方式修改历史记录:
剔除部分消息、汇总多条消息、提炼独立消息、移除消息中的非关键细节、 向消息中注入额外信息(例如用于RAG)或指令(例如用于结构化输出)等。
LangChain4j目前仅提供“记忆”功能,而非“历史”功能。如需保留完整历史记录,请手动处理。
剔除策略
剔除策略的必要性体现在以下几个方面:
- 适应大语言模型的上下文窗口限制。大语言模型单次处理的令牌数量存在上限,当对话内容超出这一限制时,就需要移除部分消息。通常优先移除最早的消息,但也可根据需要实现更复杂的算法。
- 控制成本。每个令牌都会产生费用,调用大语言模型的成本会随令牌数量递增。及时清理非必要消息能有效降低成本。 控制延迟。输入大语言模型的令牌数量越多,处理所需时间就越长。
目前,LangChain4j提供了两种开箱即用的实现方案: - 较为简单的MessageWindowChatMemory采用滑动窗口机制,保留最近的N条消息,并剔除超出容量的旧消息。但由于每条消息的令牌数可能不同,该类型内存更适合快速原型设计。
- 更高级的选项是TokenWindowChatMemory,同样采用滑动窗口机制,但以保留最近的N个令牌为核心,按需淘汰旧消息。消息不可分割,若某条消息超出容量则整条剔除。该类型内存需配合TokenCountEstimator来统计每条聊天消息的令牌数。
持久化
默认情况下,ChatMemory实现会将ChatMessage存储在内存中。 如果需要持久化存储,可以实现自定义的ChatMemoryStore, 将ChatMessage存储在你选择的任何持久化存储中:
class PersistentChatMemoryStore implements ChatMemoryStore {
@Override
public List<ChatMessage> getMessages(Object memoryId) {
// TODO: Implement getting all messages from the persistent store by memory ID.
// ChatMessageDeserializer.messageFromJson(String) and
// ChatMessageDeserializer.messagesFromJson(String) helper methods can be used to
// easily deserialize chat messages from JSON.
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
// TODO: Implement updating all messages in the persistent store by memory ID.
// ChatMessageSerializer.messageToJson(ChatMessage) and
// ChatMessageSerializer.messagesToJson(List<ChatMessage>) helper methods can be used to
// easily serialize chat messages into JSON.
}
@Override
public void deleteMessages(Object memoryId) {
// TODO: Implement deleting all messages in the persistent store by memory ID.
}
}
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.id("12345")
.maxMessages(10)
.chatMemoryStore(new PersistentChatMemoryStore())
.build();
每当向ChatMemory添加新的ChatMessage时,都会调用updateMessages()方法。 这通常在与LLM的每次交互过程中发生两次:
第一次是添加新的UserMessage时,第二次是添加新的AiMessage时。 updateMessages()方法需要更新与给定内存ID关联的所有消息。 ChatMessage可以单独存储(例如每条消息对应一条记录/行/对象), 也可以集中存储(例如整个ChatMemory对应一条记录/行/对象)。
:::note
请注意,从 ChatMemory中移除的消息也会从 ChatMemoryStore中被移除。
当消息被移除时,updateMessages()方法会被调用, 且调用时传入的消息列表将不包含已被移除的消息。
:::
每当 ChatMemory的用户请求所有消息时,就会调用 getMessages()方法。 这通常在与大语言模型(LLM)的每次交互过程中发生一次。
参数 Object memoryId的值对应于创建 ChatMemory时指定的 id。 它可以用来区分多个用户和/或对话。
getMessages()方法应返回与给定内存 ID 关联的所有消息。
当调用 ChatMemory.clear()时,会触发 deleteMessages()方法。 如果你不使用此功能,可以将此方法留空。
对 SystemMessage的特殊处理
SystemMessage 这是一种特殊类型的消息,因此与其他消息类型的处理方式不同。
- 一旦添加,SystemMessage将始终保留。
- 同一时间只能保存一条SystemMessage。
- 如果添加了内容相同的新SystemMessage,它将被忽略。
- 如果添加了内容不同的新SystemMessage,它将替换之前的消息。 默认情况下,新的SystemMessage会被添加到消息列表的末尾。您可以在创建ChatMemory时通过设置alwaysKeepSystemMessageFirst属性来更改此行为
工具消息的特殊处理
如果一条包含ToolExecutionRequest的AiMessage被移除, 为了防止某些禁止在请求中发送孤立ToolExecutionResultMessage的LLM提供商(如OpenAI)出现问题, 相关的孤立ToolExecutionResultMessage也会被自动移除。

更多推荐

所有评论(0)