当自主智能体开始触碰支付、发邮件、删库、提交工单这类不可逆副作用时,"它能跑"和"它敢跑"之间隔着一条决定生产成败的鸿沟——人机协作闸门(Human-in-the-Loop Gate)。本期把 2026 年主流的六套 Agent 框架拉到同一条审批流水线上实测:LangGraph、OpenAI Agents SDK、CrewAI、AutoGen/AG2、Microsoft Agent Framework、Pydantic AI。核心结论先行——只有 LangGraph 的 interrupt() 和 OpenAI Agents SDK 的 RunState 真正具备"跨进程、跨天"的持久化中断-恢复能力;CrewAI 与 AutoGen 停留在"会话内征求反馈";Pydantic AI 则是裸奔基线。而真正决定框架生死的不是"能不能加闸门",而是每个闸门要交多少吞吐税。
| 框架 | HITL 原语 | 审批声明方式 | 中断/恢复机制 | 状态持久化 | 审批作用域 | 首闸上手 | 吞吐税 |
|---|---|---|---|---|---|---|---|
| LangGraph | interrupt() + Command(resume=) |
命令式(节点内调用) | 检查点回放 | Checkpointer(内存/SQLite/Postgres) | 线程级(thread_id) |
~15 min | 中(可异步) |
| OpenAI Agents SDK | 工具级审批 + RunState |
声明式(工具标记 needsApproval) |
Run 中断 + 状态序列化 | RunState 可序列化 |
运行级(跨 handoff) | ~10 min | 低 |
| CrewAI | human_input=True |
任务级标志 | 阻塞式 stdin 征询 | 无原生持久化 | 任务级 | ~5 min | 高(阻塞) |
| AutoGen / AG2 | UserProxyAgent |
human_input_mode 配置 |
会话轮次等待 | 会话内存(可外挂) | 会话级 | ~10 min | 高(同步) |
| Microsoft Agent Framework | FunctionApprovalRequest |
函数工具 approval_required |
事件回调 + 恢复 | 持久化线程 | 运行级 | ~15 min | 中 |
| Pydantic AI | 无内置 | 手动拦截 | 手动暂停/续跑 | 自行实现 | 自定义 | ~20 min | 视实现 |
一句话读表:上手最快的 CrewAI 恰恰是吞吐税最高的;声明式的 OpenAI Agents SDK 在"审批作用域跨 handoff"这一项上独一份;LangGraph 在"状态可编辑、可时光旅行"上是唯一答案。
LangGraph 把 HITL 当作核心抽象而非附加功能。它的心智模型是图执行可暂停:任意节点里调用 interrupt(value),整张图冻结,value 被吐回给客户端;人类决策后,用 Command(resume=answer) 续跑,interrupt() 那一行原样返回 answer,节点逻辑无缝继续。
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import MemorySaver
def approve_node(state):
verdict = interrupt({"plan": state["draft"], "risk": state["risk"]})
# 人类拒绝 → verdict="reject",分支去重写;批准 → 放行
return {"approved": verdict == "approve"}
graph = builder.compile(checkpointer=MemorySaver())
cfg = {"configurable": {"thread_id": "order-7721"}}
graph.invoke({"draft": plan}, cfg) # 跑到 interrupt 停下
graph.invoke(Command(resume="approve"), cfg) # 从检查点回放续跑
强制三件事让它成为长会话首选:①必须挂 checkpointer;②必须给 thread_id;③状态按线程隔离。代价是这套机制默认同步阻塞——节点停在 interrupt() 期间,工作线程被占住。生产里通常把"等待人类"这段挪到外部队列(Redis/Postgres),用 thread_id 当任务号异步恢复,从而把阻塞变成轮询,吞吐税从"高"压回"中"。它的杀手锏还有 get_state_history(cfg):任意历史检查点都能 fork 出分支重放,相当于给 Agent 装了时光机,这是其余五栈都没有的能力。
OpenAI Agents SDK 走了一条与 LangGraph 完全相反的路:审批声明在工具上,不在控制流里。工具自己声明"我需要审批",运行结果把待审批项作为中断抛出,RunState 让你把整个运行状态序列化、过一会儿再反序列化续跑。
from agents import Agent, Runner, function_tool
@function_tool(needs_approval=True)
def issue_refund(order_id: str, amount: float) -> str:
return f"refunded {amount} for {order_id}"
agent = Agent(name="billing", tools=[issue_refund])
result = Runner.run_sync(agent, "给订单 7721 退 200")
# result.interruptions 暴露待审批工具调用
# 人类 approve 后:Runner.run_sync(agent, "...", state=result.state) 续跑
这套设计有两个被低估的优势。第一,审批作用域是运行级而非当前 Agent 级——官方文档明确"the approval surface is run-wide, not limited to the current top-level agent",意味着即便工具调用发生在 handoff 之后的子 Agent 身上,审批照样生效,跨角色协作不漏审。第二,RunState 是序列化对象,天然适配"用户下班了、明天早上审批"这种跨天异步场景,无需自己造 checkpointer。短板也明显:强绑 OpenAI 模型与生态,审批回调的 UI 层要自建,且 needs_approval 这种布尔粒度偏粗——想"按金额分级审批"得自己在工具内拆条件。综合吞吐税最低,是 OpenAI-only 技术栈的甜点选择。
CrewAI 把人机协作压成了一个布尔标志:给 Task 加 human_input=True,任务执行完会停下来向人类征求反馈。上手确实五分钟不到,代价是它本质是阻塞式 stdin 征询,不是真正的中断-恢复。
review = Task(
description="起草退款方案",
expected_output="退款 JSON",
human_input=True, # 任务后弹出人工反馈
agent=analyst,
)
实测三个硬伤。其一,没有原生持久化——一旦会话进程退出,等待中的审批请求随之蒸发,撑不过一次容器重启或五小时长会话。其二,作用域锁死在单个 Task,跨 Crew、跨 Agent 的统一审批策略要靠 @before_kickoff/@after_kickoff 回调自己缝。其三,human_input 只能在任务边界触发,无法在"工具调用前"这种更细粒度的点设卡——对一个准备点"发送"的邮件 Agent 来说,任务级闸门粒度太粗。CrewAI 适合做原型演示和内部低风险流程,但凡涉及不可逆副作用,它的吞吐税(全程阻塞)和持久化缺失就是红线。
AutoGen 的人机协作原语是 UserProxyAgent,通过 human_input_mode 三档配置控制介入力度:ALWAYS(每轮问)、TERMINATE(终止前问)、NEVER(全自动)。
from autogen import ConversableAgent, UserProxyAgent
user = UserProxyAgent("human", human_input_mode="TERMINATE",
is_termination_msg=lambda x: "FINAL" in x.get("content",""))
assistant = ConversableAgent("assistant", llm_config=cfg)
user.initiate_chat(assistant, message="分析并下單")
这套"人类即一个 Agent"的设计在多智能体对话里很自然——人类就是对话图里的一个节点,逢到该他说话就阻塞等待。但它的审批是会话级、同步的:审批请求绑在对话轮次上,进程崩了对话历史得靠外挂记忆恢复,没有像 LangGraph thread_id 那样的稳定恢复句柄。2025–2026 年 AutoGen 进入剧烈动荡期——微软把 AutoGen 与 Semantic Kernel 合并进新的 Microsoft Agent Framework,社区则 fork 出 AG2 续命。这意味着今天选 AutoGen,实质是在"走向 MSAF 的官方线"和"AG2 社区线"之间二选一,迁移成本必须提前计入。
微软把 AutoGen + Semantic Kernel 收编后推出的统一框架,在 HITL 上给出了一套更工程化的方案:函数工具声明 approval_required,框架在调用前抛出 FunctionApprovalRequest 事件,宿主程序拿到事件后回调 approve/reject,运行据此恢复。
# Microsoft Agent Framework(概念示意)
@agent.function_tool(approval_required=True)
def place_trade(symbol: str, qty: int):
...
# 运行时订阅
async def on_approval(req: FunctionApprovalRequest):
if req.args["qty"] < 100:
return req.approve()
return await ask_human(req) # 超阈值转人工
它的差异化在于审批决策可以是程序化的——金额、风险分、用户等级都能写进回调里做条件放行,不必事事打扰人类。结合其内置的持久化线程,FunctionApprovalRequest 能跨进程恢复,作用域是运行级。这套东西对企业特别对味:和 Azure 生态、Copilot Studio、Semantic Kernel 技能复用一脉相承。缺点同样在企业味——抽象层厚、配置项多、学习曲线陡,团队若没有 .NET/Python 双栈和 Azure 基建,首闸上手要 15 分钟起步。
Pydantic AI 把第六栈的位置留给它,不是因为它闸门强,恰恰相反——它几乎没有内置 HITL。它的强项是类型安全与结构化输出,Agent 循环很薄,要加审批只能"手动拦截":在工具调用前自己插入一个 await 人类确认的协程,状态持久化完全自己实现。
# 手动闸门:把审批做成一个普通函数工具前的钩子
async def guarded_tool(ctx, **args):
if args["amount"] > ctx.deps.threshold:
if not await ctx.deps.human.approve(args):
return "rejected"
return await do_refund(**args)
把它列进来是为了立一个对照系:当你主动拒绝框架提供的闸门抽象、选择裸写时,你拿到了最大灵活性,也接下了全部持久化、超时、重放、审计的债。 小团队、低风险、短会话场景,这套"依赖注入 + 手动钩子"反而最干净;一旦会话跨小时、审批跨人、需要审计回溯,这些手写的边角代码会迅速膨胀成事实上的私有框架——而那正是其他五栈想替你省掉的。
把六栈摆在一起,会浮现出三条贯穿性规律。
真相一:闸门粒度决定安全上限,声明方式决定工程体验。 工具级声明(OpenAI、MSAF)比节点级命令(LangGraph)更贴"副作用在哪、审批挂哪"的直觉,但牺牲了"在任意逻辑点叫停"的灵活性。LangGraph 的 interrupt() 可以在条件分支中间、在规划完成后、在校验失败时任意插入,粒度自由度最高。
真相二:没有持久化的审批都是玩具。 CrewAI 和 AutoGen 的同步征询在"用户去开会了"的现实中直接失效。真正过得了关的是把等待态外置——LangGraph 的 checkpointer 把 thread_id 当任务号写进 Postgres,OpenAI 的 RunState 序列化进对象存储,MSAF 的持久化线程。判断一个框架能否上生产,就看它的"等待人类"状态能不能在进程重启后毫秒级恢复。
真相三:吞吐税是隐形定价表。 每个同步阻塞闸门都在序列化整条流水线。一个有四处审批门的 Agent,若全做成阻塞,端到端延迟等于四次人类响应时间之和,且工作线程全程空转。对冲手段只有两个——把闸门异步化(外部队列 + 轮询恢复),或把闸门程序化(MSAF 式条件放行,能自动放行的绝不打扰人)。OpenAI Agents SDK 之所以吞吐税评"低",正是因为 RunState 让异步化几乎零成本。
flowchart TD
U[用户请求] --> A[Agent Loop 推理]
A --> T{工具调用?}
T -->|needsApproval=true| G1[审批门-声明式]
T -->|高风险节点| G2[中断门-命令式 interrupt]
G1 --> Q[审批队列-可持久化]
G2 --> CK[Checkpointer/RunState 持久化]
Q --> H{人工/规则决策}
CK --> H
H -->|approve/timeout放行| EX[执行副作用]
H -->|reject| A
H -->|edit state| F[fork历史检查点-时光旅行]
F --> A
EX --> D[审计日志-不可抵赖]
D --> A
style G1 fill:#ffe0b2
style G2 fill:#c8e6c9
style CK fill:#bbdefb
style Q fill:#bbdefb
thread_id 天然可追踪,配合 Postgres checkpointer 撑得住跨天会话。needsApproval 声明式 + RunState 序列化 + 审批作用域跨 handoff,三合一且吞吐税最低。FunctionApprovalRequest + 持久化线程 + Semantic Kernel 技能复用,最贴中大型企业治理要求。一句话收口:2026 年选 Agent 框架,与其问"它能不能加审批",不如问"它的审批等待态能不能在容器重启后 50 毫秒内恢复、能不能不打扰人就不打扰人"。 前者是所有六栈都能做到的表象,后者才是区分生产可用与玩具的分水岭。
RunState 序列化与跨 handoff 运行级审批作用域的权威说明。interrupt() / Command(resume=) 与 checkpointer 回放机制。FunctionApprovalRequest 条件放行机制。[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
