检查点
检查点(Checkpoint)
- 作用:检查点是图执行过程中每个“超步骤”(superstep)时刻状态的快照。它使得以下功能成为可能:
- 状态恢复
- 重播执行
- 容错与断点续跑
- 人机交互与内存更新
- 自动持久化:使用 LangGraph 时,无需手动保存状态,它会自动在后台处理所有检查点
1 | from langgraph.checkpoint.memory import InMemorySaver |
检查点会自动在每个节点执行后保存当前状态,包括:
- 当前节点名 :
state.next - 当前值 :
state.values - 当前执行上下文 :
state.config
线程(Thread)
- 每次运行图时,都必须指定一个唯一的
thread_id。 - 一个 thread 保存该次执行过程的所有检查点状态。
1 | config = {"configurable": {"thread_id": "1"}} |
- 可使用
graph.get_state(config)获取最新状态。 - 可使用
graph.get_state_history(config)获取全部历史状态。
Langgraph 中使用检查点(示例代码)
1 | from langgraph.checkpoint.memory import InMemorySaver |
agent 中使用检查点(示例代码)
1 | from langgraph.checkpoint.memory import InMemorySaver |
持久化检查点
| 实现名称 | 说明 |
|---|---|
InMemorySaver |
内存持久化 |
MongoDBSaver |
使用 mongodb 持久化 |
RedisSaver |
使用 Redis持久化 |
使用 redis 存储检查点
安装依赖
1
pip install langgraph-checkpoint-redis
使用案例
1
2
3
4from langgraph.checkpoint.redis import RedisSaver
with RedisSaver.from_conn_string("redis://192.168.0.108:6379") as checkpointer:
print(checkpointer)
使用 mongodb存储检查点
安装依赖
1
pip install langgraph-checkpoint-mongodb
使用案例
1
2
3
4from langgraph.checkpoint.mongodb import MongoDBSaver
with MongoDBSaver.from_conn_string("localhost:27017") as checkpointer:
print("checkpointer:", checkpointer)
重运行机制
在使用由大模型驱动的非确定性系统(例如 Agent)时,我们常常希望深入理解其决策过程。LangGraph 提供了 时间旅行功能(Time Travel) 来支持以下用途:
- 🤔 理解推理过程:分析系统如何得出当前结果。
- 🐞 调试错误:找出错误发生的位置和原因。
- 🔍 探索备选路径:尝试不同的执行分支,寻找更优结果。
核心功能是:可以从某个历史检查点(checkpoint)恢复执行,你可以选择重放旧状态,或修改状态后探索新路径。每次恢复执行都会在执行历史中生成一个新的分支。
获取历史检查点
1 | print("===============获取执行的历史检查点===================") |
选中某个重放的检查点进行更新
1 | print("========================重放的检查点进行更新===========================") |
从检查点恢复执行
注意:重放是输入的值直接传入 None 即可,然后传入更新后的检查点配置
1 | print("================重新执行===================") |
完整示例代码
1 | from langgraph.constants import START, END |
人工干预机制
LangGraph 支持强大的人工参与循环(HIL)工作流,允许在自动化过程中的任何环节进行人工干预。这在大型语言模型(LLM)驱动的应用程序中尤其有用,因为模型输出可能需要验证、更正或额外的上下文。
主要功能
- 持久化执行状态:LangGraph 在每个步骤后都会检查图状态,允许在定义好的节点处无限期地暂停执行。这支持异步的人工审查或输入,不受时间限制。
- 灵活的集成点:HIL 逻辑可以在工作流的任何点引入。这允许有针对性的人工参与,例如批准 API 调用、更正输出或引导对话
使用场景:
- 审查工具调用:在工具执行之前,人工可以审查、编辑或批准 LLM 请求的工具调用。
- 验证 LLM 输出:人工可以审查、编辑或批准 LLM 生成的内容。
- 提供上下文:使 LLM 能够明确请求人工输入以进行澄清或提供额外细节,或支持多轮对话。
核心点:
- interrupt(…) 暂停图执行,并返回需要人工处理的内容
- Command(resume=…) 用于恢复图,并携带人工提供的输入
interrupt 的使用
1 | from langgraph.types import interrupt, Command |
注意:
__interrupt__是执行结果中保存中断上下文的关键字段。中断恢复时,会重新执行节点中从头到 interrupt() 的代码块。
Command 的使用
1 | from langgraph.types import Command |
常见中断场景
审批 / 拒绝
1 | def human_review(state: State): |
根据人工输入,控制图表路径走向
数据修改
1 | def human_editing(state: State): |
适用人类对 LLM 输出进行修改。
信息补充
1 | def request_input(state: State): |
实战案例
强调在AI系统中加入人工审核环节以提升系统成熟度和稳定性,特别是在对错误容忍度低的场景下,通过人工审批、状态编辑、工具调用审查等方式保障决策正确性与系统安全。
- 审查工具调用情况(是否正确等)
- 审查和验证LLM输出
- 人工提供更好的上下文背景
人机交互的场景:

