注:以下所提的“文档”,是指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} 元"
行时:
- Agent 执行到 interrupt → 中断 → 把提示传回前端。
- 前端显示「请确认是否真的要转账 100 元?」
- 用户输入 "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)



3万+

被折叠的 条评论
为什么被折叠?



