纲要
- 为什么要加入人工审核环节
- 模型的不确定性
- 高风险场景下的安全需求
- LangGraph 人机交互机制
interrupt():在节点中暂停执行Command(resume=...):恢复执行并传递人类反馈interrupt_before/interrupt_after:在边处打断update_state:直接修改图的状态
- 实战一:等待用户输入
- 构建带反馈节点的简单图
- 使用
interrupt暂停,Command恢复
- 实战二:审查工具调用
- 在工具执行前插入审查节点
- 支持批准、拒绝、修改参数
- 实战三:编辑图的状态
- 使用
interrupt_before在特定节点前暂停 - 通过
graph.update_state()修改状态后继续
- 使用
- 完整可运行代码
- 总结与相关度说明
为什么需要人工介入
当前的大语言模型基于概率生成,即使是最先进的模型也可能产生错误决策。在关键业务场景(如金融交易、医疗建议、自动发布内容)中,一次错误的工具调用或回复可能造成严重影响。因此,在生产级智能体系统中引入 人机交互 环节,让人类能够审批、编辑甚至拒绝智能体的决策,是保障系统可靠性的重要手段。
LangGraph 为此提供了原语级的支持,可以灵活地将人工审核嵌入到工作流的任意位置。
核心机制:interrupt 与 Command
LangGraph 的人机交互依赖于两个核心 API:
interrupt():在节点内部调用,暂停图的执行,并抛出一个需要人类处理的中断事件。Command(resume=...):外部输入反馈后,通过Command恢复执行,同时可以将人类提供的数据注入到图中。
此外,还可以在编译图时通过 interrupt_before 或 interrupt_after 参数,在指定节点执行前/后自动暂停,无需在节点内部写 interrupt()。
实战一:等待用户输入
以下示例演示一个简单的工作流:step_one → human_feedback → step_three。在 human_feedback 节点中调用 interrupt(),让人类提供反馈,然后继续执行。
python
import os
from typing import TypedDict
from dotenv import load_dotenv
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import interrupt, Command
load_dotenv()
# 定义状态
class FeedbackState(TypedDict):
input: str
user_feedback: str
# 节点1:不做具体处理,只打印日志
def step_one(state: FeedbackState) -> FeedbackState:
print(">>> 执行 Step 1")
return state
# 节点2:等待人类反馈
def human_feedback(state: FeedbackState) -> FeedbackState:
print(">>> 等待人类反馈...")
# interrupt 暂停执行,提示信息会返回给调用方
feedback = interrupt("请提供你的反馈:")
# 当外部通过 Command(resume=...) 恢复时,feedback 会被赋值为传入的内容
return {"user_feedback": feedback}
# 节点3:使用反馈
def step_three(state: FeedbackState) -> FeedbackState:
print(f">>> 执行 Step 3,收到的反馈:{state['user_feedback']}")
return state
# 构建图
builder = StateGraph(FeedbackState)
builder.add_node("step_one", step_one)
builder.add_node("human_feedback", human_feedback)
builder.add_node("step_three", step_three)
builder.set_entry_point("step_one")
builder.add_edge("step_one", "human_feedback")
builder.add_edge("human_feedback", "step_three")
builder.add_edge("step_three", END)
# 激活短期记忆(持久化),用于中断恢复
memory = MemorySaver()
app = builder.compile(checkpointer=memory)
# 首次调用:会中断在 human_feedback 节点
config = {"configurable": {"thread_id": "feedback-session-1"}}
input_data = {"input": "你好"}
print("===== 首次调用(会中断)=====")
for event in app.stream(input_data, config):
for node_name, value in event.items():
if isinstance(value, dict) and "user_feedback" in value:
print(f"节点 {node_name}: 收到反馈 = {value['user_feedback']}")
# 此时执行暂停,我们需要获取中断事件并恢复
# 实际上 stream 会抛出中断信息,这里为简化演示,直接使用 invoke 配合 Command 恢复
# 方法:使用 app.invoke 并传入 Command(resume=...) 作为 input
print("\n===== 恢复执行(提供反馈)=====")
# 恢复时需要传入同一个 thread_id,并用 Command 包裹 human 的反馈
resume_input = Command(resume="我觉得很好,继续吧!")
for event in app.stream(resume_input, config):
for node_name, value in event.items():
if isinstance(value, dict) and "user_feedback" in value:
print(f"节点 {node_name}: 最终反馈 = {value['user_feedback']}")
运行这段代码,你会看到第一次调用在 human_feedback 处暂停,然后通过 Command(resume=...) 继续执行,并最终在 step_three 看到反馈内容。
实战二:审查工具调用
这是智能体最常用的场景:在工具执行前暂停,让人类审批工具的名称和参数。如果审批通过,执行工具;如果拒绝,修改参数或终止流程。
以下示例构建一个简单的天气查询智能体,工具调用前需要人类审查。
python
import os
from typing import TypedDict, Literal
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langgraph.graph import StateGraph, END, MessagesState
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.types import interrupt, Command
load_dotenv()
# 定义一个简单的天气工具(模拟)
def get_weather(city: str) -> str:
"""查询指定城市的天气"""
# 模拟返回固定天气
return f"{city}天气晴朗,25°C"
tools = [get_weather]
# 绑定工具的LLM
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
llm_with_tools = llm.bind_tools(tools)
# 定义状态
class AgentState(MessagesState):
pass
# 模型调用节点
def call_model(state: AgentState) -> AgentState:
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# 人类审查节点:在工具执行前调用
def human_review_node(state: AgentState) -> AgentState:
# 获取最后一条消息(通常包含 tool_calls)
last_msg = state["messages"][-1]
if not hasattr(last_msg, "tool_calls") or not last_msg.tool_calls:
return state # 无工具调用则跳过
# 展示待审查的工具调用
print(">>> 需要审查的工具调用:")
for call in last_msg.tool_calls:
print(f" 工具: {call['name']}, 参数: {call['args']}")
# interrupt 等待人类决策,返回字典格式的决策
decision = interrupt("请审批:输入 'continue' 批准,或提供修改后的参数 JSON")
# decision 可以是字符串,也可以是包含了修改后参数的对象
# 这里为了简单,只支持批准(字符串 'continue')
if decision == "continue":
return state
else:
# 其他情况可以拒绝或修改,示例中我们简单拒绝
print(">>> 审查未通过,终止执行")
# 通过返回空消息终止流程(实际可添加 ToolMessage 表示拒绝)
return {"messages": [ToolMessage(content="审查未通过", tool_call_id=last_msg.tool_calls[0]["id"])]}
# 构建图
builder = StateGraph(AgentState)
builder.add_node("call_model", call_model)
builder.add_node("review", human_review_node)
builder.add_node("tools", ToolNode(tools))
builder.set_entry_point("call_model")
# 条件边:模型调用后,如果有工具调用,进入审查;否则直接结束
def should_review(state: AgentState) -> str:
if tools_condition(state):
return "review"
return END
builder.add_conditional_edges("call_model", should_review, {"review": "review", END: END})
builder.add_edge("review", "tools")
builder.add_edge("tools", "call_model") # 工具执行后回到模型继续思考
memory = MemorySaver()
app = builder.compile(checkpointer=memory, interrupt_before=["review"]) # 在 review 节点前中断
# 测试:询问天气(会触发工具调用和审查)
config = {"configurable": {"thread_id": "review-tools-1"}}
input_data = {"messages": [HumanMessage(content="北京天气怎么样?")]}
print("===== 开始对话(将中断在审查前)=====")
# 第一次调用会暂停在 review 节点前
for event in app.stream(input_data, config):
pass
# 恢复:批准工具调用
print("\n===== 恢复:批准工具调用 =====")
resume_cmd = Command(resume="continue")
for event in app.stream(resume_cmd, config):
for node_name, value in event.items():
if "messages" in value and value["messages"]:
print(f"节点 {node_name}: {value['messages'][-1].content}")
运行后,你会看到工具调用被暂停,你可以选择批准(输入 continue)或修改。这里为了简化,只支持批准。实际可以解析 JSON 来更新参数。
实战三:编辑图的状态
有时我们不希望仅仅在节点内部暂停,而是想在图的任意位置暂停,并且可以修改状态(例如修改之前某个节点产生的数据)后再继续。
使用 interrupt_before 在指定节点前暂停,然后使用 graph.update_state() 直接修改状态,最后再用 Command 或 stream 继续。
python
from typing import TypedDict
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command
class EditState(TypedDict):
text: str
def step_one(state: EditState) -> EditState:
# 将输入大写
return {"text": state["text"].upper()}
def step_two(state: EditState) -> EditState:
# 添加后缀
return {"text": state["text"] + " [processed]"}
def step_three(state: EditState) -> EditState:
# 最终输出
return {"text": "最终结果: " + state["text"]}
builder = StateGraph(EditState)
builder.add_node("step_one", step_one)
builder.add_node("step_two", step_two)
builder.add_node("step_three", step_three)
builder.set_entry_point("step_one")
builder.add_edge("step_one", "step_two")
builder.add_edge("step_two", "step_three")
builder.add_edge("step_three", END)
memory = MemorySaver()
app = builder.compile(checkpointer=memory, interrupt_before=["step_two"]) # 在 step_two 前暂停
config = {"configurable": {"thread_id": "edit-state-1"}}
# 初始调用
input1 = {"text": "hello world"}
print("===== 初始调用(暂停在 step_two 前)=====")
events = list(app.stream(input1, config))
# 此时状态为 step_one 已执行,text 为 "HELLO WORLD"
print("当前状态:", app.get_state(config).values)
# 我们想要修改这个状态,比如把 text 改成其他内容
print("\n===== 更新状态 =====")
app.update_state(config, {"text": "CUSTOM TEXT"})
print("更新后状态:", app.get_state(config).values)
# 继续执行
print("\n===== 继续执行 =====")
for event in app.stream(None, config):
for node_name, value in event.items():
print(f"节点 {node_name}: {value}")
运行后你会发现,step_two 接收到的 text 已经被改成了 "CUSTOM TEXT",之后的结果也是基于修改后的值。这种能力允许人类在流程中任意点修正数据,再继续执行。
总结
人机交互是 LangGraph 为生产级 AI 应用提供的关键能力。通过 interrupt、Command、interrupt_before 和 update_state,开发者可以灵活地在工作流中插入审批、修改状态、拒绝工具调用等操作,从而在不完全信任模型决策时,确保系统安全可控。本文的三个实战示例给出了最基础的用法,实际项目中可以组合出更复杂的审核流程。
本文完整覆盖了人机交互的概念、等待用户输入、审查工具调用、编辑图状态等所有演示场景,并提供了可直接运行的代码示例。代码已可直接运行(需安装 langgraph, langchain-openai 等依赖,并配置 API Key)。