做对话类应用最核心的模块就是意图识别用户说一句话你要先判断他到底想干嘛才能走对应的处理流程。很多团队用大模型做意图识别准确率卡在 70%-80% 就上不去了尤其是碰到歧义句、反讽、口语化表达的时候经常识别错。我们在落地对话场景的时候踩了一堆坑总结了 4 个确实能提准确率的方法。下面是四个方法的对比表格方便你快速概览方法适用场景核心操作预期效果第一个方法别只用 zero-shot 提示词冷启动、准确率不稳定、歧义句和口语句较多在提示词里放 5-10 条带标注的真实业务样例让大模型跟着示例学准确率直接提升约 20%第二个方法歧义句单独做二次判断高频歧义场景如“太贵了”这类模棱两可的表达先取两个候选意图再结合对话历史做二次判断选最终意图歧义句判断准确率显著高于单次识别第三个方法低置信度样本主动收集需要持续迭代、用户表达方式不断变化让模型同时输出置信度分数低于 0.7 的样本存下来人工标注再补进 few-shot 示例三个月准确率从 72% 涨到 95%第四个方法多轮对话里要做意图漂移判断多轮对话中用户意图可能中途变化每轮对话结束都重新判断当前意图更新意图标签意图降级时切换回复策略用户对话流失率降低 18%第一个方法别只用 zero-shot 提示词很多人做意图识别就是给大模型一段提示词“判断用户这句话的意图从给定的几个分类里选一个”然后直接跑准确率特别不稳定。实际测试下来few-shot 示例对意图识别的准确率提升巨大在提示词里放 5-10 条带标注的真实样例把不同类型的歧义句、口语句都覆盖到大模型跟着示例学准确率直接涨 20%。样例不要自己编一定要用你自己业务里的真实用户对话数据那种从网上抄的通用示例根本没用。我们一开始用公开数据集的样例效果特别差换成自己业务里的真实对话准确率一下就上来了。下面是一段 Python 代码示例展示如何构造带 few-shot 示例的提示词调用大模型接口importopenai# 注意示例数据必须来自你自己业务里的真实用户对话# 不要用网上抄来的通用示例否则效果会大打折扣。few_shot_examples[{user:这个套餐能便宜点吗,intent:价格异议,},{user:算了我不买了太贵了。,intent:放弃购买,},{user:你们家发货快吗,intent:物流咨询,},]defbuild_prompt(user_input:str)-str:# 把 few-shot 示例拼进提示词让大模型跟着真实样例学examples_text\n.join(f用户{ex[user]}\n意图{ex[intent]}forexinfew_shot_examples)return(判断用户这句话的意图从给定的几个分类里选一个。\nf参考示例\n{examples_text}\nf用户{user_input}\n意图)defrecognize_intent(user_input:str)-str:responseopenai.ChatCompletion.create(modelgpt-4,messages[{role:user,content:build_prompt(user_input)},],temperature0,)returnresponse.choices[0].message.content.strip()# 示例调用print(recognize_intent(这个太贵了能不能再优惠点))第二个方法歧义句单独做二次判断大模型很容易在歧义句上出错比如用户说“这个太贵了”你分不清他是在抱怨价格想还价还是只是随口吐槽没别的意思。这种高频歧义场景不要让大模型一次性输出最终意图做二次判断第一次先把最可能的两个候选意图拿出来再给大模型加一轮上下文判断结合用户之前的对话历史再选最终意图。就拿刚才的例子用户说“太贵了”第一次先拿到两个候选“价格异议”和“随口吐槽”再把用户之前说的话喂给大模型“用户之前问了报价说要对比一下现在说太贵了他的真实意图是”这样判断的准确率比单次识别高很多。第三个方法低置信度样本主动收集大模型输出意图的时候让它同时输出一个置信度分数分数低于 0.7 的样本不要直接走流程先存下来做人工标注。我们每周把这些低置信度的样本导出来人工标注意图加到 few-shot 示例里持续迭代提示词。跑了三个月意图识别的准确率从一开始的72%涨到了95%根本不用专门微调模型就是靠持续把错例补到示例里。很多团队做意图识别写完提示词就不管了这样准确率永远上不去——你的用户每天都在说新的表达方式你不持续把错例补进去模型永远跟不上用户的说话方式。这里补充一下持续补 few-shot 示例和直接微调模型是两条不同的路各有各的适用边界对比维度持续补充 few-shot 示例直接微调模型核心优势改动轻、见效快错例当天就能补进提示词不用重新训练模型模型本身能力更强能学到更深层的语义规律适合大规模、稳定的意图体系适用场景意图类别少、样本量小、业务表达变化快需要快速迭代意图类别多、样本量大、业务规则相对稳定追求长期稳定的识别效果成本与风险几乎零训练成本但提示词会越来越长可能挤占上下文空间需要标注数据和训练资源迭代周期长但推理时提示词更短、更省 token适用边界当 few-shot 示例超过几十条、提示词明显变长或准确率不再提升时就该考虑微调当业务表达频繁变化、来不及重新训练时微调反而跟不上节奏不如继续补示例简单说两者不是二选一很多团队是先用 few-shot 快速跑起来等样本积累到一定规模、提示词塞不下的时候再切到微调把 few-shot 沉淀下来的错例作为微调的训练数据。下面是一段 Python 代码示例展示如何让大模型输出置信度分数并筛选出低于 0.7 的样本进行存储方便后续人工标注importjsonimportopenai# 低置信度样本的存储文件后续人工标注用LOW_CONFIDENCE_FILElow_confidence_samples.jsonldefrecognize_intent_with_confidence(user_input:str)-dict:# 让大模型同时输出意图和置信度分数responseopenai.ChatCompletion.create(modelgpt-4,messages[{role:user,content:(判断用户这句话的意图从给定的几个分类里选一个并输出你对这个判断的置信度分数0 到 1 之间。\nf用户{user_input}\n请按 JSON 格式返回{\intent\: \意图\, \confidence\: 0.0}),}],temperature0,)resultjson.loads(response.choices[0].message.content.strip())returnresultdefcollect_low_confidence_samples(user_input:str)-None:resultrecognize_intent_with_confidence(user_input)# 置信度低于 0.7 的样本先存下来做人工标注不要直接走流程ifresult[confidence]0.7:sample{user_input:user_input,predicted_intent:result[intent],confidence:result[confidence],}withopen(LOW_CONFIDENCE_FILE,a,encodingutf-8)asf:f.write(json.dumps(sample,ensure_asciiFalse)\n)print(f[低置信度] 已收集待标注样本{sample})else:print(f[正常处理] 意图{result[intent]}置信度{result[confidence]})# 示例调用collect_low_confidence_samples(这个太贵了能不能再优惠点)collect_low_confidence_samples(你们家发货快吗)第四个方法多轮对话里要做意图漂移判断意图识别不是只在第一句话做用户聊到一半意图是会变的。比如用户一开始问“你们这个产品多少钱”聊到一半突然说“算了我就是随便问问最近不买了”这时候你如果还按一开始“购买咨询”的意图疯狂推销用户直接就跑了。我们现在每一轮对话结束都做一次当前意图判断更新一下用户的意图标签当用户意图从“高意向咨询”掉到“随便问问”的时候就停止推销式回复改成正常聊天。这个改动之后用户对话的流失率降了 18%。下面是一段 Python 代码示例展示如何在每轮对话结束后重新判断用户当前意图、更新意图标签并在意图从高意向降级为低意向时切换回复策略importopenai# 意图等级高意向咨询 随便问问 放弃购买HIGH_INTENT高意向咨询LOW_INTENT随便问问GIVE_UP放弃购买# 当前用户的意图标签每轮对话结束后更新user_intentHIGH_INTENTdefrecognize_current_intent(user_input:str,history:list)-str:# 每轮对话结束后结合最近几轮历史重新判断当前意图history_text\n.join(f用户{turn[user]}\n助手{turn[assistant]}forturninhistory[-3:]# 只取最近 3 轮避免噪音)responseopenai.ChatCompletion.create(modelgpt-4,messages[{role:user,content:(判断用户当前这句话的意图从下面几个分类里选一个f{HIGH_INTENT}、{LOW_INTENT}、{GIVE_UP}。\nf最近对话历史\n{history_text}\nf用户当前说{user_input}\n意图),}],temperature0,)returnresponse.choices[0].message.content.strip()defchoose_reply_strategy(intent:str)-str:# 意图从高意向降级为低意向时切换回复策略推销式 - 正常聊天globaluser_intentifuser_intentHIGH_INTENTandintentin(LOW_INTENT,GIVE_UP):user_intentintentreturn正常聊天# 停止推销改成正常聊天ifintentHIGH_INTENT:user_intentintentreturn推销式回复# 用户又回到高意向恢复推销return正常聊天defhandle_turn(user_input:str,history:list)-str:# 每轮对话结束重新判断当前意图并更新标签current_intentrecognize_current_intent(user_input,history)strategychoose_reply_strategy(current_intent)print(f[意图更新]{user_intent}-{current_intent}回复策略{strategy})returnstrategy# 示例调用用户一开始高意向聊到一半降级history[{user:你们这个产品多少钱,assistant:基础版 299 元需要我详细介绍吗},{user:算了我就是随便问问最近不买了。,assistant:},]handle_turn(算了我就是随便问问最近不买了。,history)常见问题与排查下面把四个方法落地时最容易踩的坑列出来每个都附上排查思路和调优建议方便你对照自查。第一个方法few-shot 示例过拟合常见坑示例放太多、太偏模型被带偏只认示例里的说法换个说法就识别错或者示例之间互相矛盾模型无所适从。排查思路把线上真实用户输入随机抽一批逐个跑一遍看错误是不是集中在某几个示例覆盖不到的表述上再检查示例里有没有两条意图标签冲突的样本。调优建议示例控制在 5-10 条优先覆盖高频歧义句和口语句同类表达只留一条定期用线上错例替换掉不再有代表性的旧示例保持示例和真实分布一致。第二个方法二次判断的上下文喂错常见坑把整段对话历史一股脑塞给模型噪音太多模型反而被无关信息带偏或者只喂了当前句二次判断退化成单次识别。排查思路抽查二次判断的输入看喂给模型的上下文里有没有混入无关轮次对比“只喂当前句”和“喂最近 2-3 轮”的准确率差异。调优建议只取最近 2-3 轮对话作为上下文并显式告诉模型“只参考与价格、购买意向相关的历史”对候选意图做去重和排序避免两个候选语义太接近导致二次判断失效。第三个方法置信度阈值不合理常见坑阈值定死 0.7 不动导致两类问题——阈值太高大量正常样本被误判为低置信度人工标注量爆炸阈值太低真正难分的错例漏过去准确率提不上去。排查思路统计低置信度样本里人工标注后“确实标错”的比例如果这个比例很低说明阈值定高了如果很多错例的置信度都在 0.7 以上说明阈值定低了。调优建议阈值不要拍脑袋定按业务可承受的人工标注量来调每周看一次低置信度样本的标注准确率动态微调阈值比如从 0.7 调到 0.65 或 0.75。第四个方法意图漂移误判常见坑用户只是随口吐槽一句模型就判定意图降级把正常咨询流程打断或者用户真的已经放弃购买模型还死守原意图继续推销。排查思路给意图漂移加一个“连续 N 轮确认”机制单轮波动不触发切换回看流失用户的对话日志看意图标签是在哪一轮被错误切换的。调优建议意图降级不要单轮就触发连续 2-3 轮都判断为低意向再切换切换后保留原意图作为候选用户下一句又回到高意向时能快速切回来避免来回抖动。意图识别不是只在第一句话做用户聊到一半意图是会变的。比如用户一开始问“你们这个产品多少钱”聊到一半突然说“算了我就是随便问问最近不买了”这时候你如果还按一开始“购买咨询”的意图疯狂推销用户直接就跑了。我们现在每一轮对话结束都做一次当前意图判断更新一下用户的意图标签当用户意图从“高意向咨询”掉到“随便问问”的时候就停止推销式回复改成正常聊天。这个改动之后用户对话的流失率降了 18%。写在最后意图识别看起来简单就是分类问题但真到生产环境用户说话千奇百怪想做到 95% 以上的准确率真不是写个提示词就完事的得在工程上持续迭代把错例一个个补进去。本文为技术科普不构成任何商业建议。