LangChain 内存(Memory)

1. 为什么需要内存?

大型语言模型(LLM)本身是无状态的。这意味着每次你向 LLM 发送一个请求(Prompt),它都会独立处理这个请求,完全不记得之前任何的交互。这在构建一次性问答应用时没问题,但在需要多轮对话(比如聊天机器人、智能客服)的场景中,LLM 需要"记住"之前的对话内容,才能理解上下文并做出连贯的回复。

内存就是 LangChain 解决这个问题的方案。它允许你在 LLM 应用中注入和管理对话历史,让 LLM 具备"长期"记忆的能力。

简单来讲就是存储历史记录,然后在每次使用 LLM 时,不仅传入当前输入,还有历史记录

2. 内存的基本工作原理

在 LangChain 中,内存通常扮演着连接用户输入LLM 提示的桥梁。

  1. 保存历史:每次用户和 LLM 进行交互后,内存组件都会捕获并存储这次对话的输入和输出。
  2. 加载历史:在下一次 LLM 调用之前,内存会根据需要从其存储中加载相关的对话历史。
  3. 注入提示:加载的对话历史会被格式化并注入到 LLM 的提示(Prompt)中,作为上下文的一部分。这样,LLM 就能"看到"之前的对话内容,从而理解当前问题的背景。

3. 最简单的内存:对话缓冲区(ConversationBufferMemory)

ConversationBufferMemory 是 LangChain 中最基础也是最常用的内存类型。它非常简单粗暴:记住所有对话的原始文本。它会将完整的对话历史原封不动地存储起来,并在每次调用时将所有历史添加到提示中。
ConversationBufferMemory 流程 发送给LLM 保存到内存 从内存加载 添加到Prompt LLM Chain/Agent 用户输入 LLM LLM 输出 ConversationBufferMemory

python 复制代码
# memory_buffer_example.py

from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import os

# --- 配置部分 ---
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

if not os.getenv("OPENAI_API_KEY"):
    print("错误:请设置环境变量 OPENAI_API_KEY 或在代码中取消注释并设置您的密钥。")
    exit()

print("--- ConversationBufferMemory 示例开始 ---")

# 1. 定义 LLM
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)

# 2. 定义内存
# memory_key 是在 Prompt 中用来引用对话历史的变量名
# return_messages=True 表示内存返回的是消息对象列表,而不是单个字符串
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
print("已创建 ConversationBufferMemory。")

# 3. 定义带有历史占位符的 Prompt
# MessagesPlaceholder 用于告诉 Prompt 在这里插入消息列表 (chat_history)
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个友好的AI助手,擅长进行多轮对话。"),
    MessagesPlaceholder(variable_name="chat_history"), # 聊天历史的占位符
    ("human", "{input}") # 当前用户输入
])
print("已创建包含 chat_history 占位符的 Prompt。")

# 4. 创建 ConversationChain
# ConversationChain 是 LangChain 提供的一个预构建的链,专为处理对话而设计
# 它会自动处理内存的注入和更新
conversation = ConversationChain(
    llm=llm,
    memory=memory,
    prompt=prompt,
    verbose=True # 打印详细日志,可以看到内存注入到 Prompt 的过程
)
print("已创建 ConversationChain。")

# 5. 进行多轮对话
print("\n--- 开始对话 ---")

# 第一轮
print("\n用户: 你好,我叫小明。你叫什么名字?")
response1 = conversation.invoke({"input": "你好,我叫小明。你叫什么名字?"})
print(f"AI: {response1['response']}")

# 第二轮
print("\n用户: 很高兴认识你!我之前告诉你我叫什么名字了?")
response2 = conversation.invoke({"input": "很高兴认识你!我之前告诉你我叫什么名字了?"})
print(f"AI: {response2['response']}")

# 第三轮
print("\n用户: 帮我写一句关于编程的诗。")
response3 = conversation.invoke({"input": "帮我写一句关于编程的诗。"})
print(f"AI: {response3['response']}")

print("\n--- 对话结束 ---")
print("\n--- ConversationBufferMemory 示例结束 ---")
  • ConversationBufferMemory 将所有交互都作为 HumanMessageAIMessage 存储起来。
  • MessagesPlaceholder 是一个非常关键的组件,它告诉 LangChain 在构建最终发送给 LLM 的提示时,应该将 chat_history 这个变量的内容(即内存中的消息列表)插入到这里。
  • ConversationChain 是一个便利的链,它自动处理了内存的读写,简化了对话应用的构建。

4. 限制历史长度:对话缓冲区窗口内存(ConversationBufferWindowMemory)

ConversationBufferMemory 的一个缺点是,如果对话很长,内存中的历史会不断增长,导致每次发送给 LLM 的提示越来越长,最终可能超出 LLM 的上下文窗口限制(Context Window Limit),并增加 API 成本。

