大家好,我是飞哥!👋

欢迎来到吴恩达《LangChain LLM 应用开发》系列课程的第三讲。这一讲我们来解决一个聊天机器人开发中最头疼的问题:如何让 AI 记住你说过的话?


1. 为什么:LLM 的“失忆症”

Harrison 在视频开头明确指出:

“当你与这些模型交互时,本质上它们是不记得你之前说过什么的。”

每次你调用 API(比如 chat.predict(...)),对于模型来说都是一次全新的开始。它是无状态 (Stateless) 的。

但是,如果你在开发一个聊天机器人(Chatbot),用户期望的是像和朋友聊天一样,AI 能记得住上文(比如我的名字)。

所以,我们需要 Memory (记忆) 组件。它的本质工作就是:把之前的对话记录存下来,然后在下一轮对话时,把这些记录打包塞进 Prompt 里发给 AI。


2. 是什么:LangChain 的记忆类型

LangChain 提供了多种管理记忆的方式,视频中重点介绍了四种:

  1. ConversationBufferMemory (全量记忆):最简单,把所有对话原封不动地记下来。
  2. ConversationBufferWindowMemory (窗口记忆):只记最近的 K 轮对话。
  3. ConversationTokenBufferMemory (Token 记忆):按 Token 数量限制记忆长度(为了省钱)。
  4. ConversationSummaryBufferMemory (摘要记忆):用 LLM 把之前的对话总结成一段摘要存起来。

此外,视频末尾还提到了 Vector Data Memory (向量记忆)Entity Memory (实体记忆),用于更高级的场景。


3. 环境准备:API Key (DeepSeek) 与 .env 🛠️

在开始写代码之前,我们需要先搞定“入场券”。考虑到 OpenAI 的额度限制和国内访问的不便,我们这里推荐使用 DeepSeek (深度求索)

为什么选择 DeepSeek?

  • 兼容性强:DeepSeek 完美兼容 OpenAI 的 API 格式,代码几乎不用改。
  • 性价比高:价格极其亲民,且赠送额度大方。
  • 访问稳定:作为国产模型,国内直连速度快,无需魔法。

3.1 获取 DeepSeek API Key 🔑

  1. 注册/登录:访问 DeepSeek 开放平台
  2. 创建 Key:点击左侧菜单的 API keys -> Create API key
  3. 复制保存:生成的 sk-... 开头的字符串就是你的 Key。注意:只会显示一次,一定要马上复制保存!

3.2 配置 .env 文件 ⚙️

为了不把敏感的 Key 直接写在代码里(容易泄露),我们通常使用 .env 文件来管理环境变量。

  1. 创建文件:在你的项目根目录下,创建一个名为 .env 的文件(注意前面有个点)。
    • 统一配置:如果你希望所有项目共用一个 Key,可以把 .env 放在 根目录下。
  2. 写入内容
    你需要配置 Key、API 地址 (Base URL) 和模型名称。
    # DeepSeek 配置
    OPENAI_API_KEY=sk-你的DeepSeek密钥在这
    OPENAI_API_BASE=https://api.deepseek.com
    OPENAI_MODEL_NAME=deepseek-chat
    
  3. 代码调用
    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 还提到了两种更高级的记忆:

  1. Vector Data Memory (向量记忆):利用向量数据库 (Vector Database) 存储文本的 Embedding。它可以基于相似度检索相关的历史对话,而不仅仅是按时间顺序。这对于处理超长对话或需要调用很久以前的知识非常有用。
  2. Entity Memory (实体记忆):专门用来记住特定“实体”的信息。比如记住“Sam 是你的朋友”、“Sam 喜欢打篮球”。它会从对话中提取关于特定人物或对象的事实。

此外,你还可以把记忆存储在传统的数据库中(如 SQL 或 Key-Value 存储),以便进行审计或系统改进。


📝 飞哥总结

这一讲的核心在于“上下文管理”:

  1. 无状态:认清 LLM 本身不记事的本质。
  2. 外挂硬盘:Memory 就是我们在 Prompt 里专门留出的一块区域,用来放历史记录。
  3. 取舍
    • 全记:效果好,但费钱,容易爆 Token。
    • 记最近:省钱,但会忘事。
    • 记摘要:折中方案,用计算换空间(多花点钱做总结,省下存储长文本的 Token)。

一句话记住它:Memory 就是 AI 的“记事本”,决定了它能陪你聊多久而不“断片”。

下一讲,我们将学习 LangChain 最核心的积木 —— Chains (),看怎么把这些组件串起来干大事!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