背景痛点:传统客服的“三座大山”
去年双十一,我们团队守着监控大屏,眼睁睁看着客服队列从 200 人飙到 4000 人,平均等待时长 8 分钟,转化率直接掉 30%。那一刻,传统客服系统的三大硬伤暴露无遗:
- 响应慢:人工坐席线性增长,高峰期排队不可避免。
- 并发低:单体客服网关+MySQL 扛不住瞬时 5k QPS,CPU 飙红。
- 成本高:一名全职客服年薪 10w+,培训 3 个月才能上岗,离职率却高达 40%。
老板一句话:“用 AI 扛 80% 重复问题,成本砍半,响应进 1 秒。”于是,我们踏上了自研智能客服的填坑之旅。
技术选型:规则、BERT、GPT 谁更适合客服场景?
做技术选型时,我们把业界方案拉了个表格:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 规则引擎(正则+关键词) | 0 延迟、可解释、易调试 | 泛化差、维护爆炸 | 冷启动/兜底 |
| BERT+微调(意图分类+槽位填充) | 准确率高、小样本友好 | 推理 30ms+、显存占用大 | 80% 标准问答 |
| GPT 系列(生成式) | 多轮流畅、无需模板 | 不可控、易“胡说”、成本高 | 营销文案/闲聊 |
最终拍板:BERT 做意图识别,规则兜底,GPT 仅用于“富文本营销”旁路。既保证准确率,又把延迟压到 100 ms 内。
架构设计:四层积木搭高并发机器人
系统拆成四层,每层可水平扩展,下图是线上一直跑的版本:
1. 接入层
- 统一 WebSocket/HTTP 入口,用 OpenResty+Kong 做限流、灰度、AB 测试。
- 用户消息入队 Kafka,保证流量削峰填谷。
2. 意图识别层(NLU)
- 轻量 BERT 中文模型(4 层 TinyBERT,参数量 14 M),GPU 推理 15 ms。
- 输出“意图+槽位”二元组,比如
<Refund, {"order_id":"123456"}>。
3. 对话管理层(DM)
- 状态机+Redis 存储会话快照,支持多轮追问。
- 若置信度 <0.85,自动转人工,并带上完整上下文。
4. 知识库层
- FAQ 索引放 Elasticsearch,毫秒级召回。
- 知识图谱(Neo4j)维护商品-属性-售后规则,解决“对比”类问题,如“A 和 B 哪款续航长?”。
代码实现:15 分钟跑通意图识别流水线
下面给出生产环境最简可运行 Demo,依赖 transformers==4.30.0、fastapi、redis。
1. 预处理+特征提取
# nlu_service.py import torch, redis, json from transformers import BertTokenizer, BertForSequenceClassification MODEL_PATH = "./tinybert_intent" tokenizer = BertTokenizer.from_pretrained(MODEL_PATH) model = BertForSequenceClassification.from_pretrained(MODEL_PATH) model.eval() # 推理模式 r = redis.Redis(host='127.0.0.1', port=6379, decode_decode_responses=True) def predict(text: str): """返回 (intent, confidence)""" # 1. 预处理:统一半角、去表情 text = text.lower().strip() # 2. 特征提取 inputs = tokenizer(text, return_tensors="pt", max_length=32, truncation=True) # 3. 模型推理 with torch.no_grad(): logits = model(**inputs).logits probs = torch.softmax(logits, dim=-1) conf, idx = torch.max(probs, dim=-1) intent = model.config.id2label[idx.item()] return intent, round(conf.item(), 4)2. 对话状态机(状态模式+Redis)
# dialog_manager.py from enum import Enum, auto import json, redis, uuid r = redis.Redis(decode_responses=True) class State(Enum): INIT = auto() AWAIT_ORDER = auto() AWAIT_REFUND_REASON = auto() HUMAN = auto() class DialogManager: def __init__(self, user_id): self.uid = user_id self.state_key = f"dm:{user_id}:state" self.slot_key = f"dm:{user_id}:slots" def get_state(self): s = r.get(self.state_key) return State[s] if s else State.INIT def set_state(self, st: State): r.set(self.state_key, st.name, ex=3600) def update_slots(self, kv: dict): old = json.loads(r.get(self.slot_key) or "{}") old.update(kv) r.set(self.slot_key, json.dumps(old), ex=3600) def run_step(self, intent: str, slots: dict): st = self.get_state() # 简单状态转移 if st == State.INIT and intent == "Refund": self.set_state(State.AWAIT_ORDER) self.update_slots(slots) return "请提供订单号" if st == State.AWAIT_ORDER: self.update_slots(slots) self.set_state(State.AWAIT_REFUND_REASON) return "请问退款原因是?" # ... 更多状态略 return "我还不明白,转人工"FastAPI 暴露接口,一行命令uvicorn main:app --workers 4就能起 4 进程,QPS 实测 1200+。
性能优化:把 95 分位延迟压进 80 ms
1. 负载测试方案
- JMeter 脚本要点:
- 线程组 5k,Ramp-up 60 s,循环 300 次。
- 使用 CSV 数据集,保证提问不重复,防止缓存“作弊”。
- 监控 Backend Listener 把 TPS、RT 打到 InfluxDB,Grafana 实时看板。
2. 缓存策略
- 对话上下文是典型热数据:最近 30 秒重复率 27%。我们把 Redis 命中率目标定在 90%。
- 采用
hash slot + expire模式,每轮更新 TTL,防止“僵尸 key”。 - 对 FAQ 索引加一层 Caffeine 本地缓存,5s 过期,单机 QPS 提升 40%。
3. 降级方案
- 引入 Hystrix:当 BERT 推理耗时 >200 ms 或异常率 >5%,自动切换到规则引擎。
- 规则引擎用 Drools 编排出 300 条正则,覆盖 Top 20 意图,兜底准确率 70%,保证“答得上”。
避坑指南:那些踩到怀疑人生的坑
1. 对话歧义
用户一句“我要退”——退订单?退押金?还是退会员?
最佳实践:
- 先问“您要退什么”,给出可点按钮(模板消息),把答案空间缩到有限集合,再进入槽位填充。
- 对置信度 0.6~0.85 的区间,采用“反问+候选”策略,实测把误召回率从 12% 压到 3%。
2. 多轮上下文
千万别把每一轮独立扔给模型!
- 状态机里一定带“历史槽位快照”,否则用户改口“订单号填错了”时,系统会重复索要。
- Redis 序列化用 JSON 而不是 pickle,避免服务升级字段错位导致反序列化失败。
3. 模型冷启动
新场景上线,标注数据只有 200 条,模型泛化惨不忍睹。
- 先用规则跑两周,积累 5k 真实语料,再人工标注+主动学习(uncertainty sampling)迭代。
- 灰度发布期间,按用户尾号 10% 放量,并实时对比“转人工率”,达标后再全量。
效果复盘:客服效率提升 300% 是怎么做到的
上线 3 个月数据:
- 机器人拦截率 78%,转人工队列从 4000 降到 900;
- 平均响应时长 0.8 s,同比人工 45 s;
- 坐席人数缩减 35%,年节省人力成本 ≈ 120 万。
当然,系统也不是银弹。遇到情绪化投诉、复杂售后纠纷,还得靠人工“情感安抚”。AI 负责快,人工负责暖,两者边界越清晰,用户越满意。
开放讨论:如何平衡 AI 应答准确率与系统响应延迟?
在实测中,我们把 BERT 层数砍到 4 层,准确率掉 1.8%,但延迟降 50%。继续压缩模型,可用 Distillation+Quantization,可准确率必然再掉。
那么,到底要“再快一点”还是“再准一点”?你在业务里如何取舍,有没有更好的“动态阈值”或“模型路由”思路?欢迎留言一起拆坑。
踩坑笔记先写到这儿,希望给同样挣扎在客服一线的你一点参考。智能客服不是一锤子买卖,持续迭代、数据回流、用户体验才是最长情的告白。祝各位少加班,多上线!