在构建 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 提供的核心中断函数。看这个名字就知道,它的作用就是"中断"——让正在执行的图暂停下来。
from langgraph.types import interrupt
# 在工具内部调用 interrupt
human_response = interrupt({
"query": "请确认是否执行此操作",
"status": "waiting_for_human_input"
})
执行流程是这样的:
- AI 在工具中调用
interrupt - 整个图的执行立即暂停
- 状态被保存到检查点(checkpoint)
- 等待外部触发恢复
2. Command:让 AI "继续"
Command 是恢复执行的关键。看代码:
from langgraph.types import Command
# 恢复被中断的执行,传递人工回复
command = Command(resume={"data": "人工确认内容"})
graph.stream(command, config)
Command 有两种模式:
- resume 模式:从中断处继续执行,传入数据
- goto 模式:跳转到指定节点重新开始
3. Human-in-the-loop:人在环中
这个概念本质上是一种人机协作模式:
用户 → AI 处理 → [需要人工?] → 是 → 暂停等待人工输入 → 人工确认 → AI 继续
↓否
返回结果
典型应用场景:
- 审批类 Agent:AI 初筛 + 人工终审
- 客服类 Agent:AI 回答 + 人工接管复杂问题
- 内容生成 Agent:AI 生成 + 人工审核敏感内容
- 数据分析 Agent:AI 分析 + 人工解读关键决策
实战代码:手把手实现人工干预
下面是一个完整可运行的人工干预示例。基于之前的聊天机器人,增加人工干预功能。
完整代码
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 的正确使用方式
@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 的正确使用方式
# 恢复执行,传递数据
command = Command(resume={"data": "人工回复内容"})
graph.stream(command, config)
# 或者跳转到指定节点
command = Command(goto="chatbot")
graph.stream(command, config)
3. 检查点配置(必读!)
# 没有检查点 = 无法中断恢复!
graph = graph_builder.compile() # ❌ 错误
# 有检查点 = 支持中断恢复
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory) # ✅ 正确
这是很多人容易踩的坑!没有配置检查点,调用 interrupt 会报错或者无法正确保存状态。
4. 禁用并行工具调用
def chatbot(state: State):
message = llm_with_tools.invoke(state["messages"])
# 强制单工具调用,避免恢复时重复执行
assert len(message.tool_calls) <= 1
return {"messages": [message]}
运行效果展示
启动程序,输入需要人工协助的请求:
踩坑记录:这些坑我都踩过
❌ 错误1:忘记配置检查点
ValueError: Cannot interrupt - no checkpointer configured
解决方案:
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
❌ 错误2:在非工具函数中调用 interrupt
RuntimeError: interrupt() can only be called inside a tool
解决方案:把中断逻辑封装到 @tool 装饰的函数中
❌ 错误3:恢复时数据格式错误
解决方案:
# 正确格式
command = Command(resume={"data": "内容"})
❌ 错误4:中断状态检测时机不对
应该在 stream 执行完成后检测:
# 执行 stream 后
for event in graph.stream(user_input, config):
pass
# 然后检查
snapshot = graph.get_state(config)
if snapshot.next:
# 中断了
总结
本文详细介绍了 LangGraph 的人工干预机制:
核心知识点
| 概念 | 作用 |
|---|---|
interrupt | 暂停图执行,等待外部输入 |
Command | 恢复执行,可传递数据 |
checkpointer | 保存状态,支持中断恢复 |
适用场景
- 需要人工审批的关键操作
- 专业领域问答需要人工介入
- 敏感内容审核
- 复杂决策辅助
- AI 错误的人工纠正
最佳实践
- 合理设计触发条件:不要每个请求都中断,会降低体验
- 提供清晰的提示:让用户知道现在需要做什么
- 配置超时机制:避免用户忘记恢复导致挂起
- 做好错误处理:网络异常等情况要有兜底
LangGraph 的人工干预机制让 AI Agent 不再是"黑盒子",而是可以被人类监督和控制的可靠系统。这对于构建企业级应用至关重要。
扩展阅读:
本文作者:AI探索者 原文链接:juejin.cn/post/...