本章主要介绍基于LangChain4J如何实现聊天记忆的功能,解决大模型无状态下有上下文的聊天
1. 聊天记忆(Chat Memory)的介绍
手动管理和维护ChatMessage(聊天消息)是繁琐的。因此,LangChain4j提供了一个ChatMemory抽象概念,以及多种现成的实现方式。
ChatMemory可以作为独立的低级组件使用,也可以作为高级组件(如AI服务)的一部分。ChatMemory充当ChatMessage的容器(基于List),并具备以下额外功能:
- 驱逐策略(Eviction policy):决定何时移除旧消息。
- 持久化(Persistence):将聊天消息存储到持久化存储中。
- 对SystemMessage的特殊处理:系统消息通常用于定义LLM的行为和风格。
- 对工具消息的特殊处理:例如,当工具执行请求的消息被移除时,相关的工具执行结果消息也会被自动移除。
2. 记忆与历史的区别
需要注意的是,“记忆”和“历史”是相似但不同的概念:
历史(History):保留用户与AI之间所有消息的完整记录。历史是用户在界面中看到的内容,它代表了实际的对话内容。
记忆(Memory):只保留部分信息,这些信息被呈现给LLM,使其表现得好像“记得”之前的对话。记忆与历史有很大不同。根据使用的记忆算法,它可以以多种方式修改历史:例如移除一些消息、总结多条消息、删除消息中的不重要细节、注入额外信息(如用于RAG)或指令(如用于结构化输出)等。
LangChain4j目前只提供“记忆”功能,而不提供“历史”功能。如果需要保留完整的对话历史,请手动实现。
3. 驱逐策略(Eviction policy)
驱逐策略是必要的,原因包括:
- 适应LLM的上下文窗口限制:LLM能够处理的token数量是有限的。如果对话内容超出这个限制,就需要移除一些消息。通常会移除最旧的消息,但也可以根据需要实现更复杂的算法。
- 控制成本:每个token都有成本,因此对话内容越长,调用LLM的费用就越高。移除不必要的消息可以降低成本。
- 控制延迟:发送给LLM的token越多,处理时间就越长。
LangChain4j目前提供了两种现成的实现:
- MessageWindowChatMemory:这是一个简单的滑动窗口实现,保留最近的N条消息,并移除不再适合的消息。然而,由于每条消息的token数量可能不同,这种实现主要用于快速原型开发。
- TokenWindowChatMemory:这是一个更复杂的滑动窗口实现,专注于保留最近的N个token,并根据需要移除旧消息。消息是不可分割的,如果一条消息不适合,它将被完全移除。TokenWindowChatMemory需要一个Tokenizer来计算每条ChatMessage中的token数量。
4. 持久化(Persistence)
默认情况下,ChatMemory实现将ChatMessage存储在内存中。如果需要持久化,可以实现一个自定义的ChatMemoryStore,将ChatMessage存储到任何你选择的持久化存储中:
class PersistentChatMemoryStore implements ChatMemoryStore {
@Override
public List<ChatMessage> getMessages(Object memoryId) {
// 实现根据memoryId从持久化存储中获取所有消息。
// 可以使用ChatMessageDeserializer.messageFromJson(String)和
// ChatMessageDeserializer.messagesFromJson(String)辅助方法将聊天消息从JSON反序列化。
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
// 实现根据memoryId更新持久化存储中的所有消息。
// 可以使用ChatMessageSerializer.messageToJson(ChatMessage)和
// ChatMessageSerializer.messagesToJson(List<ChatMessage>)辅助方法将聊天消息序列化为JSON。
}
@Override
public void deleteMessages(Object memoryId) {
// 实现根据memoryId删除持久化存储中的所有消息。
}
}
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.id("12345")
.maxMessages(10)
.chatMemoryStore(new PersistentChatMemoryStore())
.build();
- updateMessages():每次向ChatMemory添加新的ChatMessage时都会调用此方法。这通常在每次与LLM交互时发生两次:一次是添加新的UserMessage时,另一次是添加新的AiMessage时。此方法需要更新与给定memory ID关联的所有消息。
- getMessages():当ChatMemory的使用者请求所有消息时会调用此方法。这通常在每次与LLM交互时发生一次。Object memoryId参数的值对应于创建ChatMemory时指定的id,可用于区分多个用户和/或对话。此方法需要返回与给定memory ID关联的所有消息。
- deleteMessages():当调用ChatMemory.clear()时会调用此方法。如果你不使用此功能,可以保留此方法为空。
5. 对SystemMessage的特殊处理
SystemMessage是一种特殊类型的消息,因此它与其他消息类型不同:
一旦添加,SystemMessage将始终被保留。
同一时间只能持有一个SystemMessage。
如果添加了一个内容相同的新的SystemMessage,它将被忽略。
如果添加了一个内容不同的新的SystemMessage,它将替换之前的那一个。
如果包含ToolExecutionRequest的AiMessage被移除,那么相关的ToolExecutionResultMessage也会被自动移除,以避免某些LLM提供者(例如OpenAI)禁止在请求中发送孤立的ToolExecutionResultMessage。
6. 示例
文章还提供了一些使用ChatMemory的示例,包括:
使用AiServices时的聊天记忆。
每个用户的独立聊天记忆。
持久化的聊天记忆。
每个用户的持久化聊天记忆。
使用LangChain4j的ChatMemory进行生成式AI对话。
使用传统链(With legacy Chains)
1. 带有聊天记忆的对话链(Chat memory with ConversationalChain)
在LangChain4j中,ConversationalChain是一种用于构建对话式应用的链式结构。它允许开发者将多个组件(如语言模型、记忆模块等)组合起来,实现复杂的对话逻辑。当使用ChatMemory与ConversationalChain结合时,可以实现以下功能:
- 记忆管理:通过ChatMemory,ConversationalChain能够记住之前的对话内容,从而在多轮对话中保持连贯性。
灵活的对话流程:开发者可以自定义对话流程,例如在对话中插入工具调用、处理用户输入等。
适应性:ChatMemory可以根据需要选择不同的实现方式(如MessageWindowChatMemory或TokenWindowChatMemory),以适应不同的对话场景和性能需求。
官方样例:
带有对话链的聊天记忆
2. 带有聊天记忆的对话检索链(Chat memory with ConversationalRetrievalChain)
ConversationalRetrievalChain是一种更复杂的链式结构,它结合了对话记忆和检索功能,适用于需要从大量数据中检索信息的场景。例如,在问答系统或知识库检索中,ConversationalRetrievalChain可以实现以下功能:
记忆与检索结合:通过ChatMemory,ConversationalRetrievalChain能够记住之前的对话内容,并结合检索功能从知识库中找到与当前对话相关的信息。
动态检索:在对话过程中,根据用户的输入动态检索知识库中的内容,并将检索结果融入对话中。
上下文感知:通过记忆模块,ConversationalRetrievalChain能够理解对话的上下文,并生成更准确、更相关的回答。
官方样例:
带有对话检索链的聊天记忆
总结
这篇文章详细介绍了LangChain4j中的ChatMemory功能,包括如何管理和维护聊天消息、如何实现驱逐策略、如何进行持久化存储,以及对SystemMessage的特殊处理。ChatMemory是构建聊天应用时管理对话状态的重要工具,通过合理使用驱逐策略和持久化功能,可以优化性能、降低成本,并实现更复杂的对话逻辑。
导读说明:
这是LangChain开发智能体的系列文档,欢迎连读
- 第1章:LangChain4j的聊天与语言模型
- 第2章:如何基于LangChain4j实现聊天记忆
- 第3章:在LangChain中如何设置模型参数
- 第4章:在LangChain中如何实现响应式流(Response Streaming)
- 第5章:在LangChain中如何使用AI Services
- 第6章:基于LangChain如何开发Agents,附带客户支持智能体示例
- 第7章:在LangChain中如何调用函数Tools (Function Calling)
- 第8章:LangChain检索增强生成RAG–1概述
- 第8章:LangChain检索增强生成RAG–2.1Easy RAG实现
- 第8章:LangChain检索增强生成RAG–2.2Core RAG APIs
- 第8章:LangChain检索增强生成RAG–2.3Naive RAG
- 第8章:LangChain检索增强生成RAG–2.4Advanced RAG【高级RAG】
- 第9章:LangChain让大模型结构化输出
- 第9章:LangChain结构化输出-示例1(情感分析AI服务)
- 第9章:LangChain结构化输出-示例2(数字提取服务)
- 第9章:LangChain结构化输出-示例3(日期和时间提取服务)
- 第9章:LangChain结构化输出-示例4(基于大模型从自然语言中提取POJO)
- 第9章:LangChain结构化输出-示例5(基于大模型如何精确匹配POJO的字段)
- 第9章:LangChain结构化输出-示例6(设置系统消息和用户消息模版)
- 第10章:基于LangChain的综合实战[开发企业助手,查知识、查订单、表单登记…]