做电商客服意图识别这件事,最开始我其实是有点抵触的。市面上聊意图识别的文章很多,但大多只讲某一个点:要么吹LLM万能,要么把规则和关键词批得一文不值,要么就只给你看一个准确率数字。但在真实的生产环境里,尤其是电商客服这种意图密度极高、话术又碎又杂的场景,单一方案根本撑不住。今天这篇不务虚,直接把我这边落地的一套“规则+小模型+LLM”三层混合架构拆开来讲,包括每一层解决什么问题、怎么实现、参数怎么调、上线后踩了哪些坑,以及最关键的——为什么三层各干各的反而比单一大模型省钱又稳定。
先花两句话说清楚这套东西是干什么的:用户在电商平台找客服聊售后、物流、价保、发票、退换货等等,系统需要实时判断用户来意,也就是意图识别。这个结果会决定后续转人工、自动答复、还是走特定工单流程。它需要的是一个高并发、低延迟、带可解释性、且能持续迭代的分类通道。这套架构里我用规则兜底高频、小模型承接中频、LLM处理长尾和复杂推理,三层按“先便宜后贵、先确定后模糊”的顺序联动。
1. 内容整体设计与思路拆解
1.1 为什么没直接用一整套大模型方案
先讲结论:纯LLM方案在电商客服场景里,重推理但轻高频,成本和延迟都是硬伤。
我当时先做了个PoC,把用户会话前两句丢给GPT-4级别的模型去做意图抽取,效果确实好——至少文本理解上非常准,连客服套话里的情绪、上下文翻转都能识别出来。但问题是这套方案扛不住电商场景的流量模型。大促期间,平台客服的实时并发会冲到几千QPS,每一路都要走大模型推理,响应延迟和成本直接失控。而且每天有几百万条历史会话需要用离线方式批量跑一遍,用来回流标注和修正规则,这笔账单是天文数字。
另外一个很重要的点是可解释性。电商客服和平台治理部门经常要复盘个案,问“这个意图为什么走到这个分支”。LLM给出的是一个概率分布,或者是一大段文字说明,运营的人根本没法拿这个去做质检,更没法指导规则优化。相比之下,规则引擎虽然“笨”,但每一步决策都有迹可循。
所以我的结论是:高频场景必须用确定性的方式解决,LLM只做长尾兜底。这就是三层混合架构出现的核心原因。
1.2 三层各自解决的核心问题
这套架构的分工逻辑非常像公司的组织架构:一线客服(规则层)处理标准化问题,业务主管(小模型层)处理常见但表达多样的问题,专家团队(LLM层)处理复杂和边界问题。
规则层:解决的是“确定性高、表达高度标准化”的意图。比如“我要退货”、“怎么开发票”、“运费谁出”。特点是请求量大、说法模式固定、必须零延迟响应。
小模型层:解决的是“意图明确但表达多样”的场景。同样是退款,用户可能说“钱什么时候到账”、“我没收到退款”、“订单显示退款中但银行卡没动静”。这些说法规则覆盖不全,但意图分布相对稳定,适合用文本分类模型处理。
LLM层:解决的是“需要上下文理解、多意图混合、甚至情绪判断”的复杂场景。比如用户先问物流,再问价保,中间还穿插一句抱怨,这时候需要大模型综合判断主次意图,甚至区分真实诉求和情绪发泄。
三层之间不是并列关系,而是漏斗关系。请求先走最便宜的一层,判不了再往下透传。这样既控制了平均成本,又提高了整体的意图识别覆盖率。
1.3 选型时对比过的几种方案
在动手之前,我对比过市面上主流的几种做法,简单列一下优缺点:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 纯规则/词典匹配 | 快、可控、成本低 | 覆盖度差、维护成本高 | 高频标准化场景 |
| 传统机器学习(SVM/贝叶斯) | 训练快、可解释 | 特征工程重、表达泛化差 | 小样本、特征稳定的场景 |
| 预训练小模型(BERT系) | 泛化好、效果好、可微调 | 需要标注数据、部署较复杂 | 中频、表达多样的场景 |
| Prompt工程+LLM | 零样本、理解强、免训练 | 贵、慢、不可控 | 长尾复杂场景 |
| 微调LLM | 效果上限高 | 成本极高、训练复杂、易漂移 | 专业领域深度定制 |
我最终选择的组合就是第三行加第四行,用规则去降低高频成本,用LLM处理长尾覆盖,小模型卡在中间做性价比最高的主力分类器。纯机器学习的传统方案在电商这种话术快速变化的场景里维护成本太高,我放弃了。
2. 核心细节解析与实操要点
2.1 规则层的设计:不是简单写一堆关键词
很多人对规则层的理解就是维护一个关键词表,哪个词命中就归类到对应意图。但实际做了以后你会发现,直接在原文上做关键词匹配会遇到一堆问题。
第一个问题是词面覆盖不够。用户很少会规规矩矩说“我要退货”,更多的是“不合适想退”、“能退吗”、“七天无理由怎么弄”。关键词表很容易越加越大,但覆盖率反而越来越低。
第二个问题是歧义和冲突。“退”这个字至少关联“退货”、“退款”、“退运费”三个意图。如果只做字面匹配,根本分不开。所以在规则层我用了意图触发词+意图阻断词的双层匹配结构。
举个例子,设置“退货”意图时,规则不是简单匹配“退”字,而是定义一组触发条件:
- 触发词或模式包含:退货|退回去|寄回|召回|不想穿了|不合适想退
- 且会话中出现“商品本身”相关的实体(比如商品链接、SKU)
这时候“快递退回来”这种物流场景就不会误命中到“退货”。类似的阻断词逻辑还用于区分“退款”和“退款原因”,比如“为什么退款”、“退款要多久”其实是咨询类,而不是退款申请类。
第三个问题则是正则表达式的滥用。很多人觉得规则层就是写正则,结果一条正则套三层转义,维护的人的脑子直接过载。我这边规定正则只允许处理确定性的模式,比如订单号提取、数量提取、日期提取。意图判定一律用组合式规则,比如匹配词+词性位置+槽位条件,而不是一锤子正则。
为了保证规则层不失控,所有规则上线前要做冲突测试。我会维护一批回归case,至少覆盖现有所有意图的边界表达,每次新增规则都跑一遍回归,确认没有引入误杀。
2.2 小模型层的选型与调优
小模型层我最终选了BERT系列的中文预训练模型,具体用的是bert-base-chinese。其实也试过更轻量的albert和textcnn,但电商客服的句子不长,语义却非常密集,过浅的模型在意图边界上的表现确实略差。最后综合考虑效果和线上QPS,选了12层的BERT base,精度最高,代价是推理延迟需要靠优化来补。
分类的标签体系是整个模型效果的天花板。我一开始直接用十几个原始“标准意图”做标签训练,效果很差。后来把所有意图体系重新梳理了一版,分成两级:
- 一级意图:大类,比如“退货”、“退款”、“发票”、“物流”、“价保”、“客服投诉”等,大概20个左右,用于路由决策。
- 二级意图:细分场景,比如“退货”下分“质量问题退货”、“七天无理由退货”、“多件商品部分退货”等,用于具体业务处理。
训练时我让模型直接预测二级意图,再用映射表把二级汇总到一级。这样做的好处是:模型学到的语义边界更细,上级分类的混淆自然减少了。举个例子,模型如果能区分“七天无理由退货”和“因缺货申请退货”,回到一级就几乎不会把“退款”和“退货”混在一起。
数据标注是这块最费人力的环节。我这边大概标了4.6万条客服会话首句,分为三类标签:意图标签、情绪标签(正向/中性/负向)、以及是否为有效业务请求。标注指南写了三版才稳定,关键原则是如果一句话同时有两个意图,标为主要意图+次要意图,训练时只学主要意图,避免模型学出“骑墙”的分布。
训练细节上,有一个参数很关键——max_seq_len。电商客服语句平均不到20个字,但偶尔会有60字以上的长句。我试过128和64两种情况,64时虽然快一点,但长句会被截断丢失关键信息,准确率掉了约1.8个百分点。最后设为96,兼顾速度和效果。
2.3 LLM层的定位:不是万能,是兜底
到LLM这一层,我用了工业界很成熟的方案:few-shot prompting + 结构化输出约束。模型选的是通用中英文对话模型,不追求最强推理,但要求指令跟随稳、输出格式稳定。这里额外引入了一个类似RAG的思路:把历史相似case的“意图标注”作为参考样本动态注入prompt,帮助模型稳定行为,而不是每次凭空推理。
落地的prompt结构大概是:
你是电商客服意图识别系统。请判断用户最后一条消息的主要意图和次要意图。 可选意图:退货、退款、发票、物流、价保、投诉、咨询、闲聊、其他。 规则:只输出JSON,格式如下: {"primary": "...", "secondary": "...", "confidence": 0.0-1.0, "reason": "..."} 参考示例: 用户:你们这个衣服七天可以退吗? 输出:{"primary": "退货", "secondary": "七天无理由退货", "confidence": 0.9, "reason": "提到七天和退货触发条件"} 用户输入:{这里填实际输入}为什么不用微调LLM?因为意图识别的标签体系本身就在持续迭代,今天加了“价保”可能下个月又加了“补寄小配件”。如果每次改意图体系都要重新微调一次模型,成本完全兜不住。而靠prompt注入标签定义和几个示例,改起来就只是改文本的事。
LLM层的QPS不需要高,因为经过前两层过滤后,到这里的基本只剩长尾case和低置信度case,大概占总流量的8%左右。但这8%恰恰是影响用户满意度最致命的区域——规则和小模型都搞不定的一定是难缠的,所以这里我宁可多花点推理成本,也要把准确率做上去。
3. 实操过程与核心环节实现
3.1 整体处理链路与路由逻辑
三层之间的路由逻辑是整套系统的骨架。我最终实现的是一个树状的判定流程,用伪代码表示大概是这样:
def classify(session): # 第一层:规则引擎 rule_result = rule_engine.match(session) if rule_result.confidence >= 0.95: return rule_result.intent, "rule" # 第二层:小模型 bert_result = bert_model.predict(session) if bert_result.confidence >= 0.80 and bert_result.intent not in black_list: return bert_result.intent, "bert" # 第三层:LLM llm_result = llm_router.chat(session, examples=build_fewshot(session)) return llm_result.intent, "llm"规则引擎给的是离散置信度。我这边规则引擎支持三种命中等第:“完全命中”、“候选命中”、“未命中”。只有当完全命中时才可以直接出结果,候选命中会参考小模型的输出做加权。这里的置信度不是硬编码,而是根据规则的历史胜算率动态调整的,比如某条规则过去100次触发有96次判对了,就把它调到完全命中档。
小模型的阈值是拿验证集上做的代价敏感优化。业务上“把退货误判成退款”的代价比“把退款误判成退货”低很多,因为后者会直接触发退款流程,没法收回。因此我在不同意图上设置了不同的阈值,而不是用一个全局0.8通吃。
3.2 小模型的工程落地与性能优化
小模型的推理部署我用了ONNX Runtime + 动态量化。因为意图识别是纯文本分类任务,对数值精度不敏感,动态量化几乎无损掉点,但在CPU上推理速度提升明显。
部署环境是CPU,没用GPU。原因很简单:GPU资源要留给LLM层,而且BERT base在CPU上跑优化后本来就能达到单实例几十毫秒的延迟。
线上服务封装成了一个独立的微服务,暴露两个接口:
/classify:实时单条请求分类,走完整链路/batch_classify:离线批量分类,用于回流标注
接入层的超时控制很关键。我给每层都设了独立超时:规则层要求5ms以内返回,小模型要求50ms以内,LLM要求500ms以内。如果LLM超时,直接走默认的fallback策略,即“转人工”而不是尝试猜测用户意图。在客服场景里,猜错的代价远大于不猜。
基本IO的预处理也踩了坑。文本清洗这步不能省,特别是去掉各种不可见字符、统一数字和金额格式、把“8点”这种时间表述归一化,否则同一个意思会变成两种特征。
3.3 LLM层的成本控制:单路成本直接打下来
这是我要单独说的一节。LLM推理成本是三层里最不可控的,但也是最能通过工程手段省钱的。
第一招是缓存。电商客服的意图分布极不均衡,少量高频表达占了超过一半。同一句话在10个订单上可能出现,用户的表述几乎一样,这就可以做语义级别的缓存。我按“归一化后的文本哈希+历史意图”做key,命中后直接复用上次的LLM结果,缓存命中率约有30%,直接砍掉接近三分之一的大模型调用量。
第二招是用便宜模型做初筛,贵模型只做纠错。LLM层内部我也分了级:先用一个轻量模型跑一次,如果置信度大于0.9就直接采纳,不再调用更强模型。只有低置信度case才升级到强模型。这里实测的效果是,大约55%的LLM层请求可以由轻量模型搞定,整体大模型成本降了约40%。
第三招是prompt长度控制。few-shot示例不能无限加,示例越多,token消耗越大,延迟也越高。我需要的是“每类意图至少一个正例和一个反例”,但控制在总长400 token以内。多轮对话只保留最近两轮作为上下文,更早的信息全部截断,因为意图识别看的是用户当前诉求,看太久远的对话反而容易被噪音误导。
3.4 数据回流:这套系统长跑的关键
三层系统上线不是终点,真正的持续迭代靠的是数据回流机制。LLM层判出的结果,不能直接当正确答案,但可以作为“候选标注”,和rule层、bert层给出的结果一起组成一条带多种判断的记录,进入人工评测池。
我这边搭建了一个很轻量的评测回测工具:每天从线上日志里按策略抽样1000条case,标注员在web界面上给出最终判定,系统自动对比三个层的结果,输出每天各层的准确率、召回率、误杀率。这个数据决定了第二天的阈值调整。
其中有一个很重要的观察:小模型的离线指标和线上指标会严重漂移。离线F1能有0.91,上线后经常掉到0.85甚至更低。后来查了原因,是线上会涌来大量训练集里完全没有的全新话术。所以我养成一个习惯:每天看线上badcase,抽20条人工看一遍,然后按周把badcase回流进训练集,再做增量微调。
4. 常见问题与排查技巧实录
4.1 意图边界模糊时怎么办
最常见的badcase是两个意图在表达上高度重叠。比如“退款”和“投诉”,用户说“你们这个质量也太差了我要退钱”表面是退款但内核是投诉。规则层的“阻断词”策略在这里往往失效,因为两类词都出现了。
我的解法是:在规则层遇到“候选命中冲突”时放弃判定,直接把case交给小模型。小模型学到的语义边界要比规则灵活,它见过足够多的“退款”和“投诉”表达,可以区分“要退钱”和“要讨说法”的细微差异。如果小模型的置信度依然低于阈值,才动用LLM,但必须要求LLM输出reason字段,方便人工质检。
4.2 LLM生成结果不稳定、偶尔带情绪
大模型做结构化输出时,偶尔会输出不规范的JSON,比如键多了引号、值带了注释,直接解析就会崩。我一开始用的是正则抽取,后来发现太脆弱,改成强制模型输出JSON后,再用一个修复层去兜底:先尝试json.loads,失败就尝试提取大括号内的内容再解析,再失败就返回“无法识别”,走到转人工。
另一个坑是模型会顺着用户的话说。比如用户抱怨了很久,模型居然在reason字段里写“根据用户抱怨的情绪,倾向转为投诉”,这本身没问题,但如果输出格式不受控很可能出现“reason”和“primary”矛盾的情况。所以我在prompt里额外加了规则:“只根据业务意图判定,不根据情绪判定”,并且在后置校验里增加了逻辑一致性检查。
4.3 线上实时数据倾斜问题
大促期间用户话术会短时间剧变,比如“618凑单怎么退”。小模型训练集里完全没有“凑单”这类词,误判率急升。这类问题在初版上线时暴打过我一次,后来在监控里加了“关键词新鲜度”指标:每天统计线上Top意图下出现的新词,如果某个词突然增速超过5%且从未出现在训练集里,自动打标记送入人工审核。
规则引擎应对这种情况有天然优势,新词可以直接通过维护规则快速补齐,模型层来不及重训时,规则就能顶上。这也是为什么我不建议把这套架构里的规则层拿出来单独替代掉的原因——它是线上系统的急刹车。
4.5 各层指标监控与报警
整个系统接了一套简单的业务监控大盘,但真正有意义的指标不是接口延迟和成功率,而是各层意图分布的日环比波动。如果某天“退款”意图占比突然涨了5个百分点,大概率不是用户行为变了,而是某个规则或者模型分支出了问题。出现这种情况,我会第一时间去看日志里的badcase,往往几分钟内就能定位问题是哪一层导致的。
关于报警阈值,我这边设了一个“层间分歧率”指标:同一条case在不同层的判定结果不一致的比例。正常情况下这个值在10%以内,超过15%基本可以判定有弱分类器出了状态漂移,需要立即人工介入。
最后再分享一条我在这套系统上最深的体会:三层混合架构真正难的地方不在模型选择,而在每一层之间“如何优雅地放手”。规则层该让位的时候别硬撑,小模型该认怂的时候别顶着低置信度输出,LLM该兜底的时候也别过度信任它的答案。这套系统跑了半年多,整体意图识别线上准确率稳定在91%到93%之间,单条请求平均耗时在70毫秒左右,大模型每条请求的token成本大概是纯LLM方案的六分之一。做AI应用的人心里都清楚,能上线、扛得住、修得动,比什么都重要。