业务方要的是「全自动」,我们给的是「半自动」
去年 12 月的评审会上,业务方提了一个需求:做一个能自动处理客诉的 Agent,从接到投诉到给出解决方案、执行补偿、发送通知,全链路不需要人。
我们的答复是:可以做,但要分阶段,且第一阶段必须有人工确认。对方不太满意,觉得我们在保守。这场会开了两个小时,最后靠一个妥协方案收场——我们做了,但设计了一套自主度分级。
跑了四个月,现在回头看,当时的保守是对的。这篇记录我们的分级方法、每级的实际数据,以及「人类介入点」该怎么设计。
先把「自主程度」定义清楚
争论的一半原因是大家说的「自动」不是一回事。业务方说的自动是「不用人管」,我们说的自动是「不用写代码」。所以第一件事是定义分级。
我们用了 L0~L5 六级(借鉴自动驾驶的分级思路,但内容完全按我们的场景定义):
| 级别 | 定义 | 决策者 | 执行者 | 典型场景 |
|---|---|---|---|---|
| L0 | 纯工具 | 人 | 人 | 传统系统 |
| L1 | 辅助生成 | 人 | 人 | Copilot 式代码补全、回复草稿 |
| L2 | 建议 + 人确认 | Agent 提议,人批准 | 人 | 客诉处理方案推荐 |
| L3 | 自动执行 + 事后审核 | Agent | Agent | 低风险退款、标准问答 |
| L4 | 自动执行 + 抽样审核 | Agent | Agent | 高置信度的常规操作 |
| L5 | 全自动无审核 | Agent | Agent | 我们不提供 |
关键的分界线在 L2 和 L3 之间:决策权在谁手上。L2 是 Agent 提议人拍板,L3 是 Agent 自己拍板但事后可查。这条线的位置,决定了整个系统的风险模型。
我们最后划的线是:涉及钱、涉及不可逆操作、涉及对外承诺的,最高 L2;只读操作和信息类,可以到 L3~L4。
四个月的实际数据
我们把客诉 Agent 按这个分级上线,先全量跑 L2,然后逐场景放开到 L3/L4。四个月的数据:
| 场景 | 级别 | 月均量 | 人工介入耗时 | Agent 错误率 | 事故数 |
|---|---|---|---|---|---|
| 物流咨询 | L4 | 18,400 | 0(抽样 2%) | 1.2% | 0 |
| 产品使用问答 | L4 | 12,100 | 0(抽样 3%) | 2.8% | 0 |
| 发票问题 | L3 | 4,200 | 0(全量异步审核) | 3.4% | 0 |
| 小额补偿(<50元) | L3 | 2,800 | 0(事后审核) | 1.8% | 1(多发了 12 元) |
| 退款申请(<200元) | L2 | 3,100 | 平均 40 秒/单 | 2.1% | 0 |
| 大额/争议客诉 | L1 | 860 | 全程人工 | — | 0 |
几个观察:
一是人工介入的耗时分布极不均匀。 860 单大额客诉占了人工总时长的 61%,而它们只占总量的 1.3%。这说明把最难的部分留给人工,是对的——这部分 Agent 做不好,硬做只会出事。
二是 L3 的「事后审核」几乎没起作用。 我们设计的流程是 Agent 执行完,人工在 24 小时内审核,发现问题可回滚。实际执行下来,4,200 单发票问题里,人工真正仔细看过的不到 300 单。人的注意力和「审核」这个动作是不匹配的——没有明确激励,没人会认真看一堆 Agent 已经处理完的单子。
这件事让我们把 L3 的定位改了:不指望事后审核能拦住问题,而是要求 L3 场景必须同时满足「单笔损失有上限」和「可批量回滚」两个条件。事后审核只作为兜底,不作为主要防线。
三是那 1 起事故。 小额补偿场景,Agent 给同一个用户连续发了两次 12 元补偿(第一次成功但返回超时,它以为失败了重试)。损失不大,但暴露了问题:我们的幂等控制没做好。后来强制所有写操作必须带幂等键。
人类介入点怎么设计
这是整个设计里最花心思的部分。我们试过几种模式,有的有效,有的基本没用。
模式一:事前确认(有效,但别滥用)
Agent 给出方案,人点确认才执行。这是 L2 的标准模式,效果很好——退款场景的 2.1% 错误率是全场景里最低的。
但有个前提:确认界面必须让人能快速判断。我们第一版的确认弹窗长这样:
┌────────────────────────────────────┐ │ Agent 建议执行以下操作: │ │ 调用 applyRefund(orderNo=SO2026... │ │ amount=129.00, reason=QUALITY_... │ │ [确认] [取消] │ └────────────────────────────────────┘客服同事反馈看不懂,要么全点确认(等于没审),要么全点取消(等于 Agent 白干)。改版后:
┌────────────────────────────────────┐ │ 建议:同意退款 │ │ │ │ 订单 SO20260115001 │ │ 商品 保温杯(蓝色)×1 │ │ 实付 ¥129.00 → 退款 ¥129.00 │ │ 原因 质量问题(用户提供了照片) │ │ │ │ ⚠ 该订单有 1 次历史退款记录 │ │ │ │ [同意退款] [改金额] [拒绝] │ └────────────────────────────────────┘差别在于:把技术参数翻译成业务语言,把判断依据展示出来,把异常信号高亮。改版后客服的平均处理时间从 40 秒降到 22 秒,而拦截率(点「改金额」或「拒绝」的比例)从 3% 升到 7.8%——说明他们真的在看。
模式二:置信度阈值触发(效果一般)
让 Agent 输出一个置信度,低于阈值就转人工。理论上很美,实际上问题很大:模型输出的置信度不可靠。
我们统计过,Agent 自报置信度 > 0.9 的回答里,实际错误率 2.4%;置信度 0.7~0.9 的回答,错误率 3.1%。区分度极小,基本没有实用价值。
后来我们改用了客观的、非模型生成的信号作为转人工的触发条件:
@Component public class EscalationPolicy { public Optional<String> shouldEscalate(AgentRunContext ctx, AgentAnswer answer) { // 1. 工具调用失败过 if (ctx.toolFailureCount() > 0) { return Optional.of("工具调用失败,需人工确认结果可靠性"); } // 2. 多轮仍未收敛 if (ctx.stepCount() > 12) { return Optional.of("推理步数过多,可能判断困难"); } // 3. 检索结果相关性低 if (answer.maxRetrievalScore() < 0.68) { return Optional.of("未找到高相关度的知识,答案可能不准确"); } // 4. 触发了敏感词(投诉、起诉、媒体、监管) if (sensitiveMatcher.matches(ctx.userInput())) { return Optional.of("涉及敏感表述,转人工处理"); } // 5. 金额超过阈值 if (answer.involvesAmount() && answer.amount() > 200) { return Optional.of("涉及金额超过 200 元,需人工审批"); } return Optional.empty(); } }这五条规则的转人工率是 14.2%,覆盖了 71% 的实际错误案例。比置信度阈值好用得多。
模式三:用户主动求助(必须要有)
最简单但最重要的一条:任何时候用户说「转人工」「找客服」,立刻转,不问理由。
我们第一版做了个「智能判断」——用户说转人工时,Agent 先问一句「是不是这个问题没解决?我可以再帮您看看」。这个设计被投诉过好几次,用户觉得在被推诿。后来改成无条件转接。
这个细节说明一件事:「人可以随时接管」这条通道的价值,不在于它实际被用多少次,而在于它存在本身让用户安心。我们的用户满意度调研里,有 38% 的人提到「知道随时能找人」是使用 AI 客服的前提。
放弃的部分
说两个我们尝试过又放弃的设计。
放弃一:让 Agent 自己决定要不要请示人
第一版我们给 Agent 加了一个
askForHelp工具,让它觉得没把握的时候主动求助。跑了一个月,调用率 0.7%。原因分析下来是:模型没有「知道自己不知道」的能力,或者说它没有这个动机。在它被训练成「要给出有帮助的回答」的前提下,求助对它来说是一个「失败」,它会倾向于硬答。
后来改成上面那套基于客观信号的规则,效果好了很多。这条经验挺重要的:不要指望模型能自我评估,用外部可观测的信号来判断。
放弃二:渐进式放权
我们原本设计了一套「Agent 表现好就自动提升自主度」的机制:连续 100 单无错误,就把它从 L2 升到 L3。
测试环境下跑得挺好,但我们没敢上线。原因是想清楚了一件事:自主度不是一个 Agent 的属性,是一个场景的属性。
「退款」这个场景的风险是固定的,不因为 Agent 最近表现好就降低。今天它连续 100 单没错,第 101 单遇到一个没见过的情况,照样会出错,而这一单的损失是真实的。用历史表现来预测单次的可靠性,这个逻辑本身就不成立。
现在我们的做法还是人工评审:一个场景要从 L2 升到 L3,需要积累 2,000 单以上的数据、错误率 < 1%、且通过风控评审。这个过程大概要 3 个月,慢,但是稳。
风险可控性:三条硬约束
除了分级和介入点,我们还定了三条不管什么级别都不能破的规则。
- 单笔损失上限。每个 L3/L4 场景都必须设定一个「最坏情况下的单次损失」,这个数字用来做熔断。小额补偿的上限是 50 元,所以即使 Agent 完全失控,一次也就损失 50 元;
- 可批量回滚。所有 L3 以上的写操作,必须记录完整的回滚信息,且提供批量回滚工具。我们出那次多发 12 元的事故时,用批量回滚在 8 分钟内处理完了 34 个受影响的单;
- 全局熔断开关。一个配置开关,30 秒内可以把所有 Agent 的自主执行权限降到 L2(只提议不执行)。这个开关我们用过两次,一次是模型供应商故障导致响应异常,一次是发现了一个 prompt 注入的漏洞。
@Component public class AutonomyGovernor { /** 全局自主度上限,可通过配置中心热更新 */ @Value("${agent.max-autonomy-level:L4}") private volatile int globalMaxLevel; /** 单场景熔断:连续 N 单异常就降级 */ private final Map<String, AtomicInteger> errorStreak = new ConcurrentHashMap<>(); public int effectiveLevel(String scene, int sceneLevel) { if (errorStreak.getOrDefault(scene, new AtomicInteger()).get() >= 5) { log.warn("scene {} degraded to L2 due to error streak", scene); return 2; } return Math.min(sceneLevel, globalMaxLevel); } public void recordError(String scene) { errorStreak.computeIfAbsent(scene, k -> new AtomicInteger()).incrementAndGet(); } public void recordSuccess(String scene) { errorStreak.getOrDefault(scene, new AtomicInteger()).set(0); } }那个「连续 5 单异常自动降级到 L2」的规则,上线四个月触发过 3 次,其中 2 次是真实的下游系统问题,1 次是误报。我们觉得这个误报率可以接受。
关于「能力边界」的判断
做了这四个月,我对「Agent 能做到什么程度」这件事的判断标准变了。
原来我以为是看任务复杂度——简单的任务可以自动化,复杂的需要人。现在我认为判断标准是「错误的代价」和「错误的可发现性」,而不是任务复杂度。
举个对比:
| 任务 | 复杂度 | 错误代价 | 错误可发现性 | 我们给的级别 |
|---|---|---|---|---|
| 物流状态查询 | 低 | 低(答错用户会再问) | 高(用户立刻知道) | L4 |
| 合同条款抽取 | 高 | 中(后续有人工复核) | 中 | L2 |
| 客诉情绪识别 | 中 | 低 | 低(没人会来验证) | L3(仅打标,不据此决策) |
| 自动退款 | 低 | 高(真金白银) | 低(用户不会说钱多了) | L2 |
「自动退款」这个任务技术上非常简单(就一次 API 调用),但它是所有场景里自主度最低的,因为错误代价高且不可发现。反过来「合同条款抽取」很复杂,但因为后面有人复核,可以给到 L2 甚至更高。
这个判断框架在评审新场景时很好用。现在业务方再提「全自动」需求,我们就拿这张表跟他过:这个任务的错误代价是什么?错了谁能发现?多久能发现?发现之后能不能补救?四个问题过完,自主度级别基本就确定了。
先到这
《从 Copilot 到 Autonomous Agent:能力边界的把握》这块我前前后后踩了不止一次。今天先写这些,后面想到新的再补。