Skip to content

Lab A · 客服工单智能分流 ​

预计投入 15-20 小时难度 ★★☆前置 第 4、6、7 章数据 合成 / 公开

本章你会学到:如何把一个「分错工单」的现场问题,做成有基线、有评测、有人工复核、有反馈回流的完整部署。

一句话:这个 Lab 的价值不在模型多准,而在你能证明它比规则好多少、在什么条件下可以自动放行、出问题时怎么退回来。

一个现场片段 ​

以下为合成场景,用于说明典型情境,不对应任何真实客户。

你第一次去客户现场,客服主管指着屏幕说:「昨天又有 7 张安全类工单被分到了计费队列,等了 6 个小时才转回来,客户已经在群里骂人了。」

你打开工单系统:每天约 400 张新工单,12 名一线坐席靠肉眼判断,手动拖进 6 个二线队列。分派平均 4 分钟,约每 4 张就有 1 张要转第二次,每次转派又多等几个小时。二线抱怨总接别人挑剩的活,一线抱怨标签规则三天两头变。

主管只问:「能不能让系统自己分,分不准的再给我看?你怎么证明它比现在的规则好,而且不会把安全工单再分错?」上一家供应商给过一个演示惊艳、上线一周就没人用的工具。

你的任务是回答这个问题。

交付什么 ​

做完这个 Lab,你应该有一份能放进 GitHub、也能在面试里讲 10 分钟的作品:可复现的仓库(数据、基线、模型、评测脚本)、评测报告(基线 vs 模型、分类别指标、混淆矩阵、错误分析)、可演示的分流流程、上线检查与灰度方案、复盘。

背景与用户故事 ​

设定:一家约 300 人的 B2B SaaS 公司(合成),产品提供数据看板与 API。客服体系分一线(12 人,负责首响与分派)和二线(6 个专业队列)。每天约 400 张新工单,来自邮件、在线聊天和电话记录。

三个用户故事,分别对应三类人:

  1. 一线坐席:「工单进来时直接告诉我该去哪个队列;不确定就给我两个候选,我点一下就好。」
  2. 二线主管:「我不想再收到错派工单。分错了,我要能看到它依据的是工单里哪句话。」
  3. 运营负责人:「6 周内看到首次分派时长下降,不能靠加班。出问题要能立刻退回人工,不影响 SLA。」

v1 明确不做四件事:不自动回复客户、不自动关单、不预测优先级、不做多语言。范围越窄,越容易验证。

成功指标 ​

下面的基线是「示例现场」的设定值,目标值也是教学用的合理值,不是行业标准。落地时用自己的数据重算,并写清口径。

指标定义与口径基线(示例)目标(示例)为什么重要
一级分类 macro-F16 个队列的 F1 取宏平均,避免大类掩盖小类0.55≥ 0.80主指标,衡量「每个队列都能分对」
top-2 命中率正确队列出现在前两个候选里的比例0.72≥ 0.92决定「建议档」是否有用
自动分派覆盖率置信度 ≥ 0.85、被系统直接分派的工单占比0≥ 35%覆盖率不够,业务方感受不到价值
自动分派子集准确率只看被自动分派的那部分,准确率是多少—≥ 95%覆盖率与准确率必须成对报告
人工修正率复核者改掉模型标签的比例—≤ 15%人工负担的直接度量
首次分派时长(中位数)工单创建到进入正确队列的时间4 分钟≤ 1 分钟用户能直接感知的收益
7 天重派率7 天内被转到第二个队列的工单占比25%≤ 15%误派的业务后果
单工单成本模型调用成本 / 工单数—≤ 0.02 美元决定能不能长期跑
护栏:P95 时延单张工单从进入到给出结果的耗时—≤ 2 秒超时要有兜底
护栏:安全事件数据泄露、越权、错误分派到安全队列未拦截—0不可用「平均指标好」掩盖

指标纪律

