Agent 什么时候该交给人?把人工接管设计成正常路径
Agent 卡住时,最危险的不是它停下来,而是它明明缺证据、权限或确认,却还在自信地往下做。把升级人工的条件、交接资料和恢复方式写清楚,人工接管才不是一句‘请联系客服’。
本篇阅读顺序 · 三遍读法
一篇文章,读出三种能力。
先弄懂它为什么这样工作,再把条件换一换,看方案还能不能站住,最后用代码和证据复盘一遍。顺序固定,读完才知道自己是真的会了,还是只记住了名词。
沿着直觉、公式和边界读正文,看到变量就问输入、状态、复杂度分别是什么。
把面试官的追问当作小型设计评审:更大流量、更少上下文、外部失败或安全约束出现时,局部如何重算。
沿代码、表格和证据卡复盘,最后用文末的 60 秒回答确认自己没有只记住名词。
客服 Agent 遇到一笔金额不明的退款申请。
它先查订单,没有唯一匹配;又查了两次历史对话,还是对不上;最后为了不让用户等太久,给出一句:“已为你处理退款,请留意到账。”
这句话的问题,不在语气,而在它把“还没确认”说成了“已经完成”。
真正可靠的系统,应该在这里停下来,把线索和缺口交给一个人处理。人工接管不是 Agent 失败后的补丁,而是高风险任务里提前设计好的出口。
先给一个能复述的答案
人工接管要回答四件事:什么情况必须升级、升级时把什么资料交给人、人工处理后怎样把状态交回系统、如果一直没人接又怎么办。
我会把任务状态拆成 needs_human、human_processing、resolved 和 cancelled,由程序负责状态转换,模型只负责整理事实和提出建议。进入人工队列前,系统要冻结可能产生副作用的动作,保存用户目标、已做步骤、证据、权限、预算和最后一个确定状态。人工处理后必须写回结构化结果和版本号,Agent 只能从新的状态继续,不能把旧对话重新播放一遍。
如果没有可用的人,就明确告诉用户当前卡在哪里、已经完成了什么、下一步何时再试;不要用“再想一遍”或无限重试来掩盖没有 owner 的事实。
一、先分清:什么叫“需要人”
不是所有异常都值得打断人工。网络抖了一下,可以退避重试;参数少一个,可以向用户问清楚;但下面几类情况,继续自动做通常只会放大风险。
| 情况 | 为什么不能继续自动做 | 人工要确认什么 |
|---|---|---|
| 副作用即将发生 | 扣款、发货、权限变更不可轻易撤回 | 目标资源、参数、影响范围 |
| 事实互相冲突 | Agent 没有依据替用户选边 | 哪个来源和口径有效 |
| 权限或身份不明确 | 可能把数据给错人,或越权执行 | 用户身份、资源范围、授权记录 |
| 反复没有新信息 | 重复搜索只消耗预算,不会改变结论 | 缺口在哪里,是否换路径 |
| 低置信度但高影响 | “大概没问题”不能作为上线依据 | 风险是否可接受,是否需要审批 |
| 规则无法覆盖的新情况 | 模型只能猜,系统没有可验证分支 | 新分支的处理方式和 owner |
这里有一个简单判断:如果错误成本比等待一个人更高,就应优先升级;如果只是缺少低风险参数,先向用户澄清;如果还有安全的只读工作可以做,可以交付部分结果,但要把未完成的动作单独标出来。
二、升级前先把“现场”整理好
人工最怕接到一句:“这个任务有点问题,你帮忙看一下。”
如果接手的人还要重新翻聊天记录、猜 Agent 查过什么、确认哪一步已经写入外部系统,升级本身就成了新的事故源。
最小的升级包至少包含这些字段:
{
"task_id": "refund_20260912_0187",
"status": "needs_human",
"reason": "multiple_orders_match",
"user_goal": "退掉昨天重复扣款的那一笔",
"completed": [
"读取用户身份",
"查询近 30 天订单",
"找到 2 个金额和时间都相同的订单"
],
"missing": ["无法确认目标订单"],
"evidence": [
{"source": "order_api", "ref": "order_781", "amount": 99},
{"source": "order_api", "ref": "order_794", "amount": 99}
],
"side_effects": [],
"deadline_at": "2026-09-12T11:30:00-07:00",
"owner": null,
"resume_token": "state_v18"
}
这里的 completed 只写已经被工具回执证明的动作,不能把模型计划当成已完成;side_effects 要列出已经发生或可能发生的外部变化;resume_token 用来指向持久化状态,不要把一长段聊天记录当作恢复依据。
如果证据里含有身份证号、联系方式或内部链接,给人工看的页面可以做脱敏,但审计记录仍要保留可追溯的引用或哈希。脱敏不是删除事实,更不是让接手的人只能靠猜。
三、状态机要把人工接管当成一条路
一个实用的状态流可以这样写:
received
├─ 信息不足 ───────────────> needs_clarification
├─ 低风险、可自动完成 ────> running
├─ 高风险或事实冲突 ───────> needs_human
└─ 不允许执行 ─────────────> rejected
needs_human
├─ 有人接单 ───────────────> human_processing
├─ 超过等待时间 ───────────> expired
└─ 用户取消 ───────────────> cancelled
human_processing
├─ 补齐证据并批准 ─────────> running
├─ 人工直接完成 ───────────> resolved
└─ 无法处理 ───────────────> rejected
状态转换要由服务端控制,并记录 from、to、actor、reason、version 和时间。模型可以建议“需要人工确认”,但不能自己把状态改成 resolved;人工页面上的“完成”按钮,也要重新检查任务版本,防止有人在等待期间已经处理过同一资源。
一个容易漏掉的细节是租约。人工接单后,任务不能永远挂在某个人名下;可以设置短租约和续租动作,超时就回到队列或升级给值班负责人。这样“有人接了但没有处理”不会变成系统眼里的成功。
四、交接不是转发聊天记录
交给人的资料要按决策顺序排列,而不是按消息时间堆在页面上。
我通常把接手页面分成四块:
- 用户要什么:一句话目标、关键约束和截止时间;
- 已经确定的事实:每条事实旁边放来源和时间;
- 还差什么:缺参数、冲突来源、权限或审批;
- 人可以做什么:确认、补查、拒绝、转交,分别对应明确的按钮和结果。
“继续处理”不是一个足够好的按钮。它没有告诉人要处理哪一笔、会不会产生副作用、完成后状态怎样变化。按钮应该写成“确认订单 781 并提交退款”“要求用户补充订单号”或“拒绝并说明原因”,让动作本身就带着边界。
人工做出的决定也要结构化回写:
{
"task_id": "refund_20260912_0187",
"decision": "ask_user",
"message": "请确认要退款的订单号:781 还是 794?",
"actor_id": "support_42",
"based_on_version": "state_v18",
"next_state": "needs_clarification"
}
如果人工决定直接执行写操作,提交层仍应再次检查权限、资源版本和幂等键。人工不是绕过系统规则的超级用户;只是把当前无法自动判断的那一小段决策交给了有责任的人。
五、用户看到的应该是事实,不是内部术语
内部状态可以叫 needs_human,用户不需要看到这个枚举。用户真正关心的是:
- 现在做到哪一步了;
- 为什么不能马上完成;
- 还需要他提供什么;
- 谁会继续处理,大概什么时候有结果。
比如不要说:
任务进入人工升级队列,等待 workflow owner 续租。
可以说:
我找到了两笔金额相同的订单,但还不能确定你要退哪一笔。现在先不提交退款,请回复订单号 781 或 794;如果你不确定,我也可以把两笔订单的时间和商户发给你核对。
这段话没有承诺不存在的进展,也没有把责任推给一个看不见的“系统”。它把缺口说清楚,还给了用户下一步。
六、没人接手时,系统怎么收尾
人工队列不是无限容量。要提前定义等待时间和降级动作:
| 等待状态 | 系统动作 | 用户看到什么 |
|---|---|---|
| 0~5 分钟 | 保留任务和证据,通知值班队列 | 已收到,正在确认 |
| 超过目标时限 | 提醒 owner,禁止新增副作用 | 仍在核对,给出预计时间 |
| 队列满或无人值班 | 转备用队列或交付部分结果 | 哪些已完成,哪些需要稍后继续 |
| 超过保留期限 | 关闭任务,保留审计记录 | 任务已关闭,可重新发起 |
“超时就自动完成”通常不是降级,是把未知结果伪装成成功。对于扣款、删除、发消息等动作,超过时限应保留 unknown 或 needs_human,等回执和资源状态核对清楚后再决定。
七、怎么评测人工接管做得好不好
不要只统计“转人工率”。转得太少,可能是 Agent 在硬猜;转得太多,人工队列会先被拖垮。至少看下面几组指标:
| 指标 | 说明 |
|---|---|
| 升级准确率 | 真正需要人工的任务有没有被挡住,低风险任务有没有被过度升级 |
| 接手完整率 | 人工首次打开页面后,能否直接知道目标、证据和缺口 |
| 首次处理时间 | 进入队列到有人开始处理的时间 |
| 恢复成功率 | 人工决定写回后,Agent 能否从正确状态继续 |
| 重复副作用率 | 接管、重试或恢复过程中是否重复扣款、发货、发消息 |
| 用户补充轮次 | 用户需要来回补充几次才能完成任务 |
| 超时关闭率 | 没人接、资料不全或队列拥堵造成的关闭比例 |
评测集要刻意加入边界样本:两个资源都匹配、审批刚过期、工具已经提交但响应超时、用户中途取消、人工处理后版本发生变化。只测“正常订单成功退款”,测不出接管系统是否可靠。
八、一个够小的升级判断函数
判断逻辑可以先写在普通代码里,再决定哪些部分值得交给模型:
def next_route(state, result):
if result.has_side_effect and not result.confirmed:
return "needs_human", "副作用尚未得到确认"
if result.permission == "unknown":
return "needs_human", "无法确认资源权限"
if result.conflict_count > 0:
return "needs_human", "证据存在冲突"
if state.no_progress_steps >= 2:
return "needs_human", "连续两步没有获得新信息"
if state.remaining_ms < state.minimum_commit_budget_ms:
return "needs_human", "剩余时间不足以安全提交"
if result.missing_fields:
return "needs_clarification", "缺少用户参数"
return "running", "继续自动处理"
这里的顺序很重要:先挡副作用、权限和冲突,再判断是否还有预算。不能因为“还剩很多 token”就绕过安全条件,也不能把用户需要确认的事情当成模型可以自行补齐的字段。
模型适合做的是把 reason 翻译成用户听得懂的话、整理证据摘要、提出少量澄清问题;真正的状态跳转、队列租约、幂等提交和审计记录应该由程序控制。
九、面试官继续追问时怎么答
问:什么时候应该转人工,而不是让 Agent 再试一次?
答:我会先看风险和信息增益。副作用、权限不明、证据冲突、连续无新信息或剩余预算不足时,继续尝试的收益很低而风险在上升,就进入 needs_human。如果只是缺少用户参数,进入澄清;如果是可恢复的网络错误,才按策略重试。
问:人工接管后怎样避免重复执行?
答:升级前冻结可写动作并保存任务版本、资源版本和幂等键。人工提交时重新比较版本,成功后写回结构化结果;Agent 只从新状态继续,不能重新播放旧聊天记录。外部动作已经发出但结果未知时,先对账,不直接重试。
问:人工队列也超时怎么办?
答:把队列租约、目标处理时间和关闭策略写成状态机。超时后提醒、转备用队列或交付部分结果,但不把未知状态改成成功。用户要看到已经完成的部分、未完成的原因和下一步,而不是一句模糊的“系统繁忙”。
最后检查一遍
- 哪些风险、冲突和权限情况必须升级,已经写成可测试条件
- 升级包里有目标、已完成步骤、证据、缺口、预算和版本
-
needs_human、human_processing、resolved等状态由程序控制 - 人工接单有租约,超时能回队列或交给备用 owner
- 人工决定会重新检查权限、资源版本和幂等键
- 用户看到的是当前事实和下一步,不是内部枚举和日志堆栈
- 评测集包含冲突、未知结果、取消和版本变化
- 没有人接手时,系统不会把未知结果包装成成功
人工接管做得好,用户未必会注意到它;但当 Agent 真正遇到模糊、高风险或没见过的情况时,系统不会靠猜测硬撑,而是知道该停在哪里、把什么交给谁、怎样安全地回来。这才是“自动化”可以长期运行的前提。