核心模块(Modules)
记忆(Memory)
索引(Index)

[Beta] 记忆(Memory)

大多数 LLM 应用都有对话式界面。对话的一个关键组成部分是能够引用对话中较早引入的信息。 至少,对话系统应该能够直接访问过去消息的某个窗口。 更复杂的系统需要一个不断更新的世界模型,使它能够做诸如维护实体及其关系信息之类的事情。

我们把这种存储过去交互信息的能力称为“记忆(memory)”。 LangChain 提供了许多工具来为系统添加记忆。 这些工具既可以单独使用,也可以无缝地集成到链中。

LangChain 中大多数与记忆相关的功能都被标记为 beta。原因有两个:

  1. 大多数功能(有一些例外,见下文)尚未达到生产就绪。

  2. 大多数功能(有一些例外,见下文)与旧的链(Legacy chain)配合使用,而不是较新的 LCEL 语法。

主要的例外是 ChatMessageHistory 功能。该功能在很大程度上已达到生产就绪,并且能与 LCEL 集成。

  • LCEL Runnable:关于如何将 ChatMessageHistory 与 LCEL runnable 一起使用的概览,请参阅这些文档

  • 集成 (opens in a new tab):关于各种 ChatMessageHistory 集成的介绍,请参阅这些文档

简介

记忆系统需要支持两个基本操作:读取和写入。 回忆一下,每个链都定义了一些核心执行逻辑,它期望特定的输入。 其中一些输入直接来自用户,但另一些输入可以来自记忆。 在一次运行中,链会与其记忆系统交互两次。

  1. 在接收到初始用户输入之后、执行核心逻辑之前,链会从其记忆系统读取(READ)并扩充用户输入。
  2. 在执行核心逻辑之后、返回答案之前,链会把当前运行的输入和输出写入(WRITE)记忆,以便在未来的运行中可以引用它们。

示意图,说明对话式界面中记忆系统的 READ 和 WRITE 操作。

将记忆构建到系统中

任何记忆系统的两个核心设计决策是:

  • 状态如何存储
  • 状态如何查询

存储:聊天消息列表

任何记忆的底层都是所有聊天交互的历史。 即使这些历史并非全部被直接使用,它们也需要以某种形式被存储。 LangChain 记忆模块的关键部分之一就是一系列用于存储这些聊天消息的集成, 从内存列表到持久化数据库。

查询:在聊天消息之上的数据结构和算法

维护一个聊天消息列表相当直接。 不那么直接的是建立在聊天消息之上的数据结构和算法,它们提供那些消息中最有用的视图。

一个非常简单的记忆系统可能每次运行只返回最近的消息。稍微复杂一点的记忆系统可能返回过去 K 条消息的简洁摘要。 更复杂的系统可能从存储的消息中提取实体,只返回当前运行中引用到的实体信息。

每个应用对记忆如何查询可能有不同的需求。记忆模块应该让上手简单记忆系统变得容易,并在需要时让你能编写自己的自定义系统。

  • 记忆类型:构成 LangChain 所支持记忆类型的各种数据结构和算法

入门

让我们来看看 LangChain 中记忆实际上是什么样的。 这里我们将介绍与任意记忆类交互的基础知识。

让我们看看如何在链中使用 ConversationBufferMemory。 ConversationBufferMemory 是一种极其简单的记忆形式,它只是把聊天消息保存在一个缓冲区中, 并将它们传入提示模板。

from langchain.memory import ConversationBufferMemory
 
memory = ConversationBufferMemory()
memory.chat_memory.add_user_message("hi!")
memory.chat_memory.add_ai_message("what's up?")

在链中使用记忆时,有几个关键概念需要理解。 请注意,这里我们介绍的是对大多数记忆类型都适用的通用概念。 每种单独的记忆类型很可能都有自己的参数和需要理解的概念。

从记忆返回哪些变量