只报 macro-F1 是耍流氓,只报自动覆盖率更危险。准确率、覆盖率、人工修正率必须同时出现,否则读者无法判断你到底带来了多少价值。

数据:字段、标签与合成方法 ​

工单字段 ​

字段类型用途注意
ticket_idstring唯一标识不要用自增数字当训练特征
created_atdatetime分时间窗、算时长合成数据要虚构时间
channelenum邮件 / 聊天 / 电话记录不同渠道质量差异大
subjectstring分类主要信号可能为空
bodystring分类主要信号可能很长,需截断
current_tagslist现有规则标签可作为基线输入
assigned_queueenum人工分派的最终队列这是标签来源,不是因果
resolution_notestext错误分析与复盘含 PII,必须脱敏
reopenedbool质量信号与重派率相关

标签体系 ​

v1 只做 6 个一级队列。每类都要给出判定标准和「最容易混的反例」,否则标注不一致,模型学不到稳定边界。

队列判定标准易混反例
计费 Billing发票、扣款、退款、套餐变更、税率因账号停用导致的「扣款失败」可能是账号问题
账号与登录 Account注册、登录、双因素、成员与权限管理密码重置邮件收不到,可能是邮件网关(集成)
API 与集成 Integration密钥、限流、webhook、SDK、第三方系统对接数据同步延迟既有 API 也有数据问题
数据与报表 Data导入导出、报表口径、数据丢失、同步延迟权限导致看不到报表,应归账号
安全与合规 Security可疑登录、漏洞报告、数据删除请求、合规文件任何疑似安全事件,默认归安全并人工复核
其他 Other以上都不是,或信息不足无法判断不要把所有「不确定」都丢进来,要能说出为什么

安全类要有「默认拒绝」

只要工单里出现安全信号(异常登录、数据删除请求、漏洞报告),无论模型多有把握,都强制进入安全队列并触发人工复核。这是用规则写死的,不交给模型判断。

没有真实数据时怎么合成 ​

  1. 写种子模板:6 类 × 5 个种子,每个种子描述行业、产品、问题、情绪、信息完整度。例如「制造业客户,API 限流,愤怒,没给请求 ID」。
  2. 让模型扩写:按种子生成 800 条工单,要求像真实客服邮件——有寒暄、错别字、多问题混杂、缺关键信息,并标注为合成数据。
  3. 人工标注:标注约 500 条作为金标;抽 50 条隔一天重标,前后一致率低于 85% 就回去改标签定义。
  4. 可选公开数据:公开客服工单数据集可补多样性,但使用前确认许可证允许学习用途,并检查标签能否映射到 6 类。

四个集合必须分开:few-shot 示例 60 条(每类 10 条)、开发集 120 条(调提示词用)、评测集 200 条(报指标用)、盲测集 120 条(最后只跑一次),合计 500 条。同一批模板生成的工单不能跨集合,否则就是数据泄漏。

数据红线

只能用合成数据、公开数据或已获书面授权的脱敏数据。不要把真实客户工单贴进第三方 API;不要用真实邮箱、电话、卡号测试;截图和报告里不能出现可识别信息。

基线:先做一个能跑的规则分类器 ​

任何模型结果都要和基线比。规则基线很土,但便宜、可解释、能兜底,也是和业务方沟通的锚点。

python
RULES = {
    "billing":     ["发票", "退款", "扣款", "续费", "价格", "invoice", "refund"],
    "account":     ["登录", "密码", "验证码", "注册", "成员", "权限", "2fa"],
    "integration": ["api", "密钥", "token", "限流", "webhook", "sdk", "对接"],
    "data":        ["导入", "导出", "报表", "字段", "同步", "看板", "数据"],
    "security":    ["异常登录", "漏洞", "删除数据", "泄露", "合规", "审计"],
}

def rule_classify(ticket: dict) -> tuple[str, int]:
    text = f"{ticket['subject']} {ticket['body']}".lower()
    scores = {q: sum(1 for kw in kws if kw.lower() in text) for q, kws in RULES.items()}
    best = max(scores, key=scores.get)
    return (best, scores[best]) if scores[best] > 0 else ("other", 0)

