吴恩达《LangChain LLM 应用开发精读笔记》3-Memory记忆
这一讲的核心在于“上下文管理无状态:认清 LLM 本身不记事的本质。外挂硬盘:Memory 就是我们在 Prompt 里专门留出的一块区域,用来放历史记录。取舍全记:效果好,但费钱,容易爆 Token。记最近:省钱,但会忘事。记摘要:折中方案,用计算换空间(多花点钱做总结,省下存储长文本的 Token)。一句话记住它:Memory 就是 AI 的“记事本”,决定了它能陪你聊多久而不“断片”。下一讲

大家好,我是飞哥!👋
欢迎来到吴恩达《LangChain LLM 应用开发》系列课程的第三讲。这一讲我们来解决一个聊天机器人开发中最头疼的问题:如何让 AI 记住你说过的话?
1. 为什么:LLM 的“失忆症”
Harrison 在视频开头明确指出:
“当你与这些模型交互时,本质上它们是不记得你之前说过什么的。”
每次你调用 API(比如 chat.predict(...)),对于模型来说都是一次全新的开始。它是无状态 (Stateless) 的。
但是,如果你在开发一个聊天机器人(Chatbot),用户期望的是像和朋友聊天一样,AI 能记得住上文(比如我的名字)。
所以,我们需要 Memory (记忆) 组件。它的本质工作就是:把之前的对话记录存下来,然后在下一轮对话时,把这些记录打包塞进 Prompt 里发给 AI。
2. 是什么:LangChain 的记忆类型
LangChain 提供了多种管理记忆的方式,视频中重点介绍了四种:
- ConversationBufferMemory (全量记忆):最简单,把所有对话原封不动地记下来。
- ConversationBufferWindowMemory (窗口记忆):只记最近的 K 轮对话。
- ConversationTokenBufferMemory (Token 记忆):按 Token 数量限制记忆长度(为了省钱)。
- ConversationSummaryBufferMemory (摘要记忆):用 LLM 把之前的对话总结成一段摘要存起来。
此外,视频末尾还提到了 Vector Data Memory (向量记忆) 和 Entity Memory (实体记忆),用于更高级的场景。
3. 环境准备:API Key (DeepSeek) 与 .env 🛠️
在开始写代码之前,我们需要先搞定“入场券”。考虑到 OpenAI 的额度限制和国内访问的不便,我们这里推荐使用 DeepSeek (深度求索)。
为什么选择 DeepSeek?
- 兼容性强:DeepSeek 完美兼容 OpenAI 的 API 格式,代码几乎不用改。
- 性价比高:价格极其亲民,且赠送额度大方。
- 访问稳定:作为国产模型,国内直连速度快,无需魔法。
3.1 获取 DeepSeek API Key 🔑
- 注册/登录:访问 DeepSeek 开放平台。
- 创建 Key:点击左侧菜单的
API keys->Create API key。 - 复制保存:生成的
sk-...开头的字符串就是你的 Key。注意:只会显示一次,一定要马上复制保存!
3.2 配置 .env 文件 ⚙️
为了不把敏感的 Key 直接写在代码里(容易泄露),我们通常使用 .env 文件来管理环境变量。
- 创建文件:在你的项目根目录下,创建一个名为
.env的文件(注意前面有个点)。- 统一配置:如果你希望所有项目共用一个 Key,可以把
.env放在 根目录下。
- 统一配置:如果你希望所有项目共用一个 Key,可以把
- 写入内容:
你需要配置 Key、API 地址 (Base URL) 和模型名称。# DeepSeek 配置 OPENAI_API_KEY=sk-你的DeepSeek密钥在这 OPENAI_API_BASE=https://api.deepseek.com OPENAI_MODEL_NAME=deepseek-chat - 代码调用:
Python 的dotenv库会自动寻找并读取这个文件。import os from dotenv import load_dotenv, find_dotenv # 加载环境变量 # 优先加载当前目录下的 .env 文件 # 如果当前目录没有,则自动查找(find_dotenv) _ = load_dotenv(find_dotenv()) # 配置 OpenAI API Key 和 Base URL (适配 DeepSeek) api_key = os.getenv("OPENAI_API_KEY") base_url = os.getenv("OPENAI_API_BASE") model_name = os.getenv("OPENAI_MODEL_NAME")
3.3 安装依赖库 📦
在运行代码之前,请确保你已经安装了以下 Python 库:
pip install python-dotenv openai langchain langchain-openai langchain-core
飞哥提示:一定要把
.env加入到.gitignore文件中,千万别推送到 GitHub 上,否则你的钱包可能会被刷爆!💸
4. 怎么用:实战代码 (Memory_Example.py)
代码已整理为可运行的 Python 脚本,位于本目录下的 Memory_Example.py。
4.1 基础:ConversationBufferMemory
这是最基础的记忆,就像一个无限长的记事本。
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
# 初始化模型
# 注意:DeepSeek 需要指定 base_url 和 api_key
llm = ChatOpenAI(
temperature=0.0,
model=model_name,
base_url=base_url,
api_key=api_key,
# 注意:为了让 Token 记忆能正常工作,我们需要指定一个兼容的 Tokenizer 模型名
# 强制让 LangChain 使用 gpt-3.5-turbo 的方式来计算 Token
# 如果不加这一行,后续的 Token Memory 会报错
tiktoken_model_name="gpt-3.5-turbo",
)
# 初始化记忆
memory = ConversationBufferMemory()
# 创建一个自带记忆的对话链
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=True # 开启 Verbose 可以看到 LangChain 到底发了什么 Prompt
)
# 第一轮
input_text_1 = "你好,我的名字是 Andrew"
print(f"User: {input_text_1}")
conversation.predict(input=input_text_1)
运行结果示例:
User: 你好,我的名字是 Andrew
AI: 你好,Andrew!很高兴认识你。我是DeepSeek,一个由深度求索公司创造的AI助手。今天有什么我可以帮助你的吗?无论是回答问题、聊天还是协助处理任务,我都很乐意为你提供帮助!😊
你的名字Andrew让我想起这个名字的希腊语起源,意思是“勇敢的”或“有男子气概的”。这是个很棒的名字!你是来自英语国家,还是只是喜欢这个名字呢?
# 第二轮
input_text_2 = "1+1 等于几?"
print(f"User: {input_text_2}")
conversation.predict(input=input_text_2)
运行结果示例:
User: 1+1 等于几?
AI: 1+1 等于 2,这是最基本的数学运算之一!😊
不过既然你提到了,我可以稍微扩展一下:
- 在十进制算术中,1+1=2
- 在二进制中,1+1=10(读作“一零”)
- 在布尔代数中,1+1=1(这里的1代表“真”)
- 在某些抽象代数系统中,可能会有不同的定义
但对我们日常使用来说,1+1 就是等于 2。你是想测试我的数学能力,还是有其他更深层次的问题想问呢?
# 第三轮 (考察记忆)
input_text_3 = "我叫什么名字?"
print(f"User: {input_text_3}")
response = conversation.predict(input=input_text_3)
print(f"AI: {response}")
运行结果示例:
User: 我叫什么名字?
AI: 你刚才告诉我你的名字是 Andrew!😊
这是个很好听的名字,源自希腊语“Andreas”,有“勇敢”、“刚毅”的意思。我记得我们对话开始时你就介绍了自己叫 Andrew,所以我会记住这个名字的!
有什么其他想聊的吗,Andrew?
# 查看记忆内容
print("\n记忆内容 (Buffer):")
print(memory.buffer)
运行结果示例:
记忆内容 (Buffer):
Human: 你好,我的名字是 Andrew
AI: 你好,Andrew!很高兴认识你。我是DeepSeek,一个由深度求索公司创造的AI助手。今天有什么我可以帮助你的吗?无论是回答问题、聊天还是协助处理任务,我都很乐意为你提供帮助!😊
你的名字Andrew让我想起这个名字的希腊语起源,意思是“勇敢的”或“有男子气概的”。这是个很棒的名字!你是来自英语国家,还是只是喜欢这个名字呢?
Human: 1+1 等于几?
AI: 1+1 等于 2,这是最基本的数学运算之一!😊
不过既然你提到了,我可以稍微扩展一下:
- 在十进制算术中,1+1=2
- 在二进制中,1+1=10(读作“一零”)
- 在布尔代数中,1+1=1(这里的1代表“真”)
- 在某些抽象代数系统中,可能会有不同的定义
但对我们日常使用来说,1+1 就是等于 2。你是想测试我的数学能力,还是有其他更深层次的问题想问呢?
Human: 我叫什么名字?
AI: 你刚才告诉我你的名字是 Andrew!😊
这是个很好听的名字,源自希腊语“Andreas”,有“勇敢”、“刚毅”的意思。我记得我们对话开始时你就介绍了自己叫 Andrew,所以我会记住这个名字的!
有什么其他想聊的吗,Andrew?
4.2 进阶:ConversationBufferWindowMemory
如果对话太长,Token 会不够用。Window Memory 就像金鱼的记忆,只记最近几件事。
from langchain.memory import ConversationBufferWindowMemory
# k=1 表示只记住最近的 1 轮对话
memory_window = ConversationBufferWindowMemory(k=1)
# 手动添加一些历史记录 (模拟之前的对话)
memory_window.save_context({"input": "你好"}, {"output": "你好!有什么我可以帮你的吗?"})
memory_window.save_context({"input": "没什么,随便聊聊"}, {"output": "好的,随时欢迎聊天。"})
print("当前记忆内容 (Window, k=1):")
# load_memory_variables({}) 返回当前的记忆变量
print(memory_window.load_memory_variables({}))
运行结果示例:
当前记忆内容 (Window, k=1):
{'history': 'Human: 没什么,随便聊聊\nAI: 好的,随时欢迎聊天。'}
4.3 进阶:ConversationTokenBufferMemory
这是更精准的控制,直接按 Token 限制。
from langchain.memory import ConversationTokenBufferMemory
# max_token_limit=50 表示记忆不能超过 50 个 Token (中文 Token 通常比英文多,这里稍微调大一点以便观察)
memory_token = ConversationTokenBufferMemory(llm=llm, max_token_limit=50)
# 模拟一些对话
memory_token.save_context({"input": "AI 是什么?"}, {"output": "AI 是人工智能。"})
memory_token.save_context({"input": "反向传播是什么?"}, {"output": "它是神经网络训练的核心算法。"})
memory_token.save_context({"input": "聊天机器人是什么?"}, {"output": "它是一种能模拟人类对话的程序。"})
print("当前记忆内容 (Token Limit=50):")
print(memory_token.load_memory_variables({}))
运行结果示例:
当前记忆内容 (Token Limit=50):
{'history': 'Human: 聊天机器人是什么?\nAI: 它是一种能模拟人类对话的程序。'}
(注意:具体保留多少内容取决于 Token 计算方式,DeepSeek 的 Token 计算可能略有不同)
4.4 高级:ConversationSummaryBufferMemory
这是最聪明的记忆方式。当对话变长时,它不会直接丢弃旧对话,而是用 LLM 把它总结一下。
from langchain.memory import ConversationSummaryBufferMemory
# 创建一个能总结的记忆体
# max_token_limit=100 表示当对话超过 100 token 时,触发总结
# 注意:DeepSeek 的 Token 计算可能与 OpenAI 略有不同,但原理一致
memory_summary = ConversationSummaryBufferMemory(llm=llm, max_token_limit=100)
# 假设我们塞入一段很长的对话
schedule_text = "早上8点你需要和产品团队开会。你需要为上午9点的董事会会议准备PPT演示文稿。上午10点你需要接受经理的绩效评估。上午11点你需要面试一位前端工程师候选人。中午12点你要和朋友共进午餐。"
memory_summary.save_context({"input": "你好"}, {"output": "你好!"})
memory_summary.save_context({"input": "没什么,随便聊聊"}, {"output": "好的。"})
memory_summary.save_context(
{"input": "今天的日程安排是什么?"}, {"output": f"{schedule_text}"}
)
print("当前记忆内容 (Summary - Before New Question):")
# 注意观察 'history' 字段,它不再是原始对话,而是被总结过的 (System: The human says hello...)
print(memory_summary.load_memory_variables({})["history"])
运行结果示例:
当前记忆内容 (Summary - Before New Question):
System: The human greets the AI, and they exchange pleasantries. The human then asks about their schedule for the day. The AI provides a detailed agenda, listing meetings, preparation tasks, an interview, and a lunch appointment throughout the morning.
你会发现它没有存原文,而是存了一个 System 消息形式的摘要。
5. 其他记忆类型
视频最后,Andrew Ng 还提到了两种更高级的记忆:
- Vector Data Memory (向量记忆):利用向量数据库 (Vector Database) 存储文本的 Embedding。它可以基于相似度检索相关的历史对话,而不仅仅是按时间顺序。这对于处理超长对话或需要调用很久以前的知识非常有用。
- Entity Memory (实体记忆):专门用来记住特定“实体”的信息。比如记住“Sam 是你的朋友”、“Sam 喜欢打篮球”。它会从对话中提取关于特定人物或对象的事实。
此外,你还可以把记忆存储在传统的数据库中(如 SQL 或 Key-Value 存储),以便进行审计或系统改进。
📝 飞哥总结
这一讲的核心在于“上下文管理”:
- 无状态:认清 LLM 本身不记事的本质。
- 外挂硬盘:Memory 就是我们在 Prompt 里专门留出的一块区域,用来放历史记录。
- 取舍:
- 全记:效果好,但费钱,容易爆 Token。
- 记最近:省钱,但会忘事。
- 记摘要:折中方案,用计算换空间(多花点钱做总结,省下存储长文本的 Token)。
一句话记住它:Memory 就是 AI 的“记事本”,决定了它能陪你聊多久而不“断片”。
下一讲,我们将学习 LangChain 最核心的积木 —— Chains (链),看怎么把这些组件串起来干大事!
更多推荐


所有评论(0)