掘金 人工智能 4小时前
LangChain框架入门08:全方位解析记忆组件
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了LangChain中的记忆组件,解决了大型语言模型在对话中容易“失忆”的问题。文章详细介绍了记忆组件的实现原理,包括如何读取、写入和存储对话历史信息。文中列举了多种常用的记忆组件,如ConversationBufferMemory、ConversationBufferWindowMemory、ConversationSummaryMemory和ConversationSummaryBufferMemory,并阐述了它们的特性。此外,文章还通过实际代码示例演示了如何使用ConversationBufferWindowMemory和ConversationSummaryBufferMemory构建具有多轮对话记忆能力的AI应用,并介绍了如何利用FileChatMessageHistory实现对话历史的持久化存储,以确保AI在程序重启后仍能记住上下文信息,从而提升用户体验。

🧠 **记忆组件解决AI“失忆”问题**:大语言模型本身是“无状态”的,容易在多轮对话中遗忘先前的交流内容。LangChain的记忆组件通过保存和读取历史对话信息,赋予AI“记忆”能力,使得AI能够记住上下文,提升对话的连贯性和用户体验。

💡 **记忆组件实现原理**:记忆组件的核心功能在于“读取”、“写入”和“存储”历史对话信息。在链执行前,将历史消息与用户输入一同添加到提示词中传递给大语言模型;在链执行完毕后,将用户的输入和大语言模型输出一同写入记忆组件,从而实现上下文的传递。

📚 **多种记忆组件选择**:LangChain提供了多种记忆组件以适应不同场景需求,包括保存所有历史的`ConversationBufferMemory`、限制轮数的`ConversationBufferWindowMemory`、压缩历史为摘要的`ConversationSummaryMemory`以及结合缓存和摘要的`ConversationSummaryBufferMemory`。选择合适的组件能在记忆效果和token消耗之间取得平衡。

💾 **对话历史的持久化存储**:为了解决程序重启后记忆丢失的问题,可以使用`FileChatMessageHistory`等组件将对话历史持久化到文件或数据库中,如示例中的`chat_history.txt`。这确保了AI在跨会话中也能保持对用户信息的记忆,实现更连续的交互。

⚖️ **平衡记忆与成本**:虽然记忆组件能显著提升AI的交互体验,但也会增加token消耗。开发者需要在提供流畅的对话体验与控制成本之间找到最佳平衡点,根据实际应用场景选择合适的记忆策略和存储方案。

在前面的章节中,我们学习了如何使用LangChain构建基本的对话应用,不过在和大语言模型对话时,你可能会注意到大语言模型很快就会失忆,后面聊天提问前面聊过的内容,大语言模型仿佛完全“忘记”了。

为了解决这个问题,LangChain提供了强大的记忆组件(Memory) ,能够让AI“记住”上下文对话信息。

文中所有示例代码:github.com/wzycoding/l…

一、为什么需要记忆组件

大语言模型本质上是经过大量数据训练出来的自然语言模型,用户给出输入信息,大语言模型会根据训练的数据进行预测给出指定的结果,大语言模型本身是 “无状态的” ,因此大语言模型是没有记忆能力的

当我们和大语言模型聊天时,会出现如下的情况:

Human:我叫大志,请问你是?AI:你好,大志,我是OpenAI开发的聊天机器人。Human:你知道我是谁吗?AI:我不知道,请你告诉我的名字。

我们刚刚在前一轮对话告诉大语言模型的信息,下一轮就被“遗忘了”。当我在ChatGPT官网和ChatGPT聊天时,它能记住多轮对话中的内容,这ChatGPT网页版实现了历史记忆功能。

二、记忆组件实现原理

一个记忆组件要实现的三个最基本功能:

在LangChain中,给大语言模型添加记忆功能的方法:

这样大语言模型就拥有了“记忆”功能,上述实现记忆功能的流程图如下:

三、记忆组件介绍

3.1 常见记忆组件

LangChain中的记忆组件继承关系图如下,所有常用的记忆组件都继承自BaseChatMemory类,如果我们自己想实现一个自定义的记忆组件也可以继承BaseChatMemory

下面是LangChain中常用记忆组件以及它们的特性。

组件名称特性
ConversationBufferMemory保存所有的历史对话信息
ConversationBufferWindowMemory保存最近N轮对话内容
ConversationSummaryMemory压缩历史对话为摘要信息
ConversationSummaryBufferMemory结合缓存和摘要信息
ConversationTokenBufferMemory基于token限制的历史对话信息

