1 什么是持久化能力?
简单来说,在 LangGraph 中持久化能力指的是:将AI应用的状态(如对话历史、中间结果、用户信息等)保存下来,即使程序重启或系统宕机,也能恢复之前的状态,让AI有 "记住之前发生的一切事情" 的能力。
想象一下这个场景:你今天在和智能助手聊天,说了很多重要信息。然后你关闭了应用,明天重新打开并重新与它对话时,当然希望它还记得你说过的话。
再看第二个场景:假设有一个助手,它可以搜索网络。
没有持久化:
- 用户问:"今天的天气怎么样?'
- 助手调用搜索工具,得到答案:"今天晴天,25度。
- 程序崩溃重启。
- 用户再问:"那我需要带伞吗?"
- 助手没有之前的上下文,它可能又会去调用搜索工具,而不是基于"今天晴天"这个上下文来回答"不需要"
有持久化:
- 用户问:"今天的天气怎么样?"
- 助手调用搜索工具,得到答案:"今天晴天,25度。"(这个状态,包括对话历史和工具调用结果,被自动保存)
- 程序崩溃重启。
- 用户再问:"那我需要带伞吗?"(与之前在同一会话下)
- LangGraph加载之前保存的状态,状态里记录了"今天晴天"。
- 助手看到上下文是晴天,于是直接回答:"今天是晴天,您不需要带伞。(无需再次调用搜索工具)
1.1 持久化能力(线程级、跨会话)
1、【线程级】持久化:能够【自动保存】工作流执行过程中的【状态快照】,维持单次会话的完整上下文。
- 解释1:这里的【线程持久化】和【操作系统线程】概念完全独立区分。操作系统中线程是进程内的执行单元,是操作系统能够进行调度的最小单位。而线程级持久化表示聊天过程中的单次会话的持久化信息,用来隔离不同的聊天会话。
- 解释2:这里的【状态快照】并非是之前学习过的 State 的快照。这个状态包含了所有必要的上下文信息,比如:已经调用过哪些工具、用户的输入、聊天历史、下一步要执行的节点等等。
2、【跨会话】持久化:通过 存储(Store) 保存用户信息、偏好设置等长期数据,实现不同对话间信息的持久化共享。例如,可以将用户基本情况(有高血压病史)存储,后续无论在何时何地,或无论新开几个会话窗口,都可以基于用户基本信息(有高血压病史)生成结果。
2 线程级持久化
2.1 线程级持久化是怎么工作的?
当我们开始执行工作流,过程中可能会发生崩溃 或重启导致的中断 等异常情况。根据 LangGraph 的持久化机制,线程级持久化表示能够【自动保存】工作流执行过程中的【状态快照】,维持单次会话的完整上下文。当工作流执行到某一步时,它会自动保存当前步骤的状态快照。这个状态包含了所有必要的上下文信息,比如:已经调用过哪些工具、用户的输入、聊天历史、下一步要执行的节点等等。因此,工作流在执行过程中会发生如下图所示的流程:
- 正常执行:紫色/蓝色节点和实线箭头
- 异常恢复:红色/绿色节点和虚线箭头
- 检查点回滚:橙色节点和虚线箭头

线程级持久化机制确保了:
- 状态不丢失:即使应用程序崩溃、重启,或者一个长时间的流程被中断,当它恢复时,可以从上次停止的地方继续执行,而不是从头开始。
- 支持长时间运行的任务:对于需要与用户进行多轮交互(如多步对话助手)或处理耗时极长的流程(如等待外部API回调),持久化是必不可少的。
- 检查点和回滚:我们可以将状态保存到某个时间点(检查点),并在需要时回滚到该状态。
2.2 两个核心概念
LangGraph的持久化机制------线程级持久化是其核心功能,它通过【线程】和 【检查点】这两个核心部分来实现。具体如下:
2.2.1 Threads (线程)
在 LangGraph 中,Thread 代表一个独立的工作流执行会话。可以把它想象成【与某个用户的一次完整对话历史】或【处理某个特定任务的一次完整执行过程】。例如:

Thread 的关键特性:
- 隔离性:每个 Thread 都是完全独立的,它们的状态互不干扰
- 持久化单元:Thread 是状态持久化的基本单位
- 标识符:通过唯一的 thread_id来识别
2.2.2 Checkpoints(检查点)
Checkpoint 是 Thread 在特定时刻的【状态快照】。它记录了工作流执行到某个节点时的完整状态。例如在刚才的会话中,每一次用户输入和对话结束后,都可以保存一个最新的【状态快照】:

Checkpoint 的关键特性 :
**1、****版本历史与可恢复点:**一个 Thread 可以有多个 Checkpoints,形成执行历史,使得同一个会话的历史状态可以从任意 Checkpoint 追溯和访问。
2、状态快照(StateSnapshot):他保存了工作流在某个时间点的完整状态。包含【状态值】、【下一步要执行的节点】、【与此检查点关联的配置】和【与此检查点关联的元数据】等信息。 结构如下:
StateSnapshot(
# 当前状态值(如:对话消息列表)
values={'messages': [用户消息, AI回复, 用户消息...]},
# 接下来要执行的节点
next=('generate_response',),
# 配置信息
config={'configurable': {'thread_id': '123', 'checkpoint_id': 'abc'}},
# 元数据(步骤号、来源、写入信息等)
metadata={'step': 2, 'source': 'loop', 'writes': {...}},
# 父检查点(形成链表)
parent_config={'configurable': {'thread_id': '123', 'checkpoint_id': 'def...'}},
# 创建时间
created_at=''
)

Threads与Checkpoints关系如下:

2.3 线程级持久化使用姿势
2.3.1 步骤一:配置 checkpointer 持久化存储
在定义图时,我们需要指定 checkpointer。LangGraph支持多种 checkpointer 的定义方式:
2.3.1.1 方式1:内存存储
最简单的方式,将状态保存在程序内存中。适用于开发和测试,因为程序重启后状态会丢失。用法如下所示:
from langgraph.checkpoint.memory import InMemorySaver
# 定义存储方式
checkpointer = InMemorySaver()
# 在编译图时,直接添加编译参数checkpointer
agent = agent_builder.compile(checkpointer=checkpointer)
2.3.1.2 方式2:使用 Postgres 存储库
LangGraph 提供了几个检查点存储实现,所有这些都通过独立的、可安装的库实现。适用于生产环境或需要状态持久化的场景。
- SQLite 存储 (langgraph-checkpoint-sqlite):使用 SQLite 数据库 (AsyncSqliteSaver / SqliteSaver )的LangGraph检查点的实现。非常适合实验和本地工作流程。需单独安装。
- Postgres 存储 (langgraph-checkpoint-postgres ):使用 Postgres 数据库(PostgresSaver/AsyncPostgresSaver)的LangGraph检查点的实现。非常适合在生产中使用。需单独安装。
为什么选择PostgreSQL作为存储库?
在众多持久化存储方案中,PostgreSQL 作为关系型数据库的佼佼者( PostgreSQL 和MySQL 一样,都是最流行的开源关系型数据库),具备以下显著优势,使其成为持久化的理想选择:
- **LangGraph 原生支持:**LangGraph 提供了 PostgresSaver,简化了与 PostgreSQL 的集成过程。
- **数据结构化与一致性:**关系型数据库天生适合存储结构化数据。Graph的状态,尤其是消息历史、用户档案、工具使用记录等,都可以很好地映射到表格结构中,确保数据的一致性和完整性。
- **可靠性与持久性:**PostgreSQL 提供了事务支持、ACID 特性、数据备份与恢复机制,确保数据的持久性和高可用性,即使系统崩溃也能保证数据不丢失。
- **强大的查询能力:**SQL 语言提供了灵活且强大的数据查询能力,方便我们对历史行为、用户数据进行分析、统计和审计。结合 pgvector 等扩展,甚至可以直接在数据库中进行向量相似度搜索,实现更高级的知识管理。
- **可扩展性:**通过读写分离、分区、集群等技术,PostgreSQL 可以支持大规模的并发访问和数据存储,满足Agent在生产环境中的性能需求。
- **成熟的生态系统:**拥有庞大的社区支持、丰富的工具和成熟的运维经验,降低了开发和维护成本。
1、使用 Docker 快速安装并启动 postgres:
# 1. 拉取 PostgreSQL 镜像
docker pull postgres:latest
# 2. 运行 PostgreSQL 容器
# -p 5432:5432: 将容器的5432端口映射到宿主机的5432端口
# -e POSTGRES_PASSWORD=bit: 设置PostgreSQL的postgres用户密码
# --name postgres-sql: 给容器命名
# -d: 后台运行
docker run --name postgres-sql -e POSTGRES_PASSWORD=bit -p 5432:5432 -d postgres
2、使用 Navicat 测试链接:注意默认连接 postgres 初始数据库,如果有其他数据库可以配置连接。

