【LangGraph】持久化实现的三大能力——人机交互

【LangGraph】新篇章:LangGraph 持久化的三大应用能力*重点*

上一章-> 【LangGraph】持久化实现的三大能力------记忆

在自动化的 AI 工作流中,有时候"让机器全自动"并不是最好的选择。关键操作需要人类确认,敏感数据需要人工审查,用户输入需要反复校验。LangGraph 内置的 中断(Interrupt) 机制,让你可以在任意节点暂停图执行,等待人工干预,然后精确恢复

本文带你掌握四种最实用的人机交互模式


前言

在构建 AI Agent 或自动化系统时,我们常常需要在关键环节暂停执行,等待人工反馈。例如:

  • 执行大额转账前,请求人工审批。
  • 生成重要文案后,允许编辑修改再发送。
  • 调用第三方 API 前,让用户确认参数。

LangGraph 通过 中断(Interrupt)命令(Command) 机制,优雅地实现了这一需求。本文将详细介绍四种典型应用模式,并提供可直接运行的代码示例。


一、什么是人机交互?

人机交互(Human-in-the-loop,简称 HITL)

是指在自动化流程中,系统在特定节点暂停,等待人工输入、审查或决策。

这种模式结合了 AI 的自动化能力和人类的判断力,

特别适合:

  • 高风险操作(支付、删除数据、发送邮件)
  • 需要人工质量把控的内容生成(报告、文案、代码)
  • 用户输入需要格式校验或合规检查的场景

在 LangGraph 中,核心 API 只有两个:

  • interrupt():暂停图执行,返回人工提供的值。
  • Command(resume=value)Command(goto=next_node):恢复执行并指定下一步

二、四种人机交互应用模式

2.1 批准或拒绝(Approve / Reject)

典型场景

财务 Agent 检测到一笔可疑交易,需要人工确认是否放行

实现思路

在关键操作前插入一个审批节点,调用 interrupt() 展示操作信息,等待用户输入"批准"或"拒绝"

根据输入,路由到不同分支

代码示例

python 复制代码
from typing import TypedDict, Literal, Optional
from langgraph.types import interrupt, Command

class ApprovalState(TypedDict):
    transaction_id: str
    amount: float
    recipient: str
    status: Optional[Literal["pending", "approved", "rejected"]]

def approval_node(state: ApprovalState):
    decision = interrupt({
        "question": f"是否批准以下交易?",
        "transaction": f"ID: {state['transaction_id']}, 金额: {state['amount']}元, 收款方: {state['recipient']}",
        "options": "请输入 '批准' 或 '拒绝'"
    })
    if decision == "批准":
        return Command(goto="execute_payment")
    else:
        return Command(goto="cancel_payment")

def execute_payment(state: ApprovalState):
    # 实际执行支付逻辑
    print(f" 交易 {state['transaction_id']} 已执行")
    return {"status": "approved"}

def cancel_payment(state: ApprovalState):
    print(f"交易 {state['transaction_id']} 已取消")
    return {"status": "rejected"}
用户恢复执行(通过外部代码调用)

# 当图暂停后,用户输入决定
graph.invoke(Command(resume="批准"), config=config)

2.2 查看和编辑状态(Review & Edit State)

典型场景

AI 生成了一封营销邮件草稿,人工可以修改后确认发送

实现思路

在生成节点后插入审查节点,让用户修改中间状态(例如文本内容)

修改后的内容会覆盖原有字段,继续后续节点

代码示例

python 复制代码
class DraftState(TypedDict):
    subject: str
    body: str

def generate_draft(state: DraftState):
    # 模拟 LLM 生成邮件草稿
    return {
        "subject": "限时优惠:全场8折",
        "body": "亲爱的用户,本周末所有商品8折,快来抢购吧!"
    }

def review_draft(state: DraftState):
    updated = interrupt({
        "instruction": "请修改以下邮件草稿",
        "current_subject": state["subject"],
        "current_body": state["body"],
        "提示": "你可以修改主题和正文,确认后返回 JSON 格式。"
    })
    # 假设用户返回 {"subject": "...", "body": "..."}
    return {
        "subject": updated.get("subject", state["subject"]),
        "body": updated.get("body", state["body"])
    }

