前言 :Human-in-the-Loop (人机协同) 是 Agent 从 Demo 走向落地的关键功能。但官方文档里的
interrupt_before真的能直接用在生产环境吗? 本文将分享一种 Event-Driven(事件驱动) 的 HITL 设计模式,让你的 Agent 能够像后端服务一样,与前端 UI 完美配合。
1. 原生 HITL 的困境:Static vs Dynamic
⚠️ 避坑指南 :有些读者可能会搜到 langchain.agents.middleware.HumanInTheLoopMiddleware 这个类。 疑问 :"这个 Middleware 看起来用法很简单,为什么我们还要用更复杂的 LangGraph 架构?" 真相:
- 架构代差 :
HumanInTheLoopMiddleware是为上一代 AgentExecutor (循环链)设计的,不适用于最新的 LangGraph 架构。它的"暂停"往往基于内存中的回调,一旦服务重启,等待审批的上下文可能就丢失了。 - 生产级能力 :LangGraph 是基于 状态机(State Machine) 的。它的 HITL 能力(如
checkpointer)通过数据库持久化了每一次状态变更。这意味着你可以今天暂停 Agent,下周一再恢复它,或者在中断期间修改它的记忆(Time Travel)。这是 Middleware 很难做到的。
因此,虽然 LangGraph 写起来代码多一点,但它带来的鲁棒性是生产环境必须的。
对于 LangGraph,常用的做法是 interrupt_before:
python
# Static Interrupt (静态中断)
app = workflow.compile(
checkpointer=memory,
interrupt_before=["action_node"]
)
但这在实际落地时会遇到"上下文丢失"的问题:前端如何知道为什么要中断?中断时携带的数据(Payload)在哪里? 单纯的 interrupt_before 只是让图停下来,并没有告诉外部世界"发生了什么"。虽然 LangGraph 后来推出了 interrupt() 函数(Dynamic Interrupt)来尝试解决这个问题,但在复杂的企业级业务中,我们依然面临挑战:
- 数据持久化 :
interrupt()的 payload 是运行时状态。如果我不小心重启了服务器,或者我想仅仅通过查询数据库(而不运行 Graph)来获取"当前有哪些待审批任务",运行时中断信息就很难获取了。 - 领域模型耦合 :审批流往往是业务逻辑的一部分,应该存储在
AgentState(领域模型)中,而不是隐藏在 Graph 的运行时的堆栈里。
因此,我们提出了一种 基于 State 的事件驱动模式。
2. 破局:事件驱动模式 (Event-Driven HITL)
为了解决这个问题,我们在 Deep Research Agent 项目中,设计了一套 基于事件 的 HITL 机制。
2.1 在 State 中显式定义"暂停事件"
我们不让 Graph 莫名其妙地停,而是先往 State 里写一张"请假条":
python
# src/deep_research_agent/core/state.py
class HITLEvent(TypedDict):
event_type: str # 事件类型,如 "approve_plan"
payload: Dict # 携带的数据,如生成的 Plan 内容
requires_response: bool
class AgentState(TypedDict):
# ...其他字段
pending_hitl_event: Optional[HITLEvent] # 👈 关键字段:当前的挂起事件
2.2 使用"审批节点"代替直接中断
我们不直接在 search_node 前中断,而是插入一个专门的 create_approval 节点:
python
# src/deep_research_agent/graph.py
def create_plan_approval_node(state: AgentState):
# 1. 创建事件
event = {
"event_type": "plan_approval",
"payload": {"plan": state["plan"]},
"requires_response": True
}
# 2. 更新状态,前端此时能看到这个 event
return {"pending_hitl_event": event}
# 在图里连接: plan -> create_approval -> wait_node
workflow.add_node("create_plan_approval", create_plan_approval_node)
workflow.add_edge("plan_node", "create_plan_approval")
2.3 前端的完美配合
现在,前端(React/Vue)的逻辑变得极其清晰:
javascript
// 前端伪代码
const { state } = useAgentStream();
useEffect(() => {
const event = state.pending_hitl_event;
if (event && event.requires_response) {
if (event.event_type === 'plan_approval') {
// 💡 自动弹出"计划确认"模态框
showModal(<PlanApprovalModal plan={event.payload.plan} />);
}
}
}, [state]);
看!后端的状态驱动了前端的 UI。 这才是现代 Web 应用该有的样子。
3. 为什么这很重要?
- 解耦:Graph 只需要负责产生事件,不需要关心前端怎么画 UI。
- 可观测性:在数据库或日志里,我们可以清楚地看到 Agent 在哪一刻生成了审批请求,以及用户何时给出了反馈。
- 灵活性 :你可以在
payload里塞入任何东西,比如推荐的搜索关键词、风险提示、置信度分数等,辅助人类做决策。
4. 源码与实战
这套机制的完整 Python 实现(包括 HITLManager 类和 Graph 编排)都已经开源。 如果你正在为"Agent 怎么和前端交互"发愁,这绝对是你的解药。
👉 GitHub 项目地址 :github.com/changflow/d...
虽然这种写法比原生的 interrupt 多写了几十行代码,但它换来的是一个健壮、可维护的生产级系统。工程化的魅力,往往就藏在这些"多出来"的代码里。