LangGraph 人工干预:Human-in-the-loop 机制详解

在构建 AI Agent 时,如何让机器智能与人类智慧完美协作?本文将深入讲解 LangGraph 中的人工干预机制,带你实现真正的人机协作 AI 助手。

前言

作为一个经常折腾 AI Agent 的开发者,我一直在思考一个问题:如何在自动化和人工控制之间找到平衡?

传统的 AI Agent 是完全自主的------输入指令,AI 自己决策、执行、返回结果。但在真实业务场景中,这种"全自动"模式往往不够靠谱:

  • 涉及金钱交易的操作需要人工审批
  • 敏感内容生成需要人工审核
  • 复杂问题需要专家知识辅助
  • AI 犯错了需要人工及时纠正

这时候,Human-in-the-loop(人在环中) 机制就显得尤为重要。LangGraph 作为一个专门为 Agent 设计的编排框架,提供了完善的人工干预支持。

核心概念:三分钟搞懂 interrupt、Command、Human-in-the-loop

1. interrupt:让 AI "暂停"

interrupt 是 LangGraph 提供的核心中断函数。看这个名字就知道,它的作用就是"中断"------让正在执行的图暂停下来。

python 复制代码
from langgraph.types import interrupt

# 在工具内部调用 interrupt
human_response = interrupt({
    "query": "请确认是否执行此操作",
    "status": "waiting_for_human_input"
})

执行流程是这样的:

  1. AI 在工具中调用 interrupt
  2. 整个图的执行立即暂停
  3. 状态被保存到检查点(checkpoint)
  4. 等待外部触发恢复

2. Command:让 AI "继续"

Command 是恢复执行的关键。看代码:

python 复制代码
from langgraph.types import Command

# 恢复被中断的执行,传递人工回复
command = Command(resume={"data": "人工确认内容"})
graph.stream(command, config)

Command 有两种模式:

  • resume 模式:从中断处继续执行,传入数据
  • goto 模式:跳转到指定节点重新开始

3. Human-in-the-loop:人在环中

这个概念本质上是一种人机协作模式

css 复制代码
用户 → AI 处理 → [需要人工?] → 是 → 暂停等待人工输入 → 人工确认 → AI 继续
                ↓否
              返回结果

典型应用场景:

  • 审批类 Agent:AI 初筛 + 人工终审
  • 客服类 Agent:AI 回答 + 人工接管复杂问题
  • 内容生成 Agent:AI 生成 + 人工审核敏感内容
  • 数据分析 Agent:AI 分析 + 人工解读关键决策

实战代码:手把手实现人工干预

下面是一个完整可运行的人工干预示例。基于之前的聊天机器人,增加人工干预功能。

完整代码

python 复制代码
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import AIMessage
from langchain_core.tools import tool
from langchain_community.tools.tavily_search import TavilySearchResults
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command, interrupt
import os

# 1. 定义状态类型
class State(TypedDict):
    messages: Annotated[list, add_messages]

# 2. 定义人工协助工具(核心!)
@tool
def human_assistance(query: str) -> str:
    """
    请求人工协助。
    AI 遇到复杂问题时,通过此工具暂停执行等待人工输入。
    """
    # 👇 这里是关键:调用 interrupt 暂停执行
    human_response = interrupt({
        "query": query,
        "status": "waiting_for_human_input"
    })
    return human_response["data"]

# 3. 创建图
def create_graph():
    graph_builder = StateGraph(State)
    
    # 初始化模型
    llm = ChatOpenAI(
        model="Qwen/Qwen3-Next-80B-A3B-Instruct",
        openai_api_key=os.getenv("SILICONFLOW_API_KEY"),
        openai_api_base="https://api.siliconflow.cn/v1",
        temperature=0.7
    )
    
    # 绑定工具
    tools = [TavilySearchResults(max_results=2), human_assistance]
    llm_with_tools = llm.bind_tools(tools)
    
    # 聊天节点
    def chatbot(state: State):
        message = llm_with_tools.invoke(state["messages"])
        # 禁用并行工具调用,避免恢复时重复执行
        assert len(message.tool_calls) <= 1
        return {"messages": [message]}
    
    # 添加节点和边
    graph_builder.add_node("chatbot", chatbot)
    graph_builder.add_node("tools", ToolNode(tools=tools))
    graph_builder.add_edge(START, "chatbot")
    graph_builder.add_conditional_edges("chatbot", tools_condition)
    graph_builder.add_edge("tools", "chatbot")
    
    # 👇 关键:配置检查点,支持中断和恢复
    memory = MemorySaver()
    return graph_builder.compile(checkpointer=memory)

