LangChain LangGraph
Module 1 Module 2 Module 3 Module 4 Module 5 Module 6
Module 3 · Human in the Loop
1 3-1 Streaming
2 3-2 Breakpoints
3 3-3 Edit State
4 3-4 Dyn Breakpts
5 3-5 Time Travel
SUM Module Summary
HomeLangGraphModule 3 · Human in the Loop3-4 Dyn Breakpts
📓 Notebook 📖 Explained
LANGGRAPH MODULE 3 · LESSON 4

Dynamic breakpoint: interrupt()
Let the graph decide whether to pause at runtime

Static breakpoints are fixed at compile time, while dynamic breakpoints allow nodes to trigger interruptsbased on conditions during runtime.
Learn to use langgraph.types.interrupt to achieve a truly flexible human-computer collaboration process.

1Limitations of static breakpoints: why you need dynamic breakpoints

In the first few lessons of Module 3, we learnedStatic Breakpoints: When compiling the graph, passcompile(interrupt_before=["node_name"])orinterrupt_after=to specify which nodes will trigger a pause.

Static breakpoints have a fundamental limitation:It is determined at "compile time" and has nothing to do with runtime data.

Dimensions Static Breakpoint
Set location When graph is compiled:builder.compile(interrupt_before=[...])
Trigger condition Each time it is executed to this node,unconditionallyinterrupt
Flexibility Unable to decide "whether to interrupt" based on input data
Interruption reason Fixed node name, no additional information
Typical scenario Forced observation of the output of a node during debugging and development
Typical pain points

Imagine an approval workflow: when AI-generated contentexceed a certain risk thresholdOnly when manual review is required, low-risk content should be passed directly. When implemented with static breakpoints, you can only "either always break or never break" - it cannot be doneconditional interrupt

this isDynamic BreakpointsReason for existence: Let the code inside the node function, based on the runtime data,self-determinationWhether an interrupt is triggered and the specific reason for the interrupt can be communicated to the outside world.

Module 3 Review of three major scenarios of human-machine collaboration

(1) Approval: Interruption graph, which presents the status to the user and allows the user to decide whether to continue.
(2) Debugging: Roll back the graph to a certain checkpoint to reproduce or avoid the problem
(3) Editing: Modify the intermediate state and change the subsequent behavior of the graph

Dynamic breakpoints mainly serve the (1) scenario, but are more accurate than static breakpoints——Only bother humans when needed

2The core of dynamic breakpoints: interrupt() function

LangGraph bylanggraph.types.interruptFunction implements dynamic breakpoints. When a node function is called during executioninterrupt(...)When LangGraph runs:

Import method

from langgraph.types import interrupt

# 调用 interrupt() 会暂停当前图执行
# payload 会作为中断原因暴露给外部调用方
interrupt("需要人工审核:输入内容超出长度限制")
interrupt({"reason": "high_risk", "score": 0.95})
Key design ideas

Unlike static breakpoints set at the "graph structure level",interrupt()is inBusiness logic level of node functionTriggered. This means you can use any Python logic to decide whether to break: if conditions, model confidence thresholds, rules engines, external API calls... total freedom.

Schematic diagram of the triggering mechanism of dynamic breakpoints

__start__
step_1
Execute normally and return state
The input is shorter (len ≤ 5)
step_2
Execute normally and continue the process
step_3
__end__
Longer input (len > 5)
step_2
interrupt(...)
The graph is paused, waiting for external operations
state.tasks[0].interrupts in
Record the reason for the interruption

3Complete example: Building a graph with dynamic breakpoints

The example in the notebook builds a simple three-step diagram:step_1 → step_2 → step_3, among whichstep_2Will be called when the input string exceeds 5 charactersinterrupt()

Step 1: Define State and node functions

from typing_extensions import TypedDict
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import START, END, StateGraph
from langgraph.types import interrupt   # ← 导入动态中断函数

# State 只有一个字段:input 字符串
class State(TypedDict):
    input: str

# ── step_1:普通节点,直接返回 state ──
def step_1(state: State) -> State:
    print("---Step 1---")
    return state

# ── step_2:包含动态断点逻辑的关键节点 ──
def step_2(state: State) -> State:
    # 运行时条件判断:如果输入超过 5 个字符,则中断
    if len(state['input']) > 5:
        # 调用 interrupt(),并附带说明原因的 payload
        interrupt(
            f"Received input that is longer than 5 characters: {state['input']}"
        )
    # 如果没有触发中断,则正常执行
    print("---Step 2---")
    return state

