LangChain LangGraph
Module 1 Module 2 Module 3 Module 4 Module 5 Module 6
Module 5 · Long-term Memory
1 5-1 Memory Store
2 5-2 Schema Profile
3 5-3 Schema Coll.
4 5-4 Memory Agent
SUM Module Summary
HomeLangGraphModule 5 · Long-term Memory5-1 Memory Store
📓 Notebook 📖 Explained
LANGGRAPH MODULE 5 · LESSON 1

Memory Store:跨线程长期记忆系统

用 LangGraph Store 构建能跨对话会话记住用户信息的个性化聊天机器人——深度讲解 InMemoryStore、命名空间设计、记忆读写节点与双存储编译模式。

1 记忆类型回顾:短期 vs. 长期记忆

认知科学中,记忆分为多种类型。在 AI 应用中,最重要的区分是短期记忆(within-thread)长期记忆(cross-thread)

维度短期记忆(within-thread)长期记忆(cross-thread)
作用范围单次对话会话(thread)内跨越所有对话会话(across threads)
存储机制Checkpointer(MemorySaver)LangGraph Store(InMemoryStore / 数据库)
存储内容图的完整状态快照(消息历史、中间结果)用户画像、偏好、事实(键值对)
标识键thread_iduser_id + namespace + key
类比工作记忆(当前任务上下文)语义记忆(关于用户的持久事实)
核心洞察

Checkpointer 解决"这次对话说了什么",Store 解决"这个用户是谁"。两者结合,才能让 AI 既有连续的对话上下文,又有跨会话的用户认知。

2 LangGraph Store 核心 API:put / search / get

LangGraph Store 是一个开源的持久化键值存储基类BaseStore),提供三个核心方法来管理跨线程记忆。

初始化 InMemoryStore

import uuid
from langgraph.store.memory import InMemoryStore

# 创建内存中的 Store 实例(生产环境可替换为数据库后端)
in_memory_store = InMemoryStore()

put():写入记忆

# 定义命名空间:(user_id, 分类名)
user_id = "1"
namespace_for_memory = (user_id, "memories")

# 生成唯一 key(每条记忆一个 UUID)
key = str(uuid.uuid4())

# value 必须是字典
value = {"food_preference": "I like pizza"}

# 写入 Store
in_memory_store.put(namespace_for_memory, key, value)

search():按命名空间批量检索

# 检索命名空间下的所有记忆
memories = in_memory_store.search(namespace_for_memory)

# 每条记忆的完整结构示例:
# {
#   'value': {'food_preference': 'I like pizza'},
#   'key': 'a754b8c5-...',
#   'namespace': ['1', 'memories'],
#   'created_at': '2024-11-04T22:48:16.727572+00:00',
#   'updated_at': '2024-11-04T22:48:16.727574+00:00'
# }

get():按 namespace + key 精确检索

# 精确获取单条记忆
memory = in_memory_store.get(namespace_for_memory, key)
memory.dict()  # 返回与 search()[0] 相同的结构
put vs get vs search 的使用场景

put:写入/更新单条记忆(传入相同 key 则覆盖)。get:精确读取某个已知 key 的记忆,适合"取用户画像"等固定 key 场景。search:列出某命名空间下所有记忆,适合"获取用户所有偏好"等批量场景。

3 命名空间设计:像文件系统一样组织记忆

Store 的 namespace 是一个元组(tuple),工作方式类似文件系统的目录路径,允许你按层级隔离不同用户、不同类型的记忆。

Store 命名空间结构示意

Store (InMemoryStore)
├── namespace: ("memory", "user_1")
└── key: "user_memory"{"memory": "用户是 Lance,喜欢骑行..."}
├── namespace: ("memory", "user_2")
└── key: "user_memory"{"memory": "用户是 Alice,喜欢阅读..."}
# 命名空间:按 user_id 隔离,"memory" 是分类前缀
namespace = ("memory", user_id)

# 固定 key:每个用户只保存一条"汇总画像"记忆
key = "user_memory"

# 读取:先检查记忆是否存在
existing_memory = store.get(namespace, key)

if existing_memory:
    content = existing_memory.value.get('memory')
else:
    content = "No existing memory found."
设计决策:单条汇总 vs. 多条独立记忆

本节采用"单条汇总记忆"模式:每次对话后,LLM 将新信息合并更新到同一条记忆中(用相同 key put 覆盖)。另一种模式是每条事实存一个 UUID key,形成记忆集合(见 Lesson 4)。

4 聊天机器人架构:两个记忆节点的职责

整个图非常简洁:START → call_model → write_memory → END。每次用户发送消息,都会经历"响应"和"记忆更新"两个阶段。

Memory Store 聊天机器人图结构

START
call_model
读取长期记忆 → 生成个性化回复
write_memory
反思对话 → 更新长期记忆
END
节点职责使用的记忆
call_model从 Store 读取用户画像,注入系统提示词,调用 LLM 生成个性化回复读取长期记忆(Store.get)
write_memory反思本次对话中的新信息,调用 LLM 合并更新用户画像,写入 Store读取 + 写入长期记忆(Store.get / Store.put)
函数签名的关键设计

两个节点函数都接受三个参数:state(图状态)、config(运行时配置,含 user_id)、store(BaseStore 实例,由 LangGraph 自动注入)。

5 call_model 节点:从 Store 读取记忆并个性化响应

MODEL_SYSTEM_MESSAGE = """You are a helpful assistant with memory that provides information about the user.
If you have memory for this user, use it to personalize your responses.
Here is the memory (it may be empty): {memory}"""
from langgraph.store.base import BaseStore
from langchain_core.runnables.config import RunnableConfig