# 4. 运行主函数
def main():
    graph = create_graph()
    config = {"configurable": {"thread_id": "1"}}
    
    print("🤖 人工干预演示启动!")
    print("输入 'resume' 恢复执行,输入 'quit' 退出\n")
    
    human_response = None
    is_waiting = False
    
    while True:
        # 获取输入
        if is_waiting:
            user_input = input("人工回复: ")
        else:
            user_input = input("用户: ")
        
        if user_input.lower() in ["quit", "exit", "q"]:
            break
        
        # 恢复执行
        if user_input.lower() == "resume" and is_waiting:
            if human_response:
                command = Command(resume={"data": human_response})
                graph.stream(command, config)
                human_response = None
                is_waiting = False
            continue
        
        # 保存人工回复
        if is_waiting:
            human_response = user_input
            print("已记录回复,输入 'resume' 继续\n")
            continue
        
        # 正常对话
        for event in graph.stream(
            {"messages": [{"role": "user", "content": user_input}]},
            config,
            stream_mode="values"
        ):
            if "messages" in event:
                msg = event["messages"][-1]
                if isinstance(msg, AIMessage) and msg.content:
                    print(f"助手: {msg.content}")
        
        # 检查是否中断
        snapshot = graph.get_state(config)
        if snapshot.next:
            is_waiting = True
            print("\n⏸️ 已暂停,等待人工输入...\n")

if __name__ == "__main__":
    main()

核心代码解析

1. interrupt 的正确使用方式

python 复制代码
@tool
def human_assistance(query: str) -> str:
    # interrupt 只能在 @tool 装饰的函数内部调用!
    human_response = interrupt({
        "query": query,
        "status": "waiting_for_human_input"
    })
    return human_response["data"]

注意

  • interrupt 必须在工具函数内部调用
  • 调用后函数会立即返回,后续代码不会执行
  • 恢复后,interrupt 后的代码会继续执行

2. Command 的正确使用方式

python 复制代码
# 恢复执行,传递数据
command = Command(resume={"data": "人工回复内容"})
graph.stream(command, config)

# 或者跳转到指定节点
command = Command(goto="chatbot")
graph.stream(command, config)

3. 检查点配置(必读!)

python 复制代码
# 没有检查点 = 无法中断恢复!
graph = graph_builder.compile()  # ❌ 错误

# 有检查点 = 支持中断恢复
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)  # ✅ 正确

这是很多人容易踩的坑!没有配置检查点,调用 interrupt 会报错或者无法正确保存状态。

4. 禁用并行工具调用

python 复制代码
def chatbot(state: State):
    message = llm_with_tools.invoke(state["messages"])
    # 强制单工具调用,避免恢复时重复执行
    assert len(message.tool_calls) <= 1
    return {"messages": [message]}

运行效果展示

启动程序,输入需要人工协助的请求:

踩坑记录:这些坑我都踩过

❌ 错误1:忘记配置检查点

yaml 复制代码
ValueError: Cannot interrupt - no checkpointer configured

解决方案

python 复制代码
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)

❌ 错误2:在非工具函数中调用 interrupt

sql 复制代码
RuntimeError: interrupt() can only be called inside a tool

解决方案 :把中断逻辑封装到 @tool 装饰的函数中

❌ 错误3:恢复时数据格式错误

解决方案

python 复制代码
# 正确格式
command = Command(resume={"data": "内容"})

❌ 错误4:中断状态检测时机不对

应该在 stream 执行完成后检测:

python 复制代码
# 执行 stream 后
for event in graph.stream(user_input, config):
    pass

# 然后检查
snapshot = graph.get_state(config)
if snapshot.next:
    # 中断了

总结

本文详细介绍了 LangGraph 的人工干预机制:

核心知识点

概念 作用
interrupt 暂停图执行,等待外部输入
Command 恢复执行,可传递数据
checkpointer 保存状态,支持中断恢复

适用场景

  • 需要人工审批的关键操作
  • 专业领域问答需要人工介入
  • 敏感内容审核
  • 复杂决策辅助
  • AI 错误的人工纠正

最佳实践

  1. 合理设计触发条件:不要每个请求都中断,会降低体验
  2. 提供清晰的提示:让用户知道现在需要做什么
  3. 配置超时机制:避免用户忘记恢复导致挂起
  4. 做好错误处理:网络异常等情况要有兜底

LangGraph 的人工干预机制让 AI Agent 不再是"黑盒子",而是可以被人类监督和控制的可靠系统。这对于构建企业级应用至关重要。


扩展阅读


本文作者:AI探索者 原文链接:juejin.cn/post/...

相关推荐
Rust研习社2 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒2 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro3 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax3 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH3 小时前
Koa和Express的区别
后端
MariaH3 小时前
Koa框架的使用
后端
luckdewei4 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某6 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy6 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom6 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github