# ── step_3:普通节点 ──
def step_3(state: State) -> State:
    print("---Step 3---")
    return state
Note: step_2 function body structure

ifExecute when conditions are metinterrupt(...), the execution of this graph will be paused here,NoContinue executionprint("---Step 2---"). Only when the conditions are not met, will you go downprintandreturn state. This is the essence of "conditional interrupt".

Step 2: Build the graph and compile

# 构建图结构
builder = StateGraph(State)
builder.add_node("step_1", step_1)
builder.add_node("step_2", step_2)
builder.add_node("step_3", step_3)

builder.add_edge(START, "step_1")
builder.add_edge("step_1", "step_2")
builder.add_edge("step_2", "step_3")
builder.add_edge("step_3", END)

# 准备 MemorySaver(动态断点依赖 checkpointer 保存状态)
memory = MemorySaver()

# 编译时 ★ 不需要 ★ 指定任何 interrupt_before / interrupt_after!
# 动态断点完全由节点内部的 interrupt() 调用控制
graph = builder.compile(checkpointer=memory)
The core difference from static breakpoint compilation

Static breakpoint:builder.compile(checkpointer=memory, interrupt_before=["step_2"])
Dynamic breakpoints:builder.compile(checkpointer=memory)  ←  No additional parameters required

All the logic of dynamic breakpoints is in the node function, and the compiler does not need to know any information.

Visual structure of graphs

Graph topology (looks completely linear at compile time)

__start__
step_1
Execute unconditionally
step_2
may call interrupt()
step_3
Execute unconditionally
__end__

There is no trace of "interruption" in the graph structure itself - dynamic breakpoints "appear" only at runtime. This is different from the visibility of static breakpoints (break nodes are fixed at compile time).

4Trigger an interrupt: check status and interrupt reason

Run the graph and trigger interrupt()

# 配置:使用 thread_id 追踪这次执行的状态
initial_input = {"input": "hello world"}  # 11 个字符,超过 5
thread_config = {"configurable": {"thread_id": "1"}}

# 使用 stream 运行图
for event in graph.stream(initial_input, thread_config, stream_mode="values"):
    print(event)
{'input': 'hello world'} ---Step 1--- {'input': 'hello world'} {'input': 'hello world', '__interrupt__': (Interrupt(value='Received input that is longer than 5 characters: hello world', id='placeholder-id'),)}

Observe the output:

Note: No error reported

interrupt()Will not cause the program to throw uncaught exceptions or crash.stream()Return normally, but end the iteration early. All interruption information is saved in the checkpointer, waiting for query or recovery.

Check state.next: the next node waiting to be executed

state = graph.get_state(thread_config)
print(state.next)
('step_2',)

state.nextshow('step_2',), indicating that the graph stopped at the "about to be executed" position of step_2 - step_2 triggered an interrupt, but this execution was aborted, so it is still the "next node to be executed".

Check state.tasks: read interruption reason

print(state.tasks)
(PregelTask( id='6eb3910d-e231-5ba2-b25e-28ad575690bd', name='step_2', error=None, interrupts=( Interrupt( value='Received input that is longer than 5 characters: hello world', when='during' ), ), state=None ),)

Interpretationstate.tasksKey fields of:

Fieldvaluemeaning
name 'step_2' The name of the node that triggered the interrupt
interrupts[0].value 'Received input that is longer than 5 characters: hello world' passed tointerrupt()The payload——Interruption reason, readable by humans or code
interrupts[0].when 'during' Interrupts occur "during" the execution of a node (as distinct from'before'/'after', that is, the location of the static breakpoint)
error None This is not an error, it is a controlled interrupt
Advantages of dynamic breakpoints: delivering rich interrupt reasons

pass tointerrupt()The value is not limited to strings, it is recommended to use JSON serializable structured data. For example:
interrupt({"reason": "high_risk", "score": 0.97, "flagged_content": text})
This allows human-machine interfaces to display detailed information and help humans make more accurate judgments.

5Pitfall: Simple recovery retriggers the interrupt

Mistakes that beginners often make: Thinking that they will not modify the status or provideCommand(resume=...), directlygraph.stream(None, thread_config)can continue. Let's see what happens:

# 尝试直接恢复(不修改 state)
for event in graph.stream(None, thread_config, stream_mode="values"):
    print(event)
{'input': 'hello world'}
state = graph.get_state(thread_config)
print(state.next)  # 仍然是 step_2!
('step_2',)
Infinite loop warning

