【LangGraph】 InMemorySaver 深度解析:与 Message History 的范式对比

LangGraph InMemorySaver 深度解析:与 Message History 的范式对比

在 LangChain 生态中,记忆机制经历了从"消息容器"到"状态快照"的范式跃迁。本文将深入解析 InMemorySaver 的工作原理,并与传统的 Message History(如 ConversationBufferMemory)进行全面对比,帮助你在 2026 年的 Agent 开发中做出正确选择。


一、从图片说起:InMemorySaver 到底在做什么?

让我们先回到你提供的代码片段:

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver

# 创建短期记忆实例
memory = InMemorySaver()

# 创建 Agent
agent = create_agent(
    model=llm,              # 聊天模型
    tools=tools,            # 工具列表
    system_prompt=system_prompt,
    checkpointer=memory     # 传入记忆组件 ← 关键在这里
)

这行 checkpointer=memory 的背后,是 LangGraph 对"记忆"的重新定义。它不再只是保存对话记录,而是保存整个 Agent 的执行状态快照


二、核心概念:Checkpoint 到底是什么?

2.1 Checkpoint 的本质

在 LangGraph 中,Checkpoint(检查点) 是图执行过程中每个节点完成后的完整状态快照。它保存的是:

  • 当前图的 State(所有字段的完整值)
  • 已执行节点的输出
  • 条件边的路由决策
  • 工具调用的中间结果
  • 循环计数器、重试次数等控制变量

简单来说:Checkpoint 保存的是"Agent 执行到哪一步了",而不仅仅是"之前聊了什么"。

2.2 InMemorySaver 的角色

InMemorySaver 是 LangGraph 提供的最简单的 Checkpointer 实现:

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()
# 等价于:checkpointer = MemorySaver()  # 旧别名,向后兼容

它的特点是:

特性 说明
存储位置 进程内存(RAM)
生命周期 随进程启动而创建,随进程结束而消失
适用场景 开发测试、单进程演示、单元测试
线程隔离 通过 thread_id 区分不同会话
持久化 ❌ 不支持(进程重启数据丢失)

三、InMemorySaver 的工作原理

3.1 状态流转示意

复制代码
用户输入 → [START] → Node A (LLM调用) → Checkpoint 保存 → 
→ Node B (工具调用) → Checkpoint 保存 → 
→ Node C (条件路由) → Checkpoint 保存 → [END]
                                        ↑
                              每个节点后自动保存完整 State

3.2 代码示例:断点续跑

python 复制代码
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from typing import TypedDict

class State(TypedDict):
    query: str
    result: str
    step_count: int

def step1(state: State) -> dict:
    return {"result": f"处理: {state['query']}", "step_count": 1}

def step2(state: State) -> dict:
    return {"result": state["result"] + " → 完成", "step_count": 2}

builder = StateGraph(State)
builder.add_node("step1", step1)
builder.add_node("step2", step2)
builder.add_edge(START, "step1")
builder.add_edge("step1", "step2")
builder.add_edge("step2", END)

# 编译时传入 checkpointer
app = builder.compile(checkpointer=InMemorySaver())

# 通过 thread_id 标识会话
config = {"configurable": {"thread_id": "session-001"}}

# 第一次运行
result1 = app.invoke({"query": "你好", "result": "", "step_count": 0}, config)
print(result1)  # {'query': '你好', 'result': '处理: 你好 → 完成', 'step_count': 2}

# 第二次运行:自动从上次 checkpoint 恢复
result2 = app.invoke({"query": "再见"}, config)
# 注意:这里传入的 query 会被合并到已有状态中

3.3 与 thread_id 的配合

thread_id 是 Checkpoint 机制的核心标识符:

python 复制代码
# 用户 A 的会话
config_a = {"configurable": {"thread_id": "user-a"}}

# 用户 B 的会话(完全隔离)
config_b = {"configurable": {"thread_id": "user-b"}}

# 同一用户的新会话
config_a_new = {"configurable": {"thread_id": "user-a-new"}}

每个 thread_id 对应一条独立的"状态时间线",互不干扰。


