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

相关推荐
zopple16 小时前
常见的 Spring 项目目录结构
java·后端·spring
cjy00011118 小时前
springboot的 nacos 配置获取不到导致启动失败及日志不输出问题
java·spring boot·后端
小江的记录本19 小时前
【事务】Spring Framework核心——事务管理:ACID特性、隔离级别、传播行为、@Transactional底层原理、失效场景
java·数据库·分布式·后端·sql·spring·面试
sheji341619 小时前
【开题答辩全过程】以 基于springboot的校园失物招领系统为例,包含答辩的问题和答案
java·spring boot·后端
程序员cxuan19 小时前
人麻了,谁把我 ssh 干没了
人工智能·后端·程序员
wuyikeer20 小时前
Spring Framework 中文官方文档
java·后端·spring
Victor35621 小时前
MongoDB(61)如何避免大文档带来的性能问题?
后端
Victor35621 小时前
MongoDB(62)如何避免锁定问题?
后端
wuyikeer21 小时前
Spring BOOT 启动参数
java·spring boot·后端
子木HAPPY阳VIP1 天前
Ubuntu 22.04 VMware 设置固定IP配置
人工智能·后端·目标检测·机器学习·目标跟踪