LangChain 持久化对话记忆:从入门到生产级实践

LangChain 持久化对话记忆:从入门到生产级实践

大语言模型本身是无状态的,每次调用都是全新对话。本文带你从零实现 LangChain 对话记忆的持久化,覆盖 JSON、SQLite、Redis 三种方案;深入讲解 ConversationSummaryMemory 多轮对话摘要压缩;并介绍新版 LCEL 写法,帮你在实际项目中落地多轮对话。


一、为什么需要持久化对话记忆?

LLM 的无状态性是一把双刃剑。在单轮问答中没有问题,但在以下真实场景中,缺少记忆会让用户体验大打折扣:

  • 客服机器人:用户说了订单号,下一句就忘了
  • 代码助手:每轮都要重新描述上下文
  • 个人 AI 助手:重启应用后对话从零开始

LangChain 的 Memory 模块解决的正是这个问题------在每次调用前自动将历史消息注入 Prompt,在每次响应后自动保存新消息。

但默认的 Memory 是内存存储 ,进程重启即消失。本文的核心就是:如何让它真正持久化

📌 环境说明:本文使用 LangChain 0.2.x+,Python 3.10+,以 OpenAI API 作为示例 LLM。


二、Memory 核心类型速览

LangChain 内置了多种 Memory 策略,先建立认知:

类型 策略 适用场景 Token 消耗
ConversationBufferMemory 保存全量历史 短对话 / 调试
ConversationBufferWindowMemory 保留最近 k 轮 一般对话
ConversationSummaryMemory LLM 摘要压缩 长对话 / 省 token
ConversationSummaryBufferMemory 摘要 + 近期缓冲 兼顾细节与长度

本文重点不在于类型本身,而在于将这些 Memory 与持久化存储后端结合


三、快速上手:内存记忆(跑通基础)

先用最简单的方式跑通,建立直觉。

安装依赖

复制代码
pip install langchain langchain-openai openai

最小可运行示例

ini 复制代码
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
import os

os.environ["OPENAI_API_KEY"] = "sk-..."

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
memory = ConversationBufferMemory(return_messages=True)
chain = ConversationChain(llm=llm, memory=memory)

# 第一轮
resp1 = chain.invoke({"input": "我叫小明,今年 25 岁"})
print(resp1["response"])

# 第二轮 ------ 模型能记住上文
resp2 = chain.invoke({"input": "我叫什么名字?"})
print(resp2["response"])
# 输出:你叫小明,今年 25 岁。

问题所在memory 是一个普通 Python 对象,程序一旦重启,对话历史全部丢失。


四、持久化方案一:JSON 文件存储

最轻量的方案,不依赖任何外部服务,将消息历史序列化到本地 JSON 文件。适合本地脚本、个人项目。

自定义 BaseChatMessageHistory

python 复制代码
import json
from pathlib import Path
from typing import List

from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import BaseMessage, messages_from_dict, messages_to_dict


class JSONChatHistory(BaseChatMessageHistory):
    """将对话历史持久化到本地 JSON 文件"""

    def __init__(self, session_id: str, save_dir: str = "./chat_history"):
        self.path = Path(save_dir) / f"{session_id}.json"
        self.path.parent.mkdir(parents=True, exist_ok=True)

    @property
    def messages(self) -> List[BaseMessage]:
        if not self.path.exists():
            return []
        with open(self.path, "r", encoding="utf-8") as f:
            data = json.load(f)
        return messages_from_dict(data)

    def add_messages(self, messages: List[BaseMessage]) -> None:
        existing = messages_to_dict(self.messages)
        existing.extend(messages_to_dict(messages))
        with open(self.path, "w", encoding="utf-8") as f:
            json.dump(existing, f, ensure_ascii=False, indent=2)

    def clear(self) -> None:
        if self.path.exists():
            self.path.unlink()

接入 ConversationChain

ini 复制代码
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 指定 session_id,不同用户用不同 id 隔离
history = JSONChatHistory(session_id="user_001")
memory = ConversationBufferMemory(
    chat_memory=history,
    return_messages=True
)
chain = ConversationChain(llm=llm, memory=memory)

