让应用程序拥有记忆最直接的方式是存储整个对话历史,并将其包含在每一个后续请求中。这种做法常被称为缓冲区记忆。它会逐字记录所有用户输入和模型输出,确保大型语言模型(LLM)在对话的下一轮拥有完整的上下文信息。这种方式简单且对需要回顾具体细节的短对话很有用。在现代LangChain应用中,这通过管理一个聊天历史存储并为每次新交互将完整的消息列表注入到提示模板中来实现。缓冲区记忆的工作原理其机制很直接。消息被收集并存储在一个历史对象中。当链执行时,这个过去消息列表会被取回并插入到发送给大型语言模型的提示中,通常是使用一个占位符变量。这为模型提供了完整的对话线索。下图说明了这一循环过程。每次用户发送消息时,系统都会更新消息历史,然后该历史用于构建下一次大型语言模型调用的提示。digraph G { rankdir=TB; graph [fontname="sans-serif"]; node [shape=box, style="rounded,filled", fontname="sans-serif", fillcolor="#e9ecef", color="#868e96"]; edge [color="#495057", fontname="sans-serif"]; UserInput [label="用户输入", fillcolor="#bac8ff"]; Chain [label="可运行链"]; Memory [label="消息历史存储\n(维护日志)", fillcolor="#b2f2bb"]; Prompt [label="提示模板\n(历史 + 新输入)", fillcolor="#ffec99"]; LLM [label="LLM"]; AIOutput [label="AI 输出", fillcolor="#bac8ff"]; UserInput -> Chain; Chain -> Memory [label="1. 更新存储"]; Chain -> Prompt; Memory -> Prompt [label="2. 注入消息"]; Prompt -> LLM [label="3. 发送给模型"]; LLM -> AIOutput; }带有缓冲区记忆的对话链的流程。历史存储会用最新的交流更新,然后用于填充下一个提示的上下文。实现缓冲区记忆我们来实践一下。要实现这种模式,我们使用 RunnableWithMessageHistory 来包装我们的链。这个组件会自动处理从历史存储读取和写入的逻辑。首先,请确保您已设置好环境变量,例如您的 OPENAI_API_KEY。import os from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.chat_history import InMemoryChatMessageHistory from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_core.output_parsers import StrOutputParser # 设置您的 API 密钥 # os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" # 1. 初始化 LLM llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) # 2. 创建提示模板 # 我们使用占位符来注入对话历史 prompt = ChatPromptTemplate.from_messages([ ("system", "以下是人类与人工智能之间的一次友好对话。AI健谈,并会从其上下文中提供大量具体细节。如果AI不知道某个问题的答案,它会如实说自己不知道。"), ("placeholder", "{history}"), ("human", "{input}"), ]) # 3. 创建链 chain = prompt | llm | StrOutputParser() # 4. 设置记忆管理 # 我们需要一个字典来存储不同会话的历史记录 store = {} def get_session_history(session_id: str): if session_id not in store: store[session_id] = InMemoryChatMessageHistory() return store[session_id] # 用消息历史功能包装链 conversation = RunnableWithMessageHistory( chain, get_session_history, input_messages_key="input", history_messages_key="history", ) # 开始对话 # 我们必须提供一个 session_id 配置来跟踪独立的对话 config = {"configurable": {"session_id": "alex_session"}} response1 = conversation.invoke( {"input": "Hi, my name is Alex. What's the biggest planet in our solar system?"}, config=config ) print(response1) # 继续对话 response2 = conversation.invoke( {"input": "Great, and what's its most famous feature?"}, config=config ) print(response2) # 模型记住了上下文(“木星”) response3 = conversation.invoke( {"input": "What was the name I gave you earlier?"}, config=config ) print(response3)当您运行此代码时,RunnableWithMessageHistory 会查找会话历史(如果不存在则创建),并将存储的消息注入到 {history} 占位符中。对于第二轮,系统会构建一个类似于这样的提示:以下是人类与人工智能之间的一次友好对话... [系统消息] [人类消息]: 你好,我叫Alex。我们太阳系中最大的行星是什么? [AI 消息]: 你好,Alex!我们太阳系中最大的行星是木星... [人类消息]: 太棒了,它最著名的特征是什么?历史对象已追加了第一次人机交流,为模型理解新问题指的是木星提供了必要的上下文。优点与局限这种缓冲区记忆做法的主要优点是它的完美回顾能力。模型收到完整、未经更改的历史记录,这最大限度地减少了在中短对话中误解上下文的风险。然而,主要局限在于令牌消耗。随着对话的继续,存储的历史会线性增长。这会带来两个直接后果:成本增加: 历史中的每个令牌都会随每一次新的 API 调用发送,这会大幅增加长时间对话的成本。上下文窗口限制: 最终,对话历史加上新的输入将超出大型语言模型(LLM)的最大上下文窗口(例如,4,096 或 16,384 个令牌)。当这种情况发生时,API 调用将失败。由于这一局限,这种全缓冲区方法最适用于预期对话相对简短的应用程序,例如处理单个问题的客户支持聊天机器人或简单的任务型助手。对于需要长期记忆的应用程序,您将需要更进阶的做法,我们将在下文介绍。