进入链之前,会从记忆读取各种变量。 这些变量有特定的名称,需要与链期望的变量对齐。 你可以通过调用 memory.load_memory_variables({}) 查看这些变量是什么。 注意,我们传入的空字典只是真实变量的占位符。 如果你使用的记忆类型依赖于输入变量,你可能需要传入一些变量。

memory.load_memory_variables({})
    {'history': "Human: hi!\nAI: what's up?"}

在本例中,你可以看到 load_memory_variables 返回单个键 history。 这意味着你的链(很可能还有你的提示)应该期望一个名为 history 的输入。 你通常可以通过记忆类上的参数来控制这个变量。 例如,如果你希望记忆变量以 chat_history 为键返回,可以这样做:

memory = ConversationBufferMemory(memory_key="chat_history")
memory.chat_memory.add_user_message("hi!")
memory.chat_memory.add_ai_message("what's up?")
    {'chat_history': "Human: hi!\nAI: what's up?"}

控制这些键的参数名可能因记忆类型而异,但重要的是要理解:(1) 这是可控的,(2) 如何控制它。

记忆是字符串还是消息列表

最常见的记忆类型之一涉及返回一个聊天消息列表。 它们既可以作为单个字符串全部拼接在一起返回(当它们将被传入 LLM 时有用), 也可以作为 ChatMessage 列表返回(当传入 ChatModel 时有用)。

默认情况下,它们作为单个字符串返回。 要以消息列表返回,你可以设置 return_messages=True

memory = ConversationBufferMemory(return_messages=True)
memory.chat_memory.add_user_message("hi!")
memory.chat_memory.add_ai_message("what's up?")
    {'history': [HumanMessage(content='hi!', additional_kwargs={}, example=False),
  AIMessage(content='what's up?', additional_kwargs={}, example=False)]}

哪些键被保存到记忆

链经常会接收或返回多个输入/输出键。 在这些情况下,我们怎么知道要把哪些键保存到聊天消息历史中? 这通常可以通过记忆类型上的 input_key 和 output_key 参数控制。 它们默认为 None——如果只有一个输入/输出键,就会自动使用它。 然而,如果有多个输入/输出键,那么你 必须 指定使用哪一个的名称。

端到端示例

最后,让我们看看在链中使用它。 我们将使用 LLMChain,并演示如何同时配合 LLM 和 ChatModel 使用。

使用 LLM

from langchain_openai import OpenAI
from langchain_core.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
 
 
llm = OpenAI(temperature=0)
# 注意 "chat_history" 出现在提示模板中
template = """You are a nice chatbot having a conversation with a human.
 
Previous conversation:
{chat_history}
 
New human question: {question}
Response:"""
prompt = PromptTemplate.from_template(template)
# 注意我们需要对齐 `memory_key`
memory = ConversationBufferMemory(memory_key="chat_history")
conversation = LLMChain(
    llm=llm,
    prompt=prompt,
    verbose=True,
    memory=memory
)
# 注意我们只传入 `question` 变量——`chat_history` 由记忆填充
conversation({"question": "hi"})

使用 ChatModel

from langchain_openai import ChatOpenAI
from langchain_core.prompts import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
 
 
llm = ChatOpenAI()
prompt = ChatPromptTemplate(
    messages=[
        SystemMessagePromptTemplate.from_template(
            "You are a nice chatbot having a conversation with a human."
        ),
        # 这里的 `variable_name` 必须与 memory 对齐
        MessagesPlaceholder(variable_name="chat_history"),
        HumanMessagePromptTemplate.from_template("{question}")
    ]
)
# 注意我们设置 `return_messages=True` 以适配 MessagesPlaceholder
# 注意 `"chat_history"` 与 MessagesPlaceholder 的名称对齐。
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
conversation = LLMChain(
    llm=llm,
    prompt=prompt,
    verbose=True,
    memory=memory
)
# 注意我们只传入 `question` 变量——`chat_history` 由记忆填充
conversation({"question": "hi"})

后续步骤

以上就是入门介绍! 关于更高级主题的演练,请参阅其他章节, 例如自定义记忆、多个记忆等。