def send_email(state: DraftState):
    print(f"发送邮件\n主题:{state['subject']}\n正文:{state['body']}")
    return state
用户输入示例(恢复时传入修改内容)

python
edited = {"subject": "【紧急】全场8折仅剩2天", "body": "亲爱的用户,折扣即将结束..."}
graph.invoke(Command(resume=edited), config=config)

2.3 在工具中中断(Tool Interrupts)

典型场景

一个可以发送邮件的工具,在真正调用邮件 API 之前,请求人类确认收件人、主题和内容

实现思路

直接在工具函数内部调用 interrupt()

当图执行到该工具时,会暂停并等待人工输入

人工可以修改参数或取消操作

代码示例

python 复制代码
from langchain_core.tools import tool

@tool
def send_email_tool(to: str, subject: str, body: str) -> str:
    """发送邮件给指定收件人"""
    confirmation = interrupt({
        "action": "即将发送邮件",
        "to": to,
        "subject": subject,
        "body_preview": body[:100] + ("..." if len(body) > 100 else ""),
        "message": "请确认或修改邮件信息(输入 '发送' 确认,或输入修改后的 JSON)"
    })
    # 如果用户返回字典,则覆盖原参数
    if isinstance(confirmation, dict):
        to = confirmation.get("to", to)
        subject = confirmation.get("subject", subject)
        body = confirmation.get("body", body)
        confirmed = confirmation.get("confirm", False)
    else:
        confirmed = (confirmation == "发送")
    
    if confirmed:
        # 假设真实发送逻辑
        print(f" 已发送邮件至 {to}")
        return f"邮件已发送至 {to}"
    else:
        return "用户取消了邮件发送"

效果:当 Agent 决定调用 send_email_tool 时

图会在工具内部暂停,用户返回确认信息后继续


2.4 验证人类输入(Validating Human Input)

典型场景

收集用户年龄,但要求必须是int类型并且是正数

如果输入无效,重复提示直到正确

实现思路

在一个循环内反复调用 interrupt(),每次检查输入是否合法

不合法则更新提示语,重新 interrupt(),合法则返回并继续

代码示例

python 复制代码
class FormState(TypedDict):
    age: int | None
    retries: int

def ask_age_node(state: FormState):
    prompt = "请输入你的年龄(正整数):"
    while True:
        answer = interrupt(prompt)
        if isinstance(answer, int) and answer > 0:
            return {"age": answer}
            prompt = f"'{answer}' 不是有效的年龄,请重新输入(正整数):"
    # 超时或多次失败后,可设置默认值或抛异常
    return {"age": 18}

用户体验:每次输入非法,系统会返回更明确的提示,直到正确为止


三、LangGraph 中断机制核心 API

  1. interrupt(value)

作用:暂停当前图的执行,并等待外部人工输入。value 是一个 JSON 可序列化的对象,用于向人类展示信息(如问题、操作详情、选项等),该函数会返回人工提供的值

示例:

python 复制代码
confirm = interrupt({"msg": "是否批准此交易?", "details": "转账1000元"})
# 图停在这里,等待外部调用 Command(resume=...)
# 恢复后 confirm 的值就是外部传入的内容

重要:interrupt() 只能在编译图时传入了 checkpointer 的情况下使用,因为图需要将暂停点状态保存到检查点中,否则无法恢复执行。

  1. Command(resume=value)
    作用:从外部的 invoke/stream 调用中恢复一个被 interrupt() 暂停的图。value 会成为 interrupt() 的返回值,图将从暂停点继续执行。

示例:

python 复制代码
# 在外部代码中
graph.invoke(Command(resume="批准"), config=config)
# 此时图中 interrupt() 会返回 "批准"
  1. Command(goto=node_name)
    作用:在节点函数内部动态决定下一个要执行的节点,覆盖图中预定义的边。通常结合 interrupt() 使用,根据人类输入来路由到不同的处理分支。