在 200 条评测集上跑一遍,记录 macro-F1、每类 F1 和混淆矩阵。示例现场里规则基线的 macro-F1 大约 0.55——这个数字必须由你自己跑出来,不要抄。基线越诚实,后面模型提升的说服力越强。

方案:模型 + 置信度 + 人工复核 + 反馈回流 ​

整体架构 ​

text
工单
  │
  ├─ 预处理:截断正文、正则脱敏(邮箱/电话/卡号)、字段校验
  │
  ├─ 安全规则快路径:命中安全信号 → 直接进安全队列 + 人工复核
  │
  ├─ 规则基线:高确定性关键词命中 → 直接分派(可选,先观察)
  │
  └─ LLM 分类(输出 JSON:queue / second_choice / confidence / evidence)
        │
        └─ 置信度校准后分三档
              ├─ ≥ 0.85:自动分派
              ├─ 0.60-0.85:给一线「建议 + 候选」,人工确认
              └─ < 0.60:不展示模型结论,直接人工分派
                          │
                          └─ 人工修正 → 反馈库 → 每周复盘 → few-shot / 评测集 / 规则

三档路由的行为定义 ​

档位校准后置信度系统行为一线看到什么期望占比
自动≥ 0.85直接写入正确队列只显示「已自动分派」+ 一句依据30-50%
建议0.60-0.85预填两个候选,等人工确认推荐队列 + 第二候选 + 原文依据30-40%
人工< 0.60 或校验失败不给结论,走原流程无推荐(或灰色显示「不建议参考」)20-30%

提示词与输出契约 ​

提示词必须包含:角色与目标、每类定义与反例、JSON 字段说明、不确定时的约定。

json
{
  "queue": "billing | account | integration | data | security | other",
  "second_choice": "同 queue 枚举,可与 queue 相同",
  "confidence": 0.0,
  "evidence": "工单原文中支持判断的一句话,必须逐字摘录",
  "needs_human": true,
  "reason": "判断依据,≤40 字"
}

关键约束:

  • queue 必须是枚举值,否则校验失败,转人工;
  • evidence 必须能在原文中做子串匹配,否则降置信度;
  • confidence 是自报值,不能当概率用,必须校准;
  • 多问题混杂时选「最紧急且最专业」的一个,并给第二候选。

置信度校准 ​

模型自报的「我有 90% 把握」通常偏乐观。校准方法:把开发集的自报置信度分桶,算每桶的真实准确率,得到一张映射表。下表只是格式示例,数字必须用你自己的数据跑出来。

自报置信度桶样本数实际准确率(示例)校准后值路由结果
0.9-1.0180.940.94自动
0.8-0.9270.820.82建议
0.6-0.8340.630.63建议
0.4-0.6250.410.41人工
0-0.4160.190.19人工

校准之后,再决定自动档阈值:阈值不是拍脑袋定 0.85,而是看「在保证自动子集准确率 ≥ 95% 的前提下,覆盖率最高能到多少」。

反馈回流 ​

  • 每日:抽检 20 条自动分派工单,记录是否有错;
  • 每周:30 分钟复盘,把人工修正样本归类——标签歧义、信息缺失、模型误读、新类型;
  • 每月:更新 few-shot(每类 ≤ 10 条)、补充评测集、重跑全量评测;
  • 每季度:重审标签体系,业务变了标签就要变。

不要「自动学习」用户修正

用户点一下改标签,不一定是对的:可能是他手滑,可能是当班临时规则。修正样本必须经过一次人工质量审核,才能进入 few-shot 或评测集。不要用线上修正自动微调模型,那会把噪声固化进系统。

接口与关键代码 ​

把分类能力包成一个边界清晰的接口,后面换模型、换供应商都不用改业务代码。

python
import json
import os
from openai import OpenAI

