05-LangGraph人工干预:实现人机协作的AI助手
## 前言
在构建 AI Agent 的过程中,我们经常会遇到一些复杂场景,比如:
- AI 需要在执行关键操作前获得人类确认
- 遇到不确定的问题时需要人工专业指导
- 需要人工审核 AI 生成的敏感内容
- 业务流程中需要人工决策的节点
这些场景都涉及到 **Human-in-the-loop(人在环中)** 的概念,即在 AI 自动执行的过程中引入人工干预机制。
LangGraph 作为 LangChain 团队推出的强大 Agent 编排框架,提供了完善的 **人工干预(Human-in-the-loop)** 功能。通过 `interrupt` 和 `Command` 两个核心 API,我们可以轻松实现人机协作的 AI 助手。
本文将详细介绍如何在 LangGraph 中实现人工干预功能,并提供完整的代码实现和详细解析。
---
## 核心概念
在深入代码之前,我们需要先理解三个核心概念:
### 1. interrupt - 中断执行
`interrupt` 是 LangGraph 提供的一个函数,用于在图执行过程中暂停当前流程,等待外部输入。
```python
from langgraph.types import interrupt
# 在工具中调用 interrupt,暂停执行并等待外部输入
human_response = interrupt({
"query": "请确认是否执行此操作",
"status": "waiting_for_human_input"
})
```
调用 `interrupt` 后,图的执行会暂停在当前节点,状态会被保存到检查点(checkpoint)中,可以随时恢复。
### 2. Command - 恢复执行
`Command` 是 LangGraph 的一个类型,用于恢复被中断的执行流程,并向图传递数据。
```python
from langgraph.types import Command
# 创建恢复命令,传入人工回复内容
command = Command(resume={"data": "人工确认执行"})
graph.stream(command, config)
```
`Command` 有两种模式:
- `resume`: 恢复被中断的执行,传递数据
- `goto`: 跳转到指定节点重新执行
### 3. Human-in-the-loop - 人在环中
Human-in-the-loop 是一种 AI 系统设计模式,核心思想是在 AI 自动处理大部分任务的同时,保留人类参与关键决策点的能力。
典型应用场景包括:
- **审批流程**:AI 预处理 + 人工最终审批
- **质量控制**:AI 生成内容 + 人工审核
- **复杂决策**:AI 建议 + 人工选择
- **错误处理**:AI 无法处理时转人工
---
## 代码实现
下面是基于 LangGraph 实现人工干预功能的完整代码:
```python
"""
LangGraph 教程 - 添加人工干预 (Human-in-the-loop)
本示例在 04添加记忆.py 的基础上,添加人工干预功能。
通过 interrupt 函数实现人机协作,允许在关键节点暂停执行并等待人工输入。
"""
# 过滤警告信息
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("ignore", category=UserWarning)
# 设置标准输入输出编码
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
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
from dotenv import load_dotenv
import os
# 加载环境变量
load_dotenv()
# ==================== 1. 定义状态 ====================
class State(TypedDict):
"""
定义图的状态结构。
messages: 消息列表,使用 add_messages reducer 函数
确保新消息追加到列表,而不是覆盖
"""
messages: Annotated[list, add_messages]
# ==================== 2. 定义人工协助工具 ====================
@tool
def human_assistance(query: str) -> str:
"""
请求人工协助工具。
当 AI 遇到复杂问题或需要人工确认时,调用此工具暂停执行,
等待人工输入后再继续。
Args:
query: 向人工提出的问题或请求
Returns:
人工提供的回复内容
"""
# interrupt 函数会暂停执行,等待外部输入
human_response = interrupt({
"query": query,
"status": "waiting_for_human_input"
})
return human_response["data"]
# ==================== 3. 创建图 ====================
def create_graph():
"""
创建并编译 StateGraph,添加人工干预功能。
Returns:
编译后的图对象
"""
# 创建图构建器
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
)
# 创建工具列表
search_tool = TavilySearchResults(max_results=2)
tools = [search_tool, human_assistance]
# 绑定工具到 LLM
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)
# 使用 LangGraph 预定义的 ToolNode
tool_node = ToolNode(tools=tools)
graph_builder.add_node("tools", tool_node)
# 添加边
graph_builder.add_edge(START, "chatbot")
# 添加条件边:从 chatbot 到 tools 或 END
graph_builder.add_conditional_edges(
"chatbot",
tools_condition,
)
# 添加边:从 tools 回到 chatbot(形成循环)
graph_builder.add_edge("tools", "chatbot")
# ==================== 关键:添加 MemorySaver 检查点 ====================
# 创建内存中的检查点(支持执行中断和恢复)
memory = MemorySaver()
# 编译图时传入 checkpointer
return graph_builder.compile(checkpointer=memory)
# ==================== 4. 运行聊天机器人 ====================
def stream_graph_updates(graph, user_input: str, config: dict):
"""
流式处理图更新,支持中断和恢复。
Args:
graph: 编译后的图对象
user_input: 用户输入的消息(或 Command 对象用于恢复)
config: 包含 thread_id 的配置字典
Returns:
是否发生了中断 (True/False)
"""
is_interrupted = False
for event in graph.stream(
user_input,
config,
stream_mode="values"
):
if "messages" in event:
last_message = event["messages"][-1]
# 只打印 AI 消息
if isinstance(last_message, AIMessage):
if last_message.content:
print("助手:", last_message.content)
# 检查是否有工具调用
if last_message.tool_calls:
for tool_call in last_message.tool_calls:
print(f"\n🔧 工具调用: {tool_call['name']}")
print(f" 参数: {tool_call['args']}")
# 检查是否发生中断
try:
snapshot = graph.get_state(config)
if snapshot.next:
is_interrupted = True
print(f"\n⏸️ 执行已暂停,等待人工输入...")
print(f" 当前节点: {snapshot.next}")
except Exception:
pass
return is_interrupted
def main():
"""主函数 - 运行交互式聊天机器人(支持人工干预)。"""
print("🤖 LangGraph 人工干预演示已启动!")
print("=" * 60)
print("提示:")
print(" - 输入 'quit'、'exit' 或 'q' 退出对话")
print(" - 输入 'resume' 恢复中断的执行")
print(" - 输入 'state' 查看当前状态")
print(" - 当 AI 调用 human_assistance 工具时,执行会暂停")
print(" 此时你可以输入回复内容,然后输入 'resume' 继续\n")
# 创建图
graph = create_graph()
# 配置
current_thread_id = "1"
config = {"configurable": {"thread_id": current_thread_id}}
# 用于存储人工回复
human_response = None
is_waiting_for_human = False
print(f"当前会话 ID: {current_thread_id}")
print("-" * 60)
while True:
try:
# 获取用户输入
if is_waiting_for_human:
user_input = input("人工回复: ")
else:
user_input = input("用户: ")
# 检查退出命令
if user_input.lower() in ["quit", "exit", "q"]:
print("\n👋 再见!")
break
# 检查状态
if user_input.lower() == "state":
snapshot = graph.get_state(config)
print(f"\n📊 当前会话状态 (thread_id={current_thread_id}):")
print(f" 消息数量: {len(snapshot.values.get('messages', []))}")
print(f" 下一步: {snapshot.next}")
print(f" 是否等待人工: {is_waiting_for_human}")
print("-" * 60)
continue
# 恢复执行
if user_input.lower() == "resume" and is_waiting_for_human:
if human_response:
# 创建 Command 对象恢复执行
command = Command(resume={"data": human_response})
print(f"\n▶️ 恢复执行,传入人工回复: {human_response}")
is_interrupted = stream_graph_updates(graph, command, config)
is_waiting_for_human = is_interrupted
if not is_interrupted:
human_response = None
print()
else:
print("⚠️ 请先输入人工回复内容,再输入 'resume'")
continue
# 如果正在等待人工输入,保存回复
if is_waiting_for_human:
human_response = user_input
print(f"✅ 已记录人工回复: {human_response}")
print(" 输入 'resume' 继续执行")
continue
# 正常对话流程
is_interrupted = stream_graph_updates(
graph,
{"messages": [{"role": "user", "content": user_input}]},
config
)
is_waiting_for_human = is_interrupted
if is_waiting_for_human:
print(" 请输入你的回复,然后输入 'resume' 继续")
print()
except KeyboardInterrupt:
print("\n\n👋 再见!")
break
except Exception as e:
print(f"发生错误: {e}")
import traceback
traceback.print_exc()
break
if __name__ == "__main__":
main()
```
---
## 关键代码解析
### 1. interrupt 函数的使用
`interrupt` 是实现人工干预的核心,它有以下特点:
```python
# 在工具内部调用 interrupt
@tool
def human_assistance(query: str) -> str:
# interrupt 会暂停整个图的执行
# 传入的参数会存储在中断状态中,可供后续使用
human_response = interrupt({
"query": query, # 传递给外部的问题
"status": "waiting_for_human_input" # 当前状态标识
})
# 当执行恢复时,这里会继续执行
# human_response 包含外部传入的数据
return human_response["data"]
```
**关键点**:
- `interrupt` 只能在工具(Tool)内部调用
- 调用后,当前图的状态会被保存到检查点
- 执行不会自动恢复,需要外部显式调用 `Command` 来恢复
### 2. Command 对象的使用
`Command` 用于恢复被中断的执行:
```python
from langgraph.types import Command
# 恢复模式:resume
command = Command(resume={"data": "人工回复内容"})
graph.stream(command, config)
# 跳转模式:goto(可以重新导向到任意节点)
command = Command(goto="chatbot")
graph.stream(command, config)
```
### 3. 检查点的重要性
要支持中断和恢复,必须配置检查点(Checkpoint):
```python
from langgraph.checkpoint.memory import MemorySaver
# 创建内存检查点
memory = MemorySaver()
# 编译图时传入 checkpointer
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. 用户发送请求后,AI 决定调用 `human_assistance` 工具
2. 执行暂停,显示等待人工输入
3. 人工输入回复后,输入 `resume` 恢复执行
4. AI 收到人工回复后,继续生成最终响应
---
## 踩坑记录
### 1. 忘记配置检查点
**错误**:运行时报错或中断后无法恢复
**原因**:没有在编译图时传入 `checkpointer`
**解决**:
```python
# 错误写法
graph = graph_builder.compile()
# 正确写法
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
```
### 2. 在非工具函数中调用 interrupt
**错误**:`interrupt() can only be called inside a tool`
**原因**:`interrupt` 只能在 `@tool` 装饰的函数内部调用
**解决**:将需要中断的逻辑封装到工具函数中
### 3. 并行工具调用导致重复执行
**错误**:恢复执行后,工具被调用多次
**原因**:启用了并行工具调用,恢复时可能重复执行
**解决**:禁用并行工具调用
```python
assert len(message.tool_calls) <= 1
```
### 4. Command 格式错误
**错误**:恢复执行后没有正确传递数据
**原因**:`Command` 格式不正确
**解决**:
```python
# 正确格式
command = Command(resume={"data": "人工回复"})
```
### 5. 检查中断状态时机不对
**错误**:无法正确检测到中断
**原因**:在 `stream` 之前就检查中断状态
**解决**:在 `stream` 执行完成后检查
```python
# 执行 stream 后检查
for event in graph.stream(user_input, config):
# 处理事件...
# 然后检查中断状态
snapshot = graph.get_state(config)
if snapshot.next:
# 发生了中断
```
---
## 总结
本文详细介绍了 LangGraph 中人工干预功能的实现,主要包含以下内容:
### 核心要点
1. **`interrupt` 函数**:在工具内部调用,暂停图执行并等待外部输入
2. **`Command` 对象**:用于恢复被中断的执行,可传递数据
3. **检查点(Checkpoint)**:必须配置才能支持中断和恢复
4. **禁用并行工具调用**:避免恢复时重复执行
### 应用场景
人工干预机制适用于:
- 需要人工确认的关键操作
- 专业领域的问答系统
- 内容审核和过滤
- 复杂决策辅助
- 错误处理和人工接管
### 最佳实践
1. 合理设计人工干预的触发条件,不要过度使用
2. 提供清晰的中断提示信息
3. 保存完整的对话上下文,确保恢复后连贯性
4. 考虑超时机制,避免无限等待
5. 实现优雅的错误处理
通过 LangGraph 的人工干预机制,我们可以构建更加可靠、可控的 AI Agent 系统,在自动化和人工监督之间找到完美的平衡点。
---
**相关文章推荐**:
- [LangGraph 快速入门:构建你的第一个 AI Agent]()
- [LangGraph 状态管理:如何设计高效的状态流]()
- [LangGraph 工具使用:让 AI 具备行动能力]()
---
*本文作者:码上AI_123*
*原文链接:https://blog.csdn.net/...*
更多推荐


所有评论(0)