示例:

python 复制代码
def approval_node(state):
    decision = interrupt("批准或拒绝?")
    if decision == "批准":
        return Command(goto="execute_payment")
    else:
        return Command(goto="cancel_payment")
  1. 重要注意事项:
  • 必须配置 checkpointer:

    所有涉及 interrupt() 的图,在编译时都必须传入 checkpointer(如 InMemorySaver、PostgresSaver),否则会抛出异常

  • 恢复时需使用同一个 thread_id:Command(resume=...) 必须携带与暂停时相同的 config(尤其是 thread_id),否则无法定位暂停点

  • 中断可以嵌套:

    你可以在一个节点中多次调用 interrupt(),也可以在多个节点中分别设置中断,LangGraph 会依次等待人类输入

四、完整工作流示例:结合中断与条件路由

以下是一个模拟"人工审批 --> 执行操作 --> 记录日志"的线性流程,展示了如何在实际图中使用中断

python 复制代码
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    task: str
    approved: bool
    result: str

def ask_approval(state: State):
    decision = interrupt({
        "task": state["task"],
        "message": "请批准或拒绝该任务(输入 yes/no)"
    })
    approved = (decision.lower() == "yes")
    return {
    		"approved": approved
    }

def execute_task(state: State):
    if state["approved"]:
        print(f" 执行任务:{state['task']}")
        return {"result": "成功"}
    else:
        print(f" 任务被拒绝:{state['task']}")
        return {"result": "已取消"}

builder = StateGraph(State)
builder.add_node("ask_approval", ask_approval)
builder.add_node("execute_task", execute_task)
builder.add_edge(START, "ask_approval")
builder.add_edge("ask_approval", "execute_task")
builder.add_edge("execute_task", END)

checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)

# 执行图,会在 ask_approval 处暂停
config = {"configurable": {"thread_id": "123"}}
for event in graph.stream({"task": "删除临时文件"}, config):
    print(event)

# 用户输入 yes 后恢复
graph.invoke(Command(resume="yes"), config=config)

五、总结

我们可以通过下表进行一个总结:

模式 核心能力 关键机制 典型场景
批准 / 拒绝 在关键操作前进行人工决策控制 interrupt + Command(resume) 转账、删除数据、发布内容
查看 / 编辑状态 允许人工修改中间状态数据 interrupt + 状态更新 邮件草稿、报告生成、代码审查
工具中断 在工具调用前插入人工干预 工具内部 interrupt 发送消息、调用外部 API
输入验证 循环获取并校验用户输入 interrupt + 循环校验 表单填写、参数配置

一句话interrupt() 保存现场,Command(resume=...) 恢复执行,人机协作无缝衔接

通过合理使用 LangGraph 的中断功能,我们可以让自动化系统在关键节点"停下来问问人",既保留了 AI 的效率,又引入了人类的判断和监督

今天的分享先到这里我们下期再见~

相关推荐
翔云1234562 小时前
大模型部署全流程深度解析
人工智能·ai·大模型
沐风老师3 小时前
开发AI机器人操作系统用什么编程语言?
人工智能·ai编程·机器人操作系统
念威3 小时前
弹幕互动游戏AI无人直播方案 - 可遇AI无人直播助手
人工智能·游戏
BizViewStudio3 小时前
甄选方法:2026 企业新媒体代运营的短视频精细化运营与流量转化技巧
大数据·网络·人工智能·媒体
咖啡星人k3 小时前
Vibe Coding 实践观察:从概念到云端开发工具的探索
人工智能
qq_283720053 小时前
Python+LangChain 入门到实战全教程+ 企业级案例
人工智能·langchain·#大模型·#llm·#rag·#ai 应用开发·#智能体
.柒宇.3 小时前
AI掘金头条项目部署实践指南
linux·运维·python·fastapi
WL_Aurora3 小时前
Python 算法基础篇之树和二叉树
python·算法
码点滴3 小时前
DeepSeek-V4 全景地图:两款模型、三种模式,你该怎么选?
人工智能·架构·大模型·deepseek-v4