The Agent in the previous lecture has "amnesia" every time it talks - after the invoke() is executed, the State disappears.
This lecture uses checkpointer to allow the Agent to remember the conversation history and truly realize multiple rounds of dialogue.
In the previous lecture (1-4), we built a complete ReAct Agent: it can call tools in a loop until the problem is solved. But it has a fatal flaw——Each invoke() is a completely new conversation, don’t remember anything said before.
First ask:"Add 3 and 4."——Agent calls add(3, 4) and returns 7.
Then ask again:"Multiply that by 2."——What is "that"?
The Agent without memory has no idea that "that" is 7!It might randomly guess, say multiply(2, 2) = 4, and be completely wrong.
State in LangGraph isAn object temporarily created each time invoke(). The execution process is as follows:
Essentially, this is because LangGraph's graph implementation ispure functional——Input comes in, output goes out, no side effects are retained. This is a good thing in most cases (predictable, testable), but multi-turn dialogue scenarios require state persistence.
Checkpointer(checkpointer) is the persistence layer of LangGraph. Its responsibility is: after each node of the graph is executed,Automatically serialize and save the current State to the storage backend。
checkpointer lets the graph "leave a footprint" at every step. The next time you invoke(), you no longer start from a blank page, but load all the last footprints back first, and then continue execution on this basis.
thread_id + step_number。from langgraph.checkpoint.memory import MemorySaver
# MemorySaver 把所有 checkpoint 存在 Python 进程内存里
# 本质上是一个字典:{thread_id: {step: state_snapshot}}
memory = MemorySaver()
# 编译时把 checkpointer 传进去
react_graph_memory = builder.compile(checkpointer=memory)
MemorySaverUses an in-memory dictionary to store all checkpoints, zero configuration, out-of-the-box, suitable for development phases and unit testing. The disadvantage is that all data is lost after the process is restarted.
An Agent may serve multiple users at the same time, and the conversation history of each user must be isolated.Thread IDIt is the "conversation session ID", which is equivalent to the primary key in the database.
Complete conversation history of three threadsIndependent storage, no interference with each other. Alice's "that" is 7, Bob's "that" is 25, and Carol's "that" is 42.
LangGraph does not automatically generate thread_id. you need to be inconfigPassed in, usually generated by your application layer (such as UUID, user ID, Session ID). Multiple requests from the same user use the same thread_id, and different users use different thread_ids.
import uuid
# 方式一:固定字符串(测试/demo 时常用)
config = {"configurable": {"thread_id": "1"}}
# 方式二:UUID(生产环境,每个用户 session 一个)
thread_id = str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}
# 方式三:与业务 ID 绑定(最常见的实践)
config = {"configurable": {"thread_id": f"user_{user_id}_session_{session_id}"}}
Adding memory to Agent, the code changes are extremely small——Just pass in checkpointer when compile(), all the rest of the code remains unchanged.
from langgraph.graph import (
StateGraph, MessagesState,
START, END
)
from langgraph.prebuilt import (
ToolNode, tools_condition
)
# 无 checkpointer
react_graph = builder.compile()
# 每次 invoke 都是全新对话
messages = react_graph.invoke(
{"messages": messages}
# 没有 config,无法区分对话
)
from langgraph.graph import (
StateGraph, MessagesState,
START, END
)
from langgraph.prebuilt import (
ToolNode, tools_condition
)
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
# 传入 checkpointer ← 唯一改动!
react_graph_memory = builder.compile(
checkpointer=memory
)
config = {"configurable": {"thread_id": "1"}}
# 必须传 config,带上 thread_id
messages = react_graph_memory.invoke(
{"messages": messages},
config # ← 告诉图用哪个 thread
)
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.memory import MemorySaver
# ─── 初始化 Checkpointer ───
memory = MemorySaver()
react_graph_memory = builder.compile(checkpointer=memory)
# ─── 配置:使用 thread_id = "1" ───
config = {"configurable": {"thread_id": "1"}}
# ─── 第一轮对话 ───
messages = [HumanMessage(content="Add 3 and 4.")]
result = react_graph_memory.invoke({"messages": messages}, config)
# LLM 调用 add(3, 4) → ToolMessage("7") → AIMessage("The sum is 7.")
# ✅ Checkpointer 把这整个 State 保存到 thread "1" 的 checkpoint
# ─── 第二轮对话:注意只传了新的问题 ───
messages = [HumanMessage(content="Multiply that by 2.")]
result = react_graph_memory.invoke({"messages": messages}, config)
# ✅ LangGraph 先加载 thread "1" 的历史,再追加新消息
# LLM 看到完整历史,正确理解 "that" = 7
# → 调用 multiply(7, 2) → ToolMessage("14") → AIMessage("The result is 14.")
print(result["messages"][-1].content)
# 输出:"The result is 14."
To truly understand the memory mechanism, you need to know what steps each invoke() goes through internally.
The second time you invoke() you only pass in[HumanMessage("Multiply that by 2.")], but LangGraph internally quietlyAppend it to the history of 5 messages, what LLM actually sees is 6 messages (5 historical + 1 new issue). That’s the nature of memory—the continual accumulation of a list of messages.
The following visually shows what LLM actually received during the second round of invoke().messagesWhat a difference a list makes.
After the two rounds of dialogue are executed, there are a total of messages in the State of thread "1"7 items:
# thread_id="1" 的最终 messages 列表:
[
HumanMessage(content="Add 3 and 4."),
AIMessage(content="", tool_calls=[{"name": "add", "args": {"a": 3, "b": 4}}]),
ToolMessage(content="7", name="add"),
AIMessage(content="The sum of 3 and 4 is 7."),
HumanMessage(content="Multiply that by 2."),
AIMessage(content="", tool_calls=[{"name": "multiply", "args": {"a": 7, "b": 2}}]),
ToolMessage(content="14", name="multiply"),
AIMessage(content="The result of multiplying 7 by 2 is 14."),
]
The figure below shows the graph with checkpointer inSingle invoke() internalHow to automatically save State after each node.
Because each step has a checkpoint, LangGraph supports the "time travel" function: you can load any historical checkpoint and re-execute from that point in time (the subsequent history will be overwritten). This is a powerful tool for debugging and error correction, and will be specifically explained in Module 3.
MemorySaverOnly suitable for development stage. Production environments require a checkpointer implementation that can be persisted to disk or an external database.
| Checkpointer | Storage backend | Applicable scenarios | persistence |
|---|---|---|---|
MemorySaver |
Python memory (dictionary) | Local development, unit testing, Demo | ❌ Process restart is lost |
SqliteSaver |
SQLite file | Single-machine deployment, lightweight production | ✅ Persist to file |
PostgresSaver |
PostgreSQL | Multi-instance deployment, high concurrent production | ✅ Persistence to database |
RedisSaver |
Redis | High-performance, cache-first scenario | ✅ Configurable persistence |
| Custom implementation | Any storage | Special needs (such as MongoDB, S3) | Depends on implementation |
All checkpointer implementations follow the same interface (BaseCheckpointSaver), so when switching from MemorySaver to PostgresSaver,Zero changes to business code, just replace the checkpointer object passed in at compile time.
# 开发阶段
from langgraph.checkpoint.memory import MemorySaver
graph = builder.compile(checkpointer=MemorySaver())
# 生产阶段(替换一行,其余代码不变)
from langgraph.checkpoint.postgres import PostgresSaver
with PostgresSaver.from_conn_string("postgresql://user:pass@host/db") as checkpointer:
graph = builder.compile(checkpointer=checkpointer)
# 同样的 invoke(),同样的 config,完全一致的行为
If you useLangGraph Platform(Cloud hosting service, see Module 6), the platform will automatically manage checkpointer, you don't even need to configure it manually - just use thread_id directly, and the underlying persistence is completely transparent to you.
Even if you pass checkpointer, if it is not provided when invoke()config(or no thread_id), LangGraph willReport an error. This is a mandatory constraint by design.
# ❌ 错误:有 checkpointer 但没有 config
react_graph_memory.invoke({"messages": messages})
# 抛出 ValueError: "thread_id" is required in config["configurable"]
# when a checkpointer is used
# ✅ 正确:提供 config 和 thread_id
config = {"configurable": {"thread_id": "1"}}
react_graph_memory.invoke({"messages": messages}, config)
Why is it mandatory? Because there is no thread_id, checkpointer doesn't know at all:
in the config dictionaryconfigurableNot only can it be placedthread_id, you can also put other runtime parameters (such as LLM temperature, user permissions, custom parameters, etc.). This is LangGraph's standard mechanism for implementing "runtime configuration", which avoids the confusion of stuffing all parameters into State.
With checkpointer, you can also query the historical State of a thread at any time without re-executing the graph:
# 获取某个 thread 的当前(最新)State 快照
snapshot = react_graph_memory.get_state(config)
print(snapshot.values["messages"]) # 查看完整消息历史
print(snapshot.next) # 查看下一步将执行哪个节点
# 获取某个 thread 的全部历史 checkpoints(时间旅行用)
for checkpoint in react_graph_memory.get_state_history(config):
print(checkpoint.config) # 该 checkpoint 的 config(含 checkpoint_id)
print(checkpoint.values["messages"]) # 该时刻的消息列表
This lecture is based on the complete ReAct Agent in 1-4, usingminimal code changesImplemented persistent multi-round dialogue memory.
| concept | One sentence summary |
|---|---|
| The source of statelessness | LangGraph's State is an object that is temporarily created each time invoke() is executed and discarded after execution. |
| Checkpointer | The State snapshot is automatically saved after each node is executed, and is automatically loaded and restored the next time invoke() is executed. |
| MemorySaver | Memory version of checkpointer, data will be lost after process restart, suitable for development and testing |
| Thread ID | The unique identifier of the conversation session. Multiple invoke()s with the same thread_id share and accumulate history. |
| Configuration parameters | Each time invoke() must be passed in{"configurable": {"thread_id": "xxx"}} |
| connection mechanism | The second time invoke() loads the historical State first and then appends new messages. LLM sees the complete context. |
| production switch | Replace MemorySaver with PostgresSaver/SqliteSaver, with zero changes to the business code |
1-6 will introduce how to deploy LangGraph Agent as a service (LangGraph Server) that can be accessed externally. You'll see how checkpointer works in the cloud and interacts with the Agent via the REST API and SDK.