LangChain Guide
AI Engineer
LangChain Guide

Build LLM apps with LangChain using chains, agents, memory, RAG, and orchestration patterns.

LangChain GuideMemory

LangChain Memory:让 AI 记住上下文

先说一件让我学到教训的事。

有次给一个客户做客服机器人,用的是 ConversationBufferMemory——最简单的那种,把全部对话历史都存下来,每次调用都发给模型。测试时没问题,对话十来轮,Token 消耗可以接受。

上线三周后客户来找我说"API 费用这个月涨了 8 倍"。我去看日志,有个用户跟机器人聊了 180 轮,每次调用都把前 179 轮全发给模型。到最后一轮,单次调用发了大约 40,000 个 Token。

所以讲 Memory 之前必须先说这件事:Memory 不是免费的。历史消息越长,每次 API 调用越贵。这是选 Memory 策略时最重要的约束条件。


#LLM 为什么没有记忆

每次调用 LLM API,都是一次全新的请求。模型不记得上次说了什么,因为它根本没有"上次"的概念——每个 API 调用都是独立的。

Memory 系统的本质很朴素:把历史对话保存下来,每次调用时把它塞进 Prompt 里,让模型"看起来"记得你。

第 1 轮:
  发给模型:[User: 我叫小明,是前端工程师]
  模型回:你好小明!

第 2 轮(有 Memory):
  发给模型:
  [历史] User: 我叫小明,是前端工程师
  [历史] AI: 你好小明!
  [当前] User: 我适合学 Vue 还是 React?

  → AI 知道你叫小明、知道你是前端,能给个性化答案

关键理解Token 消耗 = 系统 Prompt + 历史消息 + 新消息 + 模型回复。对话每多一轮,每次请求的 Token 就多那么多。这不是线性增长,是每次调用都带着所有历史。


#四种 Memory 策略

不同场景对历史记忆的需求不同:

策略保存方式Token 消耗信息完整度适合场景一句话
ConversationBufferMemory全部历史随轮次线性增长100%测试/原型,对话 < 20 轮"全记,只适合测试"
ConversationTokenBufferMemory最近 N Token固定上限仅最近内容Token 预算严格"只记最近,旧的丢掉"
ConversationSummaryMemoryLLM 生成摘要相对稳定摘要级别超长对话但不在乎细节"记摘要,省 Token"
ConversationSummaryBufferMemory近期全记 + 远期摘要可控近期精确 + 远期摘要生产环境"近精远摘,最均衡"

我的建议:

  • 原型/demo:随便用 ConversationBufferMemory,方便
  • 生产环境客服/助手:ConversationSummaryBufferMemory,控制成本同时不丢重要上下文
  • Token 预算极严格:ConversationTokenBufferMemory

#基础用法:ChatMessageHistory

最基础的存储单元,本质是一个消息列表:

python
from langchain_community.chat_message_histories import ChatMessageHistory history = ChatMessageHistory() history.add_user_message("你好,我是小明,前端工程师") history.add_ai_message("你好小明!很高兴认识你。") history.add_user_message("我在学 React,有什么建议?") for msg in history.messages: print(f"[{msg.type}]: {msg.content}") # [human]: 你好,我是小明,前端工程师 # [ai]: 你好小明!很高兴认识你。 # [human]: 我在学 React,有什么建议? history.clear() # 清空

#在 Chain 中集成 Memory:现代写法

现代 LangChain(0.2+)推荐用 RunnableWithMessageHistory 包装 Chain,通过 session_id 管理多用户会话。每个用户有独立的历史记录,互不干扰。

python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai import ChatOpenAI from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_community.chat_message_histories import ChatMessageHistory from langchain_core.output_parsers import StrOutputParser llm = ChatOpenAI(model="gpt-4o-mini") # Prompt 里必须有历史消息占位符 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个友好的技术助手,记住用户的背景信息,给出个性化建议。"), MessagesPlaceholder(variable_name="chat_history"), ("user", "{input}"), ]) chain = prompt | llm | StrOutputParser() # 开发环境用内存存储(重启就没了) store: dict = {} def get_session_history(session_id: str) -> ChatMessageHistory: if session_id not in store: store[session_id] = ChatMessageHistory() return store[session_id] chain_with_memory = RunnableWithMessageHistory( chain, get_session_history, input_messages_key="input", history_messages_key="chat_history", ) # 用 session_id 区分不同用户 user_a = {"configurable": {"session_id": "user_123"}} user_b = {"configurable": {"session_id": "user_456"}} # 用户 A 的对话 print(chain_with_memory.invoke({"input": "我叫小明,喜欢 Vue"}, config=user_a)) print(chain_with_memory.invoke({"input": "推荐一个适合我的框架"}, config=user_a)) # AI 会记得他叫小明、喜欢 Vue,给出基于此的推荐 # 用户 B 完全隔离 print(chain_with_memory.invoke({"input": "我是 Java 后端工程师"}, config=user_b))

#生产环境:存储必须持久化

开发时用内存字典很方便,但服务一重启历史全没了。生产环境必须把历史存到数据库。

