中断允许你在特定节点暂停图的执行,并在继续之前等待外部输入。这实现了需要外部输入才能继续的人工介入(human-in-the-loop)模式。当中断触发时,LangGraph 会利用其持久化 层保存图状态,然后无限期等待,直到你恢复执行。
中断通过在图节点的任意位置调用 interrupt() 函数来工作。该函数接受任何可 JSON 序列化的值,该值会暴露给调用者。当你准备好继续时,通过使用 Command 重新调用图来恢复执行,该 Command 会成为节点内部 interrupt() 调用的返回值。
与静态断点(在特定节点前后暂停)不同,中断是动态的 :它们可以放在代码的任何位置,并且可以根据你的应用逻辑设置条件。
检查点会保留你的位置: 检查点记录器会写入精确的图状态,以便你稍后恢复,即使处于错误状态也是如此。
thread_id 是你的指针: 设置 config={"configurable": {"thread_id": ...}} 来告诉检查点记录器加载哪个状态。
中断的有效负载通过 stream.interrupts 暴露: 当使用事件流 (graph.stream_events(..., version="v3")) 时,你传递给 interrupt() 的值会出现在 stream.interrupts 上,并且当运行因等待输入而暂停时,stream.interrupted 为 True。
你选择的 thread_id 实际上就是你持久化的游标。重用该 ID 会恢复同一个检查点;使用新值则会启动一个全新的线程,状态为空。
使用 interrupt 暂停
interrupt 函数会暂停图的执行并将一个值返回给调用者。当你在节点内调用 interrupt 时,LangGraph 会保存当前的图状态并等待你使用输入来恢复执行。
要使用 interrupt ,你需要:
一个检查点记录器 来持久化图状态(生产环境中应使用持久化的检查点记录器)
在配置中提供一个线程 ID ,以便运行时知道从哪个状态恢复
在你希望暂停的位置调用 interrupt()(有效负载必须是 JSON 可序列化的)
from langgraph . types import interrupt
def approval_node ( state : State ):
# 暂停并请求批准
approved = interrupt ( "你批准这个操作吗?" )
# 当你恢复时,Command(resume=...) 会在此处返回该值
return { "approved" : approved }
当你调用 interrupt 时,会发生以下情况:
图的执行在调用 interrupt 的准确位置被挂起
使用检查点记录器保存状态 ,以便稍后恢复执行。在生产环境中,这应当是一个持久化的检查点记录器(例如由数据库支持)
值被返回 给调用者:当使用事件流 (graph.stream_events(..., version="v3")) 时,它会出现在 stream.interrupts 上;若使用默认的 invoke() API,则会在 __interrupt__ 下返回;它可以是任何 JSON 可序列化的值(字符串、对象、数组等)
图无限期等待 ,直到你使用响应恢复执行
当你恢复时,响应会传回 节点,成为 interrupt() 调用的返回值
恢复中断
中断暂停执行后,你可以通过使用包含恢复值的 Command 再次调用图来恢复它。这个恢复值会被传递回 interrupt 调用,使得节点能够使用外部输入继续执行。
推荐使用事件流 来驱动可能中断的图——它会通过 stream.interrupts 和 stream.interrupted 暴露中断,并通过 stream.output 暴露最终状态。
from langgraph . types import Command
# Initial run - hits the interrupt and pauses
# thread_id is the persistent pointer (stores a stable ID in production)
config = { "configurable" : { "thread_id" : "thread-1" }}
stream = graph . stream_events ({ "input" : "data" }, config = config , version = "v3" )
# Drain the stream to drive the run; stream.output awaits the final state.
final = stream . output
# stream.interrupted is True when the run paused for human input, and
# stream.interrupts contains the payloads passed to interrupt().
if stream . interrupted :
print ( stream . interrupts )
# > (Interrupt(value='Do you approve this action?'),)
# Resume with the human's response
# The resume payload becomes the return value of interrupt() inside the node
resumed = graph . stream_events ( Command ( resume = True ), config = config , version = "v3" )
final = resumed . output
默认的 graph.invoke(...) API 仍然有效,并且在 result["__interrupt__"] 下暴露中断。当你不需要流式投影时可以使用它;否则,建议使用 graph.stream_events(..., version="v3")。
关于恢复的关键点:
恢复时,你必须使用与触发中断时相同的线程 ID
传递给 Command(resume=...) 的值会成为 interrupt 调用的返回值
恢复时,节点会从调用 interrupt 的节点开头重新开始 执行,因此 interrupt 之前的任何代码都会再次运行
你可以将任何 JSON 可序列化的值作为恢复值传递
Command(resume=...) 是唯一 设计用作 invoke()/stream()/stream_events() 输入的 Command 模式。其他 Command 参数(update、goto、graph)是设计用于从节点函数返回 的。不要将 Command(update=...) 作为输入传递给多轮对话——请传递一个普通输入字典。
常见模式
中断所解锁的核心能力是能够暂停执行并等待外部输入。这对于多种用例都很有用,包括:
审批工作流 :在执行关键操作(API 调用、数据库更改、金融交易)前暂停
处理多个中断 :在单次调用中恢复多个中断时,将中断 ID 与恢复值配对
审查与编辑 :在继续之前,让人类审查并修改 LLM 输出或工具调用
在工具中中断 :在执行工具调用前暂停,以便在执行前审查并编辑工具调用
验证人类输入 :在进入下一步前暂停,以验证人类输入
使用人工介入(HITL)中断进行流式处理
在构建具有人工介入工作流的交互式智能体时,你可以使用事件流 在循环中并发消费消息块和状态快照,同时处理中断。
在循环中使用 graph.stream_events(..., version="v3") 返回的类型化投影,直到运行完成:
通过 stream.messages 逐令牌地流式传输 AI 响应
通过 stream.values 观察每一步的状态快照
通过 stream.interrupted 检测中断,并从 stream.interrupts 读取其有效负载
通过再次调用 stream_events 并传入 Command(resume=...) 来恢复执行,并重复此过程直到 stream.interrupted 为 false
from langgraph . types import Command
stream_input : dict | Command = initial_input
while True :
stream = graph . stream_events ( stream_input , config = config , version = "v3" )
# Stream LLM message chunks (including any in subgraphs) as they arrive.
for message in stream . messages :
for token in message . text :
display_streaming_content ( token )
# After the run finishes (or pauses), check for interrupts and resume.
if not stream . interrupted :
final_state = stream . output
break
interrupt_info = stream . interrupts [ 0 ]. value
user_response = get_user_input ( interrupt_info )
stream_input = Command ( resume = user_response )
stream.messages : 以内容块形式输出的聊天模型输出;迭代每个 message.text 以获取令牌差异。对于嵌套子图,从 stream.subgraphs[*].messages 读取消息块。
stream.values : 每一步之后完整的状态快照
stream.interrupted / stream.interrupts : 每次运行后,检查图是否暂停;从 stream.interrupts 读取有效负载
Command(resume=...) : 作为下一个 stream_events 输入传递以恢复执行;循环直到运行完成且不再中断
处理多个中断
当并行分支同时中断时(例如,扇出到多个节点,每个节点都调用 interrupt()),你可能需要在一次调用中恢复多个中断。
在单次调用中恢复多个中断时,将每个中断 ID 映射到其恢复值。
这可以确保每个响应在运行时与正确的中断配对。
from typing import Annotated , TypedDict
import operator
from langgraph . checkpoint . memory import InMemorySaver
from langgraph . graph import END , START , StateGraph
from langgraph . types import Command , interrupt
class State ( TypedDict ):
vals : Annotated [ list [ str ], operator . add ]
def node_a ( state ):
answer = interrupt ( "question_a" )
return { "vals" : [ f "a: { answer } " ]}
def node_b ( state ):
answer = interrupt ( "question_b" )
return { "vals" : [ f "b: { answer } " ]}
graph = (
StateGraph ( State )
. add_node ( "a" , node_a )
. add_node ( "b" , node_b )
. add_edge ( START , "a" )
. add_edge ( START , "b" )
. add_edge ( "a" , END )
. add_edge ( "b" , END )
. compile ( checkpointer = InMemorySaver ())
)
config = { "configurable" : { "thread_id" : "1" }}
# Step 1: stream events to drive the run; both parallel nodes hit interrupt() and pause
stream = graph . stream_events ({ "vals" : []}, config , version = "v3" )
_ = stream . output # drive the stream to completion
# stream.interrupts contains the pending Interrupt payloads
print ( stream . interrupts )
# > (Interrupt(value='question_a', id='...'), Interrupt(value='question_b', id='...'))
# Step 2: resume all pending interrupts at once
resume_map = {
i . id : f "answer for { i . value } " for i in stream . interrupts
}
resumed = graph . stream_events ( Command ( resume = resume_map ), config , version = "v3" )
print ( "Final state:" , resumed . output )
# Final state: {'vals': ['a:answer for question_a', 'b:answer for question_b']}
批准或拒绝
中断最常见的用途之一是在关键操作前暂停并请求批准。例如,你可能希望让人类批准 API 调用、数据库更改或任何其他重要决策。
from typing import Literal
from langgraph . types import interrupt , Command
def approval_node ( state : State ) -> Command [ Literal [ " proceed " , " cancel " ]]:
# 暂停执行;有效负载会出现在 stream.interrupts(使用 stream_events 时)或 result["__interrupt__"](使用 invoke 时)
is_approved = interrupt ({
"question" : "是否继续此操作?" ,
"details" : state [ " action_details " ]
})
# 根据响应进行路由
if is_approved :
return Command ( goto = "proceed" ) # 在提供恢复有效负载后执行
else :
return Command ( goto = "cancel" )
恢复图时,传递 True 表示批准,传递 False 表示拒绝:
# 批准
graph . stream_events ( Command ( resume = True ), config = config , version = "v3" ). output
# 拒绝
graph . stream_events ( Command ( resume = False ), config = config , version = "v3" ). output
from typing import Literal , Optional , TypedDict
from langgraph . checkpoint . memory import InMemorySaver
from langgraph . graph import END , START , StateGraph
from langgraph . types import Command , interrupt
class ApprovalState ( TypedDict ):
action_details : str
status : Optional [ Literal [ " pending " , " approved " , " rejected " ]]
def approval_node ( state : ApprovalState ) -> Command [ Literal [ " proceed " , " cancel " ]]:
# Expose details so the caller can render them in a UI
decision = interrupt (
{
"question" : "Approve this action?" ,
"details" : state [ " action_details " ],
}
)
# Route to the appropriate node after resume
return Command ( goto = "proceed" if decision else "cancel" )
def proceed_node ( state : ApprovalState ):
return { "status" : "approved" }
def cancel_node ( state : ApprovalState ):
return { "status" : "rejected" }
builder = StateGraph ( ApprovalState )
builder . add_node ( "approval" , approval_node )
builder . add_node ( "proceed" , proceed_node )
builder . add_node ( "cancel" , cancel_node )
builder . add_edge ( START , "approval" )
builder . add_edge ( "proceed" , END )
builder . add_edge ( "cancel" , END )
# Use a more durable checkpointer in production
checkpointer = InMemorySaver ()
graph = builder . compile ( checkpointer = checkpointer )
config = { "configurable" : { "thread_id" : "approval-123" }}
initial = graph . stream_events (
{ "action_details" : "Transfer $500" , "status" : "pending" },
config = config ,
version = "v3" ,
)
_ = initial . output # drive the stream to completion
print ( initial . interrupts ) # -> (Interrupt(value={'question': ..., 'details': ...}),)
# Resume with the decision; True routes to proceed, False to cancel
resumed = graph . stream_events ( Command ( resume = True ), config = config , version = "v3" )
print ( resumed . output [ " status " ])
审查和编辑状态
有时,你希望让人类在继续之前审查并编辑图状态的某一部分。这对于纠正 LLM 输出、添加缺失信息或进行调整非常有用。
from langgraph . types import interrupt
def review_node ( state : State ):
# 暂停并显示当前内容以供审查(有效负载会暴露在 stream.interrupts 中)
edited_content = interrupt ({
"instruction" : "审查并编辑此内容" ,
"content" : state [ " generated_text " ]
})
# 用编辑后的版本更新状态
return { "generated_text" : edited_content }
恢复时,提供编辑后的内容:
graph . stream_events (
Command ( resume = "已编辑和改进的文本" ), # 该值将成为 interrupt() 的返回值
config = config ,
version = "v3" ,
). output
from typing import TypedDict
from langgraph . checkpoint . memory import MemorySaver
from langgraph . graph import END , START , StateGraph
from langgraph . types import Command , interrupt
class ReviewState ( TypedDict ):
generated_text : str
def review_node ( state : ReviewState ):
# Ask a reviewer to edit the generated content
updated = interrupt (
{
"instruction" : "Review and edit this content" ,
"content" : state [ " generated_text " ],
}
)
return { "generated_text" : updated }
builder = StateGraph ( ReviewState )
builder . add_node ( "review" , review_node )
builder . add_edge ( START , "review" )
builder . add_edge ( "review" , END )
checkpointer = MemorySaver ()
graph = builder . compile ( checkpointer = checkpointer )
config = { "configurable" : { "thread_id" : "review-42" }}
initial = graph . stream_events (
{ "generated_text" : "Initial draft" }, config = config , version = "v3"
)
_ = initial . output # drive the stream to completion
print ( initial . interrupts ) # -> (Interrupt(value={'instruction': ..., 'content': ...}),)
# Resume with the edited text from the reviewer
final_state = graph . stream_events (
Command ( resume = "Improved draft after review" ),
config = config ,
version = "v3" ,
)
print ( final_state . output [ " generated_text " ]) # -> "Improved draft after review"
工具中的中断
你也可以直接在工具函数内部放置中断。这使得工具在被调用时自身会暂停以等待批准,并允许在工具调用执行之前进行人工审查和编辑。
首先,定义一个使用 interrupt 的工具:
from langchain . tools import tool
from langgraph . types import interrupt
@tool
def send_email ( to : str , subject : str , body : str ):
"""Send an email to a recipient."""
# 发送前暂停;使用事件流时有效负载会暴露在 stream.interrupts 上
response = interrupt ({
"action" : "send_email" ,
"to" : to ,
"subject" : subject ,
"body" : body ,
"message" : "批准发送这封邮件吗?"
})
if response . get ( "action" ) == "approve" :
# 恢复值可以在执行前覆盖输入
final_to = response . get ( "to" , to )
final_subject = response . get ( "subject" , subject )
final_body = response . get ( "body" , body )
return f "邮件已发送至 { final_to } ,主题为 ' { final_subject } '"
return "用户取消了邮件"
当你希望审批逻辑与工具本身共存,使其在图的不同部分可重用时,这种方法非常有用。LLM 可以自然地调用该工具,并且每当工具被调用时,中断都会暂停执行,允许你批准、编辑或取消该操作。
import sqlite3
import operator
from typing import TypedDict , Annotated , Literal
from langchain . tools import tool
from langchain_anthropic import ChatAnthropic
from langgraph . checkpoint . sqlite import SqliteSaver
from langgraph . graph import StateGraph , START , END
from langgraph . types import Command , interrupt
from langchain . messages import AnyMessage , SystemMessage , ToolMessage
class AgentState ( TypedDict ):
messages : Annotated [ list [ AnyMessage ], operator . add ]
@tool
def send_email ( to : str , subject : str , body : str ):
"""Send an email to a recipient."""
# 发送前暂停;使用事件流时有效负载会暴露在 stream.interrupts 上
response = interrupt ({
"action" : "send_email" ,
"to" : to ,
"subject" : subject ,
"body" : body ,
"message" : "批准发送这封邮件吗?" ,
})
if response . get ( "action" ) == "approve" :
final_to = response . get ( "to" , to )
final_subject = response . get ( "subject" , subject )
final_body = response . get ( "body" , body )
# 实际发送邮件(此处为你的实现)
print ( f "[send_email] to= { final_to } subject= { final_subject } body= { final_body } " )
return f "邮件已发送至 { final_to } "
return "用户取消了邮件"
model = ChatAnthropic ( model = "claude-sonnet-4-6" ). bind_tools ([ send_email ])
tools_by_name = { "send_email" : send_email }
def agent_node ( state : AgentState ):
# LLM 可能决定调用工具;中断会在发送前暂停
result = model . invoke ( state [ " messages " ])
return { "messages" : [ result ]}
def tool_node ( state : AgentState ):
"""执行工具调用"""
result = []
for tool_call in state [ " messages " ][ - 1 ]. tool_calls :
tool = tools_by_name [ tool_call [ " name " ]]
observation = tool . invoke ( tool_call [ " args " ])
result . append ( ToolMessage ( content = observation , tool_call_id = tool_call [ " id " ]))
return { "messages" : result }
def should_continue ( state : AgentState ) -> Literal [ " tool_node " , END ]:
"""根据 LLM 是否进行了工具调用来决定是继续循环还是停止"""
messages = state [ " messages " ]
last_message = messages [ - 1 ]
if last_message . tool_calls :
return "tool_node"
return END
builder = StateGraph ( AgentState )
builder . add_node ( "agent" , agent_node )
builder . add_node ( "tool_node" , tool_node )
builder . add_edge ( START , "agent" )
builder . add_conditional_edges ( "agent" , should_continue , [ "tool_node" , END ]) # 路由到 "tools" 或 END
builder . add_edge ( "tool_node" , "agent" ) # 工具执行后返回
checkpointer = SqliteSaver (
sqlite3 . connect ( "tool-approval.db" , check_same_thread = False )
)
graph = builder . compile ( checkpointer = checkpointer )
config = { "configurable" : { "thread_id" : "email-workflow" }}
initial = graph . stream_events (
{
"messages" : [
{ "role" : "user" , "content" : "发邮件给 alice@example.com,讨论会议事宜" }
]
},
config = config ,
version = "v3" ,
)
initial . output # 驱动流完成
print ( initial . interrupts ) # -> (Interrupt(value={'action': 'send_email', ...}),)
# 恢复时批准并可选择编辑参数
resumed = graph . stream_events (
Command ( resume = { "action" : "approve" , "subject" : "已更新的主题" }),
config = config ,
version = "v3" ,
)
print ( resumed . output [ " messages " ][ - 1 ]) # -> send_email 返回的工具结果
验证人类输入
有时你需要验证人类的输入,如果无效则再次询问。你可以通过在一个循环中使用多个 interrupt 调用来实现这一点。
from langgraph . types import interrupt
def get_age_node ( state : State ):
prompt = "你的年龄是多少?"
while True :
answer = interrupt ( prompt ) # 使用事件流时有效负载会暴露在 stream.interrupts 上
# 验证输入
if isinstance ( answer , int ) and answer > 0 :
# 有效输入 - 继续
break
else :
# 无效输入 - 用更具体的提示再次询问
prompt = f "' { answer } ' 不是一个有效的年龄。请输入一个正数。"
return { "age" : answer }
每次你用无效输入恢复图时,它都会用更清晰的消息再次询问。一旦提供了有效输入,节点就完成,图继续执行。
import sqlite3
from typing import TypedDict
from langgraph . checkpoint . sqlite import SqliteSaver
from langgraph . graph import END , START , StateGraph
from langgraph . types import Command , interrupt
class FormState ( TypedDict ):
age : int | None
def get_age_node ( state : FormState ):
prompt = "What is your age?"
while True :
answer = interrupt ( prompt )
if isinstance ( answer , int ) and answer > 0 :
return { "age" : answer }
prompt = f "' { answer } ' is not a valid age. Please enter a positive number."
builder = StateGraph ( FormState )
builder . add_node ( "collect_age" , get_age_node )
builder . add_edge ( START , "collect_age" )
builder . add_edge ( "collect_age" , END )
checkpointer = SqliteSaver ( sqlite3 . connect ( "forms.db" ))
graph = builder . compile ( checkpointer = checkpointer )
config = { "configurable" : { "thread_id" : "form-1" }}
first = graph . stream_events ({ "age" : None }, config = config , version = "v3" )
_ = first . output # drive the stream to completion
print ( first . interrupts ) # -> (Interrupt(value='What is your age?', ...),)
# Provide invalid data; the node re-prompts
retry = graph . stream_events ( Command ( resume = "thirty" ), config = config , version = "v3" )
_ = retry . output # drive the stream to completion
print ( retry . interrupts ) # -> (Interrupt(value="'thirty' is not a valid age...", ...),)
# Provide valid data; loop exits and state updates
final = graph . stream_events ( Command ( resume = 30 ), config = config , version = "v3" )
print ( final . output [ " age " ]) # -> 30
中断的规则
当你在节点内调用 interrupt 时,LangGraph 会通过抛出一个特殊异常来挂起执行,该异常通知运行时暂停。该异常会沿着调用堆栈向上传播,并被运行时捕获,运行时通知图保存当前状态并等待外部输入。
当执行恢复时(在你提供所需的输入之后),运行时从头重新开始整个节点——它不会从调用 interrupt 的确切行恢复。这意味着在 interrupt 之前运行的任何代码都将再次执行。因此,在使用中断时,需要遵循一些重要的规则,以确保它们按预期工作。
不要将 interrupt 调用包裹在 try/except 中
interrupt 通过在调用点抛出一个特殊异常来暂停执行。如果你将 interrupt 调用包裹在 try/except 块中,你会捕获这个异常,中断将不会传回给图。
✅ 将 interrupt 调用与容易出错的代码分开
✅ 在 try/except 块中使用特定的异常类型
def node_a ( state : State ):
# ✅ 好的做法:先中断,然后单独处理
# 错误情况
interrupt ( "你叫什么名字?" )
try :
fetch_data () # 这可能会失败
except Exception as e :
print ( e )
return state
def node_a ( state : State ):
# ❌ 糟糕的做法:在裸 try/except 中包裹中断
# 会捕获中断异常
try :
interrupt ( "你叫什么名字?" )
except Exception as e :
print ( e )
return state
不要重新排序节点内的 interrupt 调用
在单个节点内使用多个中断是很常见的,但如果不小心处理,可能会导致意外行为。
当一个节点包含多个中断调用时,LangGraph 会维护一个特定于执行该节点的任务的恢复值列表。每当执行恢复时,它都会从节点的开头开始。对于遇到的每个中断,LangGraph 都会检查任务的恢复列表中是否存在匹配的值。匹配是严格基于索引的 ,因此节点内中断调用的顺序很重要。
def node_a ( state : State ):
# ✅ 好的做法:中断调用每次都以相同的顺序发生
name = interrupt ( "你叫什么名字?" )
age = interrupt ( "你的年龄是多少?" )
city = interrupt ( "你住在哪个城市?" )
return {
"name" : name ,
"age" : age ,
"city" : city
}
def node_a ( state : State ):
# ❌ 糟糕的做法:有条件地跳过中断会改变顺序
name = interrupt ( "你叫什么名字?" )
# 在第一次运行时,这可能会跳过中断
# 在恢复时,它可能不会跳过——导致索引不匹配
if state . get ( "needs_age" ):
age = interrupt ( "你的年龄是多少?" )
city = interrupt ( "你住在哪个城市?" )
return { "name" : name , "city" : city }
不要在 interrupt 调用中返回复杂的值
根据所使用的检查点记录器,复杂的值可能无法序列化(例如,你无法序列化一个函数)。为了使你的图能够适应任何部署,最佳实践是仅使用可以合理序列化的值。
✅ 向 interrupt 传递简单的、可 JSON 序列化的类型
✅ 传递包含简单值的字典/对象
def node_a ( state : State ):
# ✅ 好的做法:传递可序列化的简单类型
name = interrupt ( "你叫什么名字?" )
count = interrupt ( 42 )
approved = interrupt ( True )
return { "name" : name , "count" : count , "approved" : approved }
def validate_input ( value ):
return len ( value ) > 0
def node_a ( state : State ):
# ❌ 糟糕的做法:向 interrupt 传递函数
# 函数无法被序列化
response = interrupt ({
"question" : "你叫什么名字?" ,
"validator" : validate_input # 这会失败
})
return { "name" : response }
interrupt 之前调用的副作用必须是幂等的
由于中断会重新运行调用它们的节点,因此在 interrupt 之前调用的副作用(理想情况下)应该是幂等的。作为背景,幂等性意味着同一个操作可以被多次应用,而不会改变初始执行之外的结果。
例如,你可能会在节点内部有一个更新记录的 API 调用。如果在该调用之后又调用了 interrupt ,那么当节点恢复时,该调用会被多次重新运行,可能会覆盖初始更新或创建重复记录。
def node_a ( state : State ):
# ✅ 好的做法:使用幂等的 upsert 操作
# 多次运行此操作将得到相同的结果
db . upsert_user (
user_id = state [ " user_id " ],
status = "pending_approval"
)
approved = interrupt ( "批准此更改吗?" )
return { "approved" : approved }
🔴 不要在 interrupt 之前执行非幂等的操作
🔴 不要在未检查记录是否存在的情况下创建新记录
def node_a ( state : State ):
# ❌ 糟糕的做法:在中断之前创建新记录
# 每次恢复都会创建重复记录
audit_id = db . create_audit_log ({
"user_id" : state [ " user_id " ],
"action" : "pending_approval" ,
"timestamp" : datetime . now ()
})
approved = interrupt ( "批准此更改吗?" )
return { "approved" : approved , "audit_id" : audit_id }
与作为函数调用的子图一起使用
当在节点内调用子图时,父图将从调用子图并且触发 interrupt 的节点的开头 恢复执行。同样,子图 也将从调用 interrupt 的节点开头恢复。
def node_in_parent_graph ( state : State ):
some_code () # <-- 恢复时将重新执行这部分
# 以函数形式调用子图。
# 该子图包含一个 `interrupt` 调用。
subgraph_result = subgraph . invoke ( some_input )
# ...
def node_in_subgraph ( state : State ):
some_other_code () # <-- 恢复时这也将重新执行
result = interrupt ( "你叫什么名字?" )
# ...
使用中断进行调试
要调试和测试图,你可以使用静态中断作为断点,一次一个节点地单步通过图执行。静态中断是在节点执行之前或之后的已定义点触发的。你可以在编译图时通过指定 interrupt_before 和 interrupt_after 来设置它们。
graph = builder . compile (
interrupt_before = [ "node_a" ],
interrupt_after = [ "node_b" , "node_c" ],
checkpointer = checkpointer ,
)
# 向图传递一个线程 ID
config = {
"configurable" : {
"thread_id" : "some_thread"
}
}
# 运行图直到断点
graph . invoke ( inputs , config = config )
# 恢复图
graph . invoke ( None , config = config )
断点在 compile 时设置。
interrupt_before 指定在节点执行之前应暂停的节点。
interrupt_after 指定在节点执行之后应暂停的节点。
需要使用检查点记录器来启用断点。
图运行直到遇到第一个断点。
通过传入 None 作为输入来恢复图。这将运行图直到遇到下一个断点。
config = {
"configurable" : {
"thread_id" : "some_thread"
}
}
# 运行图直到断点
graph . invoke (
inputs ,
interrupt_before = [ "node_a" ],
interrupt_after = [ "node_b" , "node_c" ],
config = config ,
)
# 恢复图
graph . invoke ( None , config = config )
使用 interrupt_before 和 interrupt_after 参数调用 graph.invoke。这是运行时配置,每次调用都可以更改。
interrupt_before 指定在节点执行之前应暂停的节点。
interrupt_after 指定在节点执行之后应暂停的节点。
图运行直到遇到第一个断点。
通过传入 None 作为输入来恢复图。这将运行图直到遇到下一个断点。
使用 LangSmith Studio
你可以使用 LangSmith Studio 在 UI 中为你的图设置静态中断,然后再运行图。你也可以使用 UI 在执行中的任意点检查图状态。