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-2 Breakpoints
📓 Notebook 📖 Explained
LANGGRAPH MODULE 3 · LESSON 2

LangGraph Breakpoint Mechanism
Breakpoints & Human-in-the-Loop

Let the Agent pause at key nodes and wait for human approval before continuing.
Understanding breakpoints is the first step to mastering human-machine collaborative AI systems.

1Why you need breakpoints: Three motivations for Human-in-the-Loop

In Module 1 and Module 2, the Agent we built runs completely automatically: from START to END, LLM makes decisions independently, and the tools are automatically called, all in one go. But in real production scenarios,It is not always safe to make the Agent completely autonomous

LangGraph'sHuman-in-the-Loop(Human-machine collaboration) design solves three core problems:

motivation scene description Specific examples
Approval Before the Agent wants to perform an operation, it needs human confirmation to let it go. Confirm recipients before sending emails, review before performing database write operations, and approve before calling paid APIs
Debugging Able to go back to a certain historical state and re-execute to locate the problem Agent gives a wrong answer, go back to a certain step in the middle and rerun; check whether the parameters called by a tool are correct.
Editing Modify the state during graph execution and correct the Agent's intermediate results The key information extracted by LLM is incorrect. Please correct it manually before continuing; modify the tool calling parameters before executing again.
Key points of this lesson

Lesson 2 focuses on the first category:Approval. By setting a breakpoint at a specific node, the graph is paused before and after that node, waiting for human decision-making before continuing. This is the simplest and most direct way to implement Human-in-the-Loop.

Imagine a financial reimbursement agent: it can automatically analyze receipts, calculate amounts, and fill out reimbursement forms, butfinal commitThis step must be approved by a human treasurer. The breakpoint is the "waiting for approval" checkpoint you inserted.

2What are breakpoints: compile-time static breakpoints

The nature of breakpoints

Breakpoint (Breakpoint) is atWhen compiling the graph (compile phase)Just a definite, fixed interruption location. It tells LangGraph: "Every time execution reaches the front (or back) of this node, it will automatically pause. Do not continue going down and wait for external instructions."

breakpoint passedcompile()Two parameters to set:

# interrupt_before:在节点执行【之前】暂停
graph = builder.compile(
    interrupt_before=["tools"],   # 在 tools 节点运行前暂停
    checkpointer=memory
)

# interrupt_after:在节点执行【之后】暂停
graph = builder.compile(
    interrupt_after=["tools"],    # 在 tools 节点运行后暂停
    checkpointer=memory
)
Parameter description

interrupt_beforeandinterrupt_afterAccept onelist of node names(string). You can set breakpoints on multiple nodes at the same time, for exampleinterrupt_before=["tools", "send_email"]

Static breakpoints vs dynamic interrupts

"static" means the breakpoint is atcompile()It has been hard-coded - every time it is executed to that node,DefinitelyIt will be paused and there is no conditional judgment. This is consistent with the dynamic interruptions you will learn in Lesson 4 (NodeInterrupt), dynamic interrupts can decide whether to pause based on the runtime status.

type Set timing Is there any condition? Usage scenarios
static breakpoint(this lesson) when compile() Unconditionally, pause every time All tool calls require approval, fixed review points
dynamic interrupt(Lesson 4) node runtime Can be triggered conditionally based on status Approval is only required when the amount exceeds the threshold

The location of the breakpoint in the figure is shown

START
assistant
LLM thinking, decided to call the tool
BREAKPOINT
interrupt_before=["tools"]
tools
Execution tool (target node before pause)
assistant
LLM processing tool results
END

3Core premise: MemorySaver checkpointer

Why must breakpoints be used with checkpointers?

The working principle of breakpoint is: when the graph execution reaches the breakpoint,Save the current state, and then stop execution. After humans issue the "continue" command, from the saved stateResume execution. This "save and restore" mechanism ischeckpointer

necessary conditions

Breakpoints don't work without checkpointer.If you setinterrupt_beforebut no incomingcheckpointer, LangGraph will throw an error at runtime. The checkpointer is the underlying support for the breakpoint function.

How to use MemorySaver

