LangChain LangGraph
Module 1 Module 2 Module 3 Module 4 Module 5 Module 6
Module 2 · State & Memory
1 2-1 State Schema
2 2-2 Reducers
3 2-3 Multi Schemas
4 2-4 Trim & Filter
5 2-5 Chatbot Summary
6 2-6 Ext Memory
SUM Module Summary
HomeLangGraphModule 2 · State & Memory2-6 Ext Memory
📓 Notebook 📖 Explained
LANGGRAPH MODULE 2 · LESSON 6

Chatbot external persistent memory
SqliteSaver · PostgresSaver · Thread multi-session

From in-memory checkpoints to truly persistent database storage.
Let Chatbot still remember all conversations after restarting, supporting multi-user and multi-session scenarios.

1Review: What we have accomplished and what remains to be done

In the first few lessons of Module 2, we gradually built a conversational Chatbot with increasingly complete features. Core results include:

remaining core issues

MemorySaver stores all states in memory (RAM). Once the Python process exits (restarts Notebook kernel, restarts services, deploys updates), all conversation history disappears. This would be a disastrous experience for any real user.

The goal of this lesson (Lesson 6): introduceExternal database checkpoint, so that the status of the graph can be permanently written to the disk or cloud database, completely solving the problem of "amnesia when power is cut off".

Dimensions MemorySaver (memory) External database checkpoint
storage location Python process memory (RAM) SQLite files / PostgreSQL / Redis etc.
After the process restarts All data is lost The data is completely retained and can be restored
Applicable environment Development, debugging, Demo Production environment, real user system
Multi-process/multi-instance Not supported (separate memory) Support (share the same database)
Disaster tolerance None High (complete recovery after server crash)
Install dependencies No additional dependencies (LangGraph built-in) The corresponding driver package needs to be installed

2MemorySaver vs External Database: The Fundamental Differences Between the Two Types of Checkpoints

What is a checkpointer?

LangGraph’s checkpointing mechanism is itsState persistence infrastructure layer. Each time the graph executes a node, LangGraph will serialize the current complete State and hand it over to the checkpointer for storage. The next time it is called, the checkpointer is responsible for restoring the last State, and the graph can continue with the last context.

How the checkpoint mechanism works

graph.invoke()
Node execution
State update
checkpointer write
The next time you call ↓
graph.invoke()
checkpointer read
Restore State
Continue execution

Internal mechanism of MemorySaver

MemorySaverUse a Python dictionary (dict) as the storage backend. It saves the serialized state inself.storageIn this memory dictionary, the key is(thread_id, checkpoint_id)combination. This explains why it's so fast yet so fragile - the data never hits the disk.

from langgraph.checkpoint.memory import MemorySaver

# MemorySaver:数据存在 Python 进程的内存里
memory = MemorySaver()

# 编译时挂上检查点器
graph = workflow.compile(checkpointer=memory)

# 问题:当这个 Python 进程结束,memory 对象销毁,所有数据消失
Core Design: checkpointers are pluggable

The beauty of LangGraph is:The checkpointer is completely transparent to the graph's code. You just need tocompile(checkpointer=xxx)When the checkpointer is replaced, all nodes, edges, and logic of the graph do not need to be changed at all. this is standarddependency injectionDesign patterns.

How is state serialized?

LangGraph will serialize the entire State object (using JSON-compatible format by default) before writing the checkpoint. formessagesThis class of LangChain message objects implements__json__()Serialization interface, can be completely restored to the original type (HumanMessageAIMessageetc.).

3SqliteSaver: lightweight persistence solution

SQLite is the most deployed database engine in the world and is used built-in by Android, iOS, Firefox, Python standard library, etc. Its characteristics areZero configuration, single file, serverless process, ideal as the first step in upgrading from in-memory storage to persistence.

Install dependencies

# 安装 LangGraph 的 SQLite 检查点支持库
pip install langgraph-checkpoint-sqlite

Way 1: SQLite in memory mode (still not durable!)

SQLite supports a special":memory:"Connection string, indicating the creation of a purely in-memory SQLite database. This andMemorySaverSimilarly, the data disappears when the process exits, but it uses the SQLite interface:

import sqlite3

# ":memory:" 表示内存 SQLite,进程退出即消失
conn = sqlite3.connect(":memory:", check_same_thread=False)

# check_same_thread=False 允许多线程访问同一连接
(LangGraph 的异步执行需要这个参数)

Method 2: SQLite in file mode (real persistence)