ConversationBufferWindowMemory 解决了这个问题,它只记住最近 N 轮的对话
ConversationBufferWindowMemory 流程 发送给LLM 保存到内存 (仅保留最新N轮) 从内存加载 (仅加载最新N轮) 添加到Prompt LLM Chain/Agent 用户输入 LLM LLM 输出 ConversationBufferWindowMemory

python 复制代码
# memory_window_example.py

from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferWindowMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import os

# --- 配置部分 ---
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

if not os.getenv("OPENAI_API_KEY"):
    print("错误:请设置环境变量 OPENAI_API_KEY 或在代码中取消注释并设置您的密钥。")
    exit()

print("--- ConversationBufferWindowMemory 示例开始 ---")

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)

# 定义窗口大小为 2,表示只记住最近 2 轮(4条消息:2用户+2AI)对话
memory = ConversationBufferWindowMemory(memory_key="chat_history", return_messages=True, k=2)
print("已创建 ConversationBufferWindowMemory,窗口大小 k=2。")

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个友好的AI助手,只记得最近的对话。"),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{input}")
])

conversation = ConversationChain(
    llm=llm,
    memory=memory,
    prompt=prompt,
    verbose=True
)
print("已创建 ConversationChain。")

# 5. 进行多轮对话,观察内存如何截断
print("\n--- 开始对话 ---")

# 第一轮
print("\n用户: 我喜欢吃苹果。")
response1 = conversation.invoke({"input": "我喜欢吃苹果。"})
print(f"AI: {response1['response']}")

# 第二轮
print("\n用户: 你呢?你喜欢什么水果?")
response2 = conversation.invoke({"input": "你呢?你喜欢什么水果?"})
print(f"AI: {response2['response']}")

# 第三轮 - 此时第一轮对话(用户说"我喜欢吃苹果")应该被"遗忘"
print("\n用户: 我之前喜欢什么水果来着?")
response3 = conversation.invoke({"input": "我之前喜欢什么水果来着?"})
print(f"AI: {response3['response']}") # AI可能无法准确回答,因为它忘记了第一轮

# 第四轮 - 此时第二轮对话应该被遗忘
print("\n用户: 你呢?你刚才喜欢什么水果?")
response4 = conversation.invoke({"input": "你呢?你刚才喜欢什么水果?"})
print(f"AI: {response4['response']}")

print("\n--- 对话结束 ---")
print("\n--- ConversationBufferWindowMemory 示例结束 ---")
  • k=2 参数控制了窗口大小。这意味着内存将只保留最近的 2 轮完整的对话(即 2 条用户消息和 2 条 AI 消息)。
  • 你会发现,在第三轮对话中,模型可能无法回忆起第一轮用户提到的"苹果",因为它已经超出了窗口范围。

5. 总结历史:对话摘要内存(ConversationSummaryMemory)

ConversationBufferWindowMemory 虽然限制了历史长度,但可能会丢失早期对话的关键信息。ConversationSummaryMemory 旨在解决这个问题:它不直接存储所有历史,而是使用一个 LLM 对对话历史进行摘要,然后将这个摘要作为上下文提供给主 LLM。

这样,无论对话多长,每次传递给 LLM 的都是一个简洁的摘要,既能保持上下文,又不会超出令牌限制。
ConversationSummaryMemory 流程 发送给LLM 添加到历史 LLM生成摘要 摘要添加到Prompt LLM Chain/Agent 用户输入 LLM LLM 输出 完整对话历史 ConversationSummaryMemory

python 复制代码
# memory_summary_example.py

from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationSummaryMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
import os

# --- 配置部分 ---
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

if not os.getenv("OPENAI_API_KEY"):
    print("错误:请设置环境变量 OPENAI_API_KEY 或在代码中取消注释并设置您的密钥。")
    exit()

print("--- ConversationSummaryMemory 示例开始 ---")

# 定义一个用于生成摘要的 LLM
# 摘要LLM可以是一个更小的、成本更低的模型,或者与主LLM相同
summary_llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
main_llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)

# 1. 定义内存
# memory_key 和 return_messages 类似之前的内存
# llm 参数指定了用于生成摘要的 LLM
memory = ConversationSummaryMemory(llm=summary_llm, memory_key="chat_history", return_messages=True)
print("已创建 ConversationSummaryMemory。")

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个友好的AI助手,能记住所有对话的重要摘要。"),
    MessagesPlaceholder(variable_name="chat_history"), # 这里传入的是摘要
    ("human", "{input}")
])

conversation = ConversationChain(
    llm=main_llm,
    memory=memory,
    prompt=prompt,
    verbose=True
)
print("已创建 ConversationChain。")

# 5. 进行多轮对话,观察内存如何进行摘要
print("\n--- 开始对话 ---")