四、Message History:传统记忆的运作方式

4.1 传统 LangChain Memory 家族

在 LangGraph 出现之前,LangChain 提供了多种 Memory 实现:

python 复制代码
from langchain.memory import (
    ConversationBufferMemory,           # 保存完整对话
    ConversationBufferWindowMemory,     # 只保留最近 k 轮
    ConversationSummaryMemory,          # 自动总结历史
    ConversationSummaryBufferMemory,    # 结合窗口与总结
    VectorStoreRetrieverMemory,        # 向量检索记忆
)

⚠️ 重要提示 :截至 2026 年,ConversationBufferMemory 等经典 Memory 类已被官方标记为 deprecated,迁移到 LangGraph 的 checkpointer 是推荐做法。

4.2 Message History 的本质

传统 Memory 的核心是 消息列表的管理

python 复制代码
from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(return_messages=True)

# 手动保存对话
memory.save_context(
    {"input": "我叫小明"},
    {"output": "你好小明!"}
)

# 加载历史(仅返回消息列表)
history = memory.load_memory_variables({})
# {'history': [HumanMessage(content='我叫小明'), AIMessage(content='你好小明!')]}

关键特点

  • 只保存 HumanMessageAIMessage
  • 需要手动或通过 Chain 注入到 Prompt 中
  • 不保存任何业务状态(如工具调用结果、循环计数等)
  • 通过 memory_key 嵌入到 Prompt 模板

4.3 在 LCEL 中的使用

python 复制代码
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个 helpful 助手。"),
    MessagesPlaceholder(variable_name="history"),  # 历史消息占位
    ("human", "{input}")
])

chain = prompt | llm

# 包装为带历史记忆的链
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history=get_session_history,  # 返回 BaseChatMessageHistory
    input_messages_key="input",
    history_messages_key="history",
)

五、全面对比:InMemorySaver vs Message History

5.1 对比总览

维度 InMemorySaver (Checkpoint) Message History (传统 Memory)
保存对象 完整图状态(TypedDict) 消息列表(Message List)
粒度 状态级:节点输出、变量、路由决策 内容级:仅用户/AI 对话内容
作用范围 整个 LangGraph 图的所有节点 通常只影响 LLM 的 Prompt
生命周期管理 通过 thread_id 自动管理 需手动管理 session_id
断点续跑 ✅ 支持(从任意 checkpoint 恢复) ❌ 不支持(仅追加消息)
错误恢复 ✅ 支持(节点级重试、回滚) ❌ 不支持(需从头开始)
人机交互 (HITL) ✅ 原生支持(interrupt 机制) ❌ 需自行实现
时间旅行调试 ✅ 支持(回溯到任意状态) ❌ 不支持
并行分支 ✅ 支持(多状态副本管理) ❌ 不支持
持久化选项 InMemory / SQLite / Postgres / Redis 内存 / Redis / 文件
2026 官方推荐 唯一推荐方式 ⚠️ 已废弃

5.2 深度对比:三个关键差异

差异 1:保存内容的本质不同

Message History 只保存聊天记录

python 复制代码
# 传统 Memory 的内容
[
    HumanMessage(content="搜索北京天气"),
    AIMessage(content="我来帮您查询..."),
    FunctionMessage(content="{'temp': 25, 'weather': '晴'}"),  # 工具结果
    AIMessage(content="北京今天晴天,25度"),
]
# 仅此而已!没有中间状态

InMemorySaver 保存完整执行状态

python 复制代码
# Checkpoint 保存的内容(示意)
{
    "messages": [...],           # 消息列表(包含在内)
    "tool_calls": [{"name": "get_weather", "args": {"city": "北京"}}],
    "tool_outputs": [{"temp": 25, "weather": "晴"}],
    "retry_count": 0,             # 重试计数器
    "next_node": "synthesize",   # 下一个要执行的节点
    "user_confirmed": True,       # 用户确认状态
    "token_budget": 3500,         # 剩余 token 预算
}
# 包含所有业务状态!
差异 2:恢复能力的根本不同