Just change":memory:"Replace it with a file path, and SQLite will create (or open) a.dbfile. All data is written to this file and remains after the process exits:

import sqlite3
import os

# 创建存放数据库的目录
os.makedirs("state_db", exist_ok=True)

# 指定磁盘文件路径——数据将永久保存
db_path = "state_db/example.db"
conn = sqlite3.connect(db_path, check_same_thread=False)

# 用这个连接创建 SqliteSaver 检查点器
from langgraph.checkpoint.sqlite import SqliteSaver
memory = SqliteSaver(conn)
What is stored in a SQLite database?

LangGraph will be in your.dbThe required tables are automatically created in the file, mainly including:checkpoints(stores each state snapshot) andcheckpoint_writes(Stores status updates to be written). You don't need to create tables manually, SqliteSaver will automatically initialize it the first time you use it.

How does SqliteSaver work internally?

SqliteSaver reading and writing process

When writing (after invoke is executed)
  • 1. Serialize the State object to JSON
  • 2. Generate unique checkpoint_id
  • 3. Use (thread_id, checkpoint_id) as key
  • 4. INSERT INTO checkpoints …
When reading (before next invoke)
  • 1. Find the latest checkpoint by thread_id
  • 2. SELECT … ORDER BY checkpoint_id DESC
  • 3. Deserialize to original State object
  • 4. Inject the graph execution context to continue running

4Reuse message summary graph: connected to external checkpoints

These are the most important engineering practice points of this course:The definition code of the graph remains completely unchanged, just incompile()Change a checkpointer from time to time. We directly connect the complete message summary Chatbot graph of Lesson 5 to SqliteSaver.

Complete graph definition (same as Lesson 5)

from typing_extensions import Literal
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage
from langgraph.graph import END, MessagesState, StateGraph, START

model = ChatDeepSeek(model="deepseek-v4-pro", temperature=0)

# State 继承 MessagesState,额外增加 summary 字段
class State(MessagesState):
    summary: str

# ── Node 1:调用 LLM ──
def call_model(state: State):
    summary = state.get("summary", "")
    if summary:
        # 把历史摘要注入为系统消息
        system_message = f"Summary of conversation earlier: {summary}"
        messages = [SystemMessage(content=system_message)] + state["messages"]
    else:
        messages = state["messages"]
    response = model.invoke(messages)
    return {"messages": response}

# ── Node 2:生成/更新对话摘要,并删除旧消息 ──
def summarize_conversation(state: State):
    summary = state.get("summary", "")
    if summary:
        summary_message = (
            f"This is summary of the conversation to date: {summary}\n\n"
            "Extend the summary by taking into account the new messages above:"
        )
    else:
        summary_message = "Create a summary of the conversation above:"

    messages = state["messages"] + [HumanMessage(content=summary_message)]
    response = model.invoke(messages)

    # 用 RemoveMessage 删除除最近 2 条以外的所有消息
    delete_messages = [RemoveMessage(id=m.id) for m in state["messages"][:-2]]
    return {"summary": response.content, "messages": delete_messages}

# ── 路由函数:消息超过 6 条时触发摘要 ──
def should_continue(state: State) -> Literal["summarize_conversation", END]:
    if len(state["messages"]) > 6:
        return "summarize_conversation"
    return END

# ── 构建图 ──
workflow = StateGraph(State)
workflow.add_node("conversation", call_model)
workflow.add_node(summarize_conversation)
workflow.add_edge(START, "conversation")
workflow.add_conditional_edges("conversation", should_continue)
workflow.add_edge("summarize_conversation", END)

The key line: replace the checkpointer

# 之前用内存检查点:
# graph = workflow.compile(checkpointer=MemorySaver())

# 现在用 SQLite 检查点——图的其他代码零改动!
graph = workflow.compile(checkpointer=memory)  # memory 是 SqliteSaver 实例

Graph structure with external checkpoints

START
conversation
call_model: call LLM
should_continue() routing
Messages > 6
summarize_conversation
Generate summary + delete old messages
Messages ≤ 6
END
After each node execution, State is automatically written to the SQLite database file

5Thread (thread) and thread_id: the core mechanism of multi-user multi-session

What is Thread?

In LangGraph,ThreadNot an operating system thread, but aA logically isolated unit of a conversation session. Each Thread has a uniquethread_id, LangGraph uses it to distinguish conversations between different users, or different conversation contexts for the same user.