becausestate['input']still"hello world"(11 characters), step_2 execution conditionlen(state['input']) > 5is still true, sointerrupt()meetingcalled again. If you keep usingNoneResuming without changing the conditions, the graph will repeatedly stop at the same interruption point.

This differs from the behavior of static breakpoints:

root cause

The break conditions of dynamic breakpoints are data-driven. This example uses "modify state first, then useNone"Recovery" method, so that the node no longer meets the interruption conditions when it is re-run. Another type of human-computer interaction process usesCommand(resume=...)Pass manual input backinterrupt()call point.

6Correct process: modify the status and then restore it

The correct way to handle it is: use it firstgraph.update_state()Modify the state so that the interrupt condition is no longer met, and then resume execution.

Step 1: Modify state and eliminate interruption conditions

# 将 input 改为短字符串(5 个字符以内),让 step_2 的条件不再触发
graph.update_state(
    thread_config,
    {"input": "hi"},  # "hi" 只有 2 个字符,len("hi") = 2 ≤ 5
)
{'configurable': { 'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a434-06cf-6f1e-8002-0ea6dc69e075' }}

update_stateReturns the new checkpoint configuration, indicating that the status was successfully written.

Step 2: Resume execution

# 传入 None 作为 input,表示"从上次中断处继续,不添加新输入"
for event in graph.stream(None, thread_config, stream_mode="values"):
    print(event)
{'input': 'hi'} ---Step 2--- {'input': 'hi'} ---Step 3--- {'input': 'hi'}

This execution was successful! Execution process analysis:

Summary of complete operation process

Complete human-machine collaboration process with dynamic breakpoints

1
Start executiongraph.stream(initial_input, config)
2
interrupt() trigger: The picture automatically pauses and the stream returns
3
Check the cause of the interruption: readstate.tasks[0].interrupts[0].value
4
(Optional) Manual review: Determine the next step based on the cause of the interruption.
5
Modify Stategraph.update_state(config, new_values), so that the interrupt condition no longer holds
6
Resume executiongraph.stream(None, config), the pending node re-runs
Two recovery strategies

If you want to use manual input asinterrupt()The return value is passed back to the node and should be usedCommand(resume=...). This example demonstrates the status editing process: first useupdate_state()Eliminate the interrupt condition and usegraph.stream(None, config)Let the pending node run again.

7Using dynamic breakpoints via the LangGraph API

In a production environment, graphs are typically deployed on the LangGraph server (vialanggraph devStart a local development server) and clients interact with it through the LangGraph SDK. Dynamic breakpoints are also applicable in API scenarios, and the operation logic is completely consistent with the local one.

Start local development server

in module/studioRun in the directory:

langgraph dev

After the service starts, it will display:

- API: http://127.0.0.1:2024 - Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024 - API Docs: http://127.0.0.1:2024/docs

Connect and run graphs via SDK (trigger dynamic breakpoints)

from langgraph_sdk import get_client

# 连接到本地开发服务器
URL = "http://127.0.0.1:2024"
client = get_client(url=URL)

# 创建新线程(相当于一次独立的图执行会话)
thread = await client.threads.create()
input_dict = {"input": "hello world"}  # 超过 5 字符,会触发 interrupt()

async for chunk in client.runs.stream(
    thread["thread_id"],
    assistant_id="dynamic_breakpoints",  # 图的名称(在 langgraph.json 中定义)
    input=input_dict,
    stream_mode="values",
):
    print(f"Receiving new event of type: {chunk.event}...")
    print(chunk.data)
Receiving new event of type: metadata... {'run_id': '1ef6a43a-1b04-64d0-9a79-1caff72c8a89'} Receiving new event of type: values... {'input': 'hello world'} Receiving new event of type: values... {'input': 'hello world'}

End of flow event - Figure in step_2interrupt()paused.

Check remote thread status

current_state = await client.threads.get_state(thread['thread_id'])
print(current_state['next'])
['step_2']

Modify status remotely and then restore

# 更新 state:将 input 改为短字符串
await client.threads.update_state(thread['thread_id'], {"input": "hi!"})

# 恢复执行:传入 input=None 表示从中断处继续
async for chunk in client.runs.stream(
    thread["thread_id"],
    assistant_id="dynamic_breakpoints",
    input=None,  # ← 关键:None 表示恢复,而非新的输入
    stream_mode="values",
):
    print(f"Receiving new event of type: {chunk.event}...")
    print(chunk.data)
