ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent 的审批中断:哪些动作必须停下来等人

Agent 的审批中断:哪些动作必须停下来等人 说明本文讨论的是 Agent 执行动作前的人工审批中断设计属于 Agent 工程落地话题不涉及具体模型版本与价格。AI 领域版本迭代极快凡涉及版本号、价格、可用性请以你阅读时的官方页面为准。文中代码为结构示意请按自己的技术栈调整后再上生产。一、先定判据什么样的动作必须停下来做 Agent 的人大多走过同一条路第一版把审批做得很足所有写操作都弹窗确认上线两天之后人开始不看内容直接点同意一周之后审批框被当成弹窗广告。这不是责任心问题是判据的问题。中断的成本不是那一次点击而是人的注意力。注意力是稀缺资源一个人一天能认真评估的决策是几十条量级超过就退化成条件反射。所以第一个目标不是更安全而是把中断次数压到人的注意力扛得住的范围内。判据有四条命中任意一条就该停下来。第一不可逆。做完之后有没有一条恢复路径判断方法不看动词看结果软删除有回收站能做反向操作硬删除、覆盖写入、清空历史没有。同一个删除接口在带回收站的系统里可逆在直接物理删除的表上不可逆。所以这条判据要从资源元数据里读不能从工具名里猜。第二对外可见。结果会不会离开你的系统、被第三方看到发邮件、发站内公告、对外接口写入、发布页面都属于这一类。难点不在能不能回滚而在一封发错的对外通知撤回本身还会制造第二次可见事件。第三涉及资金或权限。扣款、退款、发额度、授角色、签发凭证。这类代价是不对称的少做一次只是不方便重做一次要赔钱。凡是金额、配额、角色、凭证出现在参数里就直接升到最高档不参与后面的判断。第四影响面超出单次会话。会话内的操作出错半径就是一个人改共享配置、改生产环境、批量操作一个群组半径是所有人的事。量化方式是目标对象数量影响 1 个和影响 214 个不是同一件事哪怕接口是同一个。判据要问的问题典型动作不需要停的反例不可逆有没有一次操作能恢复物理删除、覆盖写入、清空历史写草稿、写回收站对外可见结果会不会被外部看到发通知、对外写入、发布页面写内部日志、写会话内记忆资金或权限参数里有没有金额、配额、角色、凭证退款、发额度、授权、改计费查询余额、读权限列表影响面这次动到几个对象、几个租户批量改名、改共享配置、跨租户操作只改自己名下的记录四条判据是或关系但它们只是必要条件。命中判据说明这件事值得人看一眼具体谁批、等多久取决于第二章的档位。不满足这四条的动作不要接进审批。最常见的浪费是把写整体当成危险于是草稿写入、会话内临时记忆也被拦下来。这些动作不满足任何一条判据把它们排队等人唯一的产出是让人对审批框脱敏。这里要划清一个边界要不要停不能交给模型自己判断。让模型自评这个操作安全吗不可靠因为它的依据来自上下文而上下文里可能有不可信的外部内容——一段被污染的网页正文足以让清空生产库在它的描述里变成清理过期缓存。这四条判据之所以这样选正因为它们都能从工具元数据和调用参数这两个结构化来源算出来。二、把动作分成四档二值审批停或不停一定会走向两个极端要么松到拦不住要么严到没人看。四档的价值在于把要不要人看拆成三个可调的东西——谁看、看多细、等多久。档位判据谁来确认默认超时行为例子L0 可静默执行无副作用或副作用严格限于本次会话无人不适用查询、检索、写会话内临时状态L1 执行后告知有副作用但可逆且只影响发起人自己无人事前确认事后通知直接执行并向发起人回执写草稿、改自己的日程、生成预览L2 必须事前确认命中第一章判据影响面在单个租户内发起人或具备权限的操作者超时拒绝对外发通知、批量改本租户数据L3 必须双人确认涉及资金或权限或影响面跨租户、超阈值两名不同角色的审批人超时拒绝并升级退款、授权、跨租户批量操作分档的第一条纪律分级输入是结构化事实不是模型的置信度。模型说这一步是安全的、可以自动执行只能当建议降档的理由必须可枚举开了预演、目标数量在阈值内、命中的是白名单工具除此之外一律按静态档走。第二条纪律升级容易、降级难。动态调整只允许单向往上目标数量超阈值就升一档参数里出现金额就直接升到 L3。参数看着挺正常所以降一档这类推断不应该存在。分级判定写出来很短关键是输入必须是元数据加参数而不是自然语言⚠️ 代码待验证ORDER[L0,L1,L2,L3]# 工具侧静态声明基础档位 触发词标签TOOL_RISK{doc.read:(L0,None),doc.draft:(L1,None),mail.send:(L2,external),order.refund:(L2,money),role.grant:(L3,privilege),}BLAST_LIMIT100# 目标对象数超过它无条件升一档defbump(level:str,step:int1)-str:returnORDER[min(len(ORDER)-1,ORDER.index(level)step)]defdecide(tool:str,args:dict)-str:level,tagTOOL_RISK[tool]iftagin(money,privilege):returnL3# 资金与权限不参与后面的判断ifargs.get(dry_run):returnL0# 预演只读不写可静默执行ifint(args.get(target_count,1))BLAST_LIMIT:levelbump(level)# 影响面超阈值升一档returnlevel这里最值得注意的是dry_run那一行。预演是降低审批成本最有效的手段让工具先跑一遍只读版本把人需要判断的东西从你信不信我要做的这件事换成你看这个结果对不对。人对具体结果的判断能力远强于对意图的判断能力。三、闸门放在工具调用前还是在编排层这个问题看起来是实现细节其实决定了你能拿到什么信息。放在工具调用前执行器或者工具网关里能拿到工具名、适配层补全之后的最终参数、调用方身份、租户、配额余量拿不到这一步是任务里的第几步、用户原本想达成什么。放在编排层计划器和执行循环里能拿到计划全貌、已完成的步骤、会话上下文、用户原始意图拿不到参数的最终形态——有些字段要到适配层才补齐租户从凭证里取、收件人从群组展开、路径从根目录拼接。闸门位置能看到看不到适合判什么工具调用前最终参数、身份、租户、配额任务进度、用户意图基于参数的机械规则金额、对象数、目标环境、是否对外编排层计划全貌、剩余步骤、会话上下文参数最终形态任务本身该不该继续、要不要整体暂停两处都不做无全部只能靠事后对账发现问题结论不是二选一而是两层都要职责分开。编排层决定这一步要不要问人它懂任务语义执行器做兜底它懂最终参数。而且执行器这一层必须做到没有旁路任何入口、任何内部调用都得从这道闸门过。这一层一旦能被绕过前面所有判据都只是建议。把主闸门放在执行器侧还有一个很实在的理由审批摘要必须用适配层补全后的最终参数生成。举个具体的例子。审批界面上显示给「华东区运营组」发送公告看起来收件人是一个组人很自然地批了到了执行时适配层把组展开成 214 个具体成员。人批的是一个组执行的是214 个人——这是两个不同的决策。如果摘要用模型给的原始参数渲染这种差异根本不会出现在人的眼前。拦截器的骨架大致长这样⚠️ 代码待验证classApprovalGate:执行器侧闸门所有工具调用前必须经过它。def__init__(self,store,notifier,deny_list):self.storestore# 中断状态持久化self.notifiernotifier# 通知渠道self.deny_listdeny_list# 永远不允许自动放行的工具defbefore_call(self,ctx,tool:str,args:dict):leveldecide(tool,args)iftoolinself.deny_listandlevelin(L0,L1):# 静态声明说可以静默但它在黑名单里 —— 以黑名单为准levelL3iflevelL0:returnDecision(allowTrue)iflevelL1:returnDecision(allowTrue,notify_afterTrue)ticketself.store.create(run_idctx.run_id,step_indexctx.step_index,tooltool,args_snapshotargs,# 最终参数不是模型原始输出args_digestdigest(args),levellevel,idempotency_keyf{ctx.run_id}:step{ctx.step_index},)self.notifier.push(ticket.summary())returnDecision(allowFalse,wait_onticket.id)最后两个容易被忘掉的东西。黑名单优先工具声明的档位、模型给的参数、动态升级规则都可能出错所以需要一条独立的绝不放行清单优先级高于所有算出来的档位清单要短。旁路要留痕紧急放行通道不能堵死但必须可审计——谁跳过了、事后有没有补审批一个不留痕的紧急通道半年之内会变成默认通道。四、给人看的中断信息长什么样审批体验的分水岭在一件事上人拿到的是业务事实还是接口细节。把工具名和原始 JSON 直接丢给人后果可预测人看不懂字段意味着什么于是要么懒得看、无脑点同意要么看不懂就拒绝。前者给你一个我们有审批的错觉实际上闸门敞开着后者让 Agent 变成一堆走不完的废步骤。一份能让人做判断的摘要要有五样东西。要做什么——用业务名词不用接口名写给华东区运营组发送活动公告不写发送工具加一堆字段名。影响谁——数量必须具体写214 名成员而不是若干人。数字要精算不要估算约 200 人会被当成营销文案自动跳过。能不能撤销——有窗口就给出长度。发送后 2 分钟内可撤回和不可撤销是两种完全不同的心理负担。不批会怎样——任务完全停下还是走降级路径继续做别的人需要知道自己这一票的实际作用否则会倾向少惹事地拒绝。超时规则——多久没人处理会自动怎样。这一条决定了人要不要现在放下手上的事去处理。⚠️ 代码待验证【待确认动作】 要做什么 给「华东区运营组」发送活动公告模板notice_v3 影响谁 该群组 214 名成员 可撤销 发送后 2 分钟内可撤回超时不可撤销 资金/权限 无 不批会怎样 本任务停在此步已完成的前 3 步结果保留 超时规则 30 分钟无人处理则自动拒绝 [ 同意 ] [ 拒绝 ] [ 修改后重试 ] 模型补充说明未经校验 用户提到活动今晚开始希望尽快通知到人。注意最后那一段的标注方式。**摘要里的业务事实字段必须由代码渲染模型只能贡献一句被明确标注的补充说明。**如果整段摘要都交给模型生成一个被污染的输入——冒出来的网页内容、上传文件里的指令——就可以让清空生产库在人的屏幕上显示成清理过期缓存。人判断的是这段描述批的也是这段描述描述被操纵审批就形同虚设。最后一件必须支持的事拒绝要能带意见。“不同意不该是终态而应该是不同意因为收件人范围写错了”——这条意见回到模型上下文里它改完重来。能不能做到这一点决定审批是卡点还是回路。五、确认之后从哪继续中断的时间尺度是分钟到天不是毫秒。这个事实带来三个必须写进设计的前提进程会重启、会话会过期、数据会变。先说状态存在哪。存进程内存行不通因为中断跨越了进程的生命周期。状态必须落到持久存储而且落的不只是谁批了是够恢复的完整信息⚠️ 代码待验证{run_id:run_20261001_7f3c,step_index:4,state:pending,tool:mail.send,args_snapshot:{group_id:grp_east_ops,template_id:notice_v3,target_count:214},args_digest:sha256:3b1f9c...e7,target_version:etag:9f1a,idempotency_key:run_20261001_7f3c:step4,level:L2,created_at:2026-10-01T10:12:0008:00,expires_at:2026-10-01T10:42:0008:00,approver:null}三个字段值得解释。args_snapshot存的是最终参数快照不是模型输出的原始参数——人要批的和系统要执行的必须是同一份数据args_digest在恢复时确认参数没被改动target_version是目标对象的版本标识用来判断中断期间世界有没有变。进程重启怎么办答案是把执行做成显式步骤序列。每步结束后把step_index和结果摘要落盘中断只是其中一个state为pending的步骤。恢复时不是重放整个任务而是从第一个非终态步骤继续——重启就是一次普通的读表操作。中断期间上下文变了怎么处理这一处最容易漏。恢复之前必须校验四件事可能变的东西不校验的后果恢复时怎么查目标对象已删或已改对一个不存在的对象执行写入用target_version比对 etag 或更新时间凭证过期已批准的动作执行时失败人白批一次检查有效期必要时续期后再执行参数引用的资源失效群组已解散、模板已下架调用直接报错执行前对引用做一次存在性探测参数本身被改过人批的和机器做的不是同一件事比对args_digest其中最重要的一条规则要写死**参数一旦变化已批准的审批必须作废重新生成摘要再问一次。**不能靠约定要靠恢复路径里的比对逻辑强制人批的是给 214 人发这条公告执行的是给 260 人发另一条公告这是两次不同的决策。还有一条收尾规则中断记录要有过期时间。超过保留期还没决策的记录应该归档而不是继续挂在待决状态上否则恢复逻辑会在某个凌晨把一个两周前批准的退款真的执行掉。完整版资料清单本文用到的审批判据表和中断状态字段清单都整理在里面了扫码即可获取六、超时、缺席以及审批本身的幂等中断设计里最容易被简化掉的是没人管这条路径。而它偏偏最常发生——人下班、请假、开会任务就一直挂着。第一条线超时默认拒绝。超时拒绝不是保守是唯一说得通的选择。超时放行等于把没人管翻译成同意而没人管在夜里、在节假日发生的概率最高恰好是人最不可能判断风险的时候。唯一例外是动作可撤销且阻塞的代价大于误执行的代价——比如发一条 2 分钟内可撤回的内部提醒、写一条随时能删的草稿这类放进 L1 走执行后告知。L2 和 L3 一律超时拒绝。第二条线通知路由和升级。审批请求只发给发起人是个常见的错误设计。发起人出差一周任务挂一周。可行的做法是发起人加值班位并行通知谁先处理算谁的。升级链按时长递进但要按档位分渠道L2 只发群消息L3 才走强提醒或者电话。这个区分是算账凌晨把一圈人叫醒去批一个批量改名代价比这件事晚一天完成大得多。而且频繁的无效强提醒会让人对强提醒本身脱敏——真出大事时那个渠道也叫不动人了。档位超时默认是否允许超时放行通知渠道升级路径L1不等待允许以可撤销为前提执行后回执不需要L2拒绝不允许发起人加值班位的群消息15 分钟无响应扩到后备值班L3拒绝不允许发起人加两名审批人的强提醒10 分钟无响应升级到更高角色还有一条路径同样重要取消。任务可能已经不需要了——用户改了主意、上游数据已经删了、同一件事被提交了两次。取消要能级联到待审批项把记录置成已取消而不是让它到期自动变成已拒绝。在观测上这是两件不同的事前者说明上游有问题后者说明审批环节有问题。第三条线审批事件本身的幂等。审批比工具调用更容易重复来源至少三种用户连点两次同意通知系统重投推送重试、审批卡片在多个端同时显示值班交接时两个人各点了一次。处理方式是把它当成一次性的状态跃迁只允许从待决到已批准发生一次用比较并交换的语义实现第二次提交直接返回第一次的结果不触发第二次执行。⚠️ 代码待验证defdecide_ticket(ticket_id:str,approver:str,action:str)-dict:updatedstore.cas(ticket_id,expect_statepending,new_stateaction,# approved / rejectedextra{approver:approver,decided_at:now()},)ifupdatedisNone:returnstore.get(ticket_id)# 已经决策过回放同一结果ifaction!approved:return{state:rejected}# 幂等键在创建中断时就写好了这里只做复用不重新生成returnexecutor.run_once(keyupdated[idempotency_key],toolupdated[tool],argsupdated[args_snapshot],)两个细节。第一幂等键在创建中断时生成不在执行时生成。写在执行函数里的话两次执行会带两个不同的键去重完全失效。第二先落盘开始执行再真正执行。顺序反了的话可能出现执行成功但落盘失败的情况恢复逻辑会再执行一次而这一次没有任何记录能告诉它刚刚已经做过了。要划清一条边界这里讲的是审批事件的幂等——同一次批准只触发一次执行。至于执行本身超时之后怎么确认、怎么重试属于工具调用侧的问题不在这里展开。七、落地顺序与观测指标不要一上线就把所有动作接进审批。分三步。第一步只接对外可见这一类。判据最机械——结果有没有离开系统是个布尔值不依赖模型自评也不依赖业务规则并且这一类做错了没法挽回。这一步的真正目的是把通知链路、恢复路径、超时兜底这三件基础设施验证一遍它们不通接再多动作也只是把问题放大。第二步接资金与权限。这类直接进 L3双人确认并且强制先出预演结果。工作量不在审批本身在把影响范围算出来一次退款影响哪几笔、一次授权会让谁能看到什么。算不出来的动作就没法给人看也就没法批——这时该先补统计能力而不是先上闸门。**第三步接批量与影响面。**它依赖第二步的统计结果同一个动作是动 214 个对象还是 26 个。阈值应该拿观测数据反推不要凭直觉填一个整数。L0 和 L1 始终不接审批把人的注意力留给真正需要它的那几类动作。接下来是观测。审批系统的指标衡量的是人和闸门的配合质量而不只是成功率。指标口径异常信号通常意味着中断率每次任务触发的中断次数持续上升判据在变松或分级档位定低了等待时长 P95按档位分开统计L2 与 L3 量级接近路由或分级串了档位没有区分度超时率超时未决数除以中断总数偏高先查通知可达性再查判据是否过严快速批准占比决策用时低于 3 秒的批准占比持续升高审批疲劳的早期信号误放行回滚次数批准后又被回滚的次数出现即需归因判据过松的直接代价恢复异常率恢复时发现目标已变或凭证失效的比例偏高中断时长过长或第五章的校验没做第二行和第四行值得单独说。L2 和 L3 的等待时长如果差不多说明分级没有生效L3 是双人确认正常应该是 L2 的数倍接近只可能是所有请求都被路由到同一个值班位或者 L3 根本没进双人路径。而快速批准占比比中断率更值得盯——中断率上升只说明闸门变多了这个指标上升说明人已经不看了闸门在形式上存在、在事实上失效。指标定义落到配置上大致是这样⚠️ 代码待验证metrics:interrupt_rate:expr:count(approval_created) / count(agent_run)note:分工具看整体值会掩盖单类动作的判据过宽approval_wait_p95_seconds:expr:histogram_quantile(0.95,approval_wait_seconds)labels:[level,tool]note:L2 与 L3 的量级应明显不同接近即为异常approval_timeout_rate:expr:count(approval_expired) / count(approval_created)note:偏高时先查通知可达性再查判据rubber_stamp_rate:expr:count(decision_latency_seconds 3) / count(approval_approved)note:审批疲劳的早期信号优先级高于中断率rollback_after_approval:expr:count(post_approval_rollback_total)note:判据过松的直接代价用于反向校准档位resume_anomaly_rate:expr:count(resume_check_failed) / count(approval_approved)note:反映中断时长与恢复校验的质量回到落地本身。审批中断的目标从来不是把风险降到零而是把人的注意力投在少数几个真正不可逆的决策上。判断一个闸门做得好不好标准很朴素如果它的存在感是每周都要点几十次同意那它多半已经失效如果是一个月里拦下过两次事后想起来会出一身冷汗的动作它就是值得的。完整版资料清单本文用到的审批判据表、中断状态字段清单和指标定义都整理在里面了扫码即可获取附表 A关键取舍一览本文涉及的所有工程判断集中在这里方便按需回看。取舍本文结论判断依据位置判据从哪来资源元数据加调用参数不靠模型自评上下文可被污染模型描述不可信第一章写操作是否都停不停按后果分而不是按动词分统一拦写会导致审批脱敏第一章分几档四档不是二值二值只有太松和太吵两种结局第二章降档依据只认可枚举的结构化理由模型置信度不能作为降档输入第二章闸门主位置执行器侧且不允许旁路只有这里能拿到最终参数第三章摘要由谁生成业务事实由代码渲染模型只写一句补充整段交给模型会被注入操纵第四章拒绝怎么处理可带意见回到上下文不是终态决定审批是卡点还是回路第四章中断状态存哪持久存储含最终参数快照与版本标识中断跨越进程生命周期第五章重启后怎么恢复从第一个非终态步骤继续显式步骤序列使恢复变成普通读表第五章参数变了怎么办已批准作废重新生成摘要再问人批的和执行的不是同一件事第五章超时默认行为拒绝可撤销动作才允许放行没人管在夜间概率最高第六章通知发给谁发起人加值班位并行只发发起人会让任务无限期挂起第六章审批幂等键创建中断时生成不在执行时生成执行时生成会让两次执行带两个键第六章先落盘还是先执行先落盘开始执行再执行反过来会在恢复时重复执行第六章最该先接哪类对外可见类判据最机械且错了无法挽回第七章最该盯的指标快速批准占比中断率是规模指标不是质量指标第七章附表 B术语速查表术语含义人在环关键动作由人做出决策系统负责执行与记录不可逆动作执行后不存在恢复路径的动作如物理删除、覆盖写入影响面单次动作波及的对象数量与租户范围四档分级可静默执行、执行后告知、事前确认、双人确认预演只读地跑一遍动作产出结果供人判断不产生副作用黑名单优先独立于分级计算的绝不放行清单优先级最高旁路留痕紧急放行通道的使用必须记录并可事后补审批最终参数快照适配层补全后、真正用于执行的参数集合状态跃迁中断记录从待决到已批准或已拒绝的一次性状态变化比较并交换仅在当前状态符合预期时才写入新状态的并发控制方式审批疲劳中断次数过多导致人不再阅读内容而机械同意的状态快速批准占比决策用时低于阈值的批准在全部批准中的比例疲劳的观测口径写在最后这篇用到的资料写这篇文章时我把几个 Agent 项目里审批相关的取舍重新梳理了一遍顺手整理成几份配套的东西大模型学习路线图从 LLM 基础到 Agent 开发各阶段该学什么、用什么资料大模型全套教程按主题分好的视频与文档清单大模型实战好书24 本附每本适合的阶段资料是我自己整理的放在下面这个码上扫码即可获取添加时备注「大模型」优先通过。拿到之后建议先看学习路线图那一份先定位自己在哪个阶段再决定学什么比一上来就啃框架效率高得多。
返回列表