you can putthread_idUnderstood as "conversation room number": conversations in different rooms do not interfere with each other, and multiple conversations in the same room share the same historical memory.

# 创建一个对话配置,指定 thread_id
config = {"configurable": {"thread_id": "1"}}

# 第一轮对话
input_message = HumanMessage(content="hi! I'm Lance")
output = graph.invoke({"messages": [input_message]}, config)
for m in output['messages'][-1:]:
    m.pretty_print()

# 第二轮对话(同一 thread_id → 记得 Lance)
input_message = HumanMessage(content="what's my name?")
output = graph.invoke({"messages": [input_message]}, config)
for m in output['messages'][-1:]:
    m.pretty_print()
# 输出:Your name is Lance!

# 第三轮对话
input_message = HumanMessage(content="i like the 49ers!")
output = graph.invoke({"messages": [input_message]}, config)
for m in output['messages'][-1:]:
    m.pretty_print()

Multi-user scenario: different thread_ids are completely isolated

# 用户 A 的对话(thread_id="user_alice")
config_alice = {"configurable": {"thread_id": "user_alice"}}
graph.invoke({"messages": [HumanMessage(content="I'm Alice")]}, config_alice)

# 用户 B 的对话(thread_id="user_bob")——与 Alice 完全隔离
config_bob = {"configurable": {"thread_id": "user_bob"}}
graph.invoke({"messages": [HumanMessage(content="I'm Bob")]}, config_bob)

# 用户 A 继续(仍然记得 Alice 的历史)
graph.invoke({"messages": [HumanMessage(content="what's my name?")]}, config_alice)
# → 输出:Your name is Alice!
Best practices for thread_id

In a real system,thread_idCommonly usedUser ID + Session ID combination,For example"user_12345_session_abc". The same user can open multiple independent conversations (each with a different session ID), or reuse the same conversation (continue the last context). It is recommended to use UUID to generate session ID:str(uuid.uuid4())

Check the current status of Thread

usegraph.get_state(config)You can view the current complete State of a thread at any time, including the messages list and summary field:

# 查看 thread "1" 的当前状态
config = {"configurable": {"thread_id": "1"}}
graph_state = graph.get_state(config)

# graph_state.values 包含完整的 State 字典
print(graph_state.values["messages"])   # 当前保留的消息列表
print(graph_state.values["summary"])    # 当前的对话摘要
messages:
[HumanMessage("i like the 49ers!"),
AIMessage("Great! The 49ers...")]
summary:
"User's name is Lance. He..."
State saved in SQLite (after three rounds of dialogue)
messages:
[HumanMessage("new question"),
AIMessage("...")]
summary:
"User's name is Lance. He..."
After appending new messages (summary is retained)

6State persistence verification: session restoration after kernel restart

This is the most convincing demonstration of the course. Let's prove that even if the Jupyter Notebook kernel is completely restarted (equivalent to a server restart), the conversation history saved in the SQLite file still exists and can be seamlessly continued.

Verification steps

# ── 重启 Kernel 后,重新运行以下代码 ──

import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver

# 重新连接同一个 .db 文件
db_path = "state_db/example.db"
conn = sqlite3.connect(db_path, check_same_thread=False)
memory = SqliteSaver(conn)

# 重新编译图(节点/边定义完全不变)
graph = workflow.compile(checkpointer=memory)

# 使用相同的 thread_id——神奇的事情发生了
config = {"configurable": {"thread_id": "1"}}
graph_state = graph.get_state(config)

print("历史消息数量:", len(graph_state.values["messages"]))
print("对话摘要:", graph_state.values.get("summary", "(暂无摘要)"))
# 输出:上次对话的完整历史!
This is the minimum viable form of production-grade persistence

This feature of SQLite makes it very suitable forApplications deployed on a single machine(Personal assistants, native tools, small SaaS, etc.). For scenarios that require horizontal expansion (multiple servers serving simultaneously), you need to upgrade to a database that supports concurrent writing such as PostgreSQL.

Further verification: Continue this conversation

# 重启后,继续上次的对话——LLM 仍然记得 Lance 喜欢 49ers
input_message = HumanMessage(content="which nfl team do I like?")
output = graph.invoke({"messages": [input_message]}, config)
for m in output['messages'][-1:]:
    m.pretty_print()
# 即使 Kernel 重启过,仍然输出:You like the San Francisco 49ers!

7Production-level solution: PostgresSaver and deployment architecture

