文章目录
-
- [Part 10: Memory(记忆系统)](#Part 10: Memory(记忆系统))
-
- [10.1 记忆概述](#10.1 记忆概述)
- [10.2 LangGraph Checkpointer(推荐)](#10.2 LangGraph Checkpointer(推荐))
-
- [InMemorySaver 完整 Demo](#InMemorySaver 完整 Demo)
- [SqliteSaver 持久化 Demo](#SqliteSaver 持久化 Demo)
- [多用户隔离 Demo](#多用户隔离 Demo)
- [10.3 传统记忆组件](#10.3 传统记忆组件)
-
- [ConversationBufferMemory Demo](#ConversationBufferMemory Demo)
- [ConversationBufferWindowMemory Demo](#ConversationBufferWindowMemory Demo)
- [ConversationSummaryMemory Demo](#ConversationSummaryMemory Demo)
- [ConversationSummaryBufferMemory Demo](#ConversationSummaryBufferMemory Demo)
- [VectorStoreRetrieverMemory Demo](#VectorStoreRetrieverMemory Demo)
- 每种记忆的适用场景
- [10.4 在链中使用记忆](#10.4 在链中使用记忆)
-
- [LCEL + 记忆 Demo](#LCEL + 记忆 Demo)
- [带记忆的 RAG Demo](#带记忆的 RAG Demo)
- [10.5 记忆最佳实践](#10.5 记忆最佳实践)
- ==========================================
- [1. 记忆类型选择指南](#1. 记忆类型选择指南)
- =========================================="""
- ==========================================
- [2. Token 消耗优化](#2. Token 消耗优化)
- =========================================="""
- ==========================================
- [3. 长期记忆管理](#3. 长期记忆管理)
- =========================================="""
Part 10: Memory(记忆系统)
10.1 记忆概述
为什么需要记忆
默认情况下,LLM 是无状态的 ------ 每次调用都是独立的,模型不会记住之前的对话内容。但在实际应用中,我们需要:
- 多轮对话:用户期望 AI 记住之前说过的话
- 上下文连贯:后续问题可能引用之前的内容
- 个性化:记住用户的偏好和历史行为
- 任务连续性:复杂任务需要跨多轮交互完成
记忆类型
#mermaid-svg-3elUVsdEJWfufDC8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3elUVsdEJWfufDC8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3elUVsdEJWfufDC8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3elUVsdEJWfufDC8 .error-icon{fill:#552222;}#mermaid-svg-3elUVsdEJWfufDC8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3elUVsdEJWfufDC8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3elUVsdEJWfufDC8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3elUVsdEJWfufDC8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3elUVsdEJWfufDC8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3elUVsdEJWfufDC8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3elUVsdEJWfufDC8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3elUVsdEJWfufDC8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3elUVsdEJWfufDC8 .marker.cross{stroke:#333333;}#mermaid-svg-3elUVsdEJWfufDC8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3elUVsdEJWfufDC8 p{margin:0;}#mermaid-svg-3elUVsdEJWfufDC8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3elUVsdEJWfufDC8 .cluster-label text{fill:#333;}#mermaid-svg-3elUVsdEJWfufDC8 .cluster-label span{color:#333;}#mermaid-svg-3elUVsdEJWfufDC8 .cluster-label span p{background-color:transparent;}#mermaid-svg-3elUVsdEJWfufDC8 .label text,#mermaid-svg-3elUVsdEJWfufDC8 span{fill:#333;color:#333;}#mermaid-svg-3elUVsdEJWfufDC8 .node rect,#mermaid-svg-3elUVsdEJWfufDC8 .node circle,#mermaid-svg-3elUVsdEJWfufDC8 .node ellipse,#mermaid-svg-3elUVsdEJWfufDC8 .node polygon,#mermaid-svg-3elUVsdEJWfufDC8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3elUVsdEJWfufDC8 .rough-node .label text,#mermaid-svg-3elUVsdEJWfufDC8 .node .label text,#mermaid-svg-3elUVsdEJWfufDC8 .image-shape .label,#mermaid-svg-3elUVsdEJWfufDC8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-3elUVsdEJWfufDC8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3elUVsdEJWfufDC8 .rough-node .label,#mermaid-svg-3elUVsdEJWfufDC8 .node .label,#mermaid-svg-3elUVsdEJWfufDC8 .image-shape .label,#mermaid-svg-3elUVsdEJWfufDC8 .icon-shape .label{text-align:center;}#mermaid-svg-3elUVsdEJWfufDC8 .node.clickable{cursor:pointer;}#mermaid-svg-3elUVsdEJWfufDC8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3elUVsdEJWfufDC8 .arrowheadPath{fill:#333333;}#mermaid-svg-3elUVsdEJWfufDC8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3elUVsdEJWfufDC8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3elUVsdEJWfufDC8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3elUVsdEJWfufDC8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3elUVsdEJWfufDC8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3elUVsdEJWfufDC8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3elUVsdEJWfufDC8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3elUVsdEJWfufDC8 .cluster text{fill:#333;}#mermaid-svg-3elUVsdEJWfufDC8 .cluster span{color:#333;}#mermaid-svg-3elUVsdEJWfufDC8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3elUVsdEJWfufDC8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3elUVsdEJWfufDC8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-3elUVsdEJWfufDC8 .icon-shape,#mermaid-svg-3elUVsdEJWfufDC8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3elUVsdEJWfufDC8 .icon-shape p,#mermaid-svg-3elUVsdEJWfufDC8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3elUVsdEJWfufDC8 .icon-shape .label rect,#mermaid-svg-3elUVsdEJWfufDC8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3elUVsdEJWfufDC8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3elUVsdEJWfufDC8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3elUVsdEJWfufDC8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Memory 记忆系统
短期记忆
长期记忆
混合记忆
全量缓冲
ConversationBufferMemory
窗口缓冲
ConversationBufferWindowMemory
摘要记忆
ConversationSummaryMemory
向量记忆
VectorStoreRetrieverMemory
摘要+缓冲
ConversationSummaryBufferMemory
LangGraph Checkpointer
记忆类型对比表格
| 记忆类型 | 存储方式 | Token 消耗 | 信息保留 | 适用场景 |
|---|---|---|---|---|
| ConversationBufferMemory | 全部消息 | 高(线性增长) | 完整 | 短对话 |
| ConversationBufferWindowMemory | 最近 k 轮 | 固定 | 部分丢失 | 中等长度对话 |
| ConversationSummaryMemory | 对话摘要 | 低 | 摘要级别 | 长对话 |
| ConversationSummaryBufferMemory | 摘要+最近消息 | 中等 | 较好 | 长对话(推荐) |
| VectorStoreRetrieverMemory | 向量检索 | 按需 | 语义相关 | 超长对话 |
| LangGraph Checkpointer | 完整状态 | 按需加载 | 完整 | 生产环境(推荐) |
10.2 LangGraph Checkpointer(推荐)
LangGraph 的 Checkpointer 是 LangChain 1.3 中推荐的记忆管理方案,它通过保存和恢复图的完整状态来实现记忆功能。
InMemorySaver 完整 Demo
python
"""
LangGraph InMemorySaver 完整 Demo
安装: pip install langgraph
"""
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from typing import Annotated
from typing_extensions import TypedDict
# ==========================================
# 1. 定义状态
# ==========================================
class State(TypedDict):
"""定义图的状态结构"""
messages: list # 消息列表(会自动累积)
# ==========================================
# 2. 定义节点
# ==========================================
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
def chatbot(state: State):
"""聊天机器人节点:接收消息列表,返回新的消息"""
response = llm.invoke(state["messages"])
return {"messages": [response]}
# ==========================================
# 3. 构建图
# ==========================================
# 创建内存检查点
checkpointer = InMemorySaver()
# 构建状态图
graph_builder = StateGraph(State)
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)
# 编译图时传入检查点
graph = graph_builder.compile(checkpointer=checkpointer)
# ==========================================
# 4. 使用 thread_id 管理对话
# ==========================================
# 每个用户/对话使用唯一的 thread_id
config = {"configurable": {"thread_id": "user_123"}}
# 第一轮对话
result = graph.invoke(
{"messages": [("human", "我叫张三,我是一名 Python 开发者。")]},
config=config,
)
print(f"AI: {result['messages'][-1].content}")
# 第二轮对话(AI 会记住之前的信息)
result = graph.invoke(
{"messages": [("human", "我叫什么名字?我是做什么的?")]},
config=config,
)
print(f"AI: {result['messages'][-1].content}")
# AI 应该回答 "你叫张三,是一名 Python 开发者"
# ==========================================
# 5. 查看对话历史
# ==========================================
# 获取当前状态
state = graph.get_state(config)
print(f"\n当前对话消息数: {len(state.values['messages'])}")
for msg in state.values['messages']:
role = msg.type
content = msg.content[:50]
print(f" [{role}] {content}...")
SqliteSaver 持久化 Demo
python
"""
LangGraph SqliteSaver 持久化 Demo
安装: pip install langgraph
"""
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from typing_extensions import TypedDict
class State(TypedDict):
messages: list
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
def chatbot(state: State):
response = llm.invoke(state["messages"])
return {"messages": [response]}
# ==========================================
# 使用 SQLite 持久化检查点
# ==========================================
DB_PATH = "/data/user/work/langgraph_checkpoint.db"
# 创建 SQLite 检查点(数据持久化到磁盘)
with SqliteSaver.from_conn_string(DB_PATH) as checkpointer:
graph_builder = StateGraph(State)
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)
graph = graph_builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "persistent_user_1"}}
# 对话数据会持久化到 SQLite 数据库
# 即使程序重启,也能恢复之前的对话
result = graph.invoke(
{"messages": [("human", "记住:我最喜欢的颜色是蓝色。")]},
config=config,
)
print(f"AI: {result['messages'][-1].content}")
# 模拟程序重启后恢复对话
result = graph.invoke(
{"messages": [("human", "我最喜欢什么颜色?")]},
config=config,
)
print(f"AI: {result['messages'][-1].content}")
多用户隔离 Demo
python
"""
多用户隔离 Demo
使用不同的 thread_id 实现用户间的对话隔离
"""
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from typing_extensions import TypedDict
class State(TypedDict):
messages: list
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
def chatbot(state: State):
response = llm.invoke(state["messages"])
return {"messages": [response]}
checkpointer = InMemorySaver()
graph_builder = StateGraph(State)
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", END)
graph = graph_builder.compile(checkpointer=checkpointer)
# ==========================================
# 用户 A 的对话
# ==========================================
config_a = {"configurable": {"thread_id": "user_alice"}}
graph.invoke(
{"messages": [("human", "我叫 Alice,我在学习机器学习。")]},
config=config_a,
)
result = graph.invoke(
{"messages": [("human", "我叫什么名字?")]},
config=config_a,
)
print(f"Alice 的 AI: {result['messages'][-1].content}")
# 输出: 你叫 Alice
# ==========================================
# 用户 B 的对话(完全独立)
# ==========================================
config_b = {"configurable": {"thread_id": "user_bob"}}
result = graph.invoke(
{"messages": [("human", "我叫什么名字?")]},
config=config_b,
)
print(f"Bob 的 AI: {result['messages'][-1].content}")
# 输出: 我不知道你的名字(Bob 的对话是全新的)
# ==========================================
# 用户 A 继续对话(记忆仍在)
# ==========================================
result = graph.invoke(
{"messages": [("human", "我在学什么?")]},
config=config_a,
)
print(f"Alice 的 AI: {result['messages'][-1].content}")
# 输出: 你在学习机器学习
10.3 传统记忆组件
注意:传统记忆组件在 LangChain 1.3 中仍然可用,但推荐新项目使用 LangGraph Checkpointer。
ConversationBufferMemory Demo
python
"""
ConversationBufferMemory Demo
保存所有对话历史
"""
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# ==========================================
# 方式一:直接使用 Memory
# ==========================================
memory = ConversationBufferMemory(
memory_key="history", # 存储在提示中的键名(默认 "history")
return_messages=True, # 返回 Message 对象(而非字符串)
input_key="input", # 用户输入的键名
output_key="output", # AI 输出的键名
human_prefix="Human", # 人类消息前缀
ai_prefix="AI", # AI 消息前缀
)
# 手动添加对话
memory.save_context(
{"input": "你好,我叫张三。"},
{"output": "你好张三!很高兴认识你。"}
)
memory.save_context(
{"input": "我今天心情不错。"},
{"output": "很高兴听到这个消息!有什么开心的事情吗?"}
)
# 查看记忆内容
print(memory.load_memory_variables({}))
# {'history': [HumanMessage('你好,我叫张三。'), AIMessage('你好张三!...'), ...]}
# ==========================================
# 方式二:与 Chain 一起使用
# ==========================================
memory2 = ConversationBufferMemory()
conversation = ConversationChain(
llm=llm,
memory=memory2,
verbose=False, # 设为 True 可查看提示详情
)
# 多轮对话
response1 = conversation.predict(input="我叫李四,是一名数据科学家。")
print(f"AI: {response1}")
response2 = conversation.predict(input="我叫什么名字?")
print(f"AI: {response2}")
# AI 应该记得 "你叫李四"
# ==========================================
# 方式三:清空记忆
# ==========================================
memory.clear()
print(f"清空后: {memory.load_memory_variables({})}")
ConversationBufferWindowMemory Demo
python
"""
ConversationBufferWindowMemory Demo
只保留最近 k 轮对话,控制 Token 消耗
"""
from langchain.memory import ConversationBufferWindowMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
memory = ConversationBufferWindowMemory(
k=2, # 保留最近 2 轮对话(1 轮 = 1 次人类 + 1 次 AI)
memory_key="history",
return_messages=True,
)
conversation = ConversationChain(llm=llm, memory=memory)
# 进行 4 轮对话
conversation.predict(input="第一轮:我喜欢吃苹果。")
conversation.predict(input="第二轮:我也喜欢吃香蕉。")
conversation.predict(input="第三轮:我最喜欢的颜色是蓝色。")
conversation.predict(input="第四轮:我住在上海。")
# 查看记忆 ------ 只有最近 2 轮
history = memory.load_memory_variables({})["history"]
print(f"记忆中的消息数: {len(history)}")
# 应该只有第 3 轮和第 4 轮的消息
# 第 1 轮和第 2 轮的信息已被丢弃
response = conversation.predict(input="我最喜欢什么颜色?")
print(f"AI: {response}") # 应该记得蓝色(在第 3 轮中提到)
ConversationSummaryMemory Demo
python
"""
ConversationSummaryMemory Demo
使用 LLM 对对话历史进行摘要,节省 Token
"""
from langchain.memory import ConversationSummaryMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
memory = ConversationSummaryMemory(
llm=llm, # 用于生成摘要的 LLM
memory_key="history",
return_messages=False, # 返回摘要字符串(而非消息列表)
# max_token_limit=500, # 摘要的最大 Token 数(可选)
)
conversation = ConversationChain(llm=llm, memory=memory)
# 多轮对话
conversation.predict(input="我叫王五,是一名全栈工程师。")
conversation.predict(input="我擅长 Python 和 JavaScript。")
conversation.predict(input="我最近在学 Rust 语言。")
conversation.predict(input="我的目标是成为一名架构师。")
# 查看摘要
summary = memory.load_memory_variables({})["history"]
print(f"对话摘要:\n{summary}")
# 输出类似: "王五是一名全栈工程师,擅长 Python 和 JavaScript,
# 最近在学习 Rust,目标是成为架构师。"
ConversationSummaryBufferMemory Demo
python
"""
ConversationSummaryBufferMemory Demo
结合摘要和窗口缓冲,兼顾信息保留和 Token 控制
"""
from langchain.memory import ConversationSummaryBufferMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
memory = ConversationSummaryBufferMemory(
llm=llm, # 用于生成摘要的 LLM
max_token_limit=200, # Token 限制(超过此值时,旧消息会被摘要)
memory_key="history",
return_messages=True,
)
conversation = ConversationChain(llm=llm, memory=memory)
# 多轮对话
conversation.predict(input="我叫赵六,在一家 AI 公司工作。")
conversation.predict(input="我的职位是高级算法工程师。")
conversation.predict(input="我负责 NLP 相关的项目。")
conversation.predict(input="最近在做一个基于 RAG 的智能客服系统。")
# 查看记忆
variables = memory.load_memory_variables({})
print(f"当前记忆:\n{variables}")
# 旧消息会被摘要,最近的消息保持原文
VectorStoreRetrieverMemory Demo
python
"""
VectorStoreRetrieverMemory Demo
将对话历史存入向量数据库,通过语义检索获取相关记忆
"""
from langchain.memory import VectorStoreRetrieverMemory
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 创建向量存储
vectorstore = Chroma(
embedding_function=embeddings,
collection_name="conversation_memory",
)
# 创建向量记忆
memory = VectorStoreRetrieverMemory(
retriever=vectorstore.as_retriever(
search_kwargs={"k": 3}, # 每次检索 3 条相关记忆
),
memory_key="history",
return_messages=True,
)
# ==========================================
# 保存对话到记忆
# ==========================================
memory.save_context(
{"input": "我最喜欢的编程语言是 Python。"},
{"output": "Python 确实是一门很棒的语言!"}
)
memory.save_context(
{"input": "我最近在学习 LangChain 框架。"},
{"output": "LangChain 是一个强大的 LLM 应用开发框架。"}
)
memory.save_context(
{"input": "我的宠物叫小白,是一只猫。"},
{"output": "小白听起来很可爱!"}
)
memory.save_context(
{"input": "我住在北京市海淀区。"},
{"output": "海淀区是北京的高校和科技企业聚集地。"}
)
# ==========================================
# 通过语义检索获取相关记忆
# ==========================================
result = memory.load_memory_variables(
{"input": "我喜欢什么编程语言?"} # 会检索与"编程语言"相关的记忆
)
print("相关记忆:")
for msg in result["history"]:
print(f" [{msg.type}] {msg.content}")
# 输出应该包含关于 Python 和 LangChain 的对话
# 但不会包含关于宠物和住址的对话(与编程语言无关)
每种记忆的适用场景
| 记忆类型 | Token 消耗 | 信息保留 | 适用场景 |
|---|---|---|---|
| ConversationBufferMemory | 高 | 完整 | 短对话(<10 轮) |
| ConversationBufferWindowMemory | 固定 | 最近 k 轮 | 中等对话,关注近期上下文 |
| ConversationSummaryMemory | 低 | 摘要级别 | 长对话,只需大致上下文 |
| ConversationSummaryBufferMemory | 中等 | 摘要+近期 | 长对话(最平衡的选择) |
| VectorStoreRetrieverMemory | 按需 | 语义相关 | 超长对话,需要精准检索 |
10.4 在链中使用记忆
LCEL + 记忆 Demo
python
"""
LCEL + 记忆 Demo
使用 RunnableWithMessageHistory 为 LCEL 链添加记忆
"""
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain_community.chat_message_histories import ChatMessageHistory
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# ==========================================
# 1. 创建带历史消息占位符的提示
# ==========================================
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个有帮助的 AI 助手。请根据对话历史回答问题。"),
MessagesPlaceholder(variable_name="history"), # 对话历史占位符
("human", "{question}"),
])
# ==========================================
# 2. 创建链
# ==========================================
chain = prompt | llm
# ==========================================
# 3. 创建消息历史存储
# ==========================================
# 使用字典存储不同会话的消息历史
store = {} # {session_id: ChatMessageHistory}
def get_session_history(session_id: str):
"""获取或创建会话的消息历史"""
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# ==========================================
# 4. 包装链(添加记忆功能)
# ==========================================
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history, # 获取消息历史的函数
input_messages_key="question", # 输入消息的键名
history_messages_key="history",# 历史消息的键名
)
# ==========================================
# 5. 使用带记忆的链
# ==========================================
config = {"configurable": {"session_id": "session_1"}}
# 第一轮
response = chain_with_history.invoke(
{"question": "我叫小明,我是一名 AI 工程师。"},
config=config,
)
print(f"AI: {response.content}")
# 第二轮(有记忆)
response = chain_with_history.invoke(
{"question": "我叫什么名字?做什么工作的?"},
config=config,
)
print(f"AI: {response.content}")
# 应该回答 "你叫小明,是一名 AI 工程师"
# 不同会话(独立记忆)
config2 = {"configurable": {"session_id": "session_2"}}
response = chain_with_history.invoke(
{"question": "你知道我是谁吗?"},
config=config2,
)
print(f"AI: {response.content}")
# 应该回答 "我不知道"(新会话,没有历史)
带记忆的 RAG Demo
python
"""
带记忆的 RAG Demo
结合对话记忆和检索增强生成
"""
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnablePassthrough
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.documents import Document
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 准备知识库
documents = [
Document(page_content="LangChain 支持多种向量数据库,包括 Chroma、FAISS、Pinecone 等。"),
Document(page_content="RAG 系统的核心步骤:文档加载、分割、嵌入、存储、检索、生成。"),
Document(page_content="文本分割推荐使用 RecursiveCharacterTextSplitter,chunk_size 建议 500-1000。"),
]
vectorstore = Chroma.from_documents(documents, embeddings)
retriever = vectorstore.as_retriever(k=2)
# 创建提示模板(包含对话历史)
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个专业的 AI 助手。请根据以下上下文和对话历史回答问题。
上下文:
{context}
如果上下文中没有相关信息,请基于你的知识回答,但要说明这不是来自知识库。"""),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
])
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# 创建基础 RAG 链
rag_chain = (
{
"context": retriever | format_docs,
"question": RunnablePassthrough(),
}
| prompt
| llm
| StrOutputParser()
)
# 添加记忆
store = {}
def get_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
rag_with_memory = RunnableWithMessageHistory(
rag_chain,
get_history,
input_messages_key="question",
history_messages_key="history",
)
# 使用
config = {"configurable": {"session_id": "rag_session_1"}}
response = rag_with_memory.invoke("什么是 RAG?", config=config)
print(f"AI: {response}")
# 后续问题可以引用之前的回答
response = rag_with_memory.invoke("它有哪些核心步骤?", config=config)
print(f"AI: {response}")
# AI 应该理解 "它" 指的是 RAG
10.5 记忆最佳实践
"""
记忆最佳实践总结
"""
==========================================
1. 记忆类型选择指南
=========================================="""
| 对话长度 | 推荐记忆类型 | 理由 |
|---|---|---|
| < 10 轮 | ConversationBufferMemory | 简单直接,信息完整 |
| 10-50 轮 | ConversationSummaryBufferMemory | 平衡 Token 和信息保留 |
| > 50 轮 | VectorStoreRetrieverMemory | 语义检索,不受长度限制 |
| 生产环境 | LangGraph Checkpointer | 可靠、可扩展、支持持久化 |
| 多用户 | LangGraph Checkpointer (thread_id) | 天然支持用户隔离 |
| """ |
==========================================
2. Token 消耗优化
=========================================="""
- 使用 ConversationSummaryBufferMemory 而非 ConversationBufferMemory
- 设置合理的 max_token_limit(建议 500-1000)
- 定期清理过期的对话历史
- 使用 VectorStoreRetrieverMemory 进行语义检索,只加载相关记忆
- 对长对话使用摘要而非原文
- 考虑使用更便宜的模型生成摘要
"""
==========================================
3. 长期记忆管理
=========================================="""
- 重要信息提取:从对话中提取关键事实存入结构化存储
- 定期摘要:对长时间未访问的对话进行摘要
- 分层存储:近期对话保持原文,远期对话只保留摘要
- 向量检索:使用向量数据库存储历史对话,按需检索
- 用户画像:维护用户的偏好和特征信息
"""