文章目录
- 一、中断
-
- [1. 动态中断](#1. 动态中断)
-
- [1.1. 概述](#1.1. 概述)
- [1.2. 启用中断](#1.2. 启用中断)
- [3. 恢复中断](#3. 恢复中断)
- [1.4. 常见使用模式](#1.4. 常见使用模式)
-
- [1.4.1. 基础 HITL 模式](#1.4.1. 基础 HITL 模式)
- [1.4.2. 多个并行中断](#1.4.2. 多个并行中断)
- [1.4.3. 审批模式](#1.4.3. 审批模式)
- [1.4.4. 审核与编辑模式](#1.4.4. 审核与编辑模式)
- [1.4.5. 工具执行审批模式](#1.4.5. 工具执行审批模式)
- [1.4.6. 单节点串行中断模式](#1.4.6. 单节点串行中断模式)
- [1.5. 使用规范](#1.5. 使用规范)
-
- [1.5.1. 不要用 `try/except` 包裹 `interrupt()` 调用](#1.5.1. 不要用
try/except包裹interrupt()调用) - [1.5.2. 不要更改单个节点内 `interrupt` 的调用顺序](#1.5.2. 不要更改单个节点内
interrupt的调用顺序) - [1.5.3. 不要在 `interrupt()` 中传递复杂类型](#1.5.3. 不要在
interrupt()中传递复杂类型) - [1.5.4. 断点之前的副作用操作必须是幂等的](#1.5.4. 断点之前的副作用操作必须是幂等的)
- [1.5.1. 不要用 `try/except` 包裹 `interrupt()` 调用](#1.5.1. 不要用
- [1.6. 中断触发和恢复前后的检查点](#1.6. 中断触发和恢复前后的检查点)
- [2. 静态断点](#2. 静态断点)
-
- [2.1. 用法说明](#2.1. 用法说明)
- [2.2. 底层原理](#2.2. 底层原理)
- [2.3. 示例](#2.3. 示例)
-
- [2.3.1. 编译时设置断点](#2.3.1. 编译时设置断点)
- [2.3.2. 调用时设置断点](#2.3.2. 调用时设置断点)
- 二、项目部署
-
- [1. 本地部署并对接 LangSmith](#1. 本地部署并对接 LangSmith)
-
- [1.1. 补充依赖](#1.1. 补充依赖)
- [1.2. 准备本地项目](#1.2. 准备本地项目)
-
- [1.2.1. 项目结构](#1.2.1. 项目结构)
- [1.2.2. 代码清单](#1.2.2. 代码清单)
- [1.3. 启动项目](#1.3. 启动项目)
- [1.4. 调试](#1.4. 调试)
- [1.5. 调试时添加静态断点](#1.5. 调试时添加静态断点)
- [2. 本地部署并对接 AgentChatUI](#2. 本地部署并对接 AgentChatUI)
-
- [2.1. 准备文件](#2.1. 准备文件)
-
- [2.1.1. `chat_agent.py`](#2.1.1.
chat_agent.py) - [2.1.2. 更改 `langgraph.json`](#2.1.2. 更改
langgraph.json)
- [2.1.1. `chat_agent.py`](#2.1.1.
- [2.2. 对接 AgentChatUI](#2.2. 对接 AgentChatUI)
-
- [2.2.1. 重启本地服务](#2.2.1. 重启本地服务)
- [2.2.2. 访问云服务](#2.2.2. 访问云服务)
- [2.3. 测试](#2.3. 测试)
-
- [2.3.1. 对话](#2.3.1. 对话)
- [2.3.2. 工具调用](#2.3.2. 工具调用)
- [2.3.3. 查看历史记录](#2.3.3. 查看历史记录)
- 三、工具调用节点
-
- [1. 工具节点的实现](#1. 工具节点的实现)
-
- [1.1. 手动处理工具调用](#1.1. 手动处理工具调用)
- [1.2. 使用 ToolNode 处理工具调用](#1.2. 使用 ToolNode 处理工具调用)
- [2. 进阶用法](#2. 进阶用法)
-
- [2.1. ToolRuntime 介绍](#2.1. ToolRuntime 介绍)
- [2.2. 在工具中更新状态](#2.2. 在工具中更新状态)
- [2.3. 工具节点执行与容错机制](#2.3. 工具节点执行与容错机制)
-
- [2.3.1. 实现重试机制](#2.3.1. 实现重试机制)
- [2.3.2. 实现缓存机制](#2.3.2. 实现缓存机制)
- 结语
LangGraph 的价值,不只是把几个 LLM 节点连成一条链,而是把 Agent 的状态、分支、暂停和恢复都变成可观察、可持久化的流程。
一、中断
1. 动态中断
1.1. 概述
LangGraph 提供动态中断和静态断点两种机制。
- 动态中断在节点中调用
interrupt(),可以根据业务条件触发,适合人工审批、内容审核和补充信息。 - 静态断点通过
interrupt_before或interrupt_after设置,适合调试和观察节点执行过程。
动态中断触发后,图会把当前状态写入 checkpointer,并返回一个 Interrupt 对象。调用方收集人工输入,再通过 Command(resume=...) 恢复图的执行。
1.2. 启用中断
要让中断能够恢复,必须同时满足三个条件:配置 checkpointer、提供 thread_id、在节点内调用 interrupt()。
python
from typing import TypedDict
from dotenv import load_dotenv
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import interrupt
load_dotenv(override=True)
class OverallState(TypedDict, total=False):
username: str
def ask_name(state: OverallState) -> dict:
username = interrupt({"question": "请输入您的姓名"})
return {"username": username}
builder = StateGraph(OverallState)
builder.add_node("ask_name", ask_name)
builder.add_edge(START, "ask_name")
builder.add_edge("ask_name", END)
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "user-001"}}
interrupt() 的参数应当是字符串、数字、列表或字典等可 JSON 序列化的数据。建议把前端需要的字段直接组织成字典,例如 question、options 和 context。
3. 恢复中断
第一次调用会暂停在 interrupt():
python
paused = graph.invoke({}, config=config)
item = paused["__interrupt__"][0]
print(item.value)
拿到人工输入后,使用相同的配置恢复:
python
from langgraph.types import Command
finished = graph.invoke(
Command(resume="小黄"),
config=config,
)
print(finished["username"])
恢复时节点函数会从头重新执行,而不是从 interrupt() 所在的那一行继续。因此中断前的写库、扣款、发消息等副作用必须幂等,或者拆分到中断之后的独立节点中。
1.4. 常见使用模式
1.4.1. 基础 HITL 模式
HITL(Human In The Loop)指人在 Agent 执行过程中提供输入、修改或审批。最小模式就是"提问 -> 暂停 -> 恢复":
python
def collect_address(state: OverallState) -> dict:
address = interrupt({
"question": "请输入收货地址",
"required": True,
})
return {"address": address}
中断数据负责描述问题,恢复数据负责表达人的决定。两者不要混在状态字段中,便于前端和服务端分别处理。
1.4.2. 多个并行中断
多个并行节点可以同时暂停。恢复时按中断 ID 传入映射:
python
resume_map = {
item.id: get_human_answer(item.value)
for item in paused["__interrupt__"]
}
graph.invoke(Command(resume=resume_map), config=config)
不要依赖 __interrupt__ 列表的顺序,因为并行节点完成的先后顺序并不固定。
1.4.3. 审批模式
审批节点可以同时更新状态并决定下一跳,此时使用 Command(goto=...):
python
from typing import Literal
from langgraph.types import Command, interrupt
def approval_node(
state: OverallState,
) -> Command[Literal["approved", "rejected"]]:
approved = interrupt({"question": "是否批准本次操作?"})
return Command(
goto="approved" if approved else "rejected",
update={"approved": bool(approved)},
)
使用 Command 路由的节点不要再挂普通下游边,避免同一节点存在两套路由逻辑。
1.4.4. 审核与编辑模式
审核节点可以把模型生成的内容交给人修改,再将修改后的内容写回状态:
python
def review_poem(state: OverallState) -> dict:
edited = interrupt({
"instruction": "请审核并修改内容",
"content": state["draft"],
})
return {"draft": edited}
恢复值可以是修改后的完整文本,也可以是包含 action 和 content 的结构化字典。
1.4.5. 工具执行审批模式
工具调用审批通常放在 ToolNode 前面。模型先产生 tool_calls,审批节点展示工具名和参数,人同意后才进入工具节点:
python
def approve_tool(state) -> Command[Literal["tools", "reject_tool"]]:
call = state["messages"][-1].tool_calls[0]
decision = interrupt({
"tool": call["name"],
"args": call["args"],
"question": "是否允许执行?",
})
return Command(goto="tools" if decision == "approve" else "reject_tool")
1.4.6. 单节点串行中断模式
同一个节点可以多次调用 interrupt(),例如依次收集姓名、年龄和性别:
python
def collect_profile(state: OverallState) -> dict:
username = interrupt("请输入姓名")
age = interrupt("请输入年龄")
return {"username": username, "age": age}
恢复时必须保持相同的调用顺序。更复杂的表单建议拆成多个节点,让每个节点只负责一个稳定的中断。
1.5. 使用规范
1.5.1. 不要用 try/except 包裹 interrupt() 调用
interrupt() 依靠运行时的内部信号暂停图。捕获这个信号会让运行时无法识别中断。输入校验可以放在恢复之后,校验失败时再次调用 interrupt()。
1.5.2. 不要更改单个节点内 interrupt 的调用顺序
恢复值按调用顺序消费。动态改变中断数量或顺序,会让恢复值对应到错误的问题。
1.5.3. 不要在 interrupt() 中传递复杂类型
传递给前端的数据应当可以被 JSON 序列化。数据库连接、函数、生成器和自定义实例都不适合作为中断值。
1.5.4. 断点之前的副作用操作必须是幂等的
中断恢复会重跑节点。写入使用幂等键或 upsert,支付、发信等不可重复操作则应放在恢复后的节点执行。
1.6. 中断触发和恢复前后的检查点
InMemorySaver 适合 Notebook、单进程脚本和测试:
python
from langgraph.checkpoint.memory import InMemorySaver
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "thread-001"}}
实例重建后历史会消失。需要跨进程或生产持久化时,应使用数据库型 checkpointer,并继续复用稳定的 thread_id。检查点保存的是图状态,不是外部系统的副作用,因此业务操作仍需要自行保证幂等。
2. 静态断点
2.1. 用法说明
静态断点适合调试节点执行前后的状态,不承载业务输入:
python
graph = builder.compile(
checkpointer=InMemorySaver(),
interrupt_before=["ask_name"],
)
也可以在调用时设置:
python
result = graph.invoke(
{},
config=config,
interrupt_after=["ask_name"],
)
2.2. 底层原理
静态断点发生在节点边界,运行时保存当前快照并暂停。恢复时输入传 None,并保持相同的 thread_id 和断点配置。业务审批应使用动态 interrupt(),因为动态中断可以携带问题、选项和上下文。
2.3. 示例
2.3.1. 编译时设置断点
python
debug_graph = builder.compile(
checkpointer=InMemorySaver(),
interrupt_before=["tools"],
)
debug_graph.invoke(input_data, config=config)
2.3.2. 调用时设置断点
python
debug_graph.invoke(
input_data,
config=config,
interrupt_after=["llm"],
)
debug_graph.invoke(
None,
config=config,
interrupt_after=["llm"],
)
二、项目部署
1. 本地部署并对接 LangSmith
1.1. 补充依赖
powershell
pip install "langgraph-cli[inmem]==0.4.30"
1.2. 准备本地项目
1.2.1. 项目结构
text
hitl-demo/
├─ src/
│ ├─ __init__.py
│ └─ agent.py
├─ .env
└─ langgraph.json
1.2.2. 代码清单
src/agent.py 暴露名为 graph 的对象:
python
from dotenv import load_dotenv
load_dotenv(override=True)
# build_graph 来自前文
graph = build_graph().compile()
使用 langgraph dev 时不要在这里传入 checkpointer,本地 Agent Server 会负责管理检查点。
langgraph.json:
json
{
"dependencies": ["."],
"graphs": {
"weather_agent": "./src/agent.py:graph"
},
"env": ".env"
}
.env 可以加入 LangSmith 追踪配置:
properties
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=your-langsmith-key
LANGSMITH_PROJECT=langgraph-weather-agent
1.3. 启动项目
powershell
$env:PYTHONUTF8 = "1"
langgraph dev
启动后可以通过 Studio 查看图结构、输入状态和每个节点的执行记录:
https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
1.4. 调试
在 Studio 中创建线程,提交用户消息。图运行到 interrupt() 时会显示工具名称和参数,提交 approve 或 reject 后继续执行。每个线程对应一个 thread_id,因此可以多次打开同一线程查看历史状态。
1.5. 调试时添加静态断点
需要观察工具执行前的消息时,可以临时将图编译为:
python
graph = build_graph().compile(interrupt_before=["tools"])
调试结束后移除静态断点,业务审批仍然保留在动态 interrupt() 中。
2. 本地部署并对接 AgentChatUI
2.1. 准备文件
2.1.1. chat_agent.py
前端只需要消费图返回的 messages 和 __interrupt__ 字段。中断值建议使用稳定的 JSON 结构,便于 UI 渲染审批按钮。
2.1.2. 更改 langgraph.json
如果对话入口变量名为 graph,配置保持为:
json
{
"graphs": {
"chat_agent": "./src/chat_agent.py:graph"
},
"env": ".env"
}
2.2. 对接 AgentChatUI
2.2.1. 重启本地服务
修改 langgraph.json 或 Python 文件后,重启 langgraph dev,让服务重新加载图定义。
2.2.2. 访问云服务
将本地服务地址配置到 AgentChatUI 的后端入口,前端发送用户消息,后端返回 messages;出现 __interrupt__ 时渲染审批控件,再用 Command(resume=...) 提交结果。
2.3. 测试
2.3.1. 对话
发送"北京天气怎么样",确认模型先产生工具调用,随后出现人工审批。
2.3.2. 工具调用
点击批准后,确认 ToolNode 执行工具,并且消息历史中存在匹配的 ToolMessage。
2.3.3. 查看历史记录
刷新页面后复用原线程,确认历史消息和中断状态仍然可以读取。生产环境需要使用持久化 checkpointer,不要依赖进程内存。
三、工具调用节点
1. 工具节点的实现
1.1. 手动处理工具调用
手动实现工具节点有助于理解底层协议:读取最后一条 AIMessage 的 tool_calls,查找工具并执行,再构造 ToolMessage。
python
from langchain.messages import ToolMessage
def manual_tool_node(state: MessagesState) -> dict:
call = state["messages"][-1].tool_calls[0]
result = get_weather.invoke(call["args"])
return {
"messages": [
ToolMessage(content=result, tool_call_id=call["id"])
]
}
这个写法适合教学和极简实验。真实项目还需要处理无效工具名、参数校验、异常、并发调用和 Command 传播。
1.2. 使用 ToolNode 处理工具调用
推荐将手写节点替换为:
python
from langgraph.prebuilt.tool_node import ToolNode
tool_node = ToolNode(tools=[get_weather])
ToolNode 封装了标准工具执行流程,也支持多个工具调用。只要模型绑定了同一组工具,工具名和参数就能自动匹配。
2. 进阶用法
2.1. ToolRuntime 介绍
需要访问状态、上下文、tool_call_id 或流式写出器时,可以声明 runtime: ToolRuntime:
python
from langgraph.prebuilt.tool_node import ToolRuntime
@tool(parse_docstring=True)
def get_user_city(runtime: ToolRuntime) -> str:
"""读取当前用户所在城市。"""
return runtime.state.get("city", "北京")
ToolRuntime 由 ToolNode 自动注入。它与普通节点使用的 Runtime 不是同一个类型。
2.2. 在工具中更新状态
工具除了返回普通文本,也可以返回 Command(update=...)。此时要把对应的 ToolMessage 一起写入消息字段:
python
from langchain.messages import ToolMessage
from langgraph.types import Command
@tool(parse_docstring=True)
def save_weather(city: str, runtime: ToolRuntime) -> Command:
"""查询天气并保存到状态。"""
result = f"{city}:晴,25°C"
return Command(
update={
"weather": result,
"messages": [
ToolMessage(
content=result,
tool_call_id=runtime.tool_call_id,
)
],
}
)
2.3. 工具节点执行与容错机制
工具属于外部系统边界,网络波动和限流都很常见。建议把重试、超时和缓存放在工具层或 ToolNode 的包装器中,而不是让模型无限重复调用。
2.3.1. 实现重试机制
python
def wrap_tool_call(request, execute):
last_error = None
for _ in range(3):
try:
return execute(request)
except TimeoutError as exc:
last_error = exc
raise last_error
tool_node = ToolNode(tools, wrap_tool_call=wrap_tool_call)
只对可重试的异常进行重试。扣款、发货等有副作用的工具必须通过业务幂等键防止重复执行。
2.3.2. 实现缓存机制
天气、汇率等短时间内变化不大的查询可以缓存;缓存键至少应包含用户权限、城市和数据版本。带副作用的工具不要直接缓存执行结果。
结语
中断让 Agent 在关键节点停下来,工具节点让 Agent 获得真实世界的执行能力,部署服务则把这条流程交给可视化和持久化系统管理。三者组合起来,LangGraph 才不仅是一个调用模型的库,而是一套可以进入业务系统的 Agent 编排框架。