SQLite is suitable for stand-alone scenarios, but when your Chatbot needs servicesLarge number of concurrent users, or needScale out across multiple servers, you need to upgrade to an enterprise-level database like PostgreSQL.

How to use PostgresSaver

# 安装 PostgreSQL 检查点支持库
# pip install langgraph-checkpoint-postgres psycopg

from psycopg import connect
from langgraph.checkpoint.postgres import PostgresSaver

# 连接到 PostgreSQL 数据库
conn_string = "postgresql://user:password@localhost:5432/chatbot_db"
conn = connect(conn_string, autocommit=True)

# 初始化检查点表(首次使用时调用)
checkpointer = PostgresSaver(conn)
checkpointer.setup()  # 自动创建所需的数据库表

# 编译图时挂上 PostgreSQL 检查点——其余代码零改动!
graph = workflow.compile(checkpointer=checkpointer)

Production deployment architecture comparison

Plan Applicable scale advantage Limit
MemorySaver Development/Demo Zero configuration, extremely fast The process will be lost upon restart, and multiple processes are not supported.
SqliteSaver Stand-alone small application
personal tools
Zero service process, single file, simple deployment Does not support high concurrent writing and is not suitable for multiple instances
PostgresSaver Medium and Large SaaS
Enterprise applications
High concurrency, horizontal expansion, transaction security The PG service needs to be maintained and the configuration is relatively complex.
Custom checkpointer special needs Can be connected to any backend such as Redis and DynamoDB Need to implement the interface yourself

Typical production deployment architecture (PostgreSQL scenario)

User A
User B
User C
↓ HTTP/WebSocket request
LangGraph App
Example 1
LangGraph App
Example 2
LangGraph App
Example 3
↓ PostgresSaver Reading and Writing
PostgreSQL database
All users’ Thread status is centrally stored

Integration in LangGraph Studio

The Studio configuration corresponding to this lesson is inmodule-2/studio/chatbot.pyin. Uselanggraph devAfter the command starts the local development server, you can visually view the complete state history of each Thread in LangSmith Studio's UI interface, replay the conversation at any checkpoint, and even edit the State directly. This is a powerful tool for debugging external memory problems.

# 在 module-2/studio 目录下运行:
langgraph dev

# 访问 Studio UI:
# https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024

8Complete process review and best practices

Knowledge map of this course

checkpointer
  • • Responsible for serializing/deserializing State
  • • Injected during compile(), graph code is transparent
  • • MemorySaver / SqliteSaver / PostgresSaver
  • • Customizable access to any storage backend
Thread / thread_id
  • • Isolated units of logical dialogue sessions
  • • Passed to invoke() via a config dictionary
  • • Different thread_id → completely independent history
  • • Use userId + sessionId combination in production
Key points for using SqliteSaver
  • • File path for connection string (not :memory:)
  • • check_same_thread=False (multi-thread safe)
  • • Automatically create checkpoint tables, no need to create tables manually
  • • Restore all history by reattaching a file
State serialization
  • • The message object implements the JSON serialization interface
  • • Type integrity after restoration (HumanMessage, etc.)
  • • The summary field is also persisted along with the State
  • • get_state() can view the current snapshot at any time

Complete migration path

core engineering values

LangGraph takes the problem of "where to store the state" from the business logic of the graph.Complete decoupling. The Node functions, Edge routing functions, and State definitions you write do not need to know whether the data is stored in memory, SQLite, or PostgreSQL. This allows you to migrate your AI applications from demo to productionTruly zero code changes, just change a checkpointer instance.

Position on the learning route

Module 2-1 State Schema customization: TypedDict, Pydantic, MessagesState
Module 2-2 reducer mechanism: add_messages appends rather than overwrites, custom reducer
Module 2-3/4 Message pruning and filtering: controlling context window size
Module 2-5 Message summary Chatbot: summary field + RemoveMessage + MemorySaver
Module 2-6 ← External persistent memory: SqliteSaver + Thread + restart recovery
Module 3 Human-in-the-loop: Execution of the diagram is interrupted, waiting for human approval
Module 5 Cross-Thread long-term memory Store (higher-level memory beyond checkpointer)
Next step

At this point, Module 2 has completely coveredBuild a production-grade Chatbot with full memory capabilitiesAll the techniques needed: state design, message management, digest compression, external persistence, multi-user isolation. Module 3 will be introduced based on thisManual intervention (Breakpoints), allowing AI to pause and consult users at key decision points, further improving the controllability and security of the system.