场景:Agent 执行到第 5 个节点时服务器崩溃

机制 恢复行为
Message History 只能拿到前 4 轮对话记录,第 5 个节点的中间变量、条件判断全部丢失,必须从头重新执行
InMemorySaver 从第 4 个节点完成后的 checkpoint 恢复,第 5 个节点可以直接继续执行,无需重复前 4 步
差异 3:架构层级的不同
复制代码
┌─────────────────────────────────────────────────────────────┐
│                    LangGraph 架构层级                         │
├─────────────────────────────────────────────────────────────┤
│  应用层: Agent 业务逻辑 (create_agent, create_react_agent)  │
├─────────────────────────────────────────────────────────────┤
│  图层: StateGraph (节点、边、条件路由)                        │
├─────────────────────────────────────────────────────────────┤
│  状态层: State (TypedDict, 包含 messages + 业务字段)         │
├─────────────────────────────────────────────────────────────┤
│  持久层: Checkpointer (InMemorySaver / PostgresSaver)        │  ← InMemorySaver 在这里
│         - 保存完整 State 快照                                 │
│         - 支持断点续跑、HITL、时间旅行                        │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                  传统 LangChain 架构层级                      │
├─────────────────────────────────────────────────────────────┤
│  应用层: Chain / Agent (LLMChain, AgentExecutor)              │
├─────────────────────────────────────────────────────────────┤
│  提示层: PromptTemplate (包含 {history} 占位符)               │
├─────────────────────────────────────────────────────────────┤
│  记忆层: Memory (ConversationBufferMemory)                   │  ← Message History 在这里
│         - 仅管理消息列表                                     │
│         - 通过 memory_key 注入 Prompt                       │
└─────────────────────────────────────────────────────────────┘

六、生产环境升级路径

6.1 开发 → 测试 → 生产

python 复制代码
# 阶段 1:开发(内存,快速迭代)
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()

# 阶段 2:本地持久化(单机测试)
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")

# 阶段 3:生产(分布式、高可用)
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string(DATABASE_URL)
await checkpointer.setup()  # 首次运行建表

API 完全一致,只需替换 Checkpointer 实现,无需改动业务代码。

6.2 与长期记忆(Long-term Memory)的配合

LangGraph 的完整记忆架构是双轨制

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver      # 短期记忆
from langgraph.store.memory import InMemoryStore           # 长期记忆

# 短期记忆:线程级,随对话保存/恢复
checkpointer = InMemorySaver()

# 长期记忆:用户级,跨会话持久化
store = InMemoryStore()

def assistant(state, *, store, config):
    user_id = config["configurable"]["user_id"]

    # 1. 召回长期记忆(用户偏好)
    prefs = store.search((user_id, "preferences"))

    # 2. 使用短期记忆(当前对话状态)+ 长期记忆生成回复
    reply = llm.invoke(state["messages"] + prefs)

    # 3. 保存新发现到长期记忆
    if new_fact := extract_fact(state["messages"]):
        store.put((user_id, "preferences"), new_fact["key"], new_fact["value"])

    return {"messages": [reply]}

app = graph.compile(checkpointer=checkpointer, store=store)

thread_id 选择短期记忆槽位,user_id 选择长期记忆命名空间。


七、实战代码:完整对比示例

7.1 传统 Message History 方式(已废弃)

python 复制代码
# ❌ 不推荐:2026 年已废弃的写法
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

memory = ConversationBufferMemory()
chain = ConversationChain(llm=llm, memory=memory)

# 只能进行简单对话,无法处理工具调用、循环、条件分支
response = chain.predict(input="调用工具 A")
# 如果工具 A 执行失败,没有重试机制,必须从头开始

7.2 LangGraph InMemorySaver 方式(推荐)

python 复制代码
# ✅ 推荐:2026 年标准写法
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
from langchain_core.tools import tool

@tool
def risky_tool(query: str) -> str:
    # 一个可能失败的工具
    if "error" in query:
        raise ValueError("工具执行失败")
    return f"结果: {query}"