def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):
    # Step 1:从 config 中取出 user_id
    user_id = config["configurable"]["user_id"]

    # Step 2:构造命名空间,从 Store 读取记忆
    namespace = ("memory", user_id)
    existing_memory = store.get(namespace, "user_memory")

    # Step 3:提取记忆内容(首次使用时为 None)
    if existing_memory:
        existing_memory_content = existing_memory.value.get('memory')
    else:
        existing_memory_content = "No existing memory found."

    # Step 4:将记忆注入系统提示词,调用 LLM
    system_msg = MODEL_SYSTEM_MESSAGE.format(memory=existing_memory_content)
    response = model.invoke(
        [SystemMessage(content=system_msg)] + state["messages"]
    )
    return {"messages": response}
MessagesState 自动管理短期记忆

state["messages"] 中的消息历史由 MemorySaver checkpointer 自动管理——只要使用相同的 thread_id,LangGraph 会自动将历史消息持久化并在下次请求时恢复。

6 write_memory 节点:反思对话并更新长期记忆

CREATE_MEMORY_INSTRUCTION = """You are collecting information about the user to personalize your responses.

CURRENT USER INFORMATION:
{memory}

INSTRUCTIONS:
1. Review the chat history below carefully
2. Identify new information about the user, such as:
   - Personal details (name, location)
   - Preferences (likes, dislikes)
   - Interests and hobbies
3. Merge any new information with existing memory
4. Format the memory as a clear, bulleted list
5. If new information conflicts with existing memory, keep the most recent version

Based on the chat history below, please update the user information:"""
def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
    user_id = config["configurable"]["user_id"]
    namespace = ("memory", user_id)
    existing_memory = store.get(namespace, "user_memory")

    if existing_memory:
        existing_memory_content = existing_memory.value.get('memory')
    else:
        existing_memory_content = "No existing memory found."

    # LLM 反思对话,提取并合并用户信息
    system_msg = CREATE_MEMORY_INSTRUCTION.format(memory=existing_memory_content)
    new_memory = model.invoke(
        [SystemMessage(content=system_msg)] + state['messages']
    )

    # 用相同 key 覆盖写入(合并更新)
    store.put(namespace, "user_memory", {"memory": new_memory.content})
记忆是"热路径"写入(in-the-hot-path)

每次对话结束时,write_memory 节点都会同步调用一次 LLM 来更新记忆,这会增加延迟和 token 消耗。另一种是"离线"更新策略,异步在后台写入。两种策略的权衡在 Module 5 中有完整讨论。

7 双存储编译模式与配置注入

图需要同时接受 checkpointer(短期记忆)和 store(长期记忆)两个参数进行编译。

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.store.memory import InMemoryStore

builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_node("write_memory", write_memory)
builder.add_edge(START, "call_model")
builder.add_edge("call_model", "write_memory")
builder.add_edge("write_memory", END)

# 长期记忆:跨线程 Store
across_thread_memory = InMemoryStore()
# 短期记忆:线程内 Checkpointer
within_thread_memory = MemorySaver()

# 关键:同时传入 checkpointer(短期)和 store(长期)
graph = builder.compile(
    checkpointer=within_thread_memory,
    store=across_thread_memory
)
# thread_id → MemorySaver;user_id → Store
config = {
    "configurable": {
        "thread_id": "1",
        "user_id": "1"
    }
}

for chunk in graph.stream(
    {"messages": [HumanMessage(content="Hi, my name is Lance")]},
    config,
    stream_mode="values"
):
    chunk["messages"][-1].pretty_print()
LangGraph 如何自动注入 store 参数

当节点函数签名中包含 store: BaseStore 参数时,LangGraph 会在调用该节点时自动将编译时传入的 store 对象注入。这与 config 参数的注入机制相同——框架通过函数签名检测来决定是否传入这些特殊参数。

8 完整运行演示:跨线程记忆持久化

第一次对话(Thread 1):建立用户画像

Thread ID: "1" | User ID: "1"

Human: Hi, my name is Lance
AI: Hello, Lance! It's nice to meet you. How can I assist you today?

Human: I like to bike around San Francisco
AI: That sounds like a great way to explore the city, Lance! San Francisco has some beautiful routes. Do you have a favorite trail?

第二次对话(Thread 2):跨线程记忆生效

开启全新的 thread_id,但使用相同的 user_id——机器人应该能记住 Lance 的信息:

Thread ID: "2" | User ID: "1" — 新对话,旧记忆

Human: Hi! Where would you recommend that I go biking?
AI: Hi Lance! Since you enjoy biking around San Francisco, here are some fantastic routes:

1. Golden Gate Park — trails and beautiful scenery
2. The Embarcadero — stunning views of the Bay Bridge
3. Marin Headlands — across the Golden Gate Bridge
4. Presidio — network of trails with views of the bay
关键验证点

在 Thread 2 中,机器人从未见过 Lance 这个名字(Thread 2 是全新对话),也没有被告知他喜欢骑行。但因为 user_id: "1" 的长期记忆中已记录了这些信息,call_model 节点从 Store 读取后注入提示词,使得 AI 能够直接用名字称呼用户,并给出针对性的骑行建议

设计总结

组件类型标识符作用
MemorySaverCheckpointerthread_id持久化消息历史,支持对话中断与恢复
InMemoryStoreStoreuser_id + namespace + key跨线程保存用户画像,个性化所有未来对话
call_model节点(读)Store.get()每次对话开始时读取并注入用户画像
write_memory节点(写)Store.put()每次对话结束后更新用户画像
生产环境迁移

本节使用 InMemoryStore,数据在进程重启后丢失。生产环境中可替换为基于数据库的 Store 实现(如 PostgreSQL、Redis),只需更改初始化方式,节点代码和图结构完全不变。