3.2 BaseChatMemory简介

下面对BaseChatMemory的核心属性与方法进行分析:

1、属性:

chat_memory: BaseChatMessageHistory类的的对象,BaseChatMessageHistory是真正实现保存历史对话信息功能的类,这里BaseChatMemory并没有在自身去实现聊天消息的保存,而是抽象出BaseChatMessageHistory类,保持各个类遵循单一职责原则,这样做更利于项目的扩展和解耦。

chat_memory: BaseChatMessageHistory = Field(    default_factory=InMemoryChatMessageHistory)

2、方法

save_context():同步保存历史消息

load_memory_variables():加载历史记忆信息

clear():同步清空历史记忆信息

3.3 BaseChatMessageHistory简介

BaseChatMessageHistory是用来保存聊天消息历史的抽象基类,下面对BaseChatMessageHistory的核心属性与方法进行分析:

1.属性:

messages: List[BaseMessage]:用来接收和读取历史消息的只读属性

2.方法:

add_messages:批量添加消息,默认实现是每个消息都去调用一次add_message

add_message:单独添加消息,实现类必须重写这个方法,否则会抛出异常

clear():清空所有消息,实现类必须重写这个方法

BaseChatMessageHistory常见实现类如下:

下面是LangChain中常用的消息历史组件以及它们的特性,其中InMemoryChatMessageHistoryBaseChatMemory默认使用的聊天消息历史组件。

组件名称特性
InMemoryChatMessageHistory基于内存存储的聊天消息历史组件
FileChatMessageHistory基于文件存储的聊天消息历史组件
RedisChatMessageHistory基于Redis存储的聊天消息历史组件
ElasticsearchChatMessageHistory基于ES存储的聊天消息历史组件

四、记忆组件使用方法

下面以最典型的ConversationBufferWindowMemory类和ConversationSummaryBufferMemory类作为示例,来演示记忆组件的使用方法。

4.1 ConversationBufferWindowMemory用法

ConversationBufferMemory是LangChain中最简单的记忆组件,它只是简单将所有的历史对话信息进行缓存,而ConversationBufferWindowMemoryConversationBufferMemory的主要区别在于:ConversationBufferWindowMemory增加了一个限制,ConversationBufferWindowMemory只返回最近K轮对话的历史记忆,这样做的目的是为了在实现历史记忆和大语言模型token消耗之间寻找一个平衡,如果每次携带的历史消息太长,那么每次消耗的token数量都会非常多。

ConversationBufferWindowMemory使用示例如下,创建ConversationBufferWindowMemory指定return_messages为True,表示加载历史消息时返回消息列表而非字符串,指定k为2,表示最多返回两轮对话的历史记忆。

在链的第一个可运行组件位置,调用了memory组件,并读取了历史记忆,向输入参数列表中添加了一个history参数,它的值就是历史记忆信息,继续传递到下一个可运行组件,在渲染提示词模板时即可使用历史记忆信息,历史记忆信息就和用户提问一起传递给了大语言模型,大语言模型就拥有了历史记忆。

from operator import itemgetterimport dotenvfrom langchain.memory import ConversationBufferWindowMemoryfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderfrom langchain_core.runnables import RunnablePassthrough, RunnableLambdafrom langchain_openai import ChatOpenAI# 读取env配置dotenv.load_dotenv()# 1.创建提示词模板prompt = ChatPromptTemplate.from_messages([    MessagesPlaceholder("chat_history"),    ("human", "{question}"),])# 2.构建GPT-3.5模型llm = ChatOpenAI(model="gpt-3.5-turbo")# 3.创建输出解析器parser = StrOutputParser()memory = ConversationBufferWindowMemory(return_messages=True, k=2)# 4.执行链chain = RunnablePassthrough.assign(    chat_history=(RunnableLambda(memory.load_memory_variables) | itemgetter("history"))) | prompt | llm | parserwhile True:    question = input("Human:")    response = chain.invoke({"question": question})    print(f"AI:{response}")    memory.save_context({"human": question}, {"ai": response})

执行结果如下,第一轮对话中,Human告诉AI,他叫大志,随后的一轮对话,显然AI已经记住了Human的名字,但是由于我们设置的k值是2,在超过两轮对话之后大语言模型就又进入失忆状态了。

