🌟 LangChain 30 天保姆级教程 · Day 21|Memory 机制实战!让 AI 记住你说过的话,实现多轮连贯对话!

28 阅读4分钟

系列目标: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 解决超长上下文难题!