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/...

相关推荐
神奇小汤圆1 小时前
MySQL的10种高级SQL,性能飞升
后端
神奇小汤圆1 小时前
Java并发核心:你以为AQS很复杂?无非是"两个队列"和"一个状态"
后端
shark_chili2 小时前
Spring AI Alibaba 入门与实战:一文构建智能天气查询助手
后端
Java编程爱好者2 小时前
Java 高频面试题总结(2026通用版)
后端
Java水解2 小时前
Spring Boot 视图层与模板引擎
spring boot·后端
重庆穿山甲2 小时前
Java开发者的大模型入门:Spring AI Alibaba组件全攻略(二)
前端·后端
Java水解2 小时前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
重庆穿山甲2 小时前
Java开发者的大模型入门:Spring AI Alibaba组件全攻略(一)
前端·后端
Java编程爱好者2 小时前
小米二面:std::map和std::unordered_map谁更快?别只知道哈希表
后端