resp = chain.invoke({"input": "我叫小明,今年 25 岁"})
print(resp["response"])

# 重启程序后重新执行:
# history 从 user_001.json 加载,模型依然记得你是小明

✅ 现在对话记录会保存在 ./chat_history/user_001.json,程序重启后自动恢复。


五、持久化方案二:SQLite 存储

比 JSON 更结构化,支持多会话查询,适合需要查历史记录的场景。LangChain 社区包已内置支持。

安装依赖

复制代码
pip install langchain-community

使用 SQLiteChatMessageHistory

ini 复制代码
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

history = SQLChatMessageHistory(
    session_id="user_001",
    connection_string="sqlite:///chat_history.db"  # 本地 SQLite 文件
)

memory = ConversationBufferMemory(
    chat_memory=history,
    return_messages=True
)
chain = ConversationChain(llm=llm, memory=memory)

resp = chain.invoke({"input": "帮我记住:我最喜欢的颜色是蓝色"})
print(resp["response"])

# 程序重启后,记忆依然存在
resp2 = chain.invoke({"input": "我最喜欢什么颜色?"})
print(resp2["response"])
# 输出:你最喜欢的颜色是蓝色。

数据库文件 chat_history.db 会自动创建,可用 DB Browser for SQLite 等工具查看历史消息。


六、持久化方案三:Redis 存储(生产推荐)

Redis 是生产环境下最常见的选择,支持:

  • 多用户 session 隔离(不同 session_id 对应不同 key)
  • TTL 过期控制(对话记录自动清理)
  • 高并发读写(内存数据库,毫秒级响应)

安装依赖

复制代码
pip install langchain-community redis

使用 RedisChatMessageHistory

ini 复制代码
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

history = RedisChatMessageHistory(
    session_id="user_001",
    url="redis://localhost:6379/0",
    ttl=3600  # 1 小时后自动过期,单位秒
)

memory = ConversationBufferMemory(
    chat_memory=history,
    return_messages=True
)
chain = ConversationChain(llm=llm, memory=memory)

resp = chain.invoke({"input": "我在北京工作,做后端开发"})
print(resp["response"])

多用户隔离示例

ini 复制代码
def get_chain(user_id: str) -> ConversationChain:
    """为每个用户创建独立的对话链"""
    history = RedisChatMessageHistory(
        session_id=f"chat:{user_id}",  # key 格式:chat:user_001
        url="redis://localhost:6379/0",
        ttl=86400  # 24 小时过期
    )
    memory = ConversationBufferMemory(
        chat_memory=history,
        return_messages=True
    )
    return ConversationChain(
        llm=ChatOpenAI(model="gpt-4o-mini", temperature=0),
        memory=memory
    )

# 用户 A 和用户 B 完全隔离
chain_a = get_chain("user_a")
chain_b = get_chain("user_b")

chain_a.invoke({"input": "我叫 Alice"})
chain_b.invoke({"input": "我叫 Bob"})

# Alice 的链不会知道 Bob 的信息
print(chain_a.invoke({"input": "我叫什么?"})["response"])
# 输出:你叫 Alice。

七、多轮对话总结:用 ConversationSummaryMemory 压缩历史

为什么需要对话总结?

随着对话轮次增多,全量历史消息会迅速消耗大量 Token,导致:

  • 调用成本指数级上升
  • 超出模型上下文窗口(如 GPT-3.5 的 4K / 16K 限制)
  • 响应速度变慢

解决思路 :不再保存每一条原始消息,而是用 LLM 将历史对话实时压缩为摘要,只保留摘要 + 最近几轮原文。

ConversationSummaryMemory:纯摘要模式

每轮对话结束后,LLM 自动更新一份滚动摘要,不保留原始消息。

python 复制代码
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationSummaryMemory
from langchain.chains import ConversationChain

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

memory = ConversationSummaryMemory(
    llm=llm,           # 用于生成摘要的 LLM
    return_messages=True
)
chain = ConversationChain(llm=llm, memory=memory)