# 创建带 checkpoint 的 Agent
agent = create_react_agent(
    model=llm,
    tools=[risky_tool],
    checkpointer=InMemorySaver(),
)

config = {"configurable": {"thread_id": "demo"}}

# 工具失败后自动重试(LangGraph 内置)
result = agent.invoke(
    {"messages": [{"role": "user", "content": "使用 risky_tool 查询 error"}]},
    config=config
)
# 即使失败,也能从 checkpoint 恢复,无需重新调用 LLM

八、关键追问:有了 InMemorySaver,就不需要 Message History 了?

这是一个非常自然的问题,也是很多开发者的第一反应。答案是:ConversationBufferMemory 确实不需要了,但"记忆"这件事仍然需要分两层来理解。

8.1 InMemorySaver 已经包含了消息历史

在 LangGraph 的 State 定义中,messages 字段是天然存在的:

python 复制代码
from typing import TypedDict, Annotated
from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]  # 消息历史在这里
    query: str
    tool_results: dict
    retry_count: int

InMemorySaver 保存 checkpoint 时,完整的对话消息列表已经被包含在内。所以:

  • ConversationBufferMemory 能做的(保存对话历史)→ Checkpointer 能做
  • ConversationBufferWindowMemory 能做的(窗口截断)→ 通过 State 自定义逻辑实现
  • ConversationSummaryMemory 能做的(自动总结)→ 可以在节点中实现
  • VectorStoreRetrieverMemory(向量检索记忆)→ 这是长期记忆,Checkpointer 不管这个

结论:传统的 Message History 类已被 Checkpointer 完全覆盖,且能力更强。官方也已将其标记为 deprecated,新工程不应再使用。

8.2 但 InMemorySaver 有致命局限:进程重启即消失

这是很多人踩过的坑:

python 复制代码
# 开发时这样写没问题
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()

# 但生产环境如果直接部署...
# 服务器重启 → 所有 checkpoint 丢失 → 用户回来对话断了
# 多实例部署 → 实例 A 的内存,实例 B 访问不到 → 负载均衡下会话错乱
#  nightly 重启 → 昨天聊的内容全没了 → 用户体验极差

生产环境必须升级:

python 复制代码
# 阶段 1:开发(内存,快速迭代)
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()

# 阶段 2:本地持久化(单机测试)
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")

# 阶段 3:生产(分布式、高可用)
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string(DATABASE_URL)
await checkpointer.setup()  # 首次运行建表

API 完全一致,只需替换 Checkpointer 实现,业务代码零改动。

8.3 更关键的:短期记忆 ≠ 长期记忆

LangGraph 2026 年引入了双轨记忆架构,这是理解"记忆"的完整图景:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      用户与 Agent 交互                        │
└─────────────────────────────────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
   当前对话状态           跨会话用户知识            消息展示
        │                     │                     │
        ▼                     ▼                     ▼
┌───────────────┐    ┌───────────────┐    ┌───────────────┐
│  Checkpointer │    │     Store     │    │  Message List │
│  (短期记忆)    │    │  (长期记忆)    │    │  (UI 展示)    │
│               │    │               │    │               │
│ • 完整 State  │    │ • 用户偏好     │    │ • 只读展示    │
│ • 节点状态    │    │ • 跨会话事实   │    │ • 前端渲染    │
│ • 工具中间结果│    │ • 知识图谱     │    │               │
│ • 循环计数器  │    │               │    │               │
└───────────────┘    └───────────────┘    └───────────────┘
        │                     │
        ▼                     ▼
   InMemorySaver         InMemoryStore
   PostgresSaver          PostgresStore
   (随用随换)              (随用随换)

短期记忆(Checkpointer)解决的问题:

  • 当前对话的完整上下文
  • 多步 Agent 执行状态(工具调用、条件路由)
  • 断点续跑、人机交互(HITL)
  • 节点级重试、错误恢复

长期记忆(Store)解决的问题:

  • 用户说"我叫小明" → 下次开新会话还记得
  • 用户偏好"我喜欢简洁回答" → 跨会话保持一致风格
  • 积累用户画像、知识图谱 → 越用越懂用户
