AI Agent白手起家63: LangGraph 人机交互——让人类介入 AI 工作流

纲要

  • 为什么要加入人工审核环节
    • 模型的不确定性
    • 高风险场景下的安全需求
  • LangGraph 人机交互机制
    • interrupt():在节点中暂停执行
    • Command(resume=...):恢复执行并传递人类反馈
    • interrupt_before / interrupt_after:在边处打断
    • update_state:直接修改图的状态
  • 实战一:等待用户输入
    • 构建带反馈节点的简单图
    • 使用 interrupt 暂停,Command 恢复
  • 实战二:审查工具调用
    • 在工具执行前插入审查节点
    • 支持批准、拒绝、修改参数
  • 实战三:编辑图的状态
    • 使用 interrupt_before 在特定节点前暂停
    • 通过 graph.update_state() 修改状态后继续
  • 完整可运行代码
  • 总结与相关度说明

为什么需要人工介入

当前的大语言模型基于概率生成,即使是最先进的模型也可能产生错误决策。在关键业务场景(如金融交易、医疗建议、自动发布内容)中,一次错误的工具调用或回复可能造成严重影响。因此,在生产级智能体系统中引入 人机交互 环节,让人类能够审批、编辑甚至拒绝智能体的决策,是保障系统可靠性的重要手段。

LangGraph 为此提供了原语级的支持,可以灵活地将人工审核嵌入到工作流的任意位置。

核心机制:interruptCommand

LangGraph 的人机交互依赖于两个核心 API:

  • interrupt():在节点内部调用,暂停图的执行,并抛出一个需要人类处理的中断事件。
  • Command(resume=...):外部输入反馈后,通过 Command 恢复执行,同时可以将人类提供的数据注入到图中。

此外,还可以在编译图时通过 interrupt_beforeinterrupt_after 参数,在指定节点执行前/后自动暂停,无需在节点内部写 interrupt()

实战一:等待用户输入

以下示例演示一个简单的工作流:step_onehuman_feedbackstep_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() 直接修改状态,最后再用 Commandstream 继续。

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 应用提供的关键能力。通过 interruptCommandinterrupt_beforeupdate_state,开发者可以灵活地在工作流中插入审批、修改状态、拒绝工具调用等操作,从而在不完全信任模型决策时,确保系统安全可控。本文的三个实战示例给出了最基础的用法,实际项目中可以组合出更复杂的审核流程。

本文完整覆盖了人机交互的概念、等待用户输入、审查工具调用、编辑图状态等所有演示场景,并提供了可直接运行的代码示例。代码已可直接运行(需安装 langgraph, langchain-openai 等依赖,并配置 API Key)。

相关推荐
_codemonster1 小时前
Transformer的核心机制
人工智能·深度学习·transformer
Python私教1 小时前
0、null、未采集:AI Agent 反馈系统最容易踩的语义坑
人工智能·python
xushichang123_1 小时前
训练和微调生成式 AI 模型,应该选择哪些云上高性能存储服务?亚马逊云科技四类方案选型
人工智能
政安晨1 小时前
政安晨【超级AI智能体~技术简报】- 向量退潮,图谱登场:Agent 基础设施的换代信号 [2026.8]
人工智能
乐观勇敢坚强的老彭1 小时前
C++ STL 常用容器的速查表
java·c++·算法
北京晶数信息科技1 小时前
成品油交易即开票原创一体化解决方案汇报(二)
大数据·人工智能·物联网·产品经理·需求分析
北冥you鱼1 小时前
深入解析 Go 中 sync.YAML.Unmarshal:如何将 YAML 数据填充到 Config 结构体
开发语言·算法·golang
浪里镖客1 小时前
激光雷达与惯导、相机与惯导的外参标定原理
人工智能·数码相机
DO_Community1 小时前
AI 应用成本怎么算?从推理费用到完整 TCO 拆解
人工智能·gpt·agent·ai编程·llama·kimi