1、安装 langgraph-checkpoint-postgres 包
pip install -U "psycopg[binary,pool]" langgraph langgraph-checkpoint-postgres
2、设置 PostgreSQL 连接字符串 (URI)
PostgreSQL URI 通常遵循以下格式:postgresql://<user>:<password>@<host>:<port>/<database_name>
3、使用 Postgre 存储库作为检查点
使用 PostgresSaver.from_conn_string()方法 从连接字符串创建一个新的 PostgresSaver 实例。
注意:第一次使用 Postgres 检查点时需要调用 checkpointer.setup()
DB_URI = "postgresql://postgres:bit@192.168.100.233:5432/postgres"
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
# 第一次使用 Postgres 检查点时需要调用 checkpointer.setup()
checkpointer.setup()
# 编译图
agent = agent_builder.compile(checkpointer=checkpointer)
# ...后续调用...
2.3.2 步骤二:使用Thread进行执行
当我们编译好图并准备运行时,我们需要通过一个 ThreadID 来标识这次执行。
- 如果 Thread ID 不存在:LangGraph会创建一个新的 Thread,并从初始状态开始执行。
- 如果 Thread ID 已存在:LangGraph会从 Checkpointer 中加载该 Thread 的最后一次保存的状态,并从这个状态继续执行。
这里依旧使用 postgres 存储,进行第一次执行:创建一个新的Thread(thread_id="1")
from langchain.messages import HumanMessage
DB_URI = "postgresql://postgres:bit@192.168.100.233:5432/postgres"
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
# 第一次使用 Postgres 检查点时需要调用 checkpointer.setup()
checkpointer.setup()
# 编译图
agent = agent_builder.compile(checkpointer=checkpointer)
# 第一次执行,创建一个新的 Thread (thread_id="1")
config = {"configurable": {"thread_id": "1"}}
result1 = agent.invoke(
{"messages": [HumanMessage(content="今天西安的天气如何?")]},
config
)
print(f"调用 LLM 总次数: {result1["llm_calls"]}次")
for m in result1["messages"]:
m.pretty_print()
运行后,可以看到 postgres 库中,已经存储了检查点信息:
一段时间后,再次使用相同的 thread_id 调用:
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
# 第一次使用 Postgres 检查点时需要调用 checkpointer.setup()
# checkpointer.setup()
# 编译图
agent = agent_builder.compile(checkpointer=checkpointer)
# ............... 一段时间后,程序可能重启了(使用postgres存储) ...............
# 再次使用相同的 thread_id 调用
# LangGraph 会从上次的状态继续,而不是重新开始
# 此时,result2 的上下文会包含之前的对话历史
config = {"configurable": {"thread_id": "1"}}
result2 = agent.invoke(
{"messages": [HumanMessage(content="我们刚才聊到哪了?")]},
config
)
print(f"调用 LLM 总次数: {result2["llm_calls"]}次")
for m in result2["messages"]:
m.pretty_print()
# 第二次调用结果如下:
调用 LLM 总次数: 3次
================================ Human Message ================================
今天西安的天气如何?
================================ Ai Message ================================
Tool Calls:
tavily_search (call_8ALCCwF3xudzjavTFNI6rA0m)
Call ID: call_8ALCCwF3xudzjavTFNI6rA0m
Args:
query: 西安天气
================================ Tool Message ================================
(省略...)
================================ Ai Message ================================
今天西安的天气情况如下:
- **气温**: 最高温度约为18°C,最低温度约为7°C。
- **天气状况**: 多云,空气质量较好。
如果你想查看更详细的天气预报,可以访问以下链接:
- [中国气象局 - 西安天气预报](https://weather.cma.cn/web/weather/V8870.html)
- [天气网 - 西安天气](https://www.weather.com.cn/weather40d/101110101.shtml)
请根据天气情况合理安排你的出行!
================================ Human Message ================================
我们刚才聊到哪了?
================================ Ai Message ================================
我们刚才聊到西安的天气情况,包括今天的气温和天气状况。如果你有其他问题或者想讨论的内容,请告诉我!
从结果看来,result2 的上下文会包含之前的对话历史。LangGraph 会从上次的状态继续,而不是重新开始。
2.3.3 其他基本用法
2.3.3.1 获取状态快照_get_state
当使用 checkpointer 编译图时,执行时就会在每个步骤处保存状态快照。在 LangGraph 中状态快照就是 StateSnapshot 对象,其具有以下关键属性:
StateSnapshot(
# 当前状态值(如:对话消息列表)
values={'messages': [用户消息, AI回复, 用户消息...]},
# 接下来要执行的节点
next=('generate_response',),
# 配置信息
config={'configurable': {'thread_id': '123', 'checkpoint_id': 'abc'}},
# 元数据(步骤号、来源、写入信息等)
metadata={'step': 2, 'source': 'loop', 'writes': {...}},
# 父检查点(形成链表)
parent_config={'configurable': {'thread_id': '123', 'checkpoint_id': 'def...'}},
# 创建时间
created_at=''
)
我们可以使用 get_state(config) 方法,获取编译后的图的最新状态快照。让我们分别获取一下执行前与执行后的最新状态快照:
config = {"configurable": {"thread_id": "1"}}
# 调用前的状态快照
snapshot = agent.get_state(config)
print(snapshot)
result1 = agent.invoke(
{"messages": [HumanMessage(content="你好")]},
config
)
# 调用后的状态快照
snapshot = agent.get_state(config)
print(snapshot)
# 输出
StateSnapshot(
values={},
next=(),
config={'configurable': {'thread_id': '1'}},
metadata=None,
created_at=None,
parent_config=None,
tasks=(),
interrupts=()
)
StateSnapshot(
values={'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}), AIMessage(content='你好! 有什么我可以帮助你的吗? ', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 130, 'total_tokens': 141, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': None, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_efad92c60b', 'id': 'chatcmpl-CiGBdkNSGGkZ3ccpa6NEAyUHrzqls', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--f76d30e4-0e06-4f18-ab44-0e43e8ff70b8-0', usage_metadata={'input_tokens': 130, 'output_tokens': 11, 'total_tokens': 141, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}}]},
next=(),
config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf5a3-b6ff-6b2e-8001-dc5cd083ad82'}},
metadata={'source': 'loop', 'step': 1, 'parents': {}},
created_at='2025-12-02T08:38:09.968610+00:00',
parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf5a3-a7a5-6cf8-8000-ec666fd226fe'}},
tasks=(),
interrupts=()
)
2.3.3.2 获取状态历史记录_get_state_history
我们可以通过调用 **get_state_history(config) 方法,**获取给定线程的图执行的完整历史记录。这将返回与配置中提供的线程ID关联的 StateSnapshot 对象列表。
config = {"configurable": {"thread_id": "1"}}
result1 = agent.invoke(
{"messages": [HumanMessage(content="你好")]},
config
)
# 查看状态历史记录
history = list(agent.get_state_history(config))
print(history)
# 结果如下:
[
StateSnapshot(values={'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}), AIMessage(content='你好! 有什么我可以帮助你的吗? ', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 130, 'total_tokens': 141, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': None, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_efad92c60b', 'id': 'chatcmpl-CiGUUZCWPLKOuRv6sdzolSVAzwtMW', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--7d1e5db6-e8b2-42c1-9fd8-ba21c42705ac-0', usage_metadata={'input_tokens': 130, 'output_tokens': 11, 'total_tokens': 141, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf5cf-48e3-6bbb-8001-c351576393a6'}}, metadata={'source': 'loop', 'step': 1, 'parents': {}}, created_at='2025-12-02T08:57:39.538826+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf5cf-38d7-61a8-8000-2dacafb54f11'}}, tasks=(), interrupts=()),
StateSnapshot(values={'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={})]}, next=('llm_call',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf5cf-38d7-61a8-8000-2dacafb54f11'}}, metadata={'source': 'loop', 'step': 0, 'parents': {}}, created_at='2025-12-02T08:57:37.855930+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf5cf-38d4-6e0c-bfff-6ce6b2a2dd01'}}, tasks=(PregelTask(id='44c184e5-8661-3f3c-eeee-7a2f569f7e88', name='llm_call', path=('__pregel_pull', 'llm_call'), error=None, interrupts=(), state=None, result={'messages': [AIMessage(content='你好! 有什么我可以帮助你的吗? ', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 130, 'total_tokens': 141, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': None, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_efad92c60b', 'id': 'chatcmpl-CiGUUZCWPLKOuRv6sdzolSVAzwtMW', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--7d1e5db6-e8b2-42c1-9fd8-ba21c42705ac-0', usage_metadata={'input_tokens': 130, 'output_tokens': 11, 'total_tokens': 141, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}]})), interrupts=()),
StateSnapshot(values={'messages': []}, next=('__start__',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf5cf-38d4-6e0c-bfff-6ce6b2a2dd01'}}, metadata={'source': 'input', 'step': -1, 'parents': {}}, created_at='2025-12-02T08:57:37.855022+00:00', parent_config=None, tasks=(PregelTask(id='56c07f44-a6a6-a8b8-899a-d5c24b2ff671', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={})]}),), interrupts=())
]
可以看到:返回结果将按时间顺序排序,列表中的第一个检查点(StateSnapshot)是最新的。
2.3.3.3 重放
如果我们用一个 thread_id 和一个 checkpoint_id (表示检查点标识符,用于指代线程内的特
定检查点)来调用一个图,那么我们将重新执行对应于 checkpoint_id之后的步骤。如下所示:
-
先执行一次完整的流程,获取一次完整历史记录;
-
保存中间过程某一次快照,并重新执行快照后的步骤;
-
获取第二次调用后的完整历史记录,验证是否重放成功;
config = {"configurable": {"thread_id": "1"}}
第一次执行
result1 = agent.invoke(
{"messages": [HumanMessage(content="今天西安的天气如何?")]},
config
)保存调用工具前的状态
print("-" * 80)
print(f"第一次执行历史: ")
to_replay = None
for state in agent.get_state_history(config):
print("checkpoint_id: ", state.config["configurable"]["checkpoint_id"],
"消息数: ", len(state.values["messages"]),
"下一节点: ", state.next)
if len(state.values["messages"]) == 2: # 保存调用工具前的状态
to_replay = stateprint("-" * 80)
print(f"从{to_replay.next}节点开始重新执行, 重放配置: {to_replay.config}")第二次执行: 重放
result2 = agent.invoke(None, config=to_replay.config)
print("-" * 80)print(f"第二次执行历史: 重放后")
查看新的历史记录
for state in agent.get_state_history(config):
print("checkpoint_id: ", state.config["configurable"]["checkpoint_id"],
"消息数: ", len(state.values["messages"]),
"下一节点: ", state.next)result2['messages'][-1].pretty_print()
执行结果如下所示。可以看到历史记录中帮我们记录所有的执行过程,包括重放前和重放后的步骤! 这同时也证明了重放成功。
--------------------------------------------------------------------------------
第一次执行历史:
checkpoint_id: 1f0cf6bc-cb09-61e3-8003-1a74660e9324 消息数: 4 下一节点: ()
checkpoint_id: 1f0cf6bc-b148-624e-8002-f696045e95a8 消息数: 3 下一节点: ('llm_call',)
checkpoint_id: 1f0cf6bc-8a86-62a5-8001-38aa0c3d8d80 消息数: 2 下一节点: ('tool_node',)
checkpoint_id: 1f0cf6bc-7abe-6d9a-8000-ef816c492e36 消息数: 1 下一节点: ('llm_call',)
checkpoint_id: 1f0cf6bc-7abc-6ce5-bfff-5241076b5574 消息数: 0 下一节点: ('__start__',)
--------------------------------------------------------------------------------
从('tool_node',)节点开始重新执行, 重放配置: {'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf6bc-8a86-62a5-8001-38aa0c3d8d80'}}
--------------------------------------------------------------------------------
第二次执行历史: 重放后
checkpoint_id: 1f0cf6bd-0e30-6cf9-8003-f22545cde7b2 消息数: 4 下一节点: ()
checkpoint_id: 1f0cf6bc-e376-6e52-8002-9c66bdb5577a 消息数: 3 下一节点: ('llm_call',)
checkpoint_id: 1f0cf6bc-cb09-61e3-8003-1a74660e9324 消息数: 4 下一节点: ()
checkpoint_id: 1f0cf6bc-b148-624e-8002-f696045e95a8 消息数: 3 下一节点: ('llm_call',)
checkpoint_id: 1f0cf6bc-8a86-62a5-8001-38aa0c3d8d80 消息数: 2 下一节点: ('tool_node',)
checkpoint_id: 1f0cf6bc-7abe-6d9a-8000-ef816c492e36 消息数: 1 下一节点: ('llm_call',)
checkpoint_id: 1f0cf6bc-7abc-6ce5-bfff-5241076b5574 消息数: 0 下一节点: ('__start__',)
================================ Ai Message =================================
今天西安的天气情况如下:
- **天气**: 晴
- **气温**: 最高温度约为 13°C,最低温度约为 2°C
- **风速**: 东北风,约 6 英里/小时
- **空气质量**: 不健康
重放功能实际应用为时间旅行,详见下文【时间旅行(Time Travel)】篇章。
2.3.3.4 更新状态_ update_state()方法
我们还可以编辑图状态。我们使用 update_state()方法来做到这一点。
- 让我们更新用户的输入,换成其他搜索内容:
- 先执行一次完整的流程,获取一次完整历史记录;
- 保存第一次调用LLM前的步骤快照,修改用户输入来更新快照,并重新执行更新后快照步骤;
编辑 / 修改图的状态,基于历史快照生成新的状态分支,是 LangGraph 实现回溯修改、分支试错的关键 API。 搭配 Overwrite 可以覆盖替换状态字段(不是追加)。
config = {"configurable": {"thread_id": "1"}}
# 第一次执行
result1 = agent.invoke(
{"messages": [HumanMessage(content="今天西安的天气如何?")]},
config
)
# 找到调用LLM前的步骤
print("-" * 80)
print(f"第一次执行历史: ")
selected_state = None
for state in agent.get_state_history(config):
print("checkpoint_id: ", state.config["configurable"]["checkpoint_id"],
"消息数: ", len(state.values["messages"]),
"下一节点: ", state.next)
if len(state.values["messages"]) == 1: # 此时消息数为1;下一节点是'llm_call'
selected_state = state
print("-" * 80)
print(f"更新前配置: {selected_state.config}")
# 根据指定的config,更新对于步骤的值
# 更新用户输入
new_config = agent.update_state(
selected_state.config,
{"messages": Overwrite([HumanMessage(content="今天北京的天气如何?")])} # 清空消息,重新写入
)
print("-" * 80)
print(f"更新后配置: {new_config}")
# 第二次执行: 重放更新后的配置
result2 = agent.invoke(None, config=new_config)
for message in result2['messages']:
message.pretty_print()
# 输出
--------------------------------------------------------------------------------
第一次执行历史:
checkpoint_id: 1f0cf6c8-743c-6b8d-8003-d8a1130c15e0 消息数: 4 下一节点: ()
checkpoint_id: 1f0cf6c8-4244-682c-8002-bdc8b86d460d 消息数: 3 下一节点: ('llm_call',)
checkpoint_id: 1f0cf6c8-3304-6782-8001-7ebc967b7bb1 消息数: 2 下一节点: ('tool_node',)
checkpoint_id: 1f0cf6c8-1b1c-62a0-8000-42b9d34bb01d 消息数: 1 下一节点: ('llm_call',)
checkpoint_id: 1f0cf6c8-1b19-6adb-bfff-915f44142490 消息数: 0 下一节点: ('__start__',)
--------------------------------------------------------------------------------
更新前配置: {'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf6c8-1b1c-62a0-8000-42b9d34bb01d'}}
--------------------------------------------------------------------------------
更新后配置: {'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1f0cf6c8-7444-61e3-8001-d9eb91c95052'}}
================================ Human Message =================================
今天北京的天气如何?
================================ Ai Message =================================
Tool Calls:
tavily_search (call_FfUFDwThpNTBHoijgvNmwtMr)
Call ID: call_FfUFDwThpNTBHoijgvNmwtMr
Args:
query: 北京天气
================================ Tool Message =================================
(省略...)
================================ Ai Message =================================
今天北京的天气情况如下:
- 当前气温: -4°C
- 湿度: 23%
- 风速: 25.2 km/h
- 天气状况: 晴朗
- 最高气温: 预计为16°C