python 复制代码
from langgraph.checkpoint.memory import InMemorySaver   # 短期记忆
from langgraph.store.memory import InMemoryStore        # 长期记忆

checkpointer = InMemorySaver()
store = InMemoryStore()

def assistant(state, *, store, config):
    user_id = config["configurable"]["user_id"]

    # 1. 召回长期记忆(用户偏好)
    prefs = store.search((user_id, "preferences"))

    # 2. 使用短期记忆(当前对话状态)+ 长期记忆生成回复
    reply = llm.invoke(state["messages"] + prefs)

    # 3. 保存新发现到长期记忆
    if new_fact := extract_fact(state["messages"]):
        store.put((user_id, "preferences"), new_fact["key"], new_fact["value"])

    return {"messages": [reply]}

app = graph.compile(checkpointer=checkpointer, store=store)

thread_id 选择短期记忆槽位,user_id 选择长期记忆命名空间。两者配合才是完整的记忆方案。

8.4 简单聊天场景:不需要上 LangGraph

如果你只是做一个简单的聊天机器人,没有工具调用、没有多步工作流、不需要断点续跑,那么引入 LangGraph 的图复杂度是过度设计。

这时候更轻量的选择是 RunnableWithMessageHistory

python 复制代码
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# 简单的消息历史管理(不需要图的复杂度)
store = {}  # 内存存储,生产环境换成 Redis

def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个 helpful 助手。"),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

chain = prompt | llm

chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history=get_session_history,
    input_messages_key="input",
    history_messages_key="history",
)

# 使用
response = chain_with_history.invoke(
    {"input": "你好"},
    config={"configurable": {"session_id": "user-123"}}
)

选择建议:

场景 推荐方案 原因
简单聊天机器人 RunnableWithMessageHistory 轻量、无需图的复杂度
带工具调用的 Agent LangGraph + Checkpointer 需要状态管理和断点续跑
多步工作流 LangGraph + Checkpointer 需要节点级控制和恢复
需要人机交互 LangGraph + Checkpointer interrupt 机制原生支持
跨会话用户记忆 LangGraph + Store 长期记忆需要独立存储层

8.5 本节小结

有了 InMemorySaver,确实不需要 ConversationBufferMemory------功能被完全覆盖且更强大。

但"记忆"这件事仍然需要两层:短期记忆(Checkpointer 管当前会话状态)+ 长期记忆(Store 管跨会话用户知识)。

简单聊天不需要 LangGraph ------RunnableWithMessageHistory 是更轻量的选择。


八、总结:一张图看懂选择

复制代码
                    你的 Agent 需要记忆?
                           │
           ┌───────────────┴───────────────┐
           ▼                               ▼
    只需要对话上下文                  需要状态管理、断点续跑、HITL
           │                               │
           ▼                               ▼
  使用 RunnableWithMessageHistory      使用 LangGraph + Checkpointer
  (BaseChatMessageHistory)            (InMemorySaver / PostgresSaver)
           │                               │
           ▼                               ▼
    消息追加到 Prompt                 完整 State 快照 + 节点级恢复
           │                               │
           ▼                               ▼
    适合简单聊天机器人                 适合复杂 Agent、多步工作流

关键结论

  1. InMemorySaver 不是 Memory 的替代品,而是升级品 :它包含了 Message History 的能力(因为 State 中可以包含 messages 字段),同时提供了状态级持久化。

  2. 2026 年官方唯一推荐:LangChain 经典 Memory 类已废弃,新工程应直接使用 LangGraph checkpointer。

  3. 从内存到持久化零成本切换InMemorySaverSqliteSaverPostgresSaver 的 API 完全一致,开发时用最简单的内存版,上线时无缝切换。

  4. 短期记忆 + 长期记忆双轨并行 :用 checkpointer 管理当前会话状态,用 store 管理跨会话的用户知识,两者配合才是完整的记忆方案。


参考资源


后续我们将深入探讨 LangGraph 的状态管理、条件路由、人机交互等核心机制,帮助你构建生产级的 AI Agent。