维护对话上下文
LLM本身是无状态的!每次调用都是独立的,不记得之前说过什么。
# 没有记忆的对话
llm.invoke("我叫张三") # AI: 你好张三!
llm.invoke("我叫什么名字?") # AI: 抱歉,我不知道你的名字
# 👉 AI完全忘记了刚才的对话!记忆机制让AI记住对话历史,实现真正的多轮对话。
# 有记忆的对话
conversation.predict(input="我叫张三") # AI: 你好张三!
conversation.predict(input="我叫什么名字?") # AI: 你叫张三
# 👉 AI记住了之前的对话!LangChain提供了多种记忆类型,适应不同的使用场景。
本页所有示例代码都可以复制到 memory_x.py 里运行。最常见的问题不是代码写错,而是:
IDE 选的 Python 解释器 ≠ 你安装依赖的 Python 环境。
# 在你的项目根目录执行
python3 -m venv .venv
# macOS / Linux
source .venv/bin/activate
# Windows PowerShell
# .venv\Scripts\Activate.ps1# 关键点:永远用 "python -m pip",避免 pip 装到另一个 Python 上
python -m pip install -U pip setuptools wheel
# 经典 Chain API(ConversationChain / ConversationBufferMemory)建议使用 langchain-classic
# 注意:langchain-classic==1.x 依赖 langchain-core>=1.0(必须一起装对版本)
python -m pip install -U \
"langchain-classic==1.0.0" \
"langchain-core>=1,<2" \
"langchain-community>=0.3,<0.4" \
"langchain-text-splitters>=1,<2" \
"langsmith>=0.1.17,<1" \
dashscope \
redis \
pymysqlpython -c "from langchain_classic.chains import ConversationChain; from langchain_classic.memory import ConversationBufferMemory; print('import OK')"# 例如:运行窗口记忆示例
python3 memory_window.py.venv。
如果你看到 from langchain... 下面有红线,通常就是解释器没选对。
Failed to build pcapy / distutils 相关错误,这是 pcapy 自身不兼容 Python 3.12。
不降 Python 版本的做法是换成维护分支 pcapy-ng:
python -m pip install pcapy-ngimport pcapy,安装 pcapy-ng 后大多数场景仍然可以继续用;
若遇到导入差异,再根据项目实际把 import 调整为对应模块名。
ConversationSummaryBufferMemory(或 SummaryBuffer 类记忆)时遇到类似报错:
Could not import transformers / GPT2TokenizerFast.from_pretrained("gpt2") / httpx.ConnectTimeout,
本质原因通常是:它需要计算 token 数,而默认 token 计数会依赖 transformers,并且可能需要从 HuggingFace 下载 gpt2 的 tokenizer 文件。
# 必须用你正在运行脚本的那个 python 来安装
python -m pip install -U transformers# 方式1:运行脚本时临时设置(推荐)
HF_ENDPOINT=https://hf-mirror.com python3 memory_11.py# 先把 tokenizer 下载到本地缓存目录
HF_ENDPOINT=https://hf-mirror.com python -c "from transformers import GPT2TokenizerFast; GPT2TokenizerFast.from_pretrained('gpt2'); print('gpt2 tokenizer cached')"HF_ENDPOINT。
记得在运行脚本时也一起设置。
特点:把对话历史作为消息列表自动注入到 Prompt 中,适合 LCEL / Runnable 风格的链式调用。
通过 RunnableWithMessageHistory 按 session_id 取出历史消息列表,并填充到 MessagesPlaceholder("history") 这个占位符位置。
prompt | llm 这种 Runnable 风格写链import os
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_community.chat_models import ChatTongyi
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
# ⚠️ 注意:请使用 python3 运行(macOS 上 /usr/bin/python 可能不是你的 pip 环境)
# python3 memory_placeholder.py
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# 关键点1:Prompt 里用 MessagesPlaceholder 预留“消息列表”位置
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个有帮助的助手。"),
MessagesPlaceholder("history"),
("human", "{input}"),
]
)
chain = prompt | llm
# 关键点2:用一个 store 存不同 session 的 ChatMessageHistory
store = {}
def get_history(session_id: str) -> ChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
conversation = RunnableWithMessageHistory(
chain,
get_history,
input_messages_key="input",
history_messages_key="history",
)
session_id = "user_1"
print("=== 第1轮 ===")
r1 = conversation.invoke(
{"input": "我叫张三,是一名Python开发者"},
config={"configurable": {"session_id": session_id}},
)
print("AI:", r1.content, "\n")
print("=== 第2轮 ===")
r2 = conversation.invoke(
{"input": "我的职业是什么?"},
config={"configurable": {"session_id": session_id}},
)
print("AI:", r2.content, "\n")
print("=== 第3轮 ===")
r3 = conversation.invoke(
{"input": "我叫什么名字?"},
config={"configurable": {"session_id": session_id}},
)
print("AI:", r3.content, "\n")
print("=== 当前 history messages ===")
print(store[session_id].messages)核心问题:LLM 每次调用都是独立的,不记得之前说过什么。我们需要把历史对话塞进 Prompt 里。
MessagesPlaceholder 的作用:专门用来插入消息列表,确保格式正确。
不是随便起的!"history" 必须在三个地方保持一致:
{"input": "..."} → 业务输入,会进入 Promptconfig={...} → 运行参数,由 Runnable 框架消费,不会进 Prompt
RunnableWithMessageHistory 声明了需要 session_idconfig["configurable"]["session_id"] 传递
get_session_history(session_id)ChatMessageHistory 对象MessagesPlaceholder("history")
session_id = f"{user_id}:{conversation_id}"session_id = f"{tenant_id}:{user_id}:{conversation_id}"可以!但必须三处统一。比如换成 "chat_history":
总结:MessagesPlaceholder 就是预留位置,"history" 是数据键名,两者配合实现动态历史注入。
ChatMessageHistory 可以理解为“对话消息的存储层”。它负责保存/读取消息列表(history.messages),
供 RunnableWithMessageHistory 或传统 Memory(例如 ConversationBufferMemory)在每轮调用时注入历史。
| 类型 | 典型类名 | 特点 | 适用 |
|---|---|---|---|
| 内存 | ChatMessageHistory | 零依赖、最快,但进程重启即丢 | 本地开发 / Demo |
| SQLite / MySQL | SQLChatMessageHistory | 可持久化、可查询;需要 DB 连接 | 生产落盘 / 审计 |
| Redis | RedisChatMessageHistory | 性能高、支持 TTL;适合多实例共享 | 在线对话服务 |
| 文件 | FileChatMessageHistory | 简单直观,但并发/检索能力弱 | 个人项目 / 小规模 |
这一节把 ConversationBufferMemory 单独拿出来讲:它属于 LangChain 早期的 ConversationChain(传统 Chain API) 生态,思路是“把历史拼成文本塞回 Prompt”。
ConversationBufferMemory 会把所有历史对话拼成一段文本(memory.buffer),并在每次调用 ConversationChain 时自动注入到 prompt。
一句话:信息不丢,但对话越长 Token 越多。
把所有历史原封不动塞进 prompt。好处是"信息不丢",代价是"越聊越贵、越聊越慢"。
import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationBufferMemory
from langchain_community.chat_models import ChatTongyi
# ⚠️ 注意:请使用 python3 运行
# python3 memory_buffer.py
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# BufferMemory:完整保存所有对话历史(对话越长,token 越多)
memory = ConversationBufferMemory()
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
print(conversation.predict(input="我叫张三,是一名Python开发者"))
print(conversation.predict(input="我的职业是什么?"))
print(conversation.predict(input="我叫什么名字?"))
print("\n=== memory.buffer ===")
print(memory.buffer)
问题:默认的 ChatMessageHistory 只保存在内存里,程序重启后历史就丢了。
解决:把 chat_memory 换成持久化的 SQLChatMessageHistory / RedisChatMessageHistory / FileChatMessageHistory。
import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationBufferMemory
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_community.chat_models import ChatTongyi
# BufferMemory + SQLite 持久化
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# 使用 SQLChatMessageHistory 替代内存存储
memory = ConversationBufferMemory(
chat_memory=SQLChatMessageHistory(
session_id="user_123", # 每个用户的唯一标识
connection_string="sqlite:///chat_history.db" # 本地 SQLite 文件
),
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
# 第一次运行:写入对话
print(conversation.predict(input="我叫张三,是一名Python开发者"))
print(conversation.predict(input="我今年30岁,住在北京"))
print("\n=== 重启程序后再次运行 ===")
print(conversation.predict(input="我叫什么名字?我的职业是什么?"))
# 数据已保存到 chat_history.db,程序重启后仍然存在import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationBufferMemory
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_community.chat_models import ChatTongyi
# BufferMemory + MySQL 持久化(生产环境推荐)
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# 使用本地 MySQL 数据库
# 前置准备:
# 1) 安装 MySQL 驱动:python -m pip install pymysql
# 2) 创建数据库:CREATE DATABASE langchain_chat;
# 3) LangChain 会自动创建表结构
memory = ConversationBufferMemory(
chat_memory=SQLChatMessageHistory(
session_id="user_123",
connection_string="mysql+pymysql://root:password@localhost:3306/langchain_chat"
),
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
# 第一次运行:写入对话
print(conversation.predict(input="我叫张三,是一名Python开发者"))
print(conversation.predict(input="我今年30岁,住在北京"))
print("\n=== 重启程序后再次运行 ===")
print(conversation.predict(input="我叫什么名字?我的职业是什么?"))
# 数据已保存到 MySQL 数据库,多实例部署时共享import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationBufferMemory
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_community.chat_models import ChatTongyi
# BufferMemory + Redis 持久化(适合分布式部署)
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
memory = ConversationBufferMemory(
chat_memory=RedisChatMessageHistory(
session_id="user_123",
url="redis://localhost:6379/0", # Redis 连接地址
ttl=3600 # 1小时后自动过期(秒)
),
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
print(conversation.predict(input="我叫张三,请多关照"))
print(conversation.predict(input="我叫什么名字?"))
# 数据保存在 Redis,可被多个服务实例共享import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationBufferMemory
from langchain_community.chat_message_histories import FileChatMessageHistory
from langchain_community.chat_models import ChatTongyi
# BufferMemory + 文件持久化(最简单直观)
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
memory = ConversationBufferMemory(
chat_memory=FileChatMessageHistory(
file_path="session_user_123.json" # 每个 session 一个文件
),
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
print(conversation.predict(input="我叫张三,是一名开发者"))
print(conversation.predict(input="我叫什么名字?"))
# 数据保存到 session_user_123.json,可随时查看和备份特点:只保留最近K轮对话,节省Token。
只保留最近的K轮对话,旧的对话会被自动丢弃。
只记住最近 K 轮。像聊天窗口一样“只看最近消息”,能把成本控制住,但早期信息可能被忘。
import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationBufferWindowMemory
from langchain_classic.prompts import PromptTemplate
from langchain_community.chat_models import ChatTongyi
# ⚠️ 注意:请使用 python3 运行
# python3 memory_window.py
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# WindowMemory:只保留最近 k 轮(约 2k 条 message:user+ai)
memory = ConversationBufferWindowMemory(k=2)
# 关键点:用一个“更严格”的 prompt,避免模型凭常识乱猜
# 只允许基于 {history} 回答;如果历史里没有,就说不知道。
prompt = PromptTemplate(
input_variables=["history", "input"],
template=(
"你是一个严谨的助手。\n"
"规则:只能根据【对话历史】回答;不要猜测、不要脑补。\n"
"如果【对话历史】里没有答案,必须回答:\"我不知道\"。\n"
"并且:除非用户明确提问,否则不要在回复里重复用户的姓名/职业等身份信息。\n\n"
"【对话历史】\n{history}\n\n"
"【用户问题】\n{input}"
),
)
conversation = ConversationChain(llm=llm, memory=memory, prompt=prompt, verbose=True)
print("第1轮:")
print(conversation.predict(input="我叫张三,是一名Python开发者"))
print("\n第2轮:")
print(conversation.predict(input="我今年30岁"))
print("\n第3轮:")
print(conversation.predict(input="我住在北京"))
print("\n第4轮:")
print(conversation.predict(input="我喜欢跑步"))
print("\n第5轮 - 测试记忆:")
print(conversation.predict(input="我叫什么名字?我的职业是什么?"))
print("\n=== memory.buffer(窗口内当前历史)===")
print(memory.buffer)第5轮 - 测试记忆:
AI: 我不知道。
=== 当前记忆 ===
Human: 我住在北京
AI: (确认收到)
Human: 我喜欢跑步
AI: (确认收到)
👉 只保留了最近2轮对话(第3、4轮),第1轮里的“张三/Python开发者”已经不在窗口里,所以第5轮必须回答“不知道”。
问题:WindowMemory 只保留最近 K 轮,但默认也在内存中,重启后丢失。
解决:同样通过 chat_memory 注入持久化的 History 实现。
关键理解:WindowMemory 的 "窗口裁剪" 是内存层面的策略,但底层历史数据可以来自磁盘/数据库。 每次加载时从持久化存储读取完整历史,然后在内存中只保留最近 K 轮。
import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationBufferWindowMemory
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_community.chat_models import ChatTongyi
# WindowMemory + SQLite 持久化
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# WindowMemory 使用 SQL 持久化:只保留最近 k=3 轮,但数据存数据库
memory = ConversationBufferWindowMemory(
k=3, # 窗口大小:只保留最近 3 轮
chat_memory=SQLChatMessageHistory(
session_id="user_window_123",
connection_string="sqlite:///chat_history.db"
),
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
# 模拟长对话(超过窗口大小)
print("=== 前5轮对话 ===")
for i, topic in enumerate(["Python", "Java", "Go", "Rust", "TypeScript"], 1):
print(f"\n第{i}轮:")
print(conversation.predict(input=f"我喜欢用{topic}编程"))
print("\n=== 第6轮 - 测试窗口记忆 ===")
print(conversation.predict(input="我最喜欢的前3种编程语言是什么?"))
print("\n=== 重启程序后 ===")
# 重新创建 memory(从数据库加载),仍然只保留最近 3 轮
memory2 = ConversationBufferWindowMemory(
k=3,
chat_memory=SQLChatMessageHistory(
session_id="user_window_123",
connection_string="sqlite:///chat_history.db"
),
return_messages=True
)
conversation2 = ConversationChain(llm=llm, memory=memory2, verbose=True)
print(conversation2.predict(input="我最喜欢的第一种编程语言是什么?"))
# 说明:虽然数据库保存了所有历史,但 WindowMemory 只加载最近 3 轮到内存import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationBufferWindowMemory
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_classic.prompts import PromptTemplate
from langchain_community.chat_models import ChatTongyi
# WindowMemory + Redis 持久化(适合高并发分布式场景)
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# 使用 Redis 存储,设置 1 小时过期
memory = ConversationBufferWindowMemory(
k=2, # ✅ 为了演示效果,这里故意设小一点:只保留最近 2 轮
chat_memory=RedisChatMessageHistory(
session_id="session_live_123",
url="redis://localhost:6379/0",
ttl=3600 # 1小时后自动清理过期数据
),
return_messages=True
)
prompt = PromptTemplate(
input_variables=["history", "input"],
template=(
"你是一个严谨的助手。\n"
"规则:只能根据【对话历史】回答;不要猜测、不要脑补。\n"
"如果【对话历史】里没有答案,必须回答:\"我不知道\"。\n\n"
"【对话历史】\n{history}\n\n"
"【用户问题】\n{input}"
),
)
conversation = ConversationChain(llm=llm, memory=memory, prompt=prompt, verbose=True)
print("=== 写入 4 轮信息(超过 k=2)===")
print(conversation.predict(input="我的名字是张三"))
print(conversation.predict(input="我的职业是Python开发者"))
print(conversation.predict(input="我住在北京"))
print(conversation.predict(input="我喜欢跑步"))
print("\n=== 提问:验证窗口裁剪(应遗忘早期信息)===")
print(conversation.predict(input="我叫什么名字?我的职业是什么?"))
print("\n=== memory.buffer(窗口内当前历史:应该只剩最近 2 轮)===")
print(memory.buffer)
print("\n=== 模拟重启脚本:同 session_id 从 Redis 继续 ===")
memory2 = ConversationBufferWindowMemory(
k=2,
chat_memory=RedisChatMessageHistory(
session_id="session_live_123",
url="redis://localhost:6379/0",
ttl=3600,
),
return_messages=True,
)
conversation2 = ConversationChain(llm=llm, memory=memory2, prompt=prompt, verbose=True)
print(conversation2.predict(input="我住在哪里?我喜欢什么?"))
# 说明:
# - Redis 保存了该 session_id 的历史,多个服务实例可共享
# - WindowMemory 每次只取最近 k 轮注入 prompt,因此早期信息会被“窗口裁剪”特点:使用LLM总结历史对话,压缩信息。
随着对话进行,使用LLM将历史对话总结成简短的摘要,保留关键信息。
把“很长的历史”压缩成一段摘要。模型每次只读“摘要 + 当前输入”,从而把 Token 控制住。
import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationSummaryMemory
from langchain_community.chat_models import ChatTongyi
# ⚠️ 注意:请使用 python3 运行
# python3 memory_summary.py
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# SummaryMemory:用 LLM 把历史压缩成一段摘要文本
memory = ConversationSummaryMemory(llm=llm)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
print(conversation.predict(input="我叫张三,是一名Python开发者,今年30岁"))
print(conversation.predict(input="我住在北京,喜欢看科幻电影"))
print(conversation.predict(input="我最近在学习LangChain框架"))
print("\n第4轮 - 测试记忆:")
print(conversation.predict(input="总结一下我的基本信息"))
print("\n=== memory.buffer ===")
print(memory.buffer)第4轮 - 测试记忆:
AI: 根据我们的对话,你的基本信息如下:
- 姓名:张三
- 职业:Python开发者
- 年龄:30岁
- 居住地:北京
- 爱好:看科幻电影
- 当前学习:LangChain框架
=== 对话摘要 ===
{'history': '用户张三是一名30岁的Python开发者,居住在北京,喜欢科幻电影,目前正在学习LangChain框架。'}
👉 历史对话被总结成简短的摘要,保留了所有关键信息!
问题:SummaryMemory 生成的摘要只保存在内存中,重启后丢失,需要重新总结。
解决:通过持久化存储保存摘要,避免每次重启后重新调用 LLM 生成摘要。
关键理解:SummaryMemory 的核心是“用 LLM 把长历史压缩成短摘要”。 持久化后,重启程序可以直接从磁盘加载摘要,不需要重新调用 LLM 总结,节省 Token 和延迟。
import os
import pathlib
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationSummaryMemory
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_community.chat_models import ChatTongyi
# SummaryMemory + SQLite 持久化
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# ✅ 关键:SQLite 路径一定要稳定(避免“重启后找不到记录”)
# sqlite:///chat_history.db 是相对路径,取决于你运行脚本时的工作目录(cwd)。
# 用绝对路径可以保证每次都读写同一个 DB 文件。
DB_PATH = pathlib.Path("./chat_history.db").resolve()
CONN_STR = f"sqlite:///{DB_PATH}" # 三个 / 表示绝对路径
session_id = "user_summary_123" # ✅ 重启后必须保持一致,否则会被当成新会话
history = SQLChatMessageHistory(session_id=session_id, connection_string=CONN_STR)
# SummaryMemory 使用 SQL 持久化
memory = ConversationSummaryMemory(
llm=llm,
chat_memory=history,
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
# 第一轮:写入对话并生成摘要
print("=== 第一次运行:写入对话 ===")
print(conversation.predict(input="我叫张三,是一名Python开发者,今年30岁"))
print(conversation.predict(input="我住在北京,喜欢看科幻电影"))
print("\n=== 当前摘要 ===")
print(memory.buffer)
print("\n=== 重启程序后 ===")
# 重新创建 memory(从数据库加载已保存的 messages)
# 注意:SQLChatMessageHistory 持久化的是“对话消息”(Human/AI),不是 summary 文本本身。
print(f"DB 文件:{DB_PATH}")
# ✅ 使用相同的 session_id 才能读到之前的记录
history2 = SQLChatMessageHistory(session_id=session_id, connection_string=CONN_STR)
print(f"DB 中已存在 messages 数量:{len(history2.messages)}")
# ⚠️ 重要:ConversationSummaryMemory 不会自动加载之前的摘要!
# 它只会基于 chat_memory 中的原始消息重新生成摘要
memory2 = ConversationSummaryMemory(
llm=llm,
chat_memory=history2,
return_messages=True
)
# 🔧 关键修复:如果有历史消息,先预加载摘要
if len(history2.messages) > 0:
print("📝 基于 DB 中的消息预生成摘要...")
# 强制让 Memory 基于已有消息生成摘要
# 通过调用 save_context 来触发摘要生成
for i, msg in enumerate(history2.messages):
if i % 2 == 0: # Human message
memory2.save_context(
{"input": msg.content},
{"output": ""}
)
else: # AI message
memory2.save_context(
{"input": ""},
{"output": msg.content}
)
print("✅ 摘要预生成完成")
# 🔧 简化方案:直接让 ConversationChain 在第一次对话时重新生成摘要
conversation2 = ConversationChain(llm=llm, memory=memory2, verbose=True)
print("📝 开始对话,Memory 会自动基于 DB 中的消息生成摘要...")
print(conversation2.predict(input="总结一下我的基本信息"))
print("\n=== 自动生成的摘要 ===")
print(memory2.buffer)
# 结论:
# - ✅ messages 可以通过 SQLChatMessageHistory 跨进程持久化
# - ⚠️ SummaryMemory 的摘要需要重新生成(不会自动保存到数据库)
# - 🔑 session_id 是会话的唯一标识符,换了就读不到之前的数据
# - 💡 实际应用:要么接受重新生成摘要,要么额外存储摘要本身=== 记忆内容 ===
{'history': '用户张三是一名30岁的Python开发者,居住在北京,喜欢科幻电影,目前正在学习LangChain框架。'}
👉 历史对话被总结成简短的摘要,保留了所有关键信息!
问题:SummaryMemory 生成的摘要只保存在内存中,重启后丢失,需要重新总结。
解决:通过持久化存储保存摘要,避免每次重启后重新调用 LLM 生成摘要。
关键理解:SummaryMemory 的核心是“用 LLM 把长历史压缩成短摘要”。 持久化后,重启程序可以直接从磁盘加载摘要,不需要重新调用 LLM 总结,节省 Token 和延迟。
import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationSummaryMemory
from langchain_community.chat_message_histories import FileChatMessageHistory
from langchain_community.chat_models import ChatTongyi
# SummaryMemory + 文件持久化(适合需要查看/备份摘要的场景)
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# 每个用户的摘要保存到独立的 JSON 文件
memory = ConversationSummaryMemory(
llm=llm,
chat_memory=FileChatMessageHistory(
file_path="summary_user_123.json"
),
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
# 长期对话场景
print(conversation.predict(input="我是一名产品经理,负责电商APP"))
print(conversation.predict(input="我在团队里带5个人,主要做用户增长"))
print(conversation.predict(input="今年OKR是提升DAU 30%"))
print(conversation.predict(input="我们最近在做一个新功能叫直播带货"))
print("\n=== 生成的摘要 ===")
print(memory.buffer)
# 摘要已保存到 summary_user_123.json,可以随时查看、备份、审计
# 文件内容示例:
# [
# {"type": "human", "content": "我是一名产品经理..."},
# {"type": "ai", "content": "..."},
# ...
# ]特点:结合完整记忆和摘要记忆的优点。
保留最近的完整对话 + 较早对话的摘要,平衡信息完整性和Token消耗。
“最近消息保真 + 早期消息摘要”。既能保证最近几轮的细节不丢,又能把很早的历史压缩。
现象:运行 SummaryBufferMemory 时报 httpx.ConnectTimeout: timed out,原因是 LangChain 需要下载 gpt2 tokenizer 来计算 token 数,但国内访问 HuggingFace 不稳定。
# 设置国内镜像并运行
HF_ENDPOINT=https://hf-mirror.com python3 memory_summary_buffer.py# 先缓存一次,以后就不需要网络了
HF_ENDPOINT=https://hf-mirror.com python -c "from transformers import GPT2TokenizerFast; GPT2TokenizerFast.from_pretrained('gpt2'); print('gpt2 tokenizer cached')"# 添加到 ~/.zshrc 或 ~/.bashrc
echo 'export HF_ENDPOINT=https://hf-mirror.com' >> ~/.zshrc
source ~/.zshrc
# 之后直接运行即可
python3 memory_summary_buffer.py✅ gpt2 tokenizer cached successfully,之后即使不设置镜像也能正常运行。
import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationSummaryBufferMemory
from langchain_community.chat_models import ChatTongyi
# ⚠️ 注意:请使用 python3 运行
# python3 memory_summary_buffer.py
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# SummaryBufferMemory:超过 max_token_limit 后,把早期历史压成 summary,并保留最近消息原文
memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=100,
)
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=True,
)
print("第1轮:")
print(conversation.predict(input="我叫张三,是一名Python开发者"))
print("\n第2轮:")
print(conversation.predict(input="我今年30岁,住在北京"))
print("\n第3轮:")
print(conversation.predict(input="我喜欢看科幻电影,最近在学习LangChain"))
print("\n第4轮:")
print(conversation.predict(input="我的职业是什么?我住哪里?"))
print("\n=== memory.buffer ===")
print(memory.buffer)=== 记忆内容 ===
{'history': 'System: 前3轮对话的摘要...\nHuman: 这是第4轮对话\nAI: ...\nHuman: 这是第5轮对话\nAI: ...'}
👉 较早的对话被总结,最近的对话保持完整!
问题:SummaryBufferMemory 同时维护“摘要 + 最近消息”,数据量更大,更需要持久化。
解决:通过持久化存储保存混合记忆,确保重启后既能恢复摘要也能恢复最近完整消息。
关键理解:SummaryBufferMemory 是最复杂的记忆类型,它同时包含:
1)早期历史的摘要;2)最近 N 轮的完整原文。
持久化后重启程序,这两部分都能恢复,无需重新生成摘要或丢失最近消息。
import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationSummaryBufferMemory
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_community.chat_models import ChatTongyi
# SummaryBufferMemory + SQLite 持久化(最完整的生产方案)
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# SummaryBufferMemory 使用 SQL 持久化
memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=100, # 超过100 tokens时,早期历史会被总结
chat_memory=SQLChatMessageHistory(
session_id="user_summary_buffer_123",
connection_string="sqlite:///chat_history.db"
),
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
# 第一阶段:写入足够多的对话触发摘要机制
print("=== 第一阶段:写入长对话(触发摘要) ===")
print(conversation.predict(input="我叫张三,是一名Python开发者"))
print(conversation.predict(input="我今年30岁,住在北京,工作5年了"))
print(conversation.predict(input="我喜欢看科幻电影,跑步,和编程"))
print(conversation.predict(input="最近在做一个AI项目,用LangChain"))
print("\n=== 当前记忆状态(摘要+最近消息) ===")
print(memory.buffer)
print("\n=== 重启程序后 ===")
# 重新创建 memory(从数据库加载已保存的摘要+消息)
memory2 = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=100,
chat_memory=SQLChatMessageHistory(
session_id="user_summary_buffer_123",
connection_string="sqlite:///chat_history.db"
),
return_messages=True
)
conversation2 = ConversationChain(llm=llm, memory=memory2, verbose=True)
# 验证:既能回答早期事实(来自摘要),也能回答近期细节(来自完整消息)
print(conversation2.predict(input="我叫什么名字?我的职业是什么?"))
print(conversation2.predict(input="我最近在做什麼项目?"))
print("\n=== 加载后的记忆状态 ===")
print(memory2.buffer)
# 说明:数据库同时保存了摘要和最近消息,重启后完整恢复import os
from langchain_classic.chains import ConversationChain
from langchain_classic.memory import ConversationSummaryBufferMemory
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_community.chat_models import ChatTongyi
# SummaryBufferMemory + Redis 持久化(高性能分布式方案)
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
# 使用 Redis 存储,设置合适的过期时间
memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=150,
chat_memory=RedisChatMessageHistory(
session_id="enterprise_user_001",
url="redis://localhost:6379/0",
ttl=86400 # 24小时过期,适合企业级会话
),
return_messages=True
)
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)
# 企业级场景:客户咨询历史
print(conversation.predict(input="我是某科技公司的CTO,正在评估AI方案"))
print(conversation.predict(input="我们有50人的技术团队"))
print(conversation.predict(input="主要关注数据安全和成本控制"))
print(conversation.predict(input="希望能快速落地,3个月内见效果"))
# 多实例共享:同一个 session_id 可以在多个服务实例间共享记忆
# 适合:微服务架构、负载均衡、客服系统from langchain_community.chat_message_histories import ChatMessageHistory
history = ChatMessageHistory()
# 手动添加消息
history.add_user_message("你好")
history.add_ai_message("你好!有什么可以帮你的?")
# 查看消息
print(history.messages)
# 清空记忆
history.clear()import os
from dotenv import load_dotenv
from langchain_community.chat_message_histories import FileChatMessageHistory
from langchain_community.chat_models import ChatTongyi
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
# 加载环境变量(从 .env 中读取 DASHSCOPE_API_KEY)
load_dotenv()
api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
raise ValueError("请先在 .env 中配置 DASHSCOPE_API_KEY")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个有帮助的助手。"),
MessagesPlaceholder("history"),
("human", "{input}"),
]
)
chain = prompt | llm
def get_file_history(session_id: str) -> FileChatMessageHistory:
# 每个 session_id 绑定一个历史文件
return FileChatMessageHistory(f"{session_id}_history.json")
conversation = RunnableWithMessageHistory(
chain,
get_file_history,
input_messages_key="input",
history_messages_key="history",
)
session_id = "demo_user"
print("=== 第1次运行:写入对话并持久化到文件 ===")
conversation.invoke(
{"input": "我叫李雷,今年28岁,在杭州做前端工程师"},
config={"configurable": {"session_id": session_id}},
)
conversation.invoke(
{"input": "我喜欢跑步和看电影"},
config={"configurable": {"session_id": session_id}},
)
print("\n=== 模拟下次启动:重新加载同一个文件 ===")
r = conversation.invoke(
{"input": "请总结一下我的基本信息"},
config={"configurable": {"session_id": session_id}},
)
print("AI:", r.content)import os
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_community.chat_models import ChatTongyi
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 从环境变量读取 API Key(请确保已设置 DASHSCOPE_API_KEY)
os.environ["DASHSCOPE_API_KEY"] = os.environ.get("DASHSCOPE_API_KEY", "")
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
history = ChatMessageHistory()
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个有帮助的助手。"),
MessagesPlaceholder("history"),
("human", "{input}"),
]
)
def invoke(user_input: str) -> str:
messages = prompt.format_messages(history=history.messages, input=user_input)
ai = llm.invoke(messages)
history.add_user_message(user_input)
history.add_ai_message(ai.content)
return ai.content
invoke("我叫韩梅梅,是一名数据分析师")
invoke("我擅长用Python做数据可视化")
print("=== 字符串形式的 history(自己拼接)===")
history_str = "\n".join([f"[{m.type}] {m.content}" for m in history.messages])
print(type(history_str))
print(history_str)
print("\n=== 消息列表形式的 history ===")
print(type(history.messages))
for msg in history.messages:
print(f"[{msg.type}] {msg.content}")
演示如何用 RunnableWithMessageHistory 维护会话历史,并用 K 实现只保留最近几轮(Window)效果。
import os
from langchain_community.chat_models import ChatTongyi
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
# os.environ["DASHSCOPE_API_KEY"] = "your-api-key"
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个有帮助的助手。"),
MessagesPlaceholder("history"),
("human", "{input}"),
]
)
chain = prompt | llm
store = {}
def get_history(session_id: str) -> ChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
conversation = RunnableWithMessageHistory(
chain,
get_history,
input_messages_key="input",
history_messages_key="history",
)
session_id = "user_2"
K = 2 # 只保留最近 K 轮(约 2K 条消息:user+ai)
def trim_window(history: ChatMessageHistory, k: int) -> None:
keep = k * 2
if len(history.messages) > keep:
history.messages = history.messages[-keep:]
def ask(text: str) -> str:
r = conversation.invoke(
{"input": text},
config={"configurable": {"session_id": session_id}},
)
trim_window(store[session_id], K)
return r.content
print("第1轮:")
ask("我叫张三")
print("\n第2轮:")
ask("我是Python开发者")
print("\n第3轮:")
ask("我今年30岁")
print("\n第4轮:")
ask("我住在北京")
print("\n第5轮 - 测试记忆:")
response = ask("我叫什么名字?我的职业是什么?")
print("AI:", response)
print("\n=== 当前记忆(只保留最近 K 轮)===")
print(store[session_id].messages)
用一个最小可运行示例手写“摘要 + 最近消息”的混合记忆:超过阈值就把更早的消息压缩进 summary,并只保留最近 N 条原文消息。
import os
from langchain_community.chat_models import ChatTongyi
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
#os.environ["DASHSCOPE_API_KEY"] = "your-api-key"
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
history = ChatMessageHistory()
summary = ""
chat_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个有帮助的助手。历史摘要:{summary}"),
MessagesPlaceholder("history"),
("human", "{input}"),
]
)
summary_prompt = ChatPromptTemplate.from_messages(
[
("system", "把对话总结成不超过80字摘要,只保留关键事实:"),
MessagesPlaceholder("history"),
]
)
# 关键事实的作用:通过只保留最重要的信息,既节省了 token 成本,又确保了关键信息不会丢失
def maybe_summarize(max_messages: int = 8) -> None:
global summary
# 超过阈值:把较早消息压缩进 summary,只保留最近 max_messages 条原文
if len(history.messages) <= max_messages:
return
s_messages = summary_prompt.format_messages(history=history.messages)
summary = llm.invoke(s_messages).content
history.messages = history.messages[-max_messages:]
for i in range(5):
user_input = f"这是第{i+1}轮对话"
messages = chat_prompt.format_messages(summary=summary, history=history.messages, input=user_input)
ai = llm.invoke(messages)
history.add_user_message(user_input)
history.add_ai_message(ai.content)
maybe_summarize(max_messages=6)
print(f"第{i+1}轮: {ai.content}\n")
print("=== 记忆内容(summary + 最近消息)===")
print("summary:", summary)
print("history messages:", history.messages)这章的目标不是“记住名字”,而是让你能在真实业务里选对记忆方案,并能把记忆做成可控、可观测、可扩展的工程能力。
1. Buffer / Window / Summary / SummaryBuffer 的核心差异是什么?
请从“信息完整性、成本、风险(遗忘/误总结)”三方面对比,并举 1 个你项目里的典型场景选择其一。
2. 为什么“多用户/多会话”必须引入 session_id?
如果不区分 session,会出现什么线上事故?你如何在后端接口里设计它(例如 header/cookie/body)?
建议:在一个不包含本项目 static/langchain 的路径下运行示例,避免目录名遮蔽 pip 安装的包。
C1. 【编程题】实现“多会话隔离”:同一个服务进程里同时维护 2 个用户的记忆
要求:用 RunnableWithMessageHistory;user_a 记住“张三/开发者”,user_b 记住“李四/产品经理”;互相提问时不能串台。
C2. 【编程题】实现“窗口记忆”:只保留最近 K 轮(并证明它会遗忘)
要求:写一个 trim_window(),每轮对话后裁剪 history.messages;让模型在第 5 轮问“我叫什么名字?”时答不出来。
C3. 【编程题】实现“文件持久化记忆”:重启程序后还能记得之前信息
要求:用 FileChatMessageHistory;第一次运行写入两轮消息;第二次运行用同一个 session_id 继续提问并能回答。
1.【工程题】你如何评估“记忆”带来的成本,并让它可观测?
请覆盖:token 使用、延迟、失败率、session 维度统计、采样回放。
2.【架构题】如果要把记忆存到 Redis / 数据库,你会怎么设计?
请覆盖:数据结构、过期策略(TTL)、并发写入、合规(隐私/脱敏)。
掌握了记忆管理后,下一章我们将学习Prompt模板, 让提示词更加灵活和可复用!