client = OpenAI(
    base_url=os.environ.get("OPENAI_BASE_URL"),  # OpenAI 兼容网关地址,可为空走官方
    api_key=os.environ.get("OPENAI_API_KEY"),
)

SYSTEM_PROMPT = """你是客服工单分流器。队列定义、判定标准与反例见标签表。
规则:
1. 只输出 JSON,不要输出解释性文字。
2. evidence 必须逐字摘自工单原文。
3. 多问题时选最紧急且最专业的一个;无把握时 queue 填 other。
4. 涉及安全信号(异常登录、数据删除、漏洞)时 queue 必须是 security。
"""

def classify_ticket(ticket: dict) -> dict:
    user = f"主题:{ticket.get('subject','')}\n正文:{ticket.get('body','')[:2000]}"
    resp = client.chat.completions.create(
        model=os.environ["LLM_MODEL"],
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user},
        ],
        temperature=0,
        response_format={"type": "json_object"},
    )
    raw = resp.choices[0].message.content
    return validate(json.loads(raw), ticket)

def validate(pred: dict, ticket: dict) -> dict:
    allowed = {"billing", "account", "integration", "data", "security", "other"}
    ok = pred.get("queue") in allowed and 0.0 <= float(pred.get("confidence", -1)) <= 1.0
    evidence = pred.get("evidence", "")
    if evidence and evidence not in ticket.get("body", ""):
        ok = False
    pred["valid"] = ok
    return pred

AUTO_THRESHOLD, SUGGEST_THRESHOLD = 0.85, 0.60

def route(pred: dict, calibrated_conf: float) -> tuple[str, str | None]:
    if not pred["valid"]:
        return "human", None
    if calibrated_conf >= AUTO_THRESHOLD:
        return "auto_assign", pred["queue"]
    if calibrated_conf >= SUGGEST_THRESHOLD:
        return "suggest", pred["queue"]
    return "human", None

工程细节别忽略:正文截断到 2,000 字符控制成本;相同文本缓存;批量任务异步跑;每次调用记录 token、花费、模型与提示词版本,否则无法解释成本变化。

评测 ​

评测集结构 ​

分层条数目的判定方式
6 个队列,每类 25 条150主指标,覆盖常规工单人工标注
多问题混杂15考察「选最紧急」的规则人工标注 + 标注说明
信息不足 / 应归 other20考察是否乱猜人工标注
安全信号15安全红线,错一票否决人工标注 + 规则校验

必须报告的三张表 ​

  1. 总表:基线 vs 模型 v1 vs v2,含 macro-F1、top-2、自动覆盖率、自动子集准确率;
  2. 分类别表:每类 precision / recall / F1,标出最差的一类;
  3. 混淆矩阵:行是真实标签、列是预测标签,重点看「安全 → 其他」「其他 → 计费」两格。
python
from sklearn.metrics import f1_score, classification_report

macro_f1 = f1_score(y_true, y_pred, average="macro")
report = classification_report(y_true, y_pred, digits=3)
print(report)

错误分析(必做) ​

从错误里随机抽 30 条,逐条归类并写改进行动:

错误类型典型表现改进行动
标签歧义「扣款失败」被标成账号改标签定义,加反例
信息缺失只有一句「不行了」归 other 不算错;要求低置信
多问题混杂同时问发票和 API规定优先级,或试多标签
模型误读把「退款政策」当退款申请提示词加区分,补 few-shot

评测纪律 ​

  • 用开发集调提示词,评测集只在版本冻结时跑一次,盲测集最后只跑一次;
  • 评测集不进提示词和 few-shot;每次变更(提示词、模型、路由阈值、标签)都跑回归并记版本号;
  • 报告必须写清数字来自什么数据、什么版本、什么口径。