Receiving new event of type: metadata... {'run_id': '1ef64c33-fb34-6eaf-8b59-1d85c5b8acc9'} Receiving new event of type: values... {'input': 'hi!'} Receiving new event of type: values... {'input': 'hi!'}

The graph was successfully restored and executed (both step_2 and step_3 were executed normally).

The true value of API scenarios

In a production system, the graph runs on the server, and the "modify state" and "resume execution" operations come from another service (such as a human review interface, message queue, webhook callback). LangGraph SDK provides cross-process and cross-service status operation capabilities, making distributed human-computer collaboration workflow possible.

8Static breakpoints vs dynamic breakpoints: a complete comparison

Both breakpoint mechanisms have their own applicable scenarios, and understanding their differences can help make the right choice in actual projects.

Contrast Dimensions Static Breakpoint Dynamic Breakpoint
Set location When the graph is compiled,compile(interrupt_before/after=[...]) Inside the node function,interrupt(...)
import No additional import required from langgraph.types import interrupt
Interruption timing Node executionbeforebefore) orafterafter Node executionperiodwhen='during'
Trigger condition unconditionally: Interrupt every time it reaches the specified node. Conditional: It is judged by the developer using if in the node function.
Interruption reason transfer Only know "at which node it was interrupted" A JSON serializable string, dictionary, etc. payload can be passed as the reason
direct resume effect You can resume directly and the node will execute normally. use NoneWhen restored and conditions remain unchanged, it willinterrupted again; useCommand(resume=...)Can pass human input back to the node
Pre-reinstatement requirements You can choose to modify or not modify the state before resuming In this example, the state release condition is first modified; it is used when manual input needs to be returned.Command(resume=...)
Read interrupt information state.next(next node name) state.tasks[0].interrupts[0].value(specific reasons)
Suitable for the scene During debugging and development, it is necessary to observe the output of a certain node regularly. Manual review and risk interception triggered by conditions in the production environment
Typical code graph = builder.compile(checkpointer=memory, interrupt_before=["step_2"]) within nodeif condition: interrupt("reason")

How to choose?

Choose static breakpoints when you...

  • inDevelopment/Debuggingstage, you need to pause at a certain node every time to check the status.
  • buildprototype, quickly verify whether the process of the diagram meets expectations.
  • Nodes that require "approval"Manual confirmation is required every time, no exceptions
  • Hopefully the presence of breakpoints on node function codecompletely transparent(Node functions are unaware of the existence of breakpoints)

Choose dynamic breakpoints when you...

  • needconditionalityManual review (for example, if the amount exceeds the limit or the risk score is high, review is required)
  • Want to pass on to reviewersSpecific reason for interruptionand contextual information
  • It is hoped that "normal conditions will automatically pass and abnormal conditions will interrupt" to reduce the frequency of manual intervention.
  • buildproduction gradehuman-machine collaboration workflow
Both breakpoints can coexist

Static breakpoints and dynamic breakpoints are not mutually exclusive. You can use both in the same picturecompile(interrupt_before=[...])Set static breakpoints and use them in some node functionsinterrupt(...)Set dynamic breakpoints. They work independently without interfering with each other.

Examples of real engineering application scenarios

business scenario Which breakpoint to use break condition
AI content review system dynamic breakpoint Interrupt when content risk score > 0.8 for manual review
Financial approval workflow dynamic breakpoint When the reimbursement amount is > 1,000 yuan, it will be interrupted and wait for approval from the supervisor.
Code generation agent debugging static breakpoint During development, every time code is generated, it is paused to allow developers to check it.
Legal document generation dynamic breakpoint Interrupt when specific sensitive terms are involved and continue after confirmation by legal staff
Tool calls Agent security protection dynamic breakpoint Breaks when LLM decides to invoke "dangerous tools" (deletion, outgoing data)
Research assistant prototype verification static breakpoint Pause after each search node is completed to verify the quality of the search results
interrupt() payload design suggestions

In a production system, it is recommended tointerrupt()pass onstructured dictionaryInstead of a simple string, the review interface can better parse and display the information:

interrupt({
    "reason": "high_risk_content",
    "risk_score": 0.94,
    "flagged_text": state["generated_content"][:200],
    "suggested_action": "请人工审核后选择批准或拒绝"
})