系列目标:30 天从 LangChain 入门到企业级部署
今日任务:理解 Memory 类型 → 掌握 ConversationBufferMemory → 实现“带历史的 RAG 聊天机器人”!
💭 一、为什么需要 Memory?
没有记忆的 AI 像“金鱼”:
- 用户:“帮我查订单 1001”
- AI:“订单 1001 已发货。”
- 用户:“那物流单号是多少?”
- AI:“请提供订单号。”
问题:AI 忘了上一句提到的“订单 1001”!
解决方案:
✅ Memory 机制 —— 将对话历史注入每次 Prompt,让 LLM 理解上下文!
💡 今天,我们就用 LangChain 的 Memory 模块,构建一个能记住 5 轮对话的智能客服!
🧱 二、LangChain Memory 核心类型
表格
| Memory 类型 | 特点 | 适用场景 |
|---|---|---|
ConversationBufferMemory | 保存全部历史 | 短对话、调试 |
ConversationBufferWindowMemory | 只保留最近 N 轮 | 长对话、控制 token |
ConversationSummaryMemory | 用 LLM 生成摘要 | 超长对话(节省 token) |
RedisChatMessageHistory | 外部存储(分布式) | Web 应用、多实例 |
✅ Day 21 重点:
ConversationBufferWindowMemory+ RAG
🛠️ 三、动手实践 1:基础对话记忆(无 RAG)
# day21_memory_chat.py
from langchain_ollama import ChatOllama
from langchain.memory import ConversationBufferWindowMemory
from langchain.chains import ConversationChain
# 初始化 LLM 和 Memory(只记最近 3 轮)
llm = ChatOllama(model="qwen:7b", temperature=0)
memory = ConversationBufferWindowMemory(k=3)
# 创建对话链
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=True
)
# 多轮对话
print(conversation.predict(input="你好!"))
print(conversation.predict(input="我叫小明"))
print(conversation.predict(input="我叫什么名字?")) # AI 能答对!
▶️ 输出示例:
> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI...
Current conversation:
Human: 你好!
AI: 你好!有什么我可以帮你的吗?
Human: 我叫小明
AI: 很高兴认识你,小明!
Human: 我叫什么名字?
AI: 你叫小明。
✅ 成功记住“小明”!
🤖 四、动手实践 2:RAG + Memory(终极组合)
现在,我们要构建一个既能查知识库,又能记对话的客服机器人!
步骤 1:准备带 Memory 的 Prompt
from langchain_core.prompts import PromptTemplate
# 自定义 Prompt,包含 {history} 和 {context}
template = """
你是一个公司客服助手,请根据以下信息回答问题:
已知资料:
{context}
对话历史:
{history}
用户新问题:{input}
回答:
"""
PROMPT = PromptTemplate(
input_variables=["history", "context", "input"],
template=template
)
步骤 2:加载 RAG 组件(复用 Day 18)
from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings
from langchain.chains import RetrievalQA
# 向量库
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
步骤 3:创建带 Memory 的 RAG Chain
from langchain.memory import ConversationBufferWindowMemory
from langchain.chains.conversation.base import ConversationChain
# 初始化 Memory
memory = ConversationBufferWindowMemory(
memory_key="history", # 对应 Prompt 中的 {history}
input_key="input", # 用户输入字段名
k=3 # 记住最近 3 轮
)
# 创建链
rag_with_memory = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
memory=memory, # 注入 Memory!
chain_type_kwargs={"prompt": PROMPT},
return_source_documents=True
)
⚠️ 注意:
RetrievalQA支持memory参数(LangChain >=0.1.0)
步骤 4:测试多轮 RAG 对话
# 第一轮
res1 = rag_with_memory({"query": "员工年假有多少天?"})
print("Q1:", res1["result"])
# 第二轮(指代上文)
res2 = rag_with_memory({"query": "那病假呢?"}) # AI 知道在问“假期政策”
print("Q2:", res2["result"])
# 第三轮(混合上下文)
res3 = rag_with_memory({"query": "刚才说的年假能跨年吗?"})
print("Q3:", res3["result"])
▶️ 预期效果:
- Q2:正确返回病假政策(而非重复年假)
- Q3:正确引用“年假不可跨年”条款
✅ 真正实现:
- 语义检索(RAG)
- 上下文理解(Memory)
- 引用溯源(Source Documents)
☁️ 五、进阶:用 Redis 存储对话历史(Web 应用必备)
在 Web 服务中,需按 session_id 存储历史:
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain.memory import ConversationSummaryBufferMemory
def get_memory(session_id: str):
message_history = RedisChatMessageHistory(
url="redis://localhost:6379",
ttl=3600, # 1小时过期
session_id=session_id
)
return ConversationSummaryBufferMemory(
chat_memory=message_history,
max_token_limit=500,
llm=llm # 用于生成摘要
)
# 在 FastAPI 中使用
@app.post("/chat")
async def chat(req: ChatRequest):
memory = get_memory(req.session_id)
chain = RetrievalQA(..., memory=memory, ...)
return chain({"query": req.message})
✅ 支持分布式部署、会话隔离、自动过期!
⚠️ 六、注意事项 & 最佳实践
表格
| 问题 | 建议 |
|---|---|
| Token 超限 | 用 WindowMemory 或 SummaryMemory 控制长度 |
| 历史干扰检索 | 在 Prompt 中明确区分“历史”和“资料” |
| 敏感信息留存 | 定期清理 Redis;Memory 中脱敏(结合 Day 14) |
| 多用户混淆 | 务必用 session_id 隔离(Web 场景) |
| 成本增加 | 每次调用都传历史 → 增加 token 消耗 |
💡 生产建议:
- 默认只记 2~3 轮(足够覆盖 90% 场景)
- 对“重置对话”指令清空 Memory
- 监控平均对话轮数,优化
k值
📦 七、配套代码结构
langchain-30-days/
└── day21/
├── memory_rag_chat.py # 带记忆的 RAG 聊天
└── web_memory_demo.py # Redis + FastAPI 示例(进阶)
📝 八、今日小结
- ✅ 理解了 Memory 对多轮对话的必要性
- ✅ 学会了
ConversationBufferWindowMemory控制历史长度 - ✅ 实现了 RAG + Memory 的融合系统
- ✅ 掌握了用 Redis 支持 Web 应用的分布式记忆
- ✅ 知道了 Token 控制与安全清理策略
🎯 明日预告:Day 22 —— 长文档处理!MapReduce 与 Refine Chain 解决超长上下文难题!