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 更灵活,原生支持流式输出

延伸阅读


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

相关推荐
Raink老师2 小时前
【AI面试临阵磨枪】OpenClaw 与 LangChain、AutoGPT、MetaGPT 的本质区别是什么?
人工智能·面试·langchain·ai 面试·ai 应用开发面试
耿雨飞4 小时前
第五章:工具系统与函数调用 —— 从定义到执行的完整链路
人工智能·langchain
Java码农也是农4 小时前
Agent编排框架对比:LangGraph vs AutoGen vs CrewAI
langchain·autogen·langgraph·crewai
怕浪猫5 小时前
第14章 高级 Agent:LangGraph 与状态机
langchain·openai·ai编程
耿雨飞20 小时前
第四章:模型集成生态 —— Partner 包架构与 init_chat_model 统一入口
人工智能·langchain
老王熬夜敲代码1 天前
引入RAG
langchain
疯狂成瘾者1 天前
LangChain Middleware 技术解析:从“插槽机制”到 Agent 运行时控制
数据库·python·langchain
Where-1 天前
LangChain、LangGraph入门
python·langchain·langgraph
秦jh_1 天前
【LangChain】LangChain 与 LangGraph 介绍
人工智能·langchain