# 第一轮
print("\n用户: 我的项目遇到了一个问题,需要你的帮助。我正在开发一个Python脚本,它需要处理大量文本数据。")
response1 = conversation.invoke({"input": "我的项目遇到了一个问题,需要你的帮助。我正在开发一个Python脚本,它需要处理大量文本数据。"})
print(f"AI: {response1['response']}")

# 第二轮
print("\n用户: 具体来说,我需要对文本进行分词和词性标注。你有什么建议的库吗?")
response2 = conversation.invoke({"input": "具体来说,我需要对文本进行分词和词性标注。你有什么建议的库吗?"})
print(f"AI: {response2['response']}")

# 第三轮
print("\n用户: 好的,我会试试 NLTK 和 SpaCy。你认为哪个更适合大型项目?")
response3 = conversation.invoke({"input": "好的,我会试试 NLTK 和 SpaCy。你认为哪个更适合大型项目?"})
print(f"AI: {response3['response']}")

# 第四轮 - 此时内存会包含前三轮的摘要
print("\n用户: 好的,谢谢你的建议。我的项目主要是关于中文文本处理。")
response4 = conversation.invoke({"input": "好的,谢谢你的建议。我的项目主要是关于中文文本处理。"})
print(f"AI: {response4['response']}")

print("\n--- 对话结束 ---")
# 打印最终的摘要内容
print("\n当前内存中的摘要:")
print(memory.buffer)

print("\n--- ConversationSummaryMemory 示例结束 ---")

说明:

  • ConversationSummaryMemory 需要一个 llm 参数来执行摘要任务。这个 llm 可以是与主 LLM 相同的模型,也可以是专门为摘要优化的模型。
  • 通过 verbose=True 观察输出,你会发现每次调用时,LLM 接收到的 chat_history 变量会是一个不断更新的摘要字符串,而不是原始的完整消息列表。

6. 其他常用内存类型

LangChain 还提供了其他更高级或更具体用途的内存类型:

  • ConversationSummaryBufferMemory : 结合了 ConversationBufferWindowMemoryConversationSummaryMemory 的特点。它会保留最近 N 轮的完整对话,而将 N 轮之前的对话进行摘要。这样可以在短期内提供精确上下文,同时长期保持摘要记忆。
  • VectorStoreRetrieverMemory: 这种内存将对话历史存储在向量数据库中。当需要回忆信息时,它会像 RAG 那样,根据当前查询在向量数据库中检索最相关的历史片段,而不是全部或摘要。这对于需要记忆超长对话或从大量历史中检索特定信息非常有用。
  • EntityMemory: 专注于从对话中识别和记忆特定的实体(如人名、地点、概念),并将其存储在一个结构化(通常是 JSON)的知识图中。当对话中再次提到这些实体时,LLM 可以直接引用其存储的信息。

7. 选择合适的内存策略

  • ConversationBufferMemory: 适用于短对话、简单场景或调试。
  • ConversationBufferWindowMemory: 适用于需要限制上下文长度、但仍需保持一定近期对话完整性的场景。
  • ConversationSummaryMemory: 适用于长对话,需要保持核心上下文但又不能超出 LLM 上下文窗口的场景。
  • ConversationSummaryBufferMemory: 结合了短期精确记忆和长期摘要记忆的优点。
  • VectorStoreRetrieverMemory: 适用于需要从海量、复杂对话历史中智能检索相关片段的场景,或构建具备"知识库"的聊天机器人。
  • EntityMemory: 适用于需要记忆和跟踪特定实体信息(如客户档案、产品属性)的对话场景。
相关推荐
DAWN_T1715 分钟前
Transforms
pytorch·python·机器学习·jupyter·pycharm
一百天成为python专家27 分钟前
python库之jieba 库
开发语言·人工智能·python·深度学习·机器学习·pycharm·python3.11
搬砖的小码农_Sky41 分钟前
AI:机器人行业发展现状
人工智能·机器人
深圳市快瞳科技有限公司1 小时前
破解多宠管理难题,端侧AI重新定义宠物智能硬件
人工智能·智能硬件·宠物
Blossom.1181 小时前
用一张“冰裂纹”石墨烯薄膜,让被动散热也能做 AI 推理——基于亚波长裂纹等离激元的零功耗温度-逻辑门
人工智能·深度学习·神经网络·目标检测·机器学习·机器人·语音识别
cylat1 小时前
Day59 经典时序预测模型3
人工智能·python·深度学习·神经网络
萤火虫儿飞飞1 小时前
从基础加热到智能生态跨越:艾芬达用创新重构行业价值边界!
大数据·人工智能·重构
aneasystone本尊1 小时前
学习 RAGFlow 的系统架构
人工智能
Codebee1 小时前
OneCode3.0低代码引擎核心技术:常用动作事件速查手册及注解驱动开发详解
人工智能·架构
AI大模型技术社2 小时前
✅2025全网最具权威深度解析并手写RAG Pipeline
人工智能·llm·掘金·日新计划