LangGraph-interrupt的作用

 注:以下所提的“文档”,是指LangGraph官方指南(https://langchain-ai.github.io/langgraph/guides/),参考版本是0.6.8。

以下代码的开发环境

[project]
name = "my-langgraph"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "dotenv>=0.9.9",
    "langchain>=0.3.27",
    "langchain-openai>=0.3.33",
    "langfuse>=3.5.0",
    "langgraph>=0.6.7",
    "langgraph-checkpoint-sqlite>=2.0.11",
    "numpy>=2.3.3",
]

在 LangGraph 中,Command 是一个较新的工具/类型,用来增强节点(node)之间的控制流能力,使得多智能体(multi-agent)或更复杂的工作流更灵活、更动态。下面我给你一个较全面的介绍,包括 Command 的动机、用法、局限,以及在实际多代理系统中的角色。

LangGraph 中,所谓的 human assistance 机制,本质上就是让 Agent 在执行过程中「暂停」,把执行权交还给用户,等用户提供额外输入后再继续运行。

具体来说:

一、 interrupt 的作用

  • tool 方法里调用 interrupt(...),会触发一个「中断点」。
  • 运行图(graph execution)会在这个位置停止,不再继续往下执行。
  • 系统会把你传给 interrupt 的提示(例如需要人类补充的参数、说明、决策点)返回给调用方。
  • 外部调用方(通常是应用开发者包装的 UI)就能把这个请求转交给用户,等待他们输入。
  • 当用户提供了信息后,再继续从这个中断点恢复执行。

所以它的作用是:
在工具调用过程中插入人类确认或补充环节,让 LLM + 人类形成协作。

二、 应用场景

  • 需要用户确认:比如转账类操作,先生成交易 SQL,但要用户确认金额是否正确。
  • 缺少关键参数:工具运行需要的参数模型没推出来,必须人工补充。
  • 敏感操作保护:例如删除数据或执行系统命令,必须经过人类确认后才继续。

三、 执行流程示意

from langgraph.prebuilt import ToolNode
from langgraph.types import interrupt

def risky_tool(amount: float):
    # 在这里插入人类确认
    user_confirmation = interrupt({"msg": f"请确认是否真的要转账 {amount} 元?"})
    if user_confirmation != "yes":
        return "已取消操作"
    return f"已完成转账 {amount} 元"

行时:

  1. Agent 执行到 interrupt → 中断 → 把提示传回前端。
  2. 前端显示「请确认是否真的要转账 100 元?」
  3. 用户输入 "yes" → 传回 LangGraph → 从中断点恢复 → 执行后续逻辑。

从中断处开始再次运行,可以指定node的名称或checkpoint_id值。

执行前要先通过update_state方法更新状态,如果是指定node的名称,则使用方法中的as_node参数。

如果是用checkpoint_id,则要通过config。并且要用update_state返回的config来运行。

再次运行时的输入值必需为None。

四、例子程序

import uuid
from typing import TypedDict, Optional

from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import interrupt

# -------------------------
# 1) 定义 State
# -------------------------
class MyState(TypedDict, total=False):
    name: Optional[str]
    greeting: Optional[str]

# -------------------------
# 2) 节点:在这里插入人类中断
# -------------------------
def ask_name(state: MyState) -> MyState:
    if not state.get("name"):
        # 抛出中断请求;返回值会在“恢复执行”时注入给 name
        name = interrupt({"prompt": "请输入你的名字"})
        return {"name": name}
    return {}

def build_greeting(state: MyState) -> MyState:
    name = state.get("name") or "陌生人"
    return {"greeting": f"你好,{name}!(来自中断恢复后的节点执行)"}

# -------------------------
# 3) 构图 + 使用 MemorySaver()
# -------------------------
g = StateGraph(MyState)
g.add_node("ask_name", ask_name)
g.add_node("build_greeting", build_greeting)
g.add_edge(START, "ask_name")
g.add_edge("ask_name", "build_greeting")
g.add_edge("build_greeting", END)

app = g.compile(checkpointer=MemorySaver())

thread_id = str(uuid.uuid4())  # 标识一次会话

# -------------------------
# 4) 终端 Driver(同一进程内“暂停-恢复”)
# -------------------------
def run_cli(initial_name: Optional[str] = None):
    cfg = {"configurable": {"thread_id": thread_id}}
    print("=== LangGraph + MemorySaver: CLI interrupt demo ===")

    # 首次输入(可能触发中断)
    inputs: MyState = {}
    if initial_name:
        inputs["name"] = initial_name

    def drive(user_resume_value=None, as_node:str=None, checkpoint: str=None):
        need_prompt = None
        finished = False
        state_snapshot = None

        # 保证 payload 必为 dict 或 None
        if user_resume_value is None:
            payload = inputs if not app.get_state(cfg).values else None
        elif isinstance(user_resume_value, dict):
            payload = user_resume_value
        else:
            # 将字符串用户输入包装为期望的状态更新
            payload = {"name": str(user_resume_value)}

        if as_node:
            app.update_state(cfg, payload, as_node=as_node)
            payload = None
            print("从指定node开始运行")

        new_cfg = cfg
        if checkpoint:
            cfg.get("configurable")["checkpoint_id"] = checkpoint
            cfg.get("configurable")["checkpoint_ns"] = ""
            new_cfg = app.update_state(cfg, payload)
            payload = None
            print("从指定checkpoint开始运行")

        new_ckpt = None
        for event in app.stream(payload, new_cfg, ):
            # 新版中断事件通道
            # 获取最终值的时候,只能用thread_id,如果还有checkpoint_id,则只能获取那个节点的值
            state_snapshot = app.get_state({"configurable": {"thread_id": thread_id}}).values
            if "__interrupt__" in event:
                need_prompt = (event["__interrupt__"][0].value or {}).get("prompt", "需要输入:")
                s = app.get_state(new_cfg)
                new_ckpt = s.config.get("configurable").get("checkpoint_id")
            elif "__end__" in event:
                finished = True

        hist = list(app.get_state_history(new_cfg))
        for i, st in enumerate(hist):
            cp = (st.config or {}).get("configurable", {}).get("checkpoint_id", None)
            print(f"[{i}] node_next={st.next!r}  checkpoint_id={cp!r} ")

        return need_prompt, finished, state_snapshot, new_ckpt

    prompt, done, snap, ckpt = drive(user_resume_value=None)

    while not done and prompt:
        print(f"[当前 checkpoint_id: {ckpt}]")
        user_value = input(f"{prompt} > ").strip()
        # 注意:把用户输入作为“下一次 stream 的输入”,将作为 interrupt(...) 的返回值注入
        if ckpt:
            prompt, done, snap, ckpt = drive(user_resume_value=user_value, checkpoint=ckpt)
        else:
            prompt, done, snap, ckpt = drive(user_resume_value=user_value, as_node="ask_name")

    print("\n--- 执行完成 ---")
    print("最终状态:", snap)
    if snap and snap.get("greeting"):
        print("输出:", snap["greeting"])

def my_interrupt():
    run_cli(initial_name=None)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值