Human:我是大志,你是AI:你好,大志!我是ChatGPT,很高兴认识你。你今天怎么样?Human:我是谁AI:你是大志呀!不过如果你是想探索一下“我是谁”这个哲学问题,那就挺有意思的。你觉得自己是谁?Human:冰泉冷涩弦凝绝AI:哦,这句出自唐代词人李清照的《如梦令》。整句是:**冰泉冷涩弦凝绝,凝绝不通声暂歇。**Human:别有忧愁暗恨生AI:这句出自李清照的《如梦令·常记溪亭日暮》:**“别有忧愁暗恨生。”**她在这句词中表达的是深深的内心愁绪和无法言说的遗憾。(省略。。。)Human:我是谁AI:这个问题有点哲学意味了!你是在问自我认知吗?还是在想“我是谁”这个更深层次的存在意义?每个人对“我是谁”的答案都不一样,这也许是一个可以不断探索、变化的过程。你是想聊聊这个话题吗?Human:我的名字是什么AI:嗯,我并不知道你的名字哦!但如果你愿意告诉我,我会很高兴记住的。你喜欢什么名字呢?或者,你对名字有什么特别的想法吗?Human:

4.2 ConversationSummaryBufferMemory用法

ConversationSummaryBufferMemory的用法和上面的ConversationBufferWindowMemory基本一致,区别是ConversationSummaryBufferMemory是一个缓冲摘要混合记忆组件,ConversationSummaryBufferMemory支持当历史记忆超过指定的token数量就会使用指定的llm进行摘要的提取,也就是对原本的对话内容进行概括,再存储到记忆组件,这样就起到了节省token的作用。

代码示例如下,为了演示ConversationSummaryBufferMemory的实际效果,max_token_limit指定为200,并且为llm参数传入一个基于gpt-3.5-turbo大语言模型对象。

from operator import itemgetterimport dotenvfrom langchain.memory import ConversationSummaryBufferMemoryfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderfrom langchain_core.runnables import RunnablePassthrough, RunnableLambdafrom langchain_openai import ChatOpenAI# 读取env配置dotenv.load_dotenv()# 1.创建提示词模板prompt = ChatPromptTemplate.from_messages([    MessagesPlaceholder("chat_history"),    ("human", "{question}"),])# 2.构建GPT-3.5模型llm = ChatOpenAI(model="gpt-3.5-turbo")# 3.创建输出解析器parser = StrOutputParser()memory = ConversationSummaryBufferMemory(return_messages=True,                                         max_token_limit=200,                                         llm=ChatOpenAI(model="gpt-3.5-turbo")                                         )# 4.执行链chain = RunnablePassthrough.assign(    chat_history=(RunnableLambda(memory.load_memory_variables) | itemgetter("history"))) | prompt | llm | parserwhile True:    print("========================")    question = input("Human:")    response = chain.invoke({"question": question})    print(f"AI:{response}")    memory.save_context({"human": question}, {"ai": response})    print("========================")    print(f"对话历史信息:{memory.load_memory_variables({})}")

执行结果,首先Human提问详细介绍LangChain框架用法,AI回复的内容非常多,肯定超过了200个token,再次读取聊天历史消息,可以看到,多了一条系统消息,并且是用英文描述的。

这段英文描述就是对之前对话内容的摘要,之所以内容是英文的,因为生成摘要的提示词是LangChian内置的,本身就是英文的,在中文场景下使用需要对提示词进行汉化,可以指定prompt属性为自定义的提示词。

========================Human:详细的介绍一下LangChain框架的用法AI:**LangChain** 是一个用于构建基于大语言模型(LLM)的应用程序的框架。它提供了强大的功能来处理语言模型与外部数据的交互、自动化任务和多种数据源的处理,帮助开发者更高效地构建应用。(中间内容省略)如果你想开始使用 LangChain,可以从基本的 LLM 调用和简单的链式操作入手,逐渐扩展到更复杂的应用场景。========================对话历史信息:{'history': [SystemMessage(content='The human asks for a detailed introduction to the LangChain framework. The AI explains that LangChain is a framework designed for building applications based on large language models (LLMs), offering functionality for interacting with external data, automating tasks, and processing various data sources. The framework is especially useful for scenarios like building chat systems, document retrieval, API calls, and task automation.\n\nThe AI then breaks down LangChain’s core components and usage, starting with LLM integration, prompt templates, chains, agents, memory, document loaders, and tools & APIs. Examples for each component are provided to demonstrate practical implementation.\n\nFinally, the AI compares LangChain to other frameworks, highlighting its advantages such as chain operations, external tool integration, memory management, and modularity, making it a versatile choice for developing complex LLM applications. LangChain is presented as a flexible toolset for tasks ranging from customer service to automated workflows, with an emphasis on its ability to handle complex tasks efficiently.\n\nEND OF SUMMARY.')]}========================Human:

4.3 历史消息的存储

在前两个示例中,程序重启之后,大语言模型的历史记忆就会丢失,因为在BaseChatMemory内部默认使用的聊天消息历史组件是基于内存存储的InMemoryChatMessageHistory对象,接下来我们将以FileChatMessageHistory为例,将历史消息持久化到当前目录的chat_history.txt文件中,示例代码如下:

from operator import itemgetterimport dotenvfrom langchain.memory import ConversationBufferMemoryfrom langchain_community.chat_message_histories import FileChatMessageHistoryfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderfrom langchain_core.runnables import RunnablePassthrough, RunnableLambdafrom langchain_openai import ChatOpenAI# 读取env配置dotenv.load_dotenv()# 1.创建提示词模板prompt = ChatPromptTemplate.from_messages([    MessagesPlaceholder("chat_history"),    ("human", "{question}"),])# 2.构建GPT-3.5模型llm = ChatOpenAI(model="gpt-3.5-turbo")# 3.创建输出解析器parser = StrOutputParser()memory = ConversationBufferMemory(    return_messages=True,    chat_memory=FileChatMessageHistory("chat_history.txt"))# 4.执行链chain = RunnablePassthrough.assign(    chat_history=(RunnableLambda(memory.load_memory_variables) | itemgetter("history"))) | prompt | llm | parserwhile True:    question = input("Human:")    response = chain.invoke({"question": question})    print(f"AI:{response}")    memory.save_context({"human": question}, {"ai": response})

执行上面的程序,进行对话

Human:你好,我是大志,你是AI:你好,大志!我是ChatGPT,很高兴认识你。你今天怎么样?Human:我的名字是AI:哦,你的名字是大志!刚才我理解错了。那你平时喜欢做什么?Human:

执行了两轮对话后,在当前目录就会生成保存了聊天历史消息的chat_history.txt文件。

chat_history.txt 文件内容

[{"type": "human", "data": {"content": "\u4f60\u597d\uff0c\u6211\u662f\u5927\u5fd7\uff0c\u4f60\u662f", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "\u4f60\u597d\uff0c\u5927\u5fd7\uff01\u6211\u662fChatGPT\uff0c\u5f88\u9ad8\u5174\u8ba4\u8bc6\u4f60\u3002\u4f60\u4eca\u5929\u600e\u4e48\u6837\uff1f", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}, {"type": "human", "data": {"content": "\u6211\u7684\u540d\u5b57\u662f", "additional_kwargs": {}, "response_metadata": {}, "type": "human", "name": null, "id": null, "example": false}}, {"type": "ai", "data": {"content": "\u54e6\uff0c\u4f60\u7684\u540d\u5b57\u662f\u5927\u5fd7\uff01\u521a\u624d\u6211\u7406\u89e3\u9519\u4e86\u3002\u90a3\u4f60\u5e73\u65f6\u559c\u6b22\u505a\u4ec0\u4e48\uff1f", "additional_kwargs": {}, "response_metadata": {}, "type": "ai", "name": null, "id": null, "example": false, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": null}}]

关闭程序,重新启动程序,大语言模型依然记得用户的名字。

Human:我的名字是什么AI:你的名字是大志!😄 有什么想分享的关于这个名字的故事吗?Human:

五、总结

本文介绍了LangChain记忆组件的核心概念和使用方法。通过记忆组件有效解决了大语言模型"失忆"的问题,能让AI记住多轮上下文对话。LangChain提供了多种记忆组件,在实际应用中,需要根据具体场景选择合适的记忆组件和存储方式。

开发阶段可以使用基于内存的InMemoryChatMessageHistory,生产环境建议选择FileChatMessageHistoryRedisChatMessageHistory进行持久化存储。需要注意的是,记忆组件会增加token消耗,要在用户体验和成本控制之间找到平衡点。

通过本文的学习,相信你已经能够熟练使用这些记忆组件,构建出有良好用户体验的AI应用,后续将继续深入介绍LangChain的核心模块和高级用法,敬请期待。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

LangChain AI记忆 对话系统 大型语言模型 上下文管理
相关文章