基本运用:等待用户数据
等待用户输入的本质是在节点间增加人类反馈节点,定义包含input和user feedback属性的状态对象,引入interrupt组件来打断流程并等待用户反馈,通过Command组件恢复被打断的流程
示例代码
1 | from typing_extensions import TypedDict |
在智能体中引入人工介入环节,示例代码如下:
1 | import os |
执行查看节点与图结构

执行结果
1 | ---Step 1--- |
基本运用:审查工具调用
通过人机协作审查智能体的工具调用,包括设置审查节点、判断人类反馈、执行工具及结果插入,并展示了在不同反馈(继续、更新、反馈)下流程的导航与处理方式。
根据不同的人类反馈(continue、update、feedback)导航至不同节点。
示例代码
1 | from typing_extensions import TypedDict, Literal |
执行查看节点与图结构

当不涉及工具调用的时候,不会触发人工审核
1
2
3
4
5
6
7
8
9# Input
initial_input = {"messages": [{"role": "user", "content": "你好!"}]}
# Thread
thread = {"configurable": {"thread_id": "1"}}
for event in graph.stream(initial_input, thread, stream_mode="updates"):
print(event)
print("\n")执行结果
1
{'call_llm': {'messages': [AIMessage(content='你好!很高兴为您服务。我可以帮您查询天气信息,如果您需要了解某个城市的天气情况,请告诉我城市名称,我会为您查询最新的天气信息。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 33, 'prompt_tokens': 144, 'total_tokens': 177, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 144}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_08f168e49b_prod0820_fp8_kvcache', 'id': 'd95943e0-85bc-4aae-a037-393a500e8c7a', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--b490a400-5a86-403d-b459-3d972a6b1474-0', usage_metadata={'input_tokens': 144, 'output_tokens': 33, 'total_tokens': 177, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}}
一旦涉及到工具调用 就会触发人工介入
1
2
3
4
5
6
7
8
9# Input,提问到天气相关的问题
initial_input = {"messages": [{"role": "user", "content": "北京的天气如何?"}]}
# Thread
thread = {"configurable": {"thread_id": "2"}}
for event in graph.stream(initial_input, thread, stream_mode="updates"):
print(event)
print("\n")执行结果
1
2
3
4{'call_llm': {'messages': [AIMessage(content='我来帮您查询北京的天气情况。', additional_kwargs={'tool_calls': [{'id': 'call_00_nrZyXDGPbp73qAdYJ7IpMxHv', 'function': {'arguments': '{"city": "\\u5317\\u4eac"}', 'name': 'weather_search'}, 'type': 'function', 'index': 0}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 146, 'total_tokens': 173, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 18}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_08f168e49b_prod0820_fp8_kvcache', 'id': '03bd6516-9777-4042-b4ed-02ea6d6d004f', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--b4693d60-2881-4ab6-9e96-4670276ec95a-0', tool_calls=[{'name': 'weather_search', 'args': {'city': '北京'}, 'id': 'call_00_nrZyXDGPbp73qAdYJ7IpMxHv', 'type': 'tool_call'}], usage_metadata={'input_tokens': 146, 'output_tokens': 27, 'total_tokens': 173, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}})]}}
{'__interrupt__': (Interrupt(value={'question': '这是正确的吗?', 'tool_call': {'name': 'weather_search', 'args': {'city': '北京'}, 'id': 'call_00_nrZyXDGPbp73qAdYJ7IpMxHv', 'type': 'tool_call'}}, id='183597080f78b79c566b124a34223821'),)}使用Command进行人机交互
1
2
3
4
5
6
7
8
9
10from langgraph.types import Command
for event in graph.stream(
# 输入值
Command(resume={"action": "continue"}),
thread,
stream_mode="updates",
):
print(event)
print("\n")执行结果
1
2
3
4
5
6
7
8
9
10
11
12{'human_review_node': None}
----
正在搜索:北京
----
{'run_tool': {'messages': [{'role': 'tool', 'name': 'weather_search', 'content': '晴朗!', 'tool_call_id': 'call_00_nrZyXDGPbp73qAdYJ7IpMxHv'}]}}
{'call_llm': {'messages': [AIMessage(content='根据查询结果,北京目前的天气是晴朗的!天气状况很好,适合外出活动。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 172, 'total_tokens': 191, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 44}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_08f168e49b_prod0820_fp8_kvcache', 'id': 'cd0943c6-f6d0-4228-aa4e-2be23df3a798', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--d5f3dae7-d3f1-4ee5-98e3-98abc54e7071-0', usage_metadata={'input_tokens': 172, 'output_tokens': 19, 'total_tokens': 191, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}})]}}更进一步,对智能体调用的工具进行参数编辑
1
2
3
4
5
6
7
8
9# Input
initial_input = {"messages": [{"role": "user", "content": "深圳的天气如何?"}]}
# Thread
thread = {"configurable": {"thread_id": "3"}}
for event in graph.stream(initial_input, thread, stream_mode="updates"):
print(event)
print("\n")执行结果
1
2
3
4
5
6{'call_llm': {'messages': [AIMessage(content='我来帮您查询深圳的天气情况。', additional_kwargs={'tool_calls': [{'id': 'call_00_VFXEbMl2mMOo0WXRr3BdKjWP', 'function': {'arguments': '{"city": "\\u6df1\\u5733"}', 'name': 'weather_search'}, 'type': 'function', 'index': 0}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 28, 'prompt_tokens': 147, 'total_tokens': 175, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 19}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_08f168e49b_prod0820_fp8_kvcache', 'id': 'b944f986-9c42-4d99-ac66-1e90ac5d8514', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--2d7d12ff-727e-4649-a735-44aac3346a5b-0', tool_calls=[{'name': 'weather_search', 'args': {'city': '深圳'}, 'id': 'call_00_VFXEbMl2mMOo0WXRr3BdKjWP', 'type': 'tool_call'}], usage_metadata={'input_tokens': 147, 'output_tokens': 28, 'total_tokens': 175, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}})]}}
{'__interrupt__': (Interrupt(value={'question': '这是正确的吗?', 'tool_call': {'name': 'weather_search', 'args': {'city': '深圳'}, 'id': 'call_00_VFXEbMl2mMOo0WXRr3BdKjWP', 'type': 'tool_call'}}, id='0f55d9ce7b22cdd30d7d25afd89223ae'),)}1
2
3
4
5
6
7
8# 直接对工具的参数进行编辑
for event in graph.stream(
Command(resume={"action": "update", "data": {"city": "上海,中国"}}),
thread,
stream_mode="updates",
):
print(event)
print("\n")执行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{'human_review_node': {'messages': [{'role': 'ai', 'content': '我来帮您查询深圳的天气情况。', 'tool_calls': [{'id': 'call_00_VFXEbMl2mMOo0WXRr3BdKjWP', 'name': 'weather_search', 'args': {'city': '上海,中国'}}], 'id': 'run--2d7d12ff-727e-4649-a735-44aac3346a5b-0'}]}}
----
正在搜索:上海,中国
----
{'run_tool': {'messages': [{'role': 'tool', 'name': 'weather_search', 'content': '晴朗!', 'tool_call_id': 'call_00_VFXEbMl2mMOo0WXRr3BdKjWP'}]}}
{'call_llm': {'messages': [AIMessage(content='让我重新查询深圳的天气:', additional_kwargs={'tool_calls': [{'id': 'call_00_eY6qquS2SjREeAhv2oD2Q9hL', 'function': {'arguments': '{"city": "深圳"}', 'name': 'weather_search'}, 'type': 'function', 'index': 0}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 176, 'total_tokens': 196, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 48}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_08f168e49b_prod0820_fp8_kvcache', 'id': '73984e27-0ca3-421b-9d35-348907eabdf3', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--ae52f621-4c85-4e13-95a4-673939846f23-0', tool_calls=[{'name': 'weather_search', 'args': {'city': '深圳'}, 'id': 'call_00_eY6qquS2SjREeAhv2oD2Q9hL', 'type': 'tool_call'}], usage_metadata={'input_tokens': 176, 'output_tokens': 20, 'total_tokens': 196, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}})]}}
{'__interrupt__': (Interrupt(value={'question': '这是正确的吗?', 'tool_call': {'name': 'weather_search', 'args': {'city': '深圳'}, 'id': 'call_00_eY6qquS2SjREeAhv2oD2Q9hL', 'type': 'tool_call'}}, id='5d83228c73ed5e60fe886c76117d2402'),)}
基本使用:编辑图的状态
通过设置中断点和人工干预,可以在流程执行中修改状态值,实现对智能体工具选择或动作的审核编辑。

示例代码
1 | from typing_extensions import TypedDict |
执行查看节点与图结构

执行结果
1 | {'input': '你好'} |
执行到节点Step 1,就中断了,在代码中使用interrupt_before对Step 2设置了断点,此时我们可以进行人工干预,更新流状态并传入新值
1 | graph.update_state(thread, {"input": "你好 everybody!"}) |
执行结果
1 | --- |
1 | # 继续执行 |
执行结果
1 | {'input': '你好 everybody!'} |