LangChain实战07-记忆|豆包MarsCode AI 刷题

202 阅读6分钟

一.概念引入

ChatGPT之所以能够记得你之前说过的话,正是因为它使用了记忆(Memory)机制,记录了之前的对话上下文,并且把这个上下文作为提示的一部分,在最新的调用中传递给了模型。在聊天机器人的构建中,记忆机制非常重要。

二.使用ConversationChain

from langchain import OpenAI
from langchain.chains import ConversationChain

# 初始化大语言模型
llm = OpenAI(
    temperature=0.5,
    model_name="gpt-3.5-turbo-instruct"
)

# 初始化对话链
conv_chain = ConversationChain(llm=llm)

# 打印对话的模板
print(conv_chain.prompt.template)

这里我们使用ConversationChain,这个Chain最主要的特点是,它提供了包含AI 前缀和人类前缀的对话摘要格式,这个对话格式和记忆机制结合得非常紧密。

屏幕截图 2024-11-26 220148.png history存储会话记忆的地方,也就是人类和人工智能之间对话历史的信息。input新输入的地方,你可以把它看成是和ChatGPT对话时文本框中的输入。这两个参数会通过提示模板传递给 LLM,我们希望返回的输出只是对话的延续。

那么当有了history参数,以及HumanAI这两个前缀,我们就能够把历史对话信息存储在提示模板中,并作为新的提示内容在新一轮的对话过程中传递给模型。—— 这就是记忆机制的原理

三.使用ConversationBufferMemory

这里我们在构造对话链中添加了记忆这个模块并调用ConversationBufferMemory()方法。

# 导入所需的库
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.chains.conversation.memory import ConversationBufferMemory

# 初始化大语言模型
llm = ChatOpenAI(
    model=os.environ.get("LLM_MODELEND"),
    temperature=0.5,
)

# 初始化对话链
conversation = ConversationChain(llm=llm, memory=ConversationBufferMemory())

# 第一天的对话
# 回合1
conversation("我姐姐明天要过生日,我需要一束生日花束。")
print("第一次对话后的记忆:", conversation.memory.buffer)

1.我们来看看输出结果:

屏幕截图 2024-11-26 225050.png 2.接着我们继续对话。下一轮对话中,这些记忆会作为一部分传入提示。

# 回合2
conversation("她喜欢粉色玫瑰,颜色是粉色的。")
print("第二次对话后的记忆:", conversation.memory.buffer)

屏幕截图 2024-11-26 225809.png 3.下面,我们继续对话,同时打印出此时提示模板的信息。

# 回合3 (第二天的对话)
conversation("我又来了,还记得我昨天为什么要来买花吗?")
print("/n第三次对话后时提示:/n", conversation.prompt.template)
print("/n第三次对话后的记忆:/n", conversation.memory.buffer)

屏幕截图 2024-11-26 230323.png 这些聊天历史信息,都被传入了ConversationChain的提示模板中的history参数,构建出了包含聊天记录的新的提示输入。

有了记忆机制,LLM能够了解之前的对话内容,这样简单直接地存储所有内容为LLM提供了最大量的信息,但是新输入中也包含了更多的Token(所有的聊天历史记录),这意味着响应时间变慢和更高的成本。而且,当达到LLM的令牌数(上下文窗口)限制时,太长的对话无法被记住。

下面我们来看看针对Token太多、聊天历史记录过长的一些解决方案。

四.使用ConversationBufferWindowMemory

ConversationBufferWindowMemory 缓冲窗口记忆,它的思路就是只保存最新最近的几次人类和AI的互动。因此,它在之前的“缓冲记忆”基础上增加了一个窗口值 k。这意味着我们只保留一定数量的过去互动,然后“忘记”之前的互动。

from langchain.chains.conversation.memory import ConversationBufferWindowMemory

# 初始化对话链
conversation = ConversationChain(llm=llm, memory=ConversationBufferWindowMemory(k=1))

这里和上面不同的是记忆调用ConversationBufferWindowMemory()方法,令k=1意味着窗口只会记住与AI之间的最新的互动,即只保留上一次的人类回应和AI的回应。

尽管这种方法不适合记住遥远的互动,但它非常擅长限制使用的Token数量。如果只需要记住最近的互动,缓冲窗口记忆是一个很好的选择。但是,如果需要混合远期和近期的互动信息,则还有其他选择。

五.使用ConversationSummaryMemory

ConversationSummaryMemory对话总结记忆)的思路就是将对话历史进行汇总,然后再传递给history参数。这种方法旨在通过对之前的对话进行汇总来避免过度使用 Token。

它有这么几个核心特点:

  1. 汇总对话:此方法不是保存整个对话历史,而是每次新的互动发生时对其进行汇总,然后将其添加到之前所有互动的“运行汇总”中。
  2. 使用LLM进行汇总:该汇总功能由另一个LLM驱动,这意味着对话的汇总实际上是由AI自己进行的。
  3. 适合长对话:对于长对话,此方法的优势尤为明显。虽然最初使用的 Token 数量较多,但随着对话的进展,汇总方法的增长速度会减慢。与此同时,常规的缓冲内存模型会继续线性增长。
from langchain.chains.conversation.memory import ConversationSummaryBufferMemory

# 初始化对话链
conversation = ConversationChain(
    llm=llm, memory=ConversationSummaryMemory(llm=llm)
)

记忆调用ConversationSummaryMemory()方法,并且输入了模型,这个参数用来总结对话。这个帮我们总结对话的LLM,和用来回答问题的LLM,可以是同一个大模型,也可以是不同的大模型

屏幕截图 2024-11-27 151806.png 我们看到这里的 'history',不再是之前人类和AI对话的简单复制粘贴,而是经过了总结和整理之后的一个综述信息。

它的优点是对于长对话,可以减少使用的 Token 数量,因此可以记录更多轮的对话信息,使用起来也直观易懂。

不过,它的缺点是,对于较短的对话,可能会导致更高的 Token 使用。另外,对话历史的记忆完全依赖于中间汇总LLM的能力,还需要为汇总LLM使用 Token,这增加了成本,且并不限制对话长度。

六.使用ConversationSummaryBufferMemory

最后一种记忆机制是ConversationSummaryBufferMemory,即对话总结缓冲记忆,它是一种混合记忆模型,结合了上述各种记忆机制,包括ConversationSummaryMemory ConversationBufferWindowMemory的特点。这种模型旨在在对话中总结早期的互动,同时尽量保留最近互动中的原始内容。

from langchain.chains.conversation.memory import ConversationSummaryBufferMemory

# 初始化对话链
conversation = ConversationChain(
    llm=llm, memory=ConversationSummaryBufferMemory(llm=llm, max_token_limit=300)
)

记忆调用ConversationSummaryBufferMemory()方法,并且输入了模型和最大token限制。

通过max_token_limit这个参数,当最新的对话文字长度在300字之内的时候,LangChain会记忆原始对话内容;当对话文字超出了这个参数的长度,那么模型就会把所有超过预设长度的内容进行总结,以节省Token数量。

七.总结

1.四种记忆机制的比较如下:

image.png 2.当对话轮次逐渐增加时,各种记忆机制对Token的消耗数量:

image.png 以上就是学习记忆的全部内容了。