LangChain4j 聊天记忆Chat Memory
Chat Memory
ChatMessage手动维护和管理非常麻烦。因此,LangChain4j 提供了一个ChatMemory抽象以及多个开箱即用的实现。
ChatMemory可以用作独立的低级组件,也可以作为AI 服务等高级组件的一部分。
ChatMemory充当ChatMessages 的容器(由 a 支持List),并具有以下附加功能:
移除策略
持久性
SystemMessage的特殊处理
工具消息的特殊处理
记忆 vs 历史
请注意,“记忆”和“历史”是相似但又不同的概念。
历史记录保存了用户和 AI 之间的所有消息。历史记录是用户在 UI 中看到的内容。它代表了实际说过的内容。
记忆会保留一些信息,这些信息会呈现给 LLM,使其表现得好像“记住”了对话一样。记忆与历史截然不同。根据所使用的记忆算法,它可以以各种方式修改历史:删除一些消息、汇总多条消息、汇总单独的消息、从消息中删除不重要的细节、在消息中注入额外信息(例如,用于 RAG)或指令(例如,用于结构化输出)等等。
LangChain4j 目前只提供“记忆”,不提供“历史”,如果需要保留整个历史,请手动操作。
移除策略
移除策略是必要的,原因如下:
适合 LLM 的上下文窗口。LLM 一次可以处理的标记数有上限。在某些时候,对话可能会超过此限制。在这种情况下,应逐出某些消息。通常,会逐出最旧的消息,但如果需要,也可以实现更复杂的算法。
控制成本。每个令牌都有成本,使得每次调用 LLM 的成本逐渐增加。移除不必要的消息可以降低成本。
控制延迟。发送到 LLM 的 token 越多,处理它们所需的时间就越长。
目前,LangChain4j 提供 2 种开箱即用的实现:
较简单的方法MessageWindowChatMemory充当滑动窗口,保留N最新消息并驱逐不再适合的旧消息。但是,由于每条消息可以包含不同数量的标记,因此 MessageWindowChatMemory主要用于快速原型设计。
一个更复杂的选项是TokenWindowChatMemory,它也可以用作滑动窗口,但重点是保留N最新的标记,并根据需要逐出较旧的消息。消息是不可分割的。如果消息不适合,它将被完全逐出。 MessageWindowChatMemory需要 来Tokenizer计算每个 中的标记ChatMessage。
持久性
默认情况下,ChatMemory实现将ChatMessages 存储在内存中。
如果需要持久性,则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();
updateMessages()每次ChatMessage向 中添加新内容时都会调用该方法ChatMemory。这通常在与 LLM 的每次交互中发生两次:一次是在UserMessage添加新内容时,另一次是在AiMessage添加新内容时。该updateMessages()方法应该更新与给定内存 ID 相关联的所有消息。 ChatMessage可以单独存储(例如,每条消息一个记录/行/对象)或一起存储(例如,整个 的一个记录/行/对象ChatMemory)。
请注意,从 中驱逐的消息ChatMemory也将从 中驱逐ChatMemoryStore。当一条消息被驱逐时,该updateMessages()方法将使用不包括被驱逐消息的消息列表来调用。
getMessages()每当 的用户请求所有消息时,都会调用该方法ChatMemory。这通常在与 LLM 的每次交互中发生一次。参数的值Object memoryId对应于id的创建期间指定的值ChatMemory。它可用于区分多个用户和/或对话。该getMessages()方法应返回与给定内存 ID 相关联的所有消息。
deleteMessages()每当调用时,都会调用该方法ChatMemory.clear()。如果您不使用此功能,则可以将此方法留空。
SystemMessage的特殊处理
SystemMessage是一种特殊类型的消息,因此其处理方式与其他消息类型不同:
一旦添加,SystemMessage就会始终保留。
SystemMessage每次只能举行一场。
如果SystemMessage添加了新的相同内容,则会被忽略。
如果添加了具有不同内容的新内容SystemMessage,它将取代前一个内容。
tool messages的特殊处理
如果AiMessage包含ToolExecutionRequests 的 被驱逐,则以下孤立对象ToolExecutionResultMessage也会被自动驱逐,以避免出现某些 LLM 提供商(例如 OpenAI)禁止ToolExecutionResultMessage在请求中发送孤立对象的问题。
例如
- 和AiServices:
- 聊天记忆
- 为每个用户提供单独的聊天记忆
- 持久聊天记忆
- 为每个用户保留持久的聊天记忆
- 凭借Chain传统
- 使用 ConversationalChain 进行聊天记忆
- 使用 ConversationalRetrievalChain 进行聊天记忆