好消息是,只需要替换 get_session_history 函数,Chain 本身完全不用改。

Redis(推荐首选,速度快,支持自动过期)

python
from langchain_community.chat_message_histories import RedisChatMessageHistory def get_session_history(session_id: str) -> RedisChatMessageHistory: return RedisChatMessageHistory( session_id=session_id, url="redis://localhost:6379/0", ttl=86400, # 24 小时自动过期,不用手动清理 key_prefix="chat:", # Redis key 前缀,方便管理 )

MongoDB(适合需要查询历史记录的场景)

python
from langchain_mongodb.chat_message_histories import MongoDBChatMessageHistory def get_session_history(session_id: str): return MongoDBChatMessageHistory( connection_string="mongodb://localhost:27017/", session_id=session_id, database_name="my_app", collection_name="chat_history", )

SQLite(本地开发、小型项目)

python
from langchain_community.chat_message_histories import SQLChatMessageHistory def get_session_history(session_id: str): return SQLChatMessageHistory( session_id=session_id, connection_string="sqlite:///chat_history.db" )

选型:高并发生产用 Redis,需要查询历史记录用 MongoDB,本地小项目用 SQLite。


#摘要记忆:对话长了怎么办

回到开头那个故事——怎么避免 180 轮对话把 Token 撑爆?

ConversationSummaryBufferMemory 的思路是:最近的对话原样保留(细节重要),远处的对话自动压缩成摘要(节省 Token)

python
from langchain.memory import ConversationSummaryBufferMemory from langchain_openai import ChatOpenAI # 最多保留最近 500 Token 的原始历史 # 超出的部分自动用 LLM 压缩成摘要 memory = ConversationSummaryBufferMemory( llm=ChatOpenAI(model="gpt-4o-mini"), max_token_limit=500, return_messages=True, ) memory.save_context( {"input": "我在做一个电商项目,用 FastAPI + React"}, {"output": "好的,这是个很好的技术栈组合!"} ) print(memory.load_memory_variables({})) # { # "history": [ # SystemMessage(content="摘要:用户正在做 FastAPI+React 电商项目..."), # # 最近的几轮原始消息 # ] # }

这样不管对话多长,每次请求的 Token 都在可控范围内。代价是远处的对话只保留摘要,细节会丢失。如果某些重要信息(比如用户名、项目背景)你不想让摘要压缩掉,最好在 System Prompt 里再强调一遍。


#常见坑

Token 费用越来越高:大概率是用了 BufferMemory 没有上限。检查一下,换成 TokenBufferMemory 或 SummaryBufferMemory。

多用户记忆串了:session_id 设错了,两个用户共用了同一个历史。确保每个用户有唯一的 session_id,推荐用 UUID。

重启后历史消失:用了内存字典存历史。接数据库,早接早省心。

AI "忘记"了重要信息:摘要记忆压缩时把某些细节丢了。把关键信息在 System Prompt 里额外强调,不要完全依赖 Memory 来保留。

首次调用报占位符错误get_session_history 返回了 None,或者 Prompt 里的 history_messages_keyRunnableWithMessageHistory 里设置的不一致。返回空的 ChatMessageHistory(),而不是 None。


#动手练习

实现一个"个人学习助手",能记住用户的技术背景和学习目标,每次对话都基于此给出个性化建议:

python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai import ChatOpenAI from langchain_core.runnables.history import RunnableWithMessageHistory from langchain_community.chat_message_histories import ChatMessageHistory from langchain_core.output_parsers import StrOutputParser # TODO 1:创建 Prompt,System 里说明助手会记住用户背景,给个性化技术建议 # TODO 2:创建 get_session_history 函数 # TODO 3:用 RunnableWithMessageHistory 包装 # 测试: # 第 1 句:告诉助手你的背景("我是 Java 后端工程师,想学 AI 开发") # 第 2 句:问一个技术问题 # 验证:AI 的回答是否考虑了你的 Java 背景

#小结

  1. LLM 本身无状态,Memory 的本质是把历史对话塞进每次请求的 Prompt 里——对话越长,每次 API 调用越贵。
  2. 生产环境不要用 ConversationBufferMemory,没有上限,对话一长成本会爆炸。用 ConversationSummaryBufferMemory
  3. RunnableWithMessageHistory + 唯一的 session_id 是管理多用户对话的正确模式,不同用户记忆完全隔离。
  4. 历史存储必须持久化——Redis(高并发)、MongoDB(需要查询)、SQLite(小项目)。内存字典只能开发时用。
  5. 需要更复杂的状态管理(多步骤、断点恢复、分支对话),可以考虑升级到 LangGraph 的 Checkpointer。

下一步LangGraph 进阶 — 用 Checkpointer 实现更强的状态持久化,支持断点恢复和复杂工作流

相关参考RunnableWithMessageHistory 文档 | Chat Message History 持久化方案

System Design

Core system design concepts and practical case studies

Learn the trade-offs and patterns that matter in technical interviews.

Open System Design →

Related Roadmaps