MemorySaveris LangGraph's built-in memory checkpointer, which saves all state in process memory. Suitable for development, testing and stand-alone scenarios.

from langgraph.checkpoint.memory import MemorySaver

# 创建检查点器实例
memory = MemorySaver()

# 编译图时传入 checkpointer 和断点配置
graph = builder.compile(
    interrupt_before=["tools"],
    checkpointer=memory      # 必须提供!
)

The concept of Thread

For checkpointersthread_idto differentiate between different conversations/running sessions. Each time you run the graph, you need to provide a configuration dictionary:

# config:指定这次运行使用哪个 thread
thread = {"configurable": {"thread_id": "1"}}

# 同一个 thread_id = 同一个对话上下文
# 不同 thread_id = 独立的、互不干扰的对话
How checkpointers work

Every time the graph executes a node, MemorySaver will automatically save the current complete state (State) as acheckpoint. When a breakpoint is triggered and the graph is paused, the latest checkpoint records the "moment before the pause". callinvoke(None, config)When execution resumes, LangGraph continues running by reading the state from that checkpoint.

checkpointer storage location Applicable scenarios
MemorySaver Process memory (lost after restart) Development and debugging, teaching examples, prototype verification
SqliteSaver Local SQLite file Single-machine production, lightweight persistence
PostgresSaver PostgreSQL database Distributed production system, cross-process persistence

4Practical code: Constructing an Agent graph with breakpoints

Below is the complete example in Notebook. We build an Agent with math tools (addition, multiplication, division) and set a breakpoint before the tools node to allow humans to approve before the tool is called.

Step One: Define Tools and LLM

from langchain_deepseek import ChatDeepSeek

# 定义三个数学工具函数
def multiply(a: int, b: int) -> int:
    """Multiply a and b."""
    return a * b

def add(a: int, b: int) -> int:
    """Adds a and b."""
    return a + b

def divide(a: int, b: int) -> float:
    """Divide a by b."""
    return a / b

# 工具列表
tools = [add, multiply, divide]

# 绑定工具到 LLM(LLM 可以选择调用哪个工具)
llm = ChatDeepSeek(model="deepseek-v4-pro")
llm_with_tools = llm.bind_tools(tools)

Step 2: Build the graph and compile (with breakpoints)

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import MessagesState, StateGraph, START
from langgraph.prebuilt import tools_condition, ToolNode
from langchain_core.messages import SystemMessage

# 系统提示
sys_msg = SystemMessage(content="You are a helpful assistant tasked with performing arithmetic on a set of inputs.")

# 定义 assistant 节点:调用带工具的 LLM
def assistant(state: MessagesState):
    return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]}

# 构建图
builder = StateGraph(MessagesState)

builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

builder.add_edge(START, "assistant")
builder.add_conditional_edges(
    "assistant",
    tools_condition,   # 如果 LLM 要调用工具 → tools;否则 → END
)
builder.add_edge("tools", "assistant")   # 工具结果返回给 LLM

# 关键:创建检查点器 + 编译时指定断点
memory = MemorySaver()
graph = builder.compile(
    interrupt_before=["tools"],   # 在 tools 节点执行前暂停
    checkpointer=memory             # 必须提供检查点器
)
Key details: MessagesState and ToolNode

MessagesStateis LangGraph's built-in state type, which contains a reducermessagesField (automatically appended rather than overwritten).ToolNodeis a built-in tool execution node that automatically processes thetool_calls, execute the corresponding function and convert the result toToolMessageAppend to status.

Step 3: Run the graph until the first breakpoint

from langchain_core.messages import HumanMessage

# 初始输入:让 Agent 计算 2 乘以 3
initial_input = {"messages": HumanMessage(content="Multiply 2 and 3")}

# 指定 thread(对话上下文)
thread = {"configurable": {"thread_id": "1"}}

# 以流式模式运行图,直到遇到断点自动停止
for event in graph.stream(initial_input, thread, stream_mode="values"):
    event['messages'][-1].pretty_print()
