Human-in-the-loop 看起来只是一个"审批按钮",背后是一整套状态机、超时策略和框架 hack。
LangGraph 内置方案的致命限制
LangGraph 的 HumanInTheLoopMiddleware 基于 interrupt()------序列化图状态、挂起、等待外部信号。文档上的示例干净利落:
python
# 文档里的理想用法
def my_node(state):
result = interrupt("请管理员审批") # ← 等待外部审批信号
return result
问题出在我们实际接入时。我们的退款审批逻辑在 request-return tool 内部------tool 的执行上下文不是 graph node ,是 LangChain 的 ToolMessage 回调。当我们在 tool 函数内调用 interrupt(),整个图状态机挂起了,但 tool 的执行栈没有正确序列化------ToolInvocation 对象卡在 pending 状态,没有任何恢复机制能正确重建 tool 执行上下文。
具体表现:ReAct Agent 执行到 request-return tool → tool 调用 interrupt("确认退款 ¥199 → 用户 138xxx") → Agent 线程 hang 住,不抛异常,不超时,Langfuse trace 停在 tool_call start 再也没有后续。我们等了 30 分钟,trace 还是停在原地。
翻 LangGraph 源码确认了根因:checkpoint() 序列化的是 graph node 级别的 Channel 状态,不包含 tool 执行帧。当 interrupt() 在 tool 内部被调用时,恢复后 graph 从 checkpoint 恢复 node 的输入状态重新执行------但 tool 的中间产出的数据(已生成的退款明细)已经被丢掉了。
退款审批恰好发生在 tool 内部(request-return tool 里的"生成退款明细→等待确认→执行退款")。框架给了 80% 的路径------node 间审批------但你业务长在另外 20% 上------tool 内部审批。而这个 20% 恰恰是 LangGraph 的架构边界。
自研方案:一个标志位 + Redis
Tool 正常返回(不抛异常),返回内容带 _pending_approval 标志 + 退款明细摘要。Orchestrator 检测到标志位 → 摘要存入 Redis _INTERRUPT_STORE → Agent 暂停 → 审批员 API 同意/拒绝 → 恢复 Agent。
三个魔鬼细节:
- 退款明细随中断一起返回 → 审批者无需额外查询
- 审批超时 30 分钟自动拒绝 → 避免挂死
- 工具正常返回而非抛异常 → 避免 Langfuse 被 "ApprovalRequiredError" 刷屏
什么情况该换工作流引擎? 标志位+Redis 适合审批路径简单(同意/拒绝,2-3 个状态)的场景。如果你遇到这些信号就该升级了:
- 多级流转:主管→经理→财务,审批链随金额/类型动态路由
- 会签或签:多人并行审批,需全部通过或任意一人通过
- SLA 告警升级:审批超时后不是直接拒绝,而是逐级上升
- 驳回回退:审批者退回上一级修改,不是简单的"同意→执行→完成"
这时别再在 agent 里手写状态机了。两种选择:
- Temporal:代码即工作流,Saga 模式原生支持长活事务的补偿与重试。适合「审批跨越几小时甚至几天」的分布式场景。缺点是没有可视化流程建模,审批规则改一次就要改代码、发版。
- Conductor(Netflix 开源):微服务编排引擎,独立部署,官方 Python SDK 完善。WAIT 节点天然支持「暂停→等待外部回调→继续」的人工审批模式。缺点是流程定义不走 BPMN 标准,需要手写 JSON DSL(自带 UI 可以可视化查看和监控流程图,但不能像 BPMN 设计器那样拖拽建模),如果要业务人员拖拽方式创建审批流,可以考虑使用flowable通过API集成的方式。
核心原则:把审批流程从 agent 中剥离成独立的工作流服务,agent 只负责发起和接收回调。 HITL 的复杂度在于"流程",不在于"AI"------不要让 agent 承担它不该承担的编排责任。
HITL 暂停期间新消息的处理
用户挂了退款审批,又去问"那我的优惠券还能用吗"。推荐"轻量分支":只合并历史、不合并意图,两个请求独立处理。
内容安全:四层纵深防御
90% 的拦截在规则层(L1,<1ms)就结束了:
- L1 输入规则过滤(正则+敏感词库)→ 直接拒绝
- L2 本地 LLM 安全审查 → RAG Step2 与问题改写并行
- L3 云端 LLM 复核 → 可选,仅边界模糊 case
- L4 输出规则过滤 → 生成后硬阻断
让贵的组件做少的事。
隐私合规要点
三存储体系(Milvus + Redis + DB)全链路清理。假名化 ≠ 匿名化------hash 替代手机号是可逆的,GDPR 对两者要求不同。