上线检查 ​

  • 数据合规:全部为合成 / 公开 / 已授权脱敏数据,已做 PII 处理
  • 三档路由可用,低置信度稳定落到人工,人工入口不依赖模型
  • 安全信号由规则独立兜底,不经过模型判断
  • 每次分派可追溯:输入哈希、输出、置信度、模型与提示词版本
  • 一键回滚开关能把流量切回 100% 人工,已演练一次
  • 监控与告警:P95 时延、错误率、自动档准确率抽检、修正率、成本;超时、非法 JSON、供应商故障各有 Runbook
  • 用户培训到位:一线会改标签,主管每天看修正率、自动覆盖率、抽检准确率
  • 上线前跑完整回归,结果与评测报告一致

灰度方案 ​

阶段流量观察窗口放量条件回滚条件
影子模式100% 只记录不生效3 个工作日自动档准确率抽检 ≥ 95%无需回滚
灰度 110%3-5 个工作日修正率 ≤ 15%,无安全错分修正率 > 20% 或安全错分 1 次
灰度 230%3-5 个工作日指标稳定,一线反馈无恶化同上
全量100%持续连续 2 周达标一键回人工

扩展方向 ​

按「业务价值 / 风险」两个维度挑:低风险做二级标签、工单摘要草稿、相似工单推荐;中风险要补评测的做优先级与 SLA 预测、多语言;高风险最后做自动回复(必须人工审批)和自动关单(不建议)。

交付清单 ​

  • README:问题、场景、指标、如何运行
  • 数据说明:合成方法、标签定义、集合划分、许可证
  • 规则基线代码与结果
  • 模型调用、提示词版本、JSON 输出契约
  • 评测脚本与评测报告(总表、分类别、混淆矩阵、校准表)
  • 错误分析(至少 30 条,含改进动作)
  • 架构图(ASCII 或图片)与数据流说明
  • 上线检查清单、灰度方案、回滚步骤
  • 5 分钟演示脚本(业务方版本)
  • 复盘:做对了什么、失败在哪、重做会怎么改

常见误区 ​

  • 一上来就想自动回复:分流是低风险、高频、可度量的入口;自动回复是高风险、难归因的出口。顺序反了。
  • 只报准确率:90% 准确率配 1% 覆盖率毫无价值,准确率、覆盖率、人工负担要一起看。
  • 置信度不校准:模型说 0.9 就信,自动档实际只有 80%,业务方一次就失去信任。
  • 把人工复核当免费:复核也要时间,修正率降不下来,总成本可能比纯人工还高。
  • 评测集泄漏:把评测样本写进提示词,指标会好得离谱,上线立刻打回原形。

本章小结 ​

  • 先有基线,再有模型;报告里基线和模型必须同时出现。
  • 分流系统的核心不是分类器,而是「三档路由 + 人工复核 + 反馈回流」的闭环。
  • 置信度必须用开发集校准,阈值由「自动子集准确率」决定,不能拍脑袋。
  • 安全类工单用规则强制兜底,不用模型判断。
  • 评测集要分层、要防泄漏、要能解释,报告要写清口径和版本。

练习 ​

  1. 复现基线:用 200 条合成工单跑规则基线,报告 macro-F1、每类 F1 和混淆矩阵。判断标准:能说清哪两类最易混及原因。
  2. 做一次校准:把自报置信度分 5 桶,算每桶真实准确率,回答「自动档阈值定多少、覆盖率多少」。判断标准:给出至少两组阈值下的准确率/覆盖率权衡。
  3. 设计灰度:写一页灰度方案,含影子模式、三档放量、观察指标、回滚条件。判断标准:别人能照着执行,回滚不需要你本人操作。
本章资料来源: 场景、数据、指标与代码均为合成教学示例,不代表任何真实公司或数据。方法见 第 6 章 与 第 8 章;Sierra、Decagon 等案例为公司披露或供应商口径,详见 附录 C;项目章程、评测计划、上线检查模板见 附录 B。

下一步:想做文档类场景,读 Lab B 或 Lab C;回到 第 7 章 看六阶段如何贯穿。

用现场换理解,用交付换信任。