持久化01——线程级持久化

1 什么是持久化能力?

简单来说,在 LangGraph 中持久化能力指的是:将AI应用的状态(如对话历史、中间结果、用户信息等)保存下来,即使程序重启或系统宕机,也能恢复之前的状态,让AI有 "记住之前发生的一切事情" 的能力。

想象一下这个场景:你今天在和智能助手聊天,说了很多重要信息。然后你关闭了应用,明天重新打开并重新与它对话时,当然希望它还记得你说过的话。


再看第二个场景:假设有一个助手,它可以搜索网络。

没有持久化:

  1. 用户问:"今天的天气怎么样?'
  2. 助手调用搜索工具,得到答案:"今天晴天,25度。
  3. 程序崩溃重启。
  4. 用户再问:"那我需要带伞吗?"
  5. 助手没有之前的上下文,它可能又会去调用搜索工具,而不是基于"今天晴天"这个上下文来回答"不需要"

有持久化:

  1. 用户问:"今天的天气怎么样?"
  2. 助手调用搜索工具,得到答案:"今天晴天,25度。"(这个状态,包括对话历史和工具调用结果,被自动保存)
  3. 程序崩溃重启。
  4. 用户再问:"那我需要带伞吗?"(与之前在同一会话下)
  5. LangGraph加载之前保存的状态,状态里记录了"今天晴天"。
  6. 助手看到上下文是晴天,于是直接回答:"今天是晴天,您不需要带伞。(无需再次调用搜索工具)

1.1 持久化能力(线程级、跨会话)

1、【线程级】持久化:能够【自动保存】工作流执行过程中的【状态快照】,维持单次会话的完整上下文。

  • 解释1:这里的【线程持久化】和【操作系统线程】概念完全独立区分。操作系统中线程是进程内的执行单元,是操作系统能够进行调度的最小单位。而线程级持久化表示聊天过程中的单次会话的持久化信息,用来隔离不同的聊天会话。
  • 解释2:这里的【状态快照】并非是之前学习过的 State 的快照。这个状态包含了所有必要的上下文信息,比如:已经调用过哪些工具、用户的输入、聊天历史、下一步要执行的节点等等。
    2、【跨会话】持久化:通过 存储(Store) 保存用户信息、偏好设置等长期数据,实现不同对话间信息的持久化共享。

例如,可以将用户基本情况(有高血压病史)存储,后续无论在何时何地,或无论新开几个会话窗口,都可以基于用户基本信息(有高血压病史)生成结果。


2 线程级持久化

2.1 线程级持久化是怎么工作的?

当我们开始执行工作流,过程中可能会发生崩溃 或重启导致的中断 等异常情况。根据 LangGraph 的持久化机制,线程级持久化表示能够【自动保存】工作流执行过程中的【状态快照】,维持单次会话的完整上下文。当工作流执行到某一步时,它会自动保存当前步骤的状态快照。这个状态包含了所有必要的上下文信息,比如:已经调用过哪些工具、用户的输入、聊天历史、下一步要执行的节点等等。因此,工作流在执行过程中会发生如下图所示的流程:

  • 正常执行:紫色/蓝色节点和实线箭头
  • 异常恢复:红色/绿色节点和虚线箭头
  • 检查点回滚:橙色节点和虚线箭头

线程级持久化机制确保了:

  1. 状态不丢失:即使应用程序崩溃、重启,或者一个长时间的流程被中断,当它恢复时,可以从上次停止的地方继续执行,而不是从头开始。
  2. 支持长时间运行的任务:对于需要与用户进行多轮交互(如多步对话助手)或处理耗时极长的流程(如等待外部API回调),持久化是必不可少的。
  3. 检查点和回滚:我们可以将状态保存到某个时间点(检查点),并在需要时回滚到该状态。

2.2 两个核心概念

LangGraph的持久化机制------线程级持久化是其核心功能,它通过【线程】和 【检查点】这两个核心部分来实现。具体如下:

2.2.1 Threads (线程)

在 LangGraph 中,Thread 代表一个独立的工作流执行会话。可以把它想象成【与某个用户的一次完整对话历史】或【处理某个特定任务的一次完整执行过程】。例如:

Thread 的关键特性:

  1. 隔离性:每个 Thread 都是完全独立的,它们的状态互不干扰
  2. 持久化单元:Thread 是状态持久化的基本单位
  3. 标识符:通过唯一的 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 一样,都是最流行的开源关系型数据库),具备以下显著优势,使其成为持久化的理想选择:

  1. **LangGraph 原生支持:**LangGraph 提供了 PostgresSaver,简化了与 PostgreSQL 的集成过程。
  2. **数据结构化与一致性:**关系型数据库天生适合存储结构化数据。Graph的状态,尤其是消息历史、用户档案、工具使用记录等,都可以很好地映射到表格结构中,确保数据的一致性和完整性。
  3. **可靠性与持久性:**PostgreSQL 提供了事务支持、ACID 特性、数据备份与恢复机制,确保数据的持久性和高可用性,即使系统崩溃也能保证数据不丢失。
  4. **强大的查询能力:**SQL 语言提供了灵活且强大的数据查询能力,方便我们对历史行为、用户数据进行分析、统计和审计。结合 pgvector 等扩展,甚至可以直接在数据库中进行向量相似度搜索,实现更高级的知识管理。
  5. **可扩展性:**通过读写分离、分区、集群等技术,PostgreSQL 可以支持大规模的并发访问和数据存储,满足Agent在生产环境中的性能需求。
  6. **成熟的生态系统:**拥有庞大的社区支持、丰富的工具和成熟的运维经验,降低了开发和维护成本。

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 = state

    print("-" * 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()方法来做到这一点。

  1. 让我们更新用户的输入,换成其他搜索内容:
  2. 先执行一次完整的流程,获取一次完整历史记录;
  3. 保存第一次调用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
相关推荐
CoderYanger1 小时前
Java EE 进阶:5. Spring IoC&DI(第一弹):Spring IoC
java·开发语言·spring boot·spring·面试·职场和发展·java-ee
真上帝的左手1 小时前
2. 编程语言-JavaScript
开发语言·javascript
zhanghaha13141 小时前
C# 系列教程_1 基础语法篇(面向新手系统教程)
开发语言·c#
通问AI2 小时前
【实战】企业口径一致性检查:用 Python 对齐官网/招聘平台/投标文件三个来源的成立日期与注册地
开发语言·python
zyseo82 小时前
谷歌SEO 内容更新焕新流程:让老页面重新起量
java·前端·javascript
老王IT2 小时前
Go开发环境搭建(window操作系统)
开发语言·后端·golang
学逆向的2 小时前
C++ 匿名命名空间
开发语言·c++·网络安全
萧瑟余晖2 小时前
Spring Data JDBC与R2DBC详解
java·后端·spring
杰克尼2 小时前
今日面试题总结
java·开发语言·人工智能·langchain