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
# memory_buffer_example.pyfrom 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
# memory_window_example.pyfrom 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
# memory_summary_example.pyfrom 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: 适用于需要记忆和跟踪特定实体信息(如客户档案、产品属性)的对话场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/88916.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88916.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于定制开发开源AI智能名片S2B2C商城小程序的社群游戏定制策略研究

摘要:本文聚焦社群游戏定制领域,深入探讨以社群文化和用户偏好为导向的定制策略。通过分析互动游戏活动、社群文化塑造等关键要素,结合定制开发开源AI智能名片S2B2C商城小程序的技术特性,提出针对性游戏定制方案。研究旨在提升社群…

自动驾驶决策与规划

目录 自动驾驶决策与规划概述 决策与规划体系结构 分层递阶式决策规划 反应式体系结构 混合式体系结构 决策与规划系统的关键环节 路径规划 轨迹规划 行为决策 异常处理 自动驾驶的路径规划技术 维诺图法 栅格法 Dijkstra算法 A*算法 自动驾驶的行为决策方法 …

C++编译期计算:常量表达式(constexpr)全解析

在C性能优化领域,"将计算尽可能转移到编译期"是一条黄金法则。编译期计算(Compile-Time Computation)能显著减少程序运行时的开销,提升执行效率,同时还能在编译阶段暴露潜在错误。C11引入的constexpr关键字及…

【micro:bit】从入门到放弃(一):在线、离线版本的使用

1、离线版 micro:bit 1)下载地址 https://makecode.microbit.org/offline-app 2)双击安装包,makecode-microbit-setup-win64.exe,自动安装,安装成功后图标如下图所示 3)运行程序,查看版本信息 4)主界面如下 5)编程界面 点击“新建项目”或者“导入”进入编程界…

Flink Forward Asia 2025 主旨演讲精彩回顾

作为 Apache Flink 社区备受瞩目的年度盛会之一,由阿里云承办的 Flink Forward Asia 2025 于 7 月 3 日在新加坡正式拉开帷幕。From Real-Time Data Analytics to Real-Time AI,Flink 生态全面拥抱 AI本次大会上,Apache Flink 中文社区发起人…

车道偏离预警(LDW)功能介绍

车道偏离预警(LDW)功能介绍 LDW功能 通过摄像头监测前方车道线,当车辆偏离车道线时,系统发出报警提醒。 系统框图报警条件 最早报警线最迟报警线报警临界线 设置在 最早/最迟报警线 之间接口定义 输入/输出系统算法 横向偏离速度模…

软考(软件设计师)计算机网络-网络层

obsidian做markdown笔记太爽了,康奈尔模板笔记看起来舒服,CSDN试了几种方式,不支持,只能贴图了,将就看吧😏(狗头保命)工作原理图解 #mermaid-svg-e5Tgpt26jUftujKn {font-family:&qu…

nginx反向代理实现跨域请求

记录一个项目中遇到的跨域请求问题,解决跨域问题无非几种办法:1、后端解决,推荐的做法,可以看我之前写的文章,fastadmin的后台配置跨域;2、前端配置proxy代理(开发环境用)&#xff1…

利用Wisdom SSH高效搭建CI/CD工作流

在软件开发流程中,CI/CD工作流对于提升效率与确保质量起着关键作用。Wisdom SSH作为一款强大工具,其官网为ssh.wisdomheart.cn,借助AI助手能大幅简化CI/CD工作流的搭建过程。假设服务器已完成基础配置,下面为您介绍如何利用Wisdom…

深度学习-循环神经网络RNN

文章目录序列模型循环神经网络案例词的表示输出的表示矩阵运算表示交叉熵损失时间反向传播算法BPTT梯度消失与梯度爆炸GRULSTM总结序列模型 循环神经网络 st是上一个隐层的输出,体现序列数据之间有很强的关联性。 案例 > S0是初始输入,一般是0&#…

【Linux网络编程】Socket - TCP

目录 V1版本 - Echo Server 初始化服务器 启动服务器 客户端 一些BUG与问题 解决服务器无法一次处理多个请求的问题 多进程版本 多线程版本 线程池版本 V2版本 - 多线程远程执行命令 V1版本 - Echo Server 初始化服务器 TCP大部分内容与UDP是相同的,我们…

知识图谱构建简单分享

最近系统性地学习了知识图谱构建的关键技术,并结合医疗领域知识,完成了一个医疗知识图谱项目的实践。以下是整理的项目架构,欢迎交流指正。说明:当前项目实际实现主要应用了数据预处理、模型设计与预训练、模型优化与测试等核心技…

MCU中的系统控制器(System Controller)是什么?

MCU中的系统控制器(System Controller)是什么? 在微控制器(MCU)中,系统控制器(System Controller)是一个关键模块,负责管理和协调MCU内部的核心功能,确保系统…

【Datawhale夏令营】用AI做带货视频评论分析

文本分类、文本聚类 基础库: pandas, sklearn功能: 商品识别、情感分析、评论聚类商品识别: 视频文本信息,预测推广商品名称 & 情感分析:四个维度(情感倾向、用户场景、用户疑问、用户建议&#xff09…

[Meetily后端框架] AI摘要结构化 | `SummaryResponse`模型 | Pydantic库 | vs marshmallow库

第3章:摘要数据结构(Pydantic库) 欢迎回来! 在之前的第2章:API文档中,我们知道API网关提供了端点 而API文档准确告诉我们如何与这些端点通信,包括需要发送的数据格式和期望接收的数据格式。 …

深度学习 tensor及其相关操作

目录 Tensor 概念 数据类型 创建tensor 基本创建方式 1、 torch.tensor() 2、torch.Tensor() 3、torch.IntTensor() 等 创建线性张量和随机张量 1、创建线性张量 2、创建随机张量 切换设备 类型转换 与 Numpy 数据转换 1、张量转 Numpy 2、Numpy 转张量 tenso…

如何将FPGA设计的验证效率提升1000倍以上(4)

本文为系列文章的完结篇。用户应用设计中的信号,在经历编译器的多次迭代优化之后,在FPGA芯片内部运行时,可能已经被重新命名、“改头换面”或“机里机气”。要想以人工经验进行追踪,构建目标寄存器信号与RTL设计源码之间的映射关系…

Linux驱动11 --- buildroot杂项驱动开发方法

目录 一、Buildroot 1.1介绍 文件系统 1.一个完整的操作系统需要包含大量的文件 2.在嵌入式开发中目前应用最广泛的文件系统制作工具就是 buildroot,busybox 3.buildroot 制作文件系统(了解) 二、杂项驱动编程 1.1 驱动编程做的内容 2.2…

Unity物理系统由浅入深第三节:物理引擎底层原理剖析

Unity物理系统由浅入深第一节:Unity 物理系统基础与应用 Unity物理系统由浅入深第二节:物理系统高级特性与优化 Unity物理系统由浅入深第三节:物理引擎底层原理剖析 Unity物理系统由浅入深第四节:物理约束求解与稳定性 Unity 物理…

Docker一键安装中间件(RocketMq、Nginx、MySql、Minio、Jenkins、Redis)脚步

1、Docker安装RocketMq 2、Docker安装Nginx 3、Docker安装MySql 4、Docker安装Minio 5、Docker安装jenkins 6、Docker安装Redis 1、Docker安装RocketMq #!/bin/bash# 定义变量 NAMESRV_CONTAINER"rocketmq-namesrv" BROKER_CONTAINER"rocketmq-broker&quo…