系列文章
Langchain极简教程: 一、Hello Langchain
Langchain极简教程: 二、模型
Langchain极简教程: 三、数据连接
Langchain极简教程: 四、提示词
Langchain极简教程: 五、输出解析器
Langchain极简教程: 六、链
Langchain极简教程: 七、记忆组件
Langchain极简教程: 八、代理 (Agent)
Langchain极简教程: 九、一个完整的RAG案例
简介
大多数LLM应用都具有对话界面。对话的一个重要组成部分是对话历史中的信息。我们将这种存储对话历史中的信息的能力称为"记忆"。LangChain
提供了一系列记忆相关的实用工具。这些工具可以单独使用,也可以无缝地集成到一条链中。
记忆组件需要支持
- 读取
- 写入
注,每条链定义了核心执行逻辑,期望某些输入。一些输入来自用户,另一些可能来自记忆组件。在一次与LLM的交互中,链将与记忆组件交互两次:
- 接收到初始用户输入之后,执行核心逻辑之前,链从记忆组件读取历史,并以此增强用户输入。
- 执行核心逻辑之后,在返回回答之前,链把当前交互的输入和输出写入到记忆中,以便更新对话历史。
LangChain的记忆组件类型
记忆组件需要解决两大问题:
- 历史如何存储?
- 历史如何查询?
本讲通过 LangChain
提供的三种基本记忆组件类型 ConversationBufferMemory
,ConversationBufferWindowMemory
,ConversationSummaryMemory
,介绍它们对于上述问题的解决方案,并分享使用方法。
ConversationBufferMemory
ConversationBufferMemory
是 LangChain
提供的记忆组件类, 它如实地在列表中记录对话历史消息。
写入一次对话
通过 save_context
函数来保存用户输入和模型输出。
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
memory.save_context({"input": "Hi, LangChain!"}, {"output": "Hey!"})
ConversationBufferMemory
的 chat_memory
成员变量有一个 messages
变量。这是一个消息数组。通过如下代码查看消息对象列表
memory.chat_memory.messages
你应该期望看到如下输出:
[HumanMessage(content='Hi, LangChain!', additional_kwargs={}, example=False),
AIMessage(content='Hey!', additional_kwargs={}, example=False)]
当我们需要生成对话历史的文本,作为变量嵌入提示词,可以通过调用函数 load_memory_variables
获得字典对象,其中的键 history
包含了对话历史的字符串值。如下:
memory.load_memory_variables({})
你应该期望看到如下输出:
{'history': 'Human: Hi, LangChain!\nAI: Hey!'}
ConversationBufferMemory
的实现方式简单,在交互次数少,输入输出字符量不大的情况下,非常有效。但是当交互增加,字符数量增多,对话历史的字符数可能导致增强后的提示词tokens数超过上下文限制,最终导致模型调用失败。因此,LangChain
还提供了其他记忆组件类型。
ConversationBufferWindowMemory
ConversationBufferWindowMemory
持续记录对话历史,但只使用最近的K个交互。这种滑动窗口的机制,确保缓存大小不会变得过大。
用法如下:
我们指定滑动窗口的大小为1,表示查询时只返回最近1次交互。
memory = ConversationBufferWindowMemory( k=1)
memory.save_context({"input": "Hi, LangChain!"}, {"output": "Hey!"})
memory.save_context({"input": "Where are you?"}, {"output": "By your side"})
通过 load_memory_variables
读取记忆
memory.load_memory_variables({})
你应该期望看到如下输出:
{'history': 'Human: Where are you?\nAI: By your side'}
我们看看记忆组件中存储的历史交互:
memory.chat_memory.messages
输出:
[HumanMessage(content='Hi, LangChain!', additional_kwargs={}, example=False),
AIMessage(content='Hey!', additional_kwargs={}, example=False),
HumanMessage(content='Where are you?', additional_kwargs={}, example=False),
AIMessage(content='By your side', additional_kwargs={}, example=False)]
可见,组件记忆了所有交互,但是在查询时通过滑动窗口返回指定数量的交互(输入与输出)。
ConversationSummaryMemory
ConversationSummaryMemory
是稍微复杂的记忆类型。这种记忆随着时间的推移总结对话的内容,并将当前的摘要存储在记忆中,然后在需要的时候将对话摘要注入提示词或链中。ConversationSummaryMemory
对于更长的对话交互很有用,因为将过去的历史记录逐字逐句放入提示词中会占用太多Token。
注意,由于需要对于对话历史进行总结,生成摘要,因此 ConversationSummaryMemory
需要LLM的配合。我们在示例代码中将提供OpenAI的模型给 ConversationSummaryMemory
以生成摘要。
用法如下:
from langchain.memory import ConversationSummaryMemory, ChatMessageHistory
from langchain_ollama import OllamaLLM
from langchain_core.prompts import PromptTemplate
from langchain.chains import LLMChain
template = """You are a chatbot having a conversation with a human.
{conversation_history}
Human: {input}
Chatbot:"""
prompt = PromptTemplate(
input_variables=["conversation_history", "input"], template=template
)
memory = ConversationBufferMemory(memory_key="conversation_history")
llm_chain = LLMChain(
llm=OllamaLLM(temperature=0, model="qwen2.5:14b"),
prompt=prompt,
verbose=True,
memory=memory,
)
llm_chain.invoke(input="Where is Paris?")
你应该能看到如下输出:
> Entering new LLMChain chain...
Prompt after formatting:
You are a chatbot having a conversation with a human.
Human: Where is Paris?
Chatbot:
> Finished chain.
{'input': 'Where is Paris?',
'conversation_history': '',
'text': 'Paris is the capital and most populous city of France. It is located in the northern central part of the country, situated on the Seine River. The city is known for its iconic landmarks such as the Eiffel Tower, Notre-Dame Cathedral, and the Louvre Museum, among others.'}
你可能注意到了,从记忆组件中得到的对话历史的文本,相较于原始的对话文字,并没有显著地缩短。原因在于对话的交互只有3次,在这种情况下,摘要的优势并没有显示出来。
下图是不同记忆类型组件随着对话交互的增加,生成的对话历史信息的Token开销趋势。
可见,ConversationSummaryMemory
的Token开销相对平缓,这对于交互多的对话是更有效的。
图中,还展示了我们并没有介绍的类型 Summary Buffer Memory
。顾名思义,这是结合了 Summary
和 Buffer
的优势的一种记忆类型。
完整代码请参考本节课程的示例代码。
总结
本节课程中,我们学习了什么是 记忆组件
,并通过三种基本记忆组件类型 ConversationBufferMemory
,ConversationBufferWindowMemory
,ConversationSummaryMemory
,介绍它们的工作原理和使用方法。本课只介绍了 LangChain
提供的部分记忆组件,更多类型请参考官方文档 Memory Types。