Output (the output before the graph pauses at the breakpoint)
================================ Human Message ================================
Multiply 2 and 3
================================== Ai Message ==================================
Tool Calls:
  multiply (call_oFkGpnO8CuwW9A1rk49nqBpY)
  Call ID: call_oFkGpnO8CuwW9A1rk49nqBpY
  Args:
    a: 2
    b: 3

The figure outputs Human Message and AI Message (AI decides to callmultiplytool) and thenauto-pause——The tools node execution is not continued. At this point, program control is returned to you, and you can view the status and make decisions.

5Check status after pause: graph.get_state(config)

Get the current graph status

After the picture is paused, you can use it at any timegraph.get_state(config)to see a complete current status snapshot. This method returns aStateSnapshotObject that contains the current State content and a lot of useful metadata.

# 获取当前状态快照
state = graph.get_state(thread)

# 查看下一个待执行的节点
print(state.next)
output
('tools',)

state.nextReturns a tuple containing the names of the nodes to be executed next in the graph. when it shows('tools',)When, it means: the graph has indeed been paused at the tools nodebefore execution, which is exactly what we setinterrupt_before=["tools"]effect.

The complete structure of StateSnapshot

# state 对象包含多个有用属性
state = graph.get_state(thread)

state.values        # 当前 State 的全部字段值(MessagesState 里就是 messages 列表)
state.next          # 下一步要执行的节点(元组),暂停时非空,结束时为空元组
state.config        # 当前检查点的配置信息(包含 checkpoint_id)
state.metadata      # 元数据(step 计数、source 等)
state.created_at    # 检查点创建时间
state.parent_config # 上一个检查点的配置(用于时间旅行)

# 查看所有消息
for msg in state.values["messages"]:
    msg.pretty_print()
How to use state.next to determine the state of the graph

state.next == ('tools',)→ The drawing has been paused, waiting for the approval tool to be called
state.next == ()→ The graph has been executed (reached END)
This attribute is the standard way to programmatically determine whether the graph is still running.

Actual inspection of tool call content

After pausing, you can deeply check what tools the AI intends to call and what parameters it passes. This is the basis for making approval decisions:

# 获取最后一条消息(AI 的工具调用请求)
last_msg = state.values["messages"][-1]

# 查看工具调用详情
if hasattr(last_msg, 'tool_calls'):
    for tc in last_msg.tool_calls:
        print(f"工具名称: {tc['name']}")
        print(f"调用参数: {tc['args']}")
        print(f"调用 ID: {tc['id']}")
output
Tool name: multiply
Calling parameters: {'a': 2, 'b': 3}
Call ID: call_oFkGpnO8CuwW9A1rk49nqBpY

Now you know that the Agent intends to callmultiply(a=2, b=3). As a human approver, you can determine whether the call is reasonable and then decide whether to release it.

6Resume execution: graph.invoke(None, config)

Restore with None as input

This is one of the most elegant designs in the breakpoint mechanism:putNonePassed as inputinvokeorstream, the graph will continue execution from the previous checkpoint without re-entering any data.

# 人类审批后,继续执行图
# 传入 None 表示"从检查点恢复,不提供新输入"
for event in graph.stream(None, thread, stream_mode="values"):
    event['messages'][-1].pretty_print()
Output (output after continuing execution from the breakpoint)
================================== Ai Message ==================================
Tool Calls:
  multiply (call_oFkGpnO8CuwW9A1rk49nqBpY)
  Args: a: 2, b: 3

================================= Tool Message =================================
Name: multiply
6

================================== Ai Message ==================================
The result of multiplying 2 and 3 is 6.

Pay attention to the order of output:

How None works

when you pass inNone, LangGraph knows that this is not new user input, but a "recovery signal". It will be specified fromthread_idRead the saved state from the corresponding latest checkpoint and continue fromstate.nextThe node pointed to (i.e.tools) starts execution. The entire historical message chain is completely preserved.

Refuse to execute (do not restore)

If the human approver decidesrejectThis time the tool is called, just don't callinvoke(None, ...), just end the process directly. The checkpoint for this conversation remains in the MemorySaver, but there will be no further execution.

# 完整的审批流程示例
initial_input = {"messages": HumanMessage(content="Multiply 2 and 3")}
thread = {"configurable": {"thread_id": "2"}}

