
做客服系统这几年最让我头疼的不是模型效果差而是效果不稳定。今天刚上线的意图识别模型准确率跑分95%上线第二天就翻车——用户一句“你们这个衣服洗了缩水能退吗”模型死活识别不出这是“退货退款”意图。后来我复盘发现单靠一个模型去扛所有场景根本不现实。最终我落地了一套“规则小模型LLM”三层混合架构第一层用规则快速拦截高频确定性意图第二层用小模型兜住长尾模糊意图第三层让LLM处理疑难杂症和边界情况。这段时间跑下来整体准确率从92%拉到96.7%单次请求成本反而降了40%。这篇文章把我这套架构从设计思路到踩坑细节完整写出来想少走弯路的可以认真看看。1. 为什么电商客服意图识别必须上三层结构1.1 电商客服场景的意图分布被低估的“二八法则”先说一个很多人没意识到的事实电商客服会话里的用户意图分布极不均衡。我拉过我们店铺电商平台一个季度的客服会话数据按最终人工处理结果打标统计前十大意图占了全部会话量的80%以上。其中“查物流”“申请退款/退货”“改地址”“开发票”“催发货”这五个意图合起来就占了55%左右。这个分布特征直接决定了架构设计的起点既然大部分流量集中在少部分高频意图上那就没必要让所有请求都走最笨重、最昂贵的计算链路。打个比方你去医院看病不会所有症状都直接上核磁共振——绝大多数情况先让分诊台护士问几句判断个大概方向只有疑难杂症才需要专家会诊。规则层就是那个分诊台护士小模型是普通门诊医生LLM才是站在最后面的专家团队。但这里有个矛盾点用户的话术千奇百怪。同样是想退货有人会说“不想要了”有人说“这个质量太差了我要退”有人直接说“怎么申请售后”还有人情绪化地说“你们这破玩意儿赶紧给我退了”。规则只能覆盖有限的说法小模型能泛化但精度有限LLM理解能力强但慢且贵。所以三层结构不是叠床架屋而是用不同成本级别的方案去匹配不同难度的样本最终在准确率、延迟、成本之间找到平衡点。1.2 单方案各自的天花板为什么不能只押注一种技术先看纯规则方案。规则的好处是精准、可控、零延迟、完全可解释但它最大的问题在于脆弱。一个关键词匹配规则被用户换个说法就绕过了而且随着业务发展规则之间会出现冲突维护成本呈指数级上升。我们第一版就是用正则写的“退款”规则结果一句“我不是要退款我就问问运费险理赔的事”被误命中成退款意图客服回复牛头不对马嘴用户直接开骂。纯规则方案能扛住50%的简单场景再往上走性价比极低。再看纯小模型方案。用TextCNN、FastText这类轻量模型做意图分类训练和推理成本低但对语义的理解停留在“词面”层面。“箱子破了能换吗”和“箱子破了可以换吗”词面相似度高模型能认出来但“这玩意儿还能要不”这种隐晦表达小模型大概率分错意图。而且小模型本质上是在拟合训练数据分布遇到训练集里没有覆盖的意图或者多意图混合的句子输出结果基本不可信还容易高置信度地错。至于纯LLM方案理解能力确实强但有两座大山成本和延迟。客服场景对响应时间极其敏感用户发完消息等3秒以上就开始不耐烦。商业LLM API的延迟普遍在1到3秒高峰期更不稳定而且并发一上来成本根本不是中小团队能承受的。更麻烦的是LLM的输出不稳定同样的输入今天返回“退款”明天可能返回“申请售后”直接导致下游系统逻辑混乱。1.3 三层混合架构的核心思路漏斗式过滤各管一段明白了单方案的局限三层混合架构的设计就顺理成章了。核心思路用一句话概括从便宜到贵谁有把握谁先上层层过滤绝不越级。第一层规则层。用关键词、正则、业务词典精准命中高频确定性意图。规则层的定位是“物理级快”毫秒级返回准确率追求接近100%但允许覆盖率有限——覆盖不到就放行到下一层。第二层小模型层。用训练好的意图分类模型处理规则层释放的、语义模式有一定泛化性的样本。这层的定位是“批量兜底”覆盖规则覆盖不到的长尾说法同时必须引入置信度阈值机制低置信度的样本绝不硬猜。第三层LLM层。处理前两层都搞不定的疑难样本多意图混合、隐含情绪、需要背景知识推断的复杂表达。这层的定位是“精英兜底”要的是最终判断的准确性对成本和延迟的容忍度更高。这三层之间的关系不是并列投票而是串行漏斗。用户消息进来先过规则层命中就返回不命中就下沉。整个架构的巧妙之处在于最贵的LLM层面对的流量已经被前两层削掉了85%以上所以即便单次成本高整体成本依然可控。后面我会详细展开每一层的实现细节。2. 规则层搭建先把高置信意图用最低成本拦下来2.1 哪些意图适合写规则哪些不适合我现在对规则层的态度很明确只写那些“一句话绕不开必须用的词”的意图。什么叫绕不开比如开发票用户不管怎么说总得提到“发票”或“开票”这个词“发票”就是强关键词。再比如改地址用户总得说“地址”字眼或具体的省市县信息。这类意图的核心触发词非常稳定写规则收益最高。不适合写规则的意图是那些表达极度多样、没有强触发词的类型。比如“商品质量问题”——用户可能说“坏了”“破了”“不响”“有黑点”“脱线了”触发词五花八门规则根本枚举不完。这种意图就应该交给小模型去泛化。我在给规则层做意图清单时会用一个很简单的判断标准如果这个意图需要列举20个以上触发词才能覆盖80%的说法那它就适合小模型不适合规则。按这个标准我们最终只给5个高频意图写了规则开发票、查物流、催发货、改地址、申请退款/退货部分强触发词。这里有个容易被忽略的细节规则层的目标不是“覆盖所有意图”而是“用最少的规则拦住最多的确定性流量”。宁可精准拦截5个意图也不要贪多求全最后误伤一堆。2.2 规则命中逻辑与冲突处理避免规则“打架”规则层写起来简单调起来难。很多人第一批规则上线后就发现误命中率飙升根本原因是规则之间互相冲突、优先级混乱。我踩过最典型的一个坑是“退款”规则 vs “退款咨询”规则。用户说“我想问下退款到账要多久”如果匹配“退款”关键词会被分到“退款申请”意图但用户真正想表达的是“退款进度查询”。两个意图对应客服系统里完全不同的处理流程分错就直接导致用户排队错窗口。后来我把规则层改成了优先级排除条件的结构类似白名单和黑名单的组合RULES [ { intent: 退款进度查询, priority: 90, include: [退款到账, 退款什么时候, 多久到账, 退款进度], exclude: [申请, 我要退, 怎么退], }, { intent: 退款申请, priority: 80, include: [退款, 退货, 不想要了, 申请售后], exclude: [到账, 什么时候, 多久], }, ]判断逻辑是先按优先级从高到低遍历规则每条规则必须同时满足“命中include任意一条”且“命中exclude任意一条则跳过”第一条全命中的规则胜出。这个结构帮我解决了不少误命中但它也暴露了规则层的一个致命问题——维护成本会持续增长。所以我在规则层立了个规矩每条规则上线前必须写清“为什么这条规则不能交给模型”如果理由不充分一律不要加进规则层。2.3 规则层的线上表现与维护节奏我们规则层上线稳定运行后拦截了全量意图请求的62%左右线上准确率98.5%平均响应时间不到15毫秒。这个数据意味着将近六成的客服消息根本不需要跑到后面的模型层成本优势巨大。但规则层不是写一次就完事的。电商业务变化太快大促期间会临时上线“保价”“以旧换新”“凑单退款”等活动用户问法里会夹杂大量活动词汇干扰原有规则匹配。我们现在的维护节奏是大促前一周集中review一次规则业务方有新活动时即时提规则需求每个月基于bad case做一次规则的增删改。另外一个经验是规则层的词典要区分“用户口语词”和“业务规范词”。用户不会说“价保申请”他们只会说“买贵了能退差价吗”。这些口语词需要客服团队定期从真实会话里捞出来维护进词表纯靠技术团队闭门造车是造不出来的。3. 小模型层扛住长尾意图的分类器训练与调优3.1 模型选型为什么我选了TextCNN而不是BERT系列小模型层要处理的是“规则层漏下来的、有一定泛化空间的样本”对延迟要求也高。我在选型的时候对比过三个方案这里把思考过程写出来。第一个是FastText。优点是训练快、体积小但对中文这种表意文字来说词向量加n-gram的方式对语义的理解实在有限稍微复杂一点的句式就崩准确率天花板比CNN结构低5个点以上直接淘汰。第二个是TextCNN。卷积核能提取局部n-gram特征对于短文本分类任务来说TextCNN在“局部关键词组合”上的表达能力很强而且推理速度极快。实测下来在10个意图类别、3万条训练数据的场景下TextCNN的F1达到0.91单条推理时间在CPU上大概2到3毫秒。完全够用。第三个是蒸馏后的BERTTinyBERT、AlBERT之类。语义理解能力确实最强但在中国大陆的4核CPU服务器上单条推理也要20毫秒以上而且模型体积大、部署更麻烦。从投入产出比看小模型层只是整个三层架构的中间层下面还有LLM兜底没必要在第一道泛化关卡就上那么重的模型。所以我最终的选型是主模型用TextCNN数据量增加后如果精度不够再考虑蒸馏BERT打补丁。后期验证下来TextCNN加规则层的组合已经能扛住85%的线上流量完全没有上蒸馏BERT的必要。3.2 训练数据从哪来冷启动与持续迭代在小模型层的构建过程中最耗时间的不是调模型而是搞训练数据。我复盘整个数据准备过程有两条经验特别值得分享。冷启动阶段我们没有现成的标注好的意图数据。当时想了一招“半自动标注”把历史客服会话按“用户消息—客服人工选择的处理流程”做对齐客服的操作结果就是弱标签。比如用户发了消息后客服点了“创建退货申请工单”那这条消息就自动标记为“退货申请”意图。这种弱标签的准确率不是100%靠谱但胜在量足够大先粗标注再人工抽样修正两周内就攒了3万条训练数据。数据量上来之后模型的baseline就稳了。持续迭代阶段最关键的是建立bad case回流机制。小模型线上预测错的样本先进入人工抽检池客服质检团队每周抽200条做复审标注出“正确答案”然后回流到训练集做增量训练。这里有个细节增量训练不代表重新全量训练我用的是TextCNN按固定轮数加载旧模型继续训练负样本还会按比例欠采样防止模型被近期数据带偏忘记旧意图。3.3 置信度阈值怎么定宁可拒识不要硬猜很多团队做意图分类只看预测的argmax也就是得分最高的那个类别。但在真实客服场景这种做法极其危险。小模型对“没见过的话术”经常给出非常均匀的预测概率比如5个类别各20%argmax出来的类别完全随机但看起来好像挺自信。所以我在小模型层强制引入了置信度阈值机制并且分了两档高置信度档预测概率≥0.85且最大概率与第二大概率之差≥0.3直接返回预测意图。低置信度档预测概率在0.6到0.85之间或者概率差不足0.3说明模型自己也在犹豫不许直接下结论转给LLM层二次仲裁。拒绝档预测概率0.6直接判为“意图不明”交给LLM层处理同时提示客服人工介入。这个两档一刀切的做法实测下来把高置信度档的准确率拉到了94.5%同时只放行了大概60%的请求到下一层。很多团队不敢把阈值调高怕覆盖太少但在我这套架构里小模型层的任务本来就是“能确定就确定不能确定就交给下游”没必要为覆盖率死扛。阈值调的太松等于把不确定性传导给了下游后面LLM层再强也兜不住你故意放错的坏账。3.4 小模型层的线上调用细节小模型层的服务我封装成了一个独立的意图识别微服务对外提供HTTP接口内部用的是ONNX Runtime做推理加速。这里有个小坑必须提醒TextCNN在PyTorch里训练出来的模型如果用PyTorch直接做线上推理CPU下延迟不稳定偶尔会飙到20毫秒以上。我后来转成ONNX格式量化成int8延迟稳定在2到3毫秒P99也才8毫秒。另外输入侧的预处理我也踩过坑。用户消息里有表情符号、错别字、火星文这些在训练阶段如果不处理推理阶段就会出幺蛾子。我现在的标准流程是繁体转简体→全角转半角→表情符号替换为文本占位符→网络用语词典纠错。比如“泥垢了”会被纠成“你够了”“笑死”不会被纠成“笑死我了”而是保持原样具体纠错词典需要根据业务场景实时扩充。4. LLM层让大模型做裁判但别让它做所有事4.1 LLM在链路中的定位兜底、仲裁、复杂意图识别LLM在我这套架构里的定位始终是“最后一道防线”不是主力。它能做的事情有三件第一件处理低置信度样本。小模型判断不出类别的那些话术比如“你们这质量真的绝了我要找12315投诉你们”——这句话里有情绪、有威胁、有不明确的诉求规则和小模型都很难办。LLM能理解用户在对质量不满且隐含“投诉”倾向会分类为“投诉与纠纷”意图。第二件解析多意图混合的复杂句子。用户说“我地址填错了想改顺便问下什么时候发货”这句话同时包含“修改地址”和“催发货”两个意图。规则和小模型都只能输出单一标签LLM可以输出多意图JSON结构让下游系统同时处理。第三件对规则层和小模型层的结果做仲裁。这也是我最看重的价值。当规则层命中“退款申请”但小模型层给出“退款进度查询”的高置信度预测时这种情况说明两头打起来了我会把原句和两个候选意图一起丢给LLM让它判断哪个更合理。需要特别强调的是不要拿着LLM去重新跑所有的样本。如果把它放在全量流水的第一位那就回到了纯LLM方案的死胡同成本和延迟直接爆炸。4.2 Prompt设计让LLM输出可控、稳定、可解析为了让LLM输出能直接接入业务系统我的Prompt设计遵循三重约束角色约束、格式约束、示例约束。角色约束是把LLM限定在“电商客服意图识别专家”的语境里。格式约束则明确规定输出结构为JSON字段包括intent、confidence、reason。重点说下示例约束也就是few-shot。只给LLM讲规则它容易理解跑偏所以我会在Prompt里塞3到5个典型样本对覆盖单意图、多意图、歧义样本、情绪化样本这几个类型。你是电商客服意图识别专家。请判断用户消息属于以下哪个意图类目 查物流、催发货、修改地址、退款申请、退货申请、退款进度查询、开发票、商品咨询、投诉纠纷、售后维修、其他 输出JSON格式{intent: 意图名, confidence: 0到1之间的小数, reason: 不超过20字的判断理由} 示例1 用户你们这个羽绒服我上周买的今天刚到发现拉链就是坏的 输出{intent: 售后维修, confidence: 0.92, reason: 商品质量问题需要售后维修或换货} 示例2 用户我改了地址但订单还是发到旧地址去了现在怎么办 输出{intent: 修改地址, confidence: 0.95, reason: 用户明确表达改地址诉求且遇到问题} 示例3 用户别给我发短信了烦死了再发我要投诉了 输出{intent: 投诉纠纷, confidence: 0.88, reason: 用户表达不满并威胁投诉需优先处理} 现在请判断以下用户消息 用户{用户消息}这里说一个踩过的坑如果需要让LLM对每个意图输出多意图结果不要要求它一次性全部列出来否则它会理解成“强制多意图”然后过度生成。我现在的做法是默认单意图只有置信度低于0.7时额外调用一次“是否包含多个意图”的判断。两次调用虽然多花一次token但稳定性提升明显。4.3 成本与延迟控制缓存、降级、并行化LLM层最容易失控的是成本和延迟。我先说成本。当规则层和小模型层拦截掉了85%流量后真正走到LLM层的只有15%。按我们日均10万条客服消息计算就是1.5万条请求进LLM。每条请求大概输入500字、输出80字折合token数大约800到1000。按常见定价估算一天的成本大概在几块钱到几十块钱量级取决于模型月成本也就千元上下。如果一上来就把10万条全喂给LLM同样的单价日成本直接乘以6倍以上一个月多烧几万块这就是分层的意义。再说延迟。LLM API的响应时间无法像本地模型那样保证所以我做了三件事第一语义级缓存。相同或高度相似的句子直接命中缓存不调LLM。做法是把消息文本先算一个SimHash指纹相似度超过0.95就认为是同义句直接复用上次的意图结果。这个缓存策略能额外削掉LLM层大约25%的请求。第二超时降级机制。LLM调用设了2秒超时超时未返回则自动降级为“小模型结果 低置信度标记”返回给客服系统时提示“人工智能预判不准确请人工确认”。宁愿让人工兜底也不能让用户一直转圈等待。第三异步与重试。对于客服会话这种场景用户发完消息其实不介意等多几百毫秒只要不像卡死就行。我把LLM调用做成异步任务前端先给用户一个“小助手正在输入”的交互反馈LLM结果回来后通过WebSocket推送。当然如果是API同步接入的场景这个设计要反向调整异步会破坏接入方的请求-响应模型。4.4 LLM与规则层、小模型层的仲裁机制三层之间不是“规则没接住就小模型小模型没接住才LLM”这么简单实际线上充满矛盾。我把仲裁规则总结成一个决策表场景规则层结果小模型层结果LLM层结果最终决策A未命中高置信度不调用小模型结果B未命中低置信度给出意图LLM结果C命中A意图高置信度B意图不调用小模型结果优先级更高D命中A意图未命中给出A意图规则结果E命中A意图未命中给出C意图LLM结果因为LLM能看到完整上下文这张表的核心逻辑是规则层和LLM层的判断相对可信两者冲突时更相信LLM——因为规则只是词面匹配很容易被语境欺骗。而小模型的判断永远低于规则和LLM因为它是纯统计模型容易对训练集外的表达过度自信。这套仲裁规则不复杂但需要在实际运营中不断调权重。5. 三层级联的工程落地路由、监控与稳定性5.1 整体调用链路的超时控制设计链路一旦变长稳定性就成了第一个要面对的问题。规则层是本地计算不涉及网络小模型层是内网服务LLM层是外部API三者的超时控制必须有主次之分。我最终设计的超时预算是这样的用户请求总预算2500毫秒。规则层分配100毫秒实际用时不到20毫秒小模型层分配400毫秒实际P95大概50毫秒留给LLM层的预算就是2000毫秒。一旦LLM调用超过2000毫秒还没返回立即走降级分支。这里有个容易忽略的细节降级分支不能直接复用规则层或小模型层的结果因为能走到LLM层的样本本身就是前两层拿不准的。我们降级分支的判断逻辑是“交给人工客服处理”同时在客服工作台高亮提示“AI无法智能判断该消息”。实测下来这个设计虽然会让一部分消息转人工但避免了因为强行给结果导致更严重的错误服务。5.2 监控指标设计不只看准确率还要看漏斗转化三层架构上线后我建立了一套以“漏斗转化率”为核心的监控体系。每一层过滤掉多少流量、精确率如何、延迟是否稳定这些指标比单一的整体准确率更能反映架构健康度。核心指标表指标定义预警阈值规则层拦截率规则层命中的请求数 / 全量请求数 50% 时报警规则层精确率规则层命中且意图正确的请求数 / 规则层命中数 95% 时报警小模型层覆盖率小模型层高置信度命中的请求数 / 下沉请求数 55% 时报警LLM层调用率实际调用LLM的请求数 / 全量请求数 20% 时报警整体准确率全链路最终意图识别正确的比例人工抽检 94% 时报警这套指标的价值在于能快速定位是哪一层出了问题。比如某天整体准确率掉到90%先看规则层精确率如果规则层精确率掉到85%说明规则误命中激增如果规则层正常但小模型覆盖率掉到40%说明训练分布和线上分布出现了偏移需要重新做bad case分析。5.3 灰度发布与降级回滚策略三层架构上线不能直接全量切流量尤其涉及LLM外部依赖万一对方API不稳定整个客服智能系统都会瘫掉。我采用的是“按渠道灰度”的策略新版本先在一个低流量客服子渠道跑48小时观察准确率和延迟稳定后再逐步扩大到全量。灰度期间如果发现LLM调用失败率超过5%或者P99延迟超过2.5秒自动触发全量回滚到“仅规则小模型”的双层模式。这个双栈降级方案我在系统里预埋了一个开关一键切换实测能保证即使LLM完全不可用系统仍有接近80%的请求可以被前两层正确服务。这里必须强调智能客服系统的第一原则不是效果最优而是永远不能比没有AI更差。6. 实测数据、踩坑复盘与一点个人体会6.1 线上效果与成本测算分层带来的收益先说一组我们系统稳定运行3个月后的真实数据。日均处理客服消息约10万条三层架构的整体意图识别准确率人工抽检3000条计算为96.7%比之前纯规则方案高出近5个百分点。规则层拦截62%流量小模型层兜住22%LLM层处理剩余16%。P95延迟从之前纯小模型方案的220毫秒增加到650毫秒但考虑到准确率的大幅提升这是可接受的代价。成本端GPU服务器不需要额外买TextCNN走CPU推理LLM调用日均1.6万次月成本约2000元以内。这个成本规模和我们之前纠结的“直接微调一个大模型来扛所有意图识别”的方案相比后者光是GPU训练和推理资源就要每月多烧将近2万元效果还不一定比得上三层混合。6.2 踩过的几个典型的坑每个都让我折腾几天第一个坑是规则层误命中情绪化表达。前面提到过的“我不是要退款我就是问问运费险理赔的事”被命中成退款申请这个坑的根源在于规则只做了include匹配没做exclude约束。后来我在每条规则上都强制要求同时提供正反两面的关键词组虽然维护成本高了但误命中率直接降了一半以上。第二个坑是小模型训练数据里的“标签噪声”。我们冷启动时用客服工单做弱标注结果“退款申请”意图里混进去大量“退款进度查询”的样本导致模型对这两个意图死活分不清。后来我重新清洗了1万条边界样本又专门构造了“为什么还不退款”“退款到哪一步了”这类混淆样本加入训练集两个意图的区分F1才从0.78提升到0.93。第三个坑比较隐蔽是LLM返回的JSON格式偶发不合法。商业LLM API偶尔会在JSON前后加解释性文字或者返回Markdown代码块包裹。直接json.loads解析会有小概率异常异常一多就影响整条链路稳定性。我现在的做法是解析前先做一个健壮性处理——去Markdown包裹、截取第一个“{”到最后一个“}”的子串再进json.loads解析失败则强制降级为人工处理。6.3 最后分享一点架构思维上的心得这套三层混合架构最大的启发不是技术层面的而是思路层面不要迷信任何单一技术的上限要把不同技术放到它最擅长的那一层。规则擅长确定性小模型擅长泛化性LLM擅长理解力三者组合才能形成既快又准又贵的完整闭环。如果你现在也在搭建客服意图识别系统我的建议是别急着直接把全量流量喂给LLM先从规则层开始把你能用关键词和正则解决的高频场景全部用规则解决然后反复问自己一个问题“这条尚不能被规则覆盖的样本交给小模型靠谱吗不靠谱的话值得为它去调用一次LLM吗”问明白了架构自然就成形了。