chain.invoke({"input": "我叫小明,是一名前端工程师,在深圳工作"})
chain.invoke({"input": "我最近在学习 React 和 TypeScript"})
chain.invoke({"input": "我的目标是今年内转型全栈"})

# 查看当前摘要内容(不是原始消息列表)
print(memory.buffer)
# 输出类似:
# 小明是一名前端工程师,在深圳工作。
# 他正在学习 React 和 TypeScript,目标是今年内转型全栈开发。

# 第四轮:模型基于摘要回答,而非原始对话
resp = chain.invoke({"input": "根据我的情况,给我推荐一个学习路线"})
print(resp["response"])

✅ 优点:Token 消耗恒定(摘要长度固定),适合超长对话。

⚠️ 缺点:摘要有信息损失,细节问题(如"我刚才说的第二点是什么")可能无法准确回答。


ConversationSummaryBufferMemory:摘要 + 缓冲混合模式(推荐)

这是更实用的方案:近期消息保持原文,超出 Token 阈值的旧消息自动压缩为摘要。兼顾细节与长度控制。

python 复制代码
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationSummaryBufferMemory
from langchain.chains import ConversationChain

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

memory = ConversationSummaryBufferMemory(
    llm=llm,
    max_token_limit=300,   # 超过 300 token 的旧消息才会被摘要
    return_messages=True
)
chain = ConversationChain(llm=llm, memory=memory)

# 模拟多轮对话
topics = [
    "我叫小红,是产品经理",
    "我们公司在做一款 AI 写作工具",
    "目标用户是内容创作者和自媒体博主",
    "核心功能是自动生成大纲和润色文章",
    "我们计划下个月上线 Beta 版",
]
for msg in topics:
    chain.invoke({"input": msg})

# 查看当前 memory 状态
print("=== 摘要部分 ===")
print(memory.moving_summary_buffer)

print("\n=== 近期原文消息 ===")
for m in memory.chat_memory.messages:
    print(f"[{m.type}] {m.content[:50]}...")

# 继续对话------模型同时拥有摘要上下文 + 近期原文
resp = chain.invoke({"input": "我们产品的目标用户是哪些人?"})
print("\n=== 模型回复 ===")
print(resp["response"])

将摘要记忆与持久化后端结合

ConversationSummaryBufferMemory 同样支持自定义 chat_memory,可以接入前几节的 JSON / SQLite / Redis 后端:

ini 复制代码
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationSummaryBufferMemory
from langchain.chains import ConversationChain
from langchain_community.chat_message_histories import RedisChatMessageHistory

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 使用 Redis 作为持久化后端
history = RedisChatMessageHistory(
    session_id="summary_user_001",
    url="redis://localhost:6379/0",
    ttl=86400
)

memory = ConversationSummaryBufferMemory(
    llm=llm,
    max_token_limit=500,
    chat_memory=history,      # 持久化后端
    return_messages=True
)
chain = ConversationChain(llm=llm, memory=memory)

resp = chain.invoke({"input": "我在做一个电商项目,使用 Django + Vue 技术栈"})
print(resp["response"])

# 程序重启后,摘要和近期消息均从 Redis 恢复

📌 注意 :摘要本身也会以消息形式存入 chat_memory,重启后 ConversationSummaryBufferMemory 会自动加载并继续维护滚动摘要,无需额外处理。


三种记忆策略对比

策略 保存内容 Token 上限风险 细节保真度 推荐场景
ConversationBufferMemory 全量原文 短对话 / 调试
ConversationSummaryMemory 仅摘要 超长对话 / 省成本
ConversationSummaryBufferMemory 摘要 + 近期原文 中(可控) 中高 生产场景推荐

八、进阶:LCEL + RunnableWithMessageHistory(新版推荐写法)

LangChain 0.2.x 开始推荐使用 LCEL(LangChain Expression Language)替代 ConversationChain。新写法更灵活、更易于组合。

旧写法 vs 新写法

对比项 ConversationChain(旧) LCEL(新)
灵活性 低,接口固定 高,可自由组合
流式输出 不友好 原生支持
维护状态 自动 显式传入
官方推荐 逐步废弃 ✅ 推荐