# 运行直到断点
for event in graph.stream(initial_input, thread, stream_mode="values"):
    event['messages'][-1].pretty_print()

# 等待人类审批
user_approval = input("Do you want to call the tool? (yes/no): ")

if user_approval.lower() == "yes":
    # 审批通过:继续执行
    for event in graph.stream(None, thread, stream_mode="values"):
        event['messages'][-1].pretty_print()
else:
    # 审批拒绝:不继续执行
    print("Operation cancelled by user.")

Breakpoint execution process panorama

graph.stream(input, thread)
Pass in user input and start execution
assistant node execution
LLM decides to call tool
BREAKPOINT trigger
State saved to checkpoint · Execution paused · Control returned to user
approve
graph.stream(None, thread)
Restore from checkpoint
tools node execution
Tool calls actually happen
END
reject
invoke(None) is not called
process terminated

7The difference between interrupt_before vs interrupt_after

Comparison of two breakpoint modes

parameter Pause time state.values at this time Applicable scenarios
interrupt_before=["tools"] tools node executionbefore Contains AI's tool call request (AIMessage with tool_calls), but no ToolMessage yet in toolsbefore executionApproval: See what the AI plans to call and what parameters it passes to decide whether to release it.
interrupt_after=["tools"] tools node executionafter Tool call request containing AI + ToolMessage (the tool has been executed and the result has been returned) in toolsafter executionReview results: Review whether the return value of the tool is reasonable, and then decide whether to let LLM continue processing.

interrupt_before: Approval tool call (Notebook example)

Scenario: Let a human confirm the recipient and content before sending an email

# 在 send_email 节点执行前暂停
graph = builder.compile(
    interrupt_before=["send_email"],
    checkpointer=memory
)

# 图暂停时,state 里包含 AI 拟好的邮件内容(工具调用参数)
# 人类可以查看收件人、主题、正文,决定是否发送
state = graph.get_state(thread)
tool_call = state.values["messages"][-1].tool_calls[0]
print(f"收件人: {tool_call['args']['to']}")
print(f"主题: {tool_call['args']['subject']}")
# → 人类确认后:graph.invoke(None, thread)

interrupt_after: Audit tool results

Scenario: After the search results are returned, humans review whether they are trustworthy sources

# 在 web_search 节点执行后暂停
graph = builder.compile(
    interrupt_after=["web_search"],
    checkpointer=memory
)

# 图暂停时,搜索已经完成,state 里有 ToolMessage(搜索结果)
# 人类可以检查来源、筛选掉不可信的结果
state = graph.get_state(thread)
# 最后一条是 ToolMessage(工具执行结果)
search_result = state.values["messages"][-1].content
print(f"搜索结果: {search_result}")
# → 确认结果可信后:graph.invoke(None, thread) 让 LLM 继续总结

Timing comparison of the two modes

interrupt_before
assistant execution
PAUSE
The tool is not executed at this time
↓(approved)
tools execution
interrupt_after
assistant execution
tools execution
PAUSE
At this point the tool has been executed
↓(Audit result passed)
assistant continues processing
Select suggestions

Useinterrupt_before: When you want to intercept "dangerous operations" before they occur. For example: writing to the database, sending emails, calling paid APIs, executing code - such operations are irreversible and must be approved before execution.

Useinterrupt_after: When you want to audit the quality of "intermediate results". For example: After searching the web page, review whether the source is credible; LLM has extracted the information, confirm that the extraction is accurate before continuing.

8Complete user approval process integrated with LangGraph API

Complete local approval code mode

Integrate all the previous knowledge into a complete and usable approval process template:

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_core.messages import HumanMessage

# ─── 构建图(假设 tools、llm_with_tools、assistant 已定义)───
memory = MemorySaver()
graph = builder.compile(
    interrupt_before=["tools"],
    checkpointer=memory
)

