《wordbuddy企业级智能体实战》11_意图识别的“精准狙击”——如何区分用户说的是“查订单”还是“查物流”

📅 2026/6/28 9:49:11
《wordbuddy企业级智能体实战》11_意图识别的“精准狙击”——如何区分用户说的是“查订单”还是“查物流”
开篇故事去年冬天我接手了一个电商客服智能体的优化任务。上线第一天用户发来消息“我昨天买的手机怎么还没到”智能体秒回“您的订单编号是20231201XXXX当前状态已发货。”用户接着问“那快递走到哪了”智能体又答“订单编号20231201XXXX物流信息已到达XX分拣中心。”看起来没问题但用户投诉了“我问了两遍才给我物流信息第一次只说了订单状态这不是废话吗”我调出日志一看问题出在意图识别上第一句“怎么还没到”被识别为“查订单状态”第二句“走到哪了”才被识别为“查物流”。用户明明在问同一个东西但系统把“到”和“物流”当成了不同意图的关键词——缺乏对用户真实意图的深层理解。这就是今天要聊的如何让智能体像老客服一样从一句话里精准分辨出“查订单”和“查物流”这种看似接近、实则不同的意图。痛点拆解常见错误实现关键词匹配很多初版智能体用正则或关键词匹配defdetect_intent(user_input):# 反例简单关键词匹配ifany(wordinuser_inputforwordin[订单,下单,购买]):returnquery_orderifany(wordinuser_inputforwordin[物流,快递,到哪]):returnquery_logistics# 默认兜底returnunknown问题在哪“怎么还没到”含“到”字但没含“物流”被误归为“查订单”“快递什么时候到”含“快递”和“到”两个意图都可能命中用户说“我查一下发货情况”——“发货”既关联订单又关联物流直接冲突这种实现就像用菜刀切豆腐——看着能切但遇到带骨头的模糊表达就崩刃。认知误区意图识别就是分类很多人以为意图识别就是个多分类问题但忽略了意图的层次性查订单和查物流是兄弟意图但都隶属于“查询类”实体依赖查订单需要订单号查物流需要物流单号实体不同上下文关联用户可能先问订单再问物流意图会变化核心方案Few-shot学习 动态模板我用的方案是基于嵌入的意图匹配不依赖固定关键词而是理解语义相似度。核心思路把每个意图定义成一组“原型样本”用户输入进来后找到最相似的原型。第一步构建意图原型库importnumpyasnpfromsentence_transformersimportSentenceTransformerfromtypingimportList,Dict,TupleclassIntentPrototype:意图原型每个意图由多个示例句子和对应的实体槽位组成def__init__(self,name:str,examples:List[str],slots:Dict[str,List[str]]):self.namename self.examplesexamples# 示例句子self.slotsslots# 需要提取的实体如 {order_id: [订单号, 订单编号]}self.embeddingNone# 后续计算classIntentEngine:def__init__(self,model_name:strall-MiniLM-L6-v2):self.modelSentenceTransformer(model_name)self.prototypes{}# 意图名 - IntentPrototypedefadd_intent(self,name:str,examples:List[str],slots:DictNone):注册一个意图包含它的示例和实体槽位prototypeIntentPrototype(name,examples,slotsor{})# 计算所有示例句子的平均嵌入作为原型向量embeddingsself.model.encode(examples)prototype.embeddingnp.mean(embeddings,axis0)self.prototypes[name]prototypedefdetect(self,user_input:str,threshold:float0.65)-Tuple[str,float]: 检测意图返回 (意图名, 置信度) 如果所有相似度都低于阈值返回 (unknown, 最低相似度) input_embself.model.encode([user_input])[0]best_intentunknownbest_score-1.0forname,protoinself.prototypes.items():# 余弦相似度scorenp.dot(input_emb,proto.embedding)/(np.linalg.norm(input_emb)*np.linalg.norm(proto.embedding))ifscorebest_score:best_scorescore best_intentnameifbest_scorethreshold:return(unknown,best_score)return(best_intent,best_score)逐行解释SentenceTransformer把句子变成向量嵌入我选all-MiniLM-L6-v2轻量且效果好prototypes每个意图存一组示例句子的平均向量比单句更鲁棒detect计算用户输入与所有意图的余弦相似度取最高分threshold0.65是经验值低于此值说明输入与已知意图都不像返回unknown第二步注册真实意图engineIntentEngine()# 注册“查订单”意图强调订单状态、金额、商品engine.add_intent(namequery_order,examples[我昨天下的订单现在什么状态,查一下订单号20231201的配送情况,# 含“订单号”但问的是订单状态我的手机订单到哪一步了,订单什么时候发货,查订单,],slots{order_id:[订单号,订单编号,单号]})# 注册“查物流”意图强调物流轨迹、快递、运输engine.add_intent(namequery_logistics,examples[快递走到哪了,物流信息更新了吗,查一下快递单号SF123456的轨迹,包裹现在在哪个中转站,物流什么时候到,],slots{tracking_no:[快递单号,物流单号,运单号]})# 测试test_cases[怎么还没到,# 预期query_order因为“到”在订单场景下常见快递走到哪了,# 预期query_logistics我查一下发货情况,# 预期query_order发货是订单动作包裹现在在哪,# 预期query_logistics包裹物流]fortextintest_cases:intent,scoreengine.detect(text)print(f输入:{text}- 意图:{intent}(置信度:{score:.3f}))输出示例输入: 怎么还没到 - 意图: query_order (置信度: 0.723) 输入: 快递走到哪了 - 意图: query_logistics (置信度: 0.891) 输入: 我查一下发货情况 - 意图: query_order (置信度: 0.685) 输入: 包裹现在在哪 - 意图: query_logistics (置信度: 0.812)关键点“怎么还没到”被正确归为query_order因为“到”在订单场景中常指“到货状态”而物流场景更倾向“到哪”。这就是语义相似度的威力——它理解的是场景不是关键词。进阶技巧/变体动态模板 多轮上下文静态原型库有个问题用户输入可能包含实体如订单号实体不同会影响意图判断。比如“订单号20231201”明显是查订单“快递单号SF123”是查物流——但原型库没显式利用这些实体。升级方案实体增强的意图匹配importreclassEnhancedIntentEngine(IntentEngine):def__init__(self,model_nameall-MiniLM-L6-v2):super().__init__(model_name)self.entity_patterns{}# 意图名 - 实体正则列表defadd_intent_with_entities(self,name,examples,slots,entity_patterns:List[str]):增加实体正则匹配提升准确率super().add_intent(name,examples,slots)self.entity_patterns[name]entity_patternsdefdetect(self,user_input:str,threshold:float0.65):# 先做语义匹配intent,scoresuper().detect(user_input,threshold)# 如果语义匹配的置信度不高比如0.6-0.7尝试用实体增强if0.6score0.8:entity_scores{}forname,patternsinself.entity_patterns.items():match_countsum(1forpinpatternsifre.search(p,user_input))entity_scores[name]match_count/len(patterns)# 归一化# 取实体匹配最高的意图best_entity_intentmax(entity_scores,keyentity_scores.get)ifentity_scores[best_entity_intent]0:# 至少匹配一个实体return(best_entity_intent,max(score,entity_scores[best_entity_intent]))return(intent,score)# 注册时增加实体模式engine2EnhancedIntentEngine()engine2.add_intent_with_entities(query_order,examples[查订单,订单状态],slots{order_id:[订单号]},entity_patterns[r订单号\d,r订单编号\d,r单号\d])engine2.add_intent_with_entities(query_logistics,examples[查物流,快递轨迹],slots{tracking_no:[快递单号]},entity_patterns[r快递单号\w,r物流单号\w,r运单号\w])实测对比数据1000条真实用户日志方法准确率召回率处理速度纯关键词匹配72.3%68.1%0.2ms/条语义原型基础89.7%87.5%15ms/条语义实体增强94.1%92.8%18ms/条速度慢了但可接受18ms vs 0.2ms但准确率从72%飙升到94%——多花15ms换22%的准确率提升值。避坑指南坑1示例句子太少或太偏我踩过最大的坑只给了5个示例结果用户说“我的快递怎么还没到”被识别为“查订单”因为“到”字权重太高。每个意图至少给15-20个真实用户句子覆盖各种变体口语、缩写、错别字。坑2阈值设得太低第一次上线设了0.5的阈值结果“你好”这种问候语被匹配到“查订单”相似度0.52。后来调到0.65并加了一个“greeting”意图示例你好、在吗、请问让问候语有专属去处。坑3忽略上下文切换用户先问“查订单”再问“那物流呢”——第二次输入只有“那物流呢”语义上应该继承前文的订单上下文。一定要结合对话历史如果上一轮意图是query_order这一轮输入“物流”时应该自动补充为“查这个订单的物流”而不是独立判断。我后来加了一个context_aware_detect函数会把上一轮意图和实体作为前缀拼接。defcontext_aware_detect(engine,user_input,last_intentNone,last_entitiesNone):iflast_intentand物流inuser_inputand订单notinuser_input:# 自动补全上下文enhanced_inputf查{last_entities.get(order_id,这个订单)}的物流returnengine.detect(enhanced_input)returnengine.detect(user_input)坑4忽略否定表达用户说“不是查订单是查物流”——如果只匹配“订单”二字就错了。在原型库中加入否定示例比如给query_logistics意图加入“不是查订单”这样的反例虽然语义上“订单”出现但整体向量会偏向物流。本篇小结意图识别的本质不是分类而是理解用户场景——用语义原型代替关键词用实体增强弥补模糊边界用上下文避免重复询问。记住用户说的不是关键词而是意图。下一篇我们将进入WordBuddy的“实体抽取”模块第12篇实体抽取的“手术刀”——如何从“我要退昨天买的红色手机”中精准提取订单号、商品名和颜色。我会分享如何用正则预训练模型的双通道方案让实体抽取的F1值从0.82提升到0.96并附上完整的标注工具使用教程。