
1. 双11那天的惨痛教训单一路径为什么撑不住意图识别1.1 从一次客服水位爆表事故说起先讲个真实的事。去年双11大促我们电商平台的客服机器人被用户问题直接冲垮了不是服务器崩了是意图识别模块的准确率崩了。前一天晚上压测指标还挺好看整体意图识别准确率93.7%。结果大促当天一开门大量用户涌进来问我东西还没到怎么还不发货再不来我就退了——这些夹杂着情绪、上下文和模糊表达的话术直接把之前基于关键词模板的老式意图引擎打回原形。转人工率从平时的18%飙到41%人工客服排队时间最长到了6分钟差评率肉眼可见地涨。事后复盘我把当天接待的会话日志捞出来逐条分析发现了一个尴尬的事实用户根本不会按照我们预设的话术模板说话。有人把退款说成把钱还我有人把改地址说成寄错了怎么办还有人一句话里同时包含催发货、问物流、想退款三个意图。这种问题靠一套固定规则或者一个简单的文本分类模型根本没有办法同时兼顾覆盖率和准确率。这让我下定决心重构整个意图识别系统也就是这篇文章要讲的规则 小模型 LLM 的三层混合架构。1.2 规则、小模型、大模型各自的认知盲区在开始设计之前我先把市面上主流的意图识别方案做了个系统的对比分析。这个对比不是拍脑袋而是基于我们实际线上数据的验证结果。纯规则方案响应速度极快毫秒级出结果逻辑完全可控。但它有两个致命问题——第一规则只能覆盖你想到的情况覆盖不了用户千奇百怪的表达方式第二规则之间的优先级和冲突管理越做越复杂维护成本呈指数级上涨。我们早期用了一个将近3000条关键词和正则规则的方案每加一条规则就要担心会不会误伤其他意图后期基本没人敢动那套规则库。纯小模型方案我用我们已有的标注数据训练了BERT系列的分类模型在测试集上准确率能做到88%左右泛化能力比规则强不少。但它的问题在于——细粒度意图区分能力有限比如取消订单和修改订单这两个意图在文本上只差一两个词BERT分类器经常搞混另外对于一句话里包含多个意图的情况传统分类模型天然不擅长处理因为分类模型的设计假设就是一个样本对应一个标签。纯LLM方案大模型的语义理解和推理能力确实强多意图识别、上下文理解、口语化表达都能很好地处理。但是有两个现实障碍——第一成本高客服场景的请求量是以百万计算的全量走LLM的推理成本我们测算过大约是纯规则方案的200倍以上第二延迟不稳定LLM服务在高峰期的p99延迟可能到3到5秒这个延迟对于客服场景来说几乎是不可接受的用户早跑了。所以结论很明确没有任何单一方案能同时满足准确性、覆盖率、延迟、成本这四个维度的要求。我们需要的是一个能发挥各自长处的混合架构。1.3 混合架构的本质让每种能力待在各自的射程里说得直白一点三层混合架构的设计思路类似生产线上的人工分拣流水线先自动过一遍标准件标准件直接打包走人稍微复杂的再过一个专业质检员最后的疑难杂症才上升到工程师级别处理。每解决一层剩下需要继续向上传递的量就大幅降低。这套架构在工程上的核心价值有三个第一把大量的确定性请求拦截在最底层。电商客服场景有个规律——查订单退换货开发票这类高频意图的文本表达相对固定80%的请求其实集中在20%的意图类型里。这些请求完全可以用规则层和小模型层快速消化不需要动用昂贵的LLM。第二让复杂的请求得到真正的语义理解。那些长尾的、模糊的、多意图混杂的请求才需要LLM这样具备推理能力的模型来处理。在大模型时代很多团队容易走极端——要么啥都用LLM要么完全不信LLM。实际上LLM最好的定位不是替代所有意图识别能力而是做兜底承接规则和小模型处理不了的那部分请求。第三每一层之间形成验证和降级关系。当规则层和小模型层的意图结果不一致时可以引入LLM作为仲裁者当LLM服务异常时可以降级到小模型的预测结果保证系统在极端情况下仍然有可用性。这个架构的核心逻辑就是一句话用最少最便宜的计算解决大部分问题把复杂问题集中上抛每一层都做自己最擅长的事。2. 第一层规则引擎把确定性问题拦在入口2.1 规则层到底管哪些意图很多人一听规则就觉得这是过时的技术但在我实际落地后我的观点完全相反——规则层是整套架构里性价比最高的部分关键是要知道边界在哪里。我们规则层负责的意图类型严格限定在以下三个特征同时满足的范围内表达形式高度固定。比如查订单用户通常说查一下订单订单查询看下我的订单虽然有几个变体但核心词订单是稳定出现的。检索型而非推理型。比如查物流、查发票、看优惠券这类意图的本质是信息检索用户只是想拿到对应信息不需要复杂的语义推断。高频且量级大。只有请求量足够大拦截才有意义。我们当时梳理下来确定由规则层处理的意图有11个包括查订单、查物流、开发票、查优惠券、查积分、修改地址、取消订单、退换货、催发货、联系人工、投诉建议。这11个意图大约覆盖了线上总请求量的47%但每天真正走到LLM层的请求不到总请求量的15%剩下38%被小模型层消化了。2.2 先别急着写规则先做1000条真实会话的意图摸底规则层设计的第一步不是写代码而是做语料分析。很多团队上来就对着业务方的需求文档编规则写出来的东西全是书面语一到线上就失灵。正确做法是直接从线上会话日志里拉最近一个月的样本人工逐条标注看看用户到底是怎么表达的。我当时从会话日志里随机抽了1000条和查订单相关的用户消息做了词频统计。排名靠前的说法是这几个订单出现472次、物流出现213次、发货出现198次、到哪出现121次、进度出现87次。但这只是粗粒度的统计真正对规则设计有启发的是一些边缘案例。比如有用户直接发来一个订单号什么话都没说也有用户说我那个快递呢没有任何和订单相关的关键词。基于这1000条语料我总结出规则设计的三个基本策略核心词 同义词扩展。以订单为核心词向下扩展订单单子快递物流包裹等同义词群每个同义词群对应一个词槽。词槽组合模式。比如查订单的典型模式是〔查询动作词〕〔订单同义词〕也可以直接是〔订单同义词〕或者〔订单号〕这三种模式。否定词处理优先于肯定匹配。规则引擎里最重要的不是识别是什么而是排除不是什么。比如用户说不查订单我要退掉这个时候如果只匹配到订单就判定为查订单意图就出大问题了所以否定词不、不要、取消、算了的权重必须高于正向匹配词。2.3 关键词 正则 词槽模板的落地写法基于上面三个策略规则层我用Python写了一个轻量级的匹配引擎。这里贴一下核心代码逻辑import re # 词槽定义 SLOTS { order_word: [订单, 单子, 快递, 物流, 包裹, 货物], query_action: [查, 查询, 看, 看看, 找, 搜, 知道], negative_word: [不, 不要, 取消, 算了, 不是, 没], order_number_pattern: r[A-Za-z0-9]{12,20} } # 意图规则引擎 def match_order_query(text: str) - tuple: 返回 (是否匹配, 置信度, 匹配详情) # 1. 否定词优先检查 if any(neg in text for neg in SLOTS[negative_word]): return False, 0.0, {negative_hit: True} # 2. 订单号直查 if re.search(SLOTS[order_number_pattern], text): return True, 0.98, {type: order_number} # 3. 常规模式查询动作 订单词 has_query any(q in text for q in SLOTS[query_action]) has_order any(o in text for o in SLOTS[order_word]) if has_query and has_order: return True, 0.95, {type: queryorder} # 4. 只有订单词兜底 if has_order: return True, 0.70, {type: order_only} return False, 0.0, {type: no_match}关于这个实现有几个经验值得说第一置信度直接写在规则里。规则引擎的输出不应该是简单的0/1而是一个带置信度的结构化结果。这为后续的多层路由提供了基础——置信度高于0.9的请求直接返回0.7到0.9的请求可以进入小模型层做二次验证低于0.7的交给LLM。这个置信度阈值的设定不是拍脑袋定的是基于线上1000条样本的匹配结果回归分析得出的。第二正则表达式只用于强结构化的场景。比如订单号、手机号、邮箱这些有明确格式的实体正则是最可靠的。但不要试图用正则去匹配自然语言那些复杂的.*?嵌套正则会让你在维护时崩溃而且效果也不好。我在团队里定了一个规矩任何一行正则如果超过80个字符必须拆开重写。第三规则的优先级用具体度排序而不是手工维护优先级字段。这是我实践出来的一个技巧。匹配条件越多的规则优先级越高比如查询动作订单词的优先级要高于只有订单词。只有订单词这条其实是兜底规则防止漏匹配但因为它的置信度只有0.7所以会把一部分请求疑似匹配地抛给下一层去验证。2.4 规则层的性能与维护别踩动态加载的坑规则层的性能测试结果单条消息的平均处理时间在0.3毫秒左右比我们预期的还要快。这个数字在线上实际跑下来也没太大波动因为纯正则和字符串匹配对CPU的压力很小单机每秒能处理好几万条请求根本不需要专门的服务作为一个库函数嵌入在客服主流程里就行。真正的坑是规则的维护方式。最开始我把所有规则写在一个Python文件里每次改规则都要发版流程走完要半小时。后来改成了JSON配置 动态加载。这里必须提醒一句动态加载规则一定要做结果对比测试。我记得有一次改了一条规则的正则表达式原本以为只是优化性能结果上线后发现查订单的召回率掉了5个百分点原因是一个正则的贪婪匹配范围变了。从那以后我定了个规矩规则变更必须跑一遍固定的回归测试集至少100条历史标注样本覆盖所有意图类型保证准确率不下滑才可以上线。3. 第二层小模型用文本分类承接泛化表达3.1 小模型的选型逻辑为什么是6层BERT而不是更大的规则层解决的是确定性问题但用户的话术总是有各种意想不到的变体。比如我那个快递都三天了还没动静——这句话里有快递订单词也有三天隐含催发货规则层会根据快递词槽命中查物流或催发货但到底是哪个规则层分不清。这时候就需要小模型出场。我们用的是BERT-base-6层蒸馏版不是原版12层。为什么刻意选小一点的模型三个原因第一客服意图识别这个任务的难度分级其实用不到12层BERT的表达能力。意图识别本质上是短文本分类任务大部分输入就一句话、几十个字不需要特别深的语义理解。第二推理延迟。我们要求意图识别服务的TP99小于50毫秒6层BERT在GPU上单条推理大概10到15毫秒CPU上大概30到40毫秒12层BERT在CPU上的推理延迟会到60到80毫秒超预算。第三部署成本。我们用的是一张T4显卡就可以支撑日常QPS在300左右的6层BERT推理服务功耗和运维成本都比较可控。如果用12层模型可能需要2张卡才能扛住高峰期的流量。最终我选了HuggingFace上的bert-base-chinese蒸馏为6层的版本蒸馏过程用的是教师模型13层版在相同数据上的软标签。这里有个细节直接用transformers库的distilbert-base-chinese也可以但我们是在自己的业务数据上重新做了蒸馏效果会更好因为教师模型的soft label里包含了对业务意图的模糊判断信息。3.2 样本构造冷启动没有标注数据怎么办小模型训练绕不开的一个问题是没有标注数据怎么办。我们接手的系统并没有一个完整的意图标注数据集只有一些老系统积累的会话记录。我的做法分三步第一步基于规则引擎自动打标。把规则引擎的匹配结果作为弱标签直接拿来训练初版模型。这一步的逻辑是规则引擎在20%的高频意图上准确率其实是很高的比如查订单这类规则准确率能到95%以上。用这些高置信度的规则匹配结果做种子数据虽然覆盖的意图不全但足够让模型学会最基本的意图表征。第二步主动学习选样本。初版模型上线后把所有预测置信度在0.4到0.7之间的模糊区样本捞出来人工标注。这类样本是信息量最大的因为它们恰恰是模型没把握、规则也没覆盖的情况。我们大概人工标了1.2万条这类模糊样本模型的准确率提升非常明显直接从82%跳到了86%以上。第三步弱标签清洗。后期我写了个工具统计每一条弱标签样本通过全量分类后的预测结果凡是和弱标签不一致且模型置信度高于0.85的样本自动转入人工复核队列。这个机制能持续纠错防止规则引擎的错误标记污染训练集。这里必须说明整个训练集最终是人工精标 规则弱标 主动学习补充三者混合的比例大概为4:3:3。训练集总量在5.8万条左右类别覆盖了28个意图类型除了规则层的11个高频意图之外还额外覆盖了17个长尾意图比如投诉快递员改签预约礼品卡绑定等。3.3 训练细节类别不均衡、阈值校准、增量迭代小模型训练的坑我逐个说。类别不均衡是客服意图识别任务里最先遇到的问题。查订单的样本可能占30%但礼品卡绑定的样本不到1%。直接用原始分布去训练模型会倾向于把所有样本都预测成高频类别。我的解决方案是两层先对高频类别做欠采样控制每个类别的样本量上限再用Focal Loss替换标准的CrossEntropy Loss让模型在训练时更关注那些难分的低频类别样本。这两个操作叠加后低频类别的F1从0.58提升到了0.76。阈值校准是我认为整个小模型层最重要的环节。很多团队训练完模型只看准确率直接拿argmax的softmax概率作为真实置信度用。但softmax的概率分布是校准不佳的0.8的概率不代表确实有80%的把握。我用temperature scaling方法做了概率校准——具体做法是在验证集上学习一个温度系数T把模型的logits除以T之后再过softmax让输出的概率更接近真实置信度。这个操作做完之后模型的0.9以上置信度区间的实际准确率一路从82%提升到了97%。校准后的阈值策略是置信度大于等于0.85的意图直接返回置信度在0.5到0.85之间的视为低置信度意图准备进入LLM细分置信度低于0.5的说明模型认为自己没见过这类样本直接转LLM处理。增量迭代是持续提升模型效果的关键。每周做一次增量训练把上一周线上预测结果中标为低置信度但最终被LLM判定为明确意图的样本纳入训练集。这样做的好处是模型会不断学习新出现的表达方式每周的增量训练大约能带来0.3到0.5个百分点的准确率提升。3.4 部署形态ONNX Flask还是直接走推理服务关于小模型的部署我们第一次上线时用的是transformers库的Pipeline直接封装成Flask接口。线上跑了一周发现两个问题一是显存占用太高每次请求都会重复加载模型二是推理速度不稳定T4上的p99延迟有时会飙到200毫秒以上。后来改成了ONNX Runtime部署# 把PyTorch模型导出为ONNX python -m transformers.onnx --modelbert-distill-6layer-chinese onnx/ # 启动ONNX Runtime推理服务CPU下即可支撑日常QPS约150 python -m onnxruntime.server --model_pathonnx/model.onnx --port8080 --workers4ONNX Runtime部署方式的性能对比FP32精度下T4 GPU上的单条推理平均延迟从11毫秒降到6毫秒CPU上的平均延迟从35毫秒降到16毫秒。而且显存占用降低了约60%因为ONNX Runtime不需要同时维持PyTorch的整个运行时环境。性能问题解决之后我们还在推理服务外面加了一层缓存。客服场景有大量的重复问题——同一类问题的文本相似度很高用文本MD5做缓存命中率有22%左右这22%的请求直接0延迟返回进一步缓解了整体服务的压力。4. 第三层LLMAgent式意图解析补上最后一块拼图4.1 什么场景必须交给LLM规则层和小模型层已经能解决70%以上的请求了剩下那部分为什么要用LLM我在线上数了一下主要有四类场景多意图混杂。比如我买的手机壳什么时候发货另外我想换一个颜色——这里同时包含查物流和修改订单两个意图小模型分类器只能给一个标签规则层更是无能为力。长上下文依赖。用户可能先问我昨天下的那个单子中间问了个商品问题然后又回头说那个单子能退款吗。这里的那个单子需要结合上文才能指代到正确的目标。隐含意图和模糊表达。比如用户说你们这个牌子是不是店大欺客啊这属于投诉或者负向反馈但并没有直接出现投诉关键词。规则层和小模型层置信度都低的长尾问题。比如我朋友的账号绑定在我手机上怎么解绑这种涉及账号与设备的复杂操作流程需要拆解为多个子任务。这些场景的共同点是需要理解、推理和上下文关联。规则和小模型本质上做的是匹配而LLM做的是理解。所以LLM在架构里的角色不是替代前两层而是专门负责那些前两层处理不了的疑难杂症。4.2 Prompt设计让LLM输出结构化意图LLM输出的不可控是大家最担心的。为了解决这个问题我在Prompt设计上下足了功夫。核心是让LLM输出一种结构化的JSON格式而不是自由文本。我使用的Prompt模板大致如下SYSTEM_PROMPT 你是一个电商客服意图识别引擎。用户会发来一段客服对话你需要识别其中的用户意图。 要求 1. 从预定义意图列表中选择最匹配的意图每个请求最多返回3个意图按优先级排序。 2. 如果同一句话包含多个意图必须全部识别出来。 3. 输出必须是一个JSON对象包含 - intents: 意图列表每项包含intent意图名称和confidence0到1的置信度 - slots: 从用户消息中抽取的关键实体比如订单号、商品名、地址、电话号码等 - needs_human: 布尔值表示是否需要转人工客服 - reason: 识别依据一句话描述为什么判定为该意图 预定义意图列表 query_order, query_logistics, cancel_order, modify_order, return_product, refund, complaint, contact_human, invoice, coupon, points, dark_store_pickup, ... 用户消息{user_message} 历史对话可选{history} 请直接输出JSON不要输出任何其他内容。 这里有几个设计细节第一Few-shot示例必须在Prompt中体现。只靠System Prompt和意图定义列表LLM的表现会差很多尤其对于多意图场景。我在Prompt里加了两条few-shot示例一条是单意图一条是双意图效果提升非常明显。在实测中加了few-shot之后多意图识别的F1从0.71提升到0.83。第二用needs_human字段作为安全阀。电商客服场景有一些情况是模型不应该处理的——涉及账号安全、资金问题、人身攻击等。与其让LLM自己去判断要不要回复不如直接让它输出这个字段。凡是LLM判断需要转人工的请求系统直接跳过后续回复模块接入人工队列。第三输出JSON解析失败的兜底方案。这是LLM接入工程化最常遇到的问题。LLM偶尔会输出不完整的JSON、带markdown代码块包裹的JSON、或者直接输出自然语言。我的做法是先用正则从输出内容里提取{...}部分尝试用json.loads解析解析失败的话就降低优先级直接用小模型的分类结果实在不行就转人工。这个兜底链路在高峰期非常重要我见过太多团队因为JSON解析失败导致意图识别模块大面积超时的情况。4.3 成本和延迟的工程化控制LLM层的成本控制是这套架构真正能落地的前提。我当时的测算如果全部请求都走LLM每月推理成本在8万到12万之间但控制在15%流量之后每月成本降到1.5万以内。具体控制手段第一模型选型梯度化。我们内部同时接了两个模型一个开源的中型模型32B用于常规的复杂意图识别一个商业API的旗舰模型只在少数极端复杂的情况下才调用比如用户消息超过500字且前两层都没处理或者用户带有强烈的负面情绪需要更细腻的意图分析。这样按难易度分层调用平均成本压低了不少。第二Prompt缓存。当用户的多轮对话中前面的对话历史是重复的同一个会话内每一次请求都会带上之前的历史我们会对历史部分进行哈希缓存命中缓存的请求在发送给LLM前可以直接复用之前对话历史的上下文向量减少输入token的重复计算。这个优化让LLM层的平均输入token量下降了约18%。第三异步化和超时控制。客服场景用户体验要求高不能因为LLM慢就卡住整个流程。我们对LLM层设置了2秒的超时超时后直接降级到小模型的结果。同时对需要LLM处理的请求采用异步回填模式——先返回小二正在为您查询请稍等给用户LLM结果出来后通过WebSocket推送给前端。这个体验虽然不如秒回但比完全放弃LLM处理要好得多。4.4 LLM输出不稳定时的降级手段LLM是出了名的不稳定同样的Prompt同一个问题可能这次输出query_order下次输出query_logistics。针对这个问题我们的策略是引入累计投票和结果置信度校验。当多个意图结果出现时我们不是直接返回LLM的第一个结果而是要求LLM对高置信度的意图给出解释性的reason字段。这个reason不是给用户看的是给我们的校验引擎看的。校验引擎检查reason里的关键词是否和意图定义匹配比如意图是refundreason里至少应该出现退款或退货或钱等词否则就判定为可能不准确降级到小模型结果。另外我们做了一版LLM意图识别缓存库——每次LLM识别成功的样本会把用户消息 意图结果的组合写入一个缓存表。下次遇到完全相同的用户消息md5匹配直接走缓存而不去调用LLM。这个缓存命中率大概在9%左右虽然不高但省掉的全是硬成本。5. 三层的路由与仲裁赛马、阈值与降级5.1 路由策略规则优先还是同时跑三层模型不是简单的串联——规则不行转小模型小模型不行转LLM那种做法最大的问题是如果规则层的判断出错了后面两层根本没有机会修正它。所以在实际设计中我采用的是规则层先跑 小模型伴随验证 LLM兜底仲裁的并行路由模式。具体流程是这样的用户消息进来后规则层和小模型层同时开始处理。规则层优势是快小模型也快两者都在毫秒级完成。规则层的结果如果置信度大于0.9直接返回如果不满足则使用小模型的结果。小模型的置信度大于0.85的返回小模型结果0.5到0.85的进入LLM做进一步验证低于0.5的直接进入LLM。LLM层只接收前两层没搞定或者需要仲裁的请求大约占总流量的15%。这个并行 阈值路由的设计相比纯串行有几个好处规则层和小模型层可以互相验证比如规则层判为查物流且置信度0.9但小模型判为催发货且置信度0.95这时候就要进入仲裁逻辑而不是直接采信某一层的输出。5.2 置信度阈值校准的调优经验直接说结论每一层的置信度阈值都不是固定的尤其在不同业务阶段需要重新校准。我们用了大概3个月的时间把线上的一次会话日志拿回来逐条分析对三层的阈值做了多轮调优。最终的参数配置如下表格各层置信度阈值与路由策略对应关系层级高置信度阈值中置信度区间低置信度区间路由动作规则层≥0.900.70-0.900.70直接返回 / 转小模型验证 / 转LLM小模型层≥0.850.50-0.850.50直接返回 / 转LLM细分 / 转LLM重新分类LLM层≥0.80-0.80直接返回 / 转人工兜底小模型的0.85这个阈值是用验证集上的precision-recall曲线确定的原则是高置信度区间的准确率必须达到95%以上宁缺毋滥。把阈值定高会让更多请求上抛到LLM增加成本定低了会让错误意图直接下发到下游任务引发用户投诉。0.85这个值在我们数据上是precision和recall的一个平衡点。5.3 意图冲突时怎么仲裁规则层和小模型层、小模型层和LLM层之间的结果不一致是常态。仲裁逻辑我采用三层协商机制规则层与LLM层冲突时以LLM为准但必须记录规则层的意见。因为LLM在语义理解上的能力明显强于规则规则层的置信度再高也只能说明文本形式匹配了不代表语义意图匹配了。小模型层与LLM层冲突时如果LLM给出的置信度大于0.85以LLM为准否则返回小模型的结果。这里隐含的逻辑是小模型的概率是经过温度校准的如果LLM的置信度并没有显著高于小模型就选择用更稳定的那个。三层都给出不同意图时直接转人工。这种情况在线上很少见但一旦出现说明用户表达极其模糊或上下文特别复杂与其冒险让机器猜不如让真人处理。仲裁结果最后会写入日志作为后续迭代的重要依据。我每周都会看一遍仲裁记录从里面挖出大量有价值的badcase用于优化每一层的规则和模型。5.4 异常降级链路与监控任何系统都会出问题关键是出问题时怎么办。我的降级策略是从重到轻逐级降级LLM层挂了所有原计划进入LLM的请求全部自动降级为小模型的结果。小模型可能不如LLM精准但至少有结果不至于让用户等死。小模型层挂了只走规则层。规则层只处理能高置信度匹配的请求其他请求直接转人工。规则层挂了规则层的代码逻辑很简单崩溃的概率很低。但为了预防我在规则层之前加了一层基于字典的极简匹配——当规则引擎不可用时直接用关键词字典命中的意图作为兜底结果。所有层都不稳定实时监控模块会监控三层的平均响应时间和错误率一旦连续5分钟内任意一层的错误率超过20%或p99延迟超过阈值自动发出告警并切断该层在路由中的调用。这套降级链路保证了即使在最极端的情况下系统仍然有可用的意图识别结果返回而不是直接让用户面对机器人下线的提示。6. 线上验证与迭代怎么证明这套架构真的有用6.1 评估指标不只是准确率意图识别系统的评估不能只看一个准确率。我在线上和离线两个维度都建立了指标体系离线评估主要用三个指标——准确率预测正确的意图占总预测的比例、宏平均F1每个意图单独算F1再取平均避免高频意图掩盖低频意图的问题、以及兜底率指不能被正确识别的请求占总请求的比例。兜底率还有一个更实用的变体——误判率也就是机器把A意图错误地识别为B意图并触发了错误动作的比例。线上评估更关心业务指标——转人工率用户请求转为人工处理的比例越低说明机器能处理的越多、首响时长首次响应时间越低体验越好、CSAT评分用户满意度以及意图识别人工复核通过率从线上抽一批识别结果给质检团队人工复核计算通过率。其中人工复核通过率是最可靠的指标因为我们系统识别准确率再高实际用户买不买账才是最终裁判。6.2 小流量实验与灰度切换上线之前我们做了严格的小流量实验。具体的做法是先将10%的线上流量切到新架构其余90%继续走老系统用双方同源流量对比业务指标的差异。10%流量实验跑了一周结果非常明显表格新旧系统线上指标对比10%流量实验周期7天指标旧系统纯规则新系统三层混合变化意图识别准确率82.4%93.8%11.4%转人工率31.5%18.2%-13.3%首响时长p95420ms380ms-40ms平均回复轮次4.7轮3.2轮-1.5轮这些数据足够说清楚三层混合架构的价值。准确率大幅提升的同时转人工率显著下降和用户对话的轮次也变少了因为一次就能正确理解用户意图后续的澄清追问就少了。灰度切换的过程比想象中顺利但有一个重要的教训上线后前三天必须每小时盯一次线上人工复核通过率和badcase日志。我们第一天就发现了一个问题——LLM对于改地址这类操作意图的判断过于聪明了。用户说能不能改下收货地址LLM判断为modify_order意图理论上没问题但仔细看才发现这个用户的订单已经发货了根本不支持改地址。这个问题的本质是意图识别和业务状态没有打通——意图识别得再准如果不知道订单当前状态依然会做出无效回复。所以我们后来加了业务状态校验模块在LLM判断意图后、触发动作前先去订单系统校验订单状态避免无效动作。6.3 Badcase回流闭环每周迭代的节奏架构上线不是终点持续优化才是常态。我搭建了一个Badcase回流闭环核心流程是线上badcase收集从质检团队、用户投诉、人工会话记录中每周汇总出意图识别失败或引起用户不满的case。根因分类逐条分析这些badcase归类到具体的失败层——是规则层没覆盖到小模型分错了LLM理解偏了还是业务状态校验环节出了问题针对性优化根据根因分类结果对应修改规则、增补训练样本、调整Prompt或修正业务校验逻辑。回归验证每次优化上线前都必须拿过去4周的完整badcase集合跑一遍回归测试保证修复一个问题的同时没有引入新问题。这个闭环跑起来之后我们每两周能稳定提升0.5到1个百分点的线上意图识别准确率。到上线第三个月人工复核通过率稳定在94%左右基本达到了我当初设计这个架构时的预期。电商客服意图识别这个场景规则、小模型、LLM没有任何一个是万能的但把三者用合理的路由逻辑组合起来就能构建出一个准确率、覆盖率、延迟、成本都基本可控的系统。套用一句我们工程团队内部常说的大白话——把不花钱的先干完便宜的干一部分贵的只在真正值钱的时候用。这恐怕是所有实际落地意图识别系统的团队都应该好好琢磨的一件事。