def run_with_approval(user_question: str, thread_id: str):
    """完整的带审批的 Agent 执行函数"""
    thread = {"configurable": {"thread_id": thread_id}}
    initial_input = {"messages": HumanMessage(content=user_question)}

    print("=== Agent 开始执行 ===")

    # 第一阶段:运行到断点
    for event in graph.stream(initial_input, thread, stream_mode="values"):
        event['messages'][-1].pretty_print()

    # 检查是否暂停在断点(如果 next 非空,说明还有待执行的节点)
    state = graph.get_state(thread)
    if not state.next:
        print("=== 图已执行完毕(无需工具调用)===")
        return

    print(f"\n=== 图已暂停,下一步: {state.next} ===")

    # 显示工具调用详情供人类审查
    last_msg = state.values["messages"][-1]
    if hasattr(last_msg, 'tool_calls'):
        print("\n待审批的工具调用:")
        for tc in last_msg.tool_calls:
            print(f"  工具: {tc['name']}")
            print(f"  参数: {tc['args']}")

    # 等待人类审批
    approval = input("\n是否批准执行工具?(yes/no): ")

    if approval.lower() == "yes":
        print("\n=== 审批通过,继续执行 ===")
        for event in graph.stream(None, thread, stream_mode="values"):
            event['messages'][-1].pretty_print()
    else:
        print("=== 操作已被用户取消 ===")

# 使用示例
run_with_approval("Multiply 2 and 3", thread_id="session-001")

Using breakpoints via LangGraph API (SDK)

In production environments, graphs are typically deployed as LangGraph services (vialanggraph devStart a local server, or deploy to LangSmith Studio). At this point you passLangGraph SDKWhen interacting with graphs, the logic of breakpoints is exactly the same:

from langgraph_sdk import get_client
from langchain_core.messages import HumanMessage

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

# 创建新 thread
thread = await client.threads.create()

# 流式运行图,并在 API 层面指定断点
# 注意:也可以直接在 compile() 里设置,这里是另一种方式
initial_input = {"messages": HumanMessage(content="Multiply 2 and 3")}

async for chunk in client.runs.stream(
    thread["thread_id"],
    assistant_id="agent",
    input=initial_input,
    stream_mode="values",
    interrupt_before=["tools"],      # 在 API 层面设置断点
):
    print(f"Event type: {chunk.event}")
    messages = chunk.data.get('messages', [])
    if messages:
        print(messages[-1])

# 审批后继续(传入 None)
async for chunk in client.runs.stream(
    thread["thread_id"],
    "agent",
    input=None,                        # None = 从检查点恢复
    stream_mode="values",
    interrupt_before=["tools"],
):
    messages = chunk.data.get('messages', [])
    if messages:
        print(messages[-1])
How to start local development server

in module/studioRun in directorylanggraph dev, after starting the local development server, you will get:
API: http://127.0.0.1:2024
Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
In the Studio UI, you can visually see the execution status of the graph, breakpoint triggering and manual recovery, which is very suitable for debugging.

Summary of core concepts

Breakpoints core points
Set breakpoint
  • compile(interrupt_before=[...])
  • compile(interrupt_after=[...])
  • • Must be provided together withcheckpointer
  • • Multiple node names can be specified
check status
  • graph.get_state(config)
  • state.nextView nodes to be executed
  • state.valuesView full status
  • • Check tool call parameters
Resume execution
  • graph.invoke(None, config)
  • graph.stream(None, config)
  • Nonemeans "recover from checkpoint"
  • • If rejected, it will not be called directly.
Practical application scenarios
  • • Approval of dangerous tool calls
  • • Validate intermediate conclusions of AI
  • • Compliance review process
  • • Manual quality control gates
Understand in one sentence
Breakpoint = Insert a "wait for human nod" checkpoint in the diagram.interrupt_beforeCheck before going under the knife,interrupt_afterReview after completion.MemorySaverIt's the notebook that "remembers where the diagram was executed".invoke(None)It's an "OK, go ahead" command.
Learning Path: Next Steps

After mastering static breakpoints, Lesson 3 (Edit State & Human Feedback) will teach you how to pause the graphModifyStatus - not just "release" or "reject", but "I'll change the parameters before continuing." Lesson 4(Dynamic Breakpoints) will introduce how to selectively trigger interrupts based on runtime conditions instead of pausing every time.