完整 LCEL 示例(搭配 Redis)

ini 复制代码
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 RedisChatMessageHistory

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 1. 定义 Prompt,留出历史消息占位符
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个友好的 AI 助手,能记住用户的信息。"),
    MessagesPlaceholder(variable_name="history"),  # 历史消息注入位置
    ("human", "{input}"),
])

# 2. 构建基础链
base_chain = prompt | llm

# 3. 定义 history 工厂函数
def get_history(session_id: str) -> RedisChatMessageHistory:
    return RedisChatMessageHistory(
        session_id=session_id,
        url="redis://localhost:6379/0",
        ttl=3600
    )

# 4. 包装为带记忆的链
chain_with_history = RunnableWithMessageHistory(
    base_chain,
    get_session_history=get_history,
    input_messages_key="input",
    history_messages_key="history",
)

# 5. 调用时传入 session_id(支持多用户)
config = {"configurable": {"session_id": "user_001"}}

resp1 = chain_with_history.invoke(
    {"input": "我是一名 Python 工程师,在上海工作"},
    config=config
)
print(resp1.content)

resp2 = chain_with_history.invoke(
    {"input": "我在哪个城市工作?"},
    config=config
)
print(resp2.content)
# 输出:你在上海工作。

流式输出(LCEL 的优势)

lua 复制代码
# 只需将 invoke 改为 stream
for chunk in chain_with_history.stream(
    {"input": "用 3 句话介绍一下 Python 的优势"},
    config=config
):
    print(chunk.content, end="", flush=True)

九、四种方案横向对比

方案 依赖 持久化 多用户隔离 TTL 支持 推荐场景
内存(默认) 本地调试
JSON 文件 ✅(文件名) 个人工具 / 脚本
SQLite ✅(session_id) 小型应用
Redis Redis 服务 生产环境

选型建议:

  • 本地开发 / 快速验证 → JSON 文件
  • 单机部署的小型应用 → SQLite
  • 多用户 / 高并发 / 需要自动清理 → Redis
  • 追求灵活性和流式响应 → 搭配 LCEL 新写法

十、总结

本文从 LangChain Memory 的基础概念出发,完整覆盖了以下内容:

  1. JSON 文件:零依赖,适合本地项目
  2. SQLite:结构化存储,无需额外服务
  3. Redis:生产首选,支持 TTL 和高并发
  4. 多轮对话总结ConversationSummaryBufferMemory 控制 Token 消耗,兼顾细节与长度
  5. LCEL 新写法RunnableWithMessageHistory 更灵活,原生支持流式输出

延伸阅读


如果这篇文章对你有帮助,欢迎点赞收藏 🙌 有问题欢迎在评论区交流!

相关推荐
lbb 小魔仙14 小时前
Python + LangChain 环境搭建完全指南:从零构建本地 RAG 知识库(附 Ollama 本地模型集成)
开发语言·python·langchain
风落无尘14 小时前
我用 LangChain 写了一个带“定速巡航”的向量化工具,发布到 PyPI 了!
人工智能·python·langchain
BU摆烂会噶14 小时前
【LangGraph】 流式处理入门
人工智能·python·langchain·人机交互
大模型真好玩15 小时前
LangChain DeepAgents 速通指南(八)—— DeepAgents流式输出详解
人工智能·langchain·agent
沪漂阿龙15 小时前
AI Agent爆火,但你真的懂LangChain吗?——大模型智能体开发完全指南
人工智能·langchain
庞轩px15 小时前
LangChain不是“套壳”——它解决了什么实际问题
langchain·大模型·agent·tool·ai应用开发
qq_2837200515 小时前
LangChain 动态模型中间件实战使用技巧
中间件·langchain·middleware·wrap_model_call
JaydenAI17 小时前
拆解LangChain执行引擎[博文汇总-17篇]
langchain·langgraph·pregel
深海鱼在掘金18 小时前
深入浅出 LangChain —— 第十一章:实战一 智能客服系统
人工智能·langchain·agent
深海鱼在掘金18 小时前
深入浅出 LangChain —— 第十章:上下文工程与安全护栏
人工智能·langchain·agent