资讯动态

给RAG系统加上意图识别:从原理到落地避坑

发布时间:2026/10/20 3:55:28 来源:尧图企业网站定制
项目落地那天我盯着用户提问日志发了好一会儿呆。几千条真实问题里真正需要触发知识库检索的不到六成剩下的“你好”“谢谢”“你们有什么产品”全被送进了检索管道白白消耗算力不说还经常把无关文档拼进答案里结果就是顾客觉得系统“变笨了”。给RAG系统加一层意图识别就这么从“锦上添花”变成了“上线刚需”。这篇文章写给两类人刚接触RAG、想知道意图识别为什么是避不开的一环的小白以及已经在跑RAG、想把手上的系统打磨得更稳的程序员。我会把RAG意图识别的原理、三种主流实现方案、完整的判定流程代码、线上会踩的坑全部摊开讲给你可以直接抄作业的落地配置。1. RAG系统的意图识别到底是什么为什么绕不开1.1 先从一次糟糕的检索说起很多人把RAG想得太简单用户提问系统去文档库里找相似片段拼给大模型回答。这个流程听上去顺理成章但放到真实业务里立刻变形。我见过一个典型case用户在某产品的帮助中心问“退款要多久到账”RAG系统准确检索到了退款政策文档回答得很完美。但紧接着用户追问了一句“那你们客服几点下班”这本质上是个即时问答结果系统还是老老实实去检索知识库匹配到一堆退款周期、售后流程的文档拼出来的答案牛头不对马嘴。这不是模型能力不行而是检索环节缺了把关。RAG的“增强”是有条件的只有在确实需要外部知识时检索才有价值。如果用户问题本身自带完整上下文或者根本不是个知识类问题强行检索只会引入噪声。意图识别做的工作就是在问题进入检索管道之前先判个方向这问题要不要查文档、要查哪个文档库、查完怎么组织答案。日常对话里人脑会自然做这件事。朋友问你“晚上吃什么”你不会先去翻菜谱数据库再回答。这属于常识和记忆。但大模型不会默认做这件事它会倾向于“有文档就检索有上下文就生成”依赖于系统工程去约束它。意图识别就是给系统装上这个约束器。1.2 链路里的位置加了跟没加差在哪儿完整看一条带意图识别的RAG请求链路通常长这样用户输入 - 意图识别 - 判定分支 - 检索开关 / 检索配置 - 文档召回 - Prompt组装 - 大模型生成 - 输出不放意图识别的系统链路是用户输入 - 编码 - 检索 - Prompt组装 - 大模型生成 - 输出区别不在于多了一个模块而在于多了个“分流阀”。没有分流阀的系统无论什么类型的问题都走同一条重管道结果就是纯闲聊问题被送去检索浪费算力还容易带偏答案多轮对话中的“它”“那个价格”这类指代词因为没有结合前文判定意图检索出的文档语义漂移知识库有多个主题域时不知道用户问的是哪一类全库召回导致命中率低加了意图识别之后同样的系统能分情况处理闲聊直接进模型对话明确的知识问题走检索复杂的对比分析问题可以走更强的检索配置或多路召回。整个系统的行为从“一刀切”变成“按需分配”。我用一个场景类比你把意图识别理解成前台接待员。没有接待员所有访客都直接冲进会议室技术团队被迫接待推销员有了接待员访客先被问清来意再被带到对应的部门。RAG系统里意图识别就是这个接待员。它不负责回答问题但负责把问题导向正确的处理通道。2. 设计意图分类体系先定清楚分几类、怎么分2.1 一套够用的分类三类起步五类封顶做意图识别第一件事不是选技术方案而是定分类体系。分类粒度太粗等于没分闲聊和知识类问题混在一起下游没法差异化处理分类粒度太细标注成本高模型容易混淆。以多数业务场景的经验我建议从三类起步意图类型定义典型问题下游动作直答型问题本身包含完整答案不需要外部文档“按你们的规定退款周期是7天吗”不做检索直接让大模型基于通用能力回答检索型问题需要从知识库/文档中查找具体信息“保修期内屏幕碎了能换吗”触发RAG检索组装带上下文的Prompt操作型用户想做某个动作或询问某个功能的用法“怎么导出报表”“帮我查一下订单状态”接入对应工具链或功能入口说明文档这三类基本覆盖了绝大多数业务场景。如果发现某类检索型问题内部有明显差异再细分。比如一个知识库同时包含产品FAQ、规章制度、技术文档就把“检索型”拆成“FAQ检索”“资料检索”“技术文档检索”每一类绑定不同的检索库。一般不建议超过五类超过五类之后不仅标注成本上升类间边界也会模糊容易误判。2.2 分类体系设计的两条实践原则第一条原则以“下游动作”为中心定义类别而不是以“用户语义”为中心。用户说话方式千变万化但下游能做的动作是有限的。与其纠结一句话属于“情感倾诉”还是“建议咨询”不如先问自己这句话需要触发检索吗需要触发哪个库需要走工具吗这么一想分类立刻清晰。第二条原则每类至少配三个典型问题和三个边界问题。典型的用于让模型理解这类长什么样边界的用于告诉模型“像但不属于”。比如“退款要多久”是典型的检索型“你觉得退款快不快”可以算边界问题——它更接近评价类直答而不是事实查询。边界问题会在测试阶段发挥大作用因为它直接决定误判率。2.3 要不要为了意图识别单独维护一根专业名词表要而且建议从第一天就建。业务里存在大量缩写、产品名、内部术语模型如果不认识会把这些词当作通用词处理导致检索型问题被误判成直答型。比如“SDK集成报错”“CNAME解析失败”“网关超时”听起来像技术人员日常对话实际上每个都是需要查对应文档的知识问题。维护方式不复杂一张线上配置表格字段包括术语、所属意图类、对应的检索库名称业务人员也能维护。每次出现新的高频术语就往里加一周同步一次到判定模块。这个小投入换来的是意图识别对领域词汇的敏感度大幅提升。3. 三种落地技术方案规则、小模型、大模型提示词3.1 方案一规则关键词模板最可控的入门做法规则方案的本质是维护一套关键词和正则模板用命中结果直接判意图。优点是零推理成本、毫秒级响应、完全可控缺点是覆盖不了复杂表达。适合两种场景意图类别少且表达稳定的场景或者作为后续方案的“前置过滤器”。我个人的习惯是先搭一层规则再决定要不要上更重的方案。比如命中“怎么”“如何”“步骤”“教程” - 检索型命中“你好”“谢谢”“再见” - 直答型命中“登录”“下载”“导出”这类动词 产品名 - 操作型这套规则不用太复杂十几条正则就能覆盖三成左右流量。别小看这三成它让90%的闲聊问题根本不会走到耗时的模型判定环节在线服务压力直接降一档。规则层的代码表达很直观我贴一个当前使用的简化版本import re class IntentRuleEngine: def __init__(self, config: dict): # config 结构{retrieval: {patterns: [...], terms: [...]}, # operation: {...}} self.rules {} for intent, cfg in config.items(): patterns cfg.get(patterns, []) terms cfg.get(terms, []) self.rules[intent] { regex: [re.compile(p, re.IGNORECASE) for p in patterns], terms: [t.lower() for t in terms], } def match(self, text: str) - str | None: # 按优先级顺序检查不同意图的规则 for intent in [retrieval, operation, direct]: item self.rules.get(intent) if not item: continue for regex in item[regex]: if regex.search(text): return intent lowered text.lower() if any(term in lowered for term in item[terms]): return intent return None这段逻辑的核心是“按优先级遍历提前返回”。高优意图放前面比如检索型的规则优先级要高于直答型否则“怎么登录”里包含“怎么”这个词可能先被当成闲聊处理。调优先级的顺序其实就是调整业务对的判断权重。3.2 方案二小模型分类器精度与速度的平衡选择规则管不了的那七成问题交给小模型分类器。这里的“小模型”指参数规模在1亿以内、本地可部署的文本分类模型比如基于常见预训练模型的微调版本。相比大模型它能做到单次推理几十毫秒成本几乎可忽略而且完全私有化。实现路径分三步准备数据、微调、导出上线。数据方面每个意图类别准备800到2000条标注样本比较稳妥。来源可以从线上日志里捞真实用户问题再人工标注冷启动阶段可以先用规则把能判的先判掉抽一部分未命中的问题人工标注。另外注意按类别平衡样本数量避免一个类别数据量特别大、另一个特别小否则训练出来的模型会偏科。预处理时建议做两件事统一英文大小写、保留用户原始文本不做纠错。用户的口语化表达本身就是特征过度清洗反而丢信息。比如用户打“退款去哪弄”你把它纠正成“退款去哪里办理”模型反而学不到真实语感。推理端可以套一层置信度阈值模型会输出每个类别的置信度最高置信度超过某个阈值比如0.75才接受这个判定否则落到兜底分支默认走检索型因为检索型是最安全的选择多查一次文档只是多花点算力不会给用户致命错误。这个“宁可多检索也不强行直答”的设计思路非常实用后面我会展开讲。3.3 方案三大模型提示词分类零样本但需要留心可控性第三种方案不训练模型直接让大模型在系统提示词里对用户问题做意图分类。它的优势是零样本、上线快、对边界表达的理解力强劣势是每次判断都有推理成本而且大模型偶尔会在输出格式上出幺蛾子——说好了输出JSON偏给你夹带一句解释。我用过一个稳定的提示词模板结构如下你是系统的意图识别模块。请判断用户问题的类别只允许输出一个单词 - direct问题本身就是答案或无需查询任何资料 - retrieval问题需要从知识库/文档中查找信息 - operation问题涉及操作某个功能或询问功能用法 判断依据 1. 如果问题包含具体业务规则、事实细节、文档中的条款输出 retrieval 2. 如果问题可直接从常识或对话上下文回答输出 direct 3. 如果问题涉及系统操作步骤、按钮位置、功能入口输出 operation 用户问题{query} 只输出一个单词不要解释。这个模板有两个容易被忽略的细节。一是“判断依据”必须给大模型靠它理解分类边界不给依据的提示词效果类似让新员工猜公司流程准确率上不去二是“只输出一个单词不要解释”必须写否则大模型会顺手解析问题把流程吹得天花乱坠下游解析就得去垃圾信息里捞结果。大模型方案的工程实现也很简单把输出限定到三个词的集合里解析时遇到意料之外的输出就丢弃重试或落到兜底分支。如果和上面两个方案结合可以在规则判定无结果之后先用小模型出一次结果小模型置信度不够再上大模型。这套三层过滤体系兼顾了成本、速度和精度。4. 落地实操带完整代码的意图识别RAG入口实现4.1 核心数据结构和接口设计不论你采用哪种方案意图识别结果最终要形成统一的判定对象供下游检索模块消费。下面这段数据结构是我习惯的写法class IntentResult: def __init__(self): self.intent_type direct # 最终判定意图 self.confidence 0.0 # 置信度 self.source # 判定来源rule / small_model / llm self.retrieve_flag False # 是否触发检索 self.retrieval_scope None # 检索范围如 faq / tech_docs / policy self.prompt_template default # 使用的Prompt模板标识 self.trace {} # 完整判定过程供日志排查retrieve_flag是核心字段下游检索模块只需要看这一个字段决定要不要召回文档。retrieval_scope决定从哪个知识库检索。source字段用于观察哪个判定层在起作用这是后续调优的重要依据——如果规则层每天都在命中说明用户表达比较稳定如果规则层长期不命中就该考虑扩充术语表了。接口层面一个完整的判定函数大概是这样的def intent_pipeline(query: str, history: list[dict], rule_engine, small_model, llm_classifier) - IntentResult: # 第一步规则层 rule_result rule_engine.match(query) if rule_result: return build_intent_result(rule_result, 0.95, rule) # 第二步小模型层带置信度过滤 sm_pred, sm_conf small_model.predict_topk(query, k2) if sm_conf 0.75: return build_intent_result(sm_pred, sm_conf, small_model) # 第三步大模型层此处为兜底方案 llm_pred llm_classifier.classify(query) if llm_pred in ALLOWED_INTENTS: return build_intent_result(llm_pred, 0.6, llm) # 兜底默认检索 return build_intent_result(retrieval, 0.5, fallback)这套流程的顺序有讲究规则层成本最低放最前小模型成本居中大模型最贵放最后。前两层能搞定就不碰大模型能省下不少推理费用。4.2 结合上下文的意图识别多轮对话不脱轨单轮判定有一个死角——用户直接追问“那退款呢”。这句话脱离语境任何分类器都难判断是问退款原因、退款时间还是退款流程。多轮场景下必须把对话上下文喂给判定模块。我给每个会话维护一个最近两轮的压缩摘要。实现上不用复杂每轮生成的答案末尾加一个“意图标签快照”存储上一轮的意图和检索结果下一轮判定时把快照拼接进当前query。示例如下def build_classify_text(query: str, history: list[dict]) - str: if len(history) 2: return query # 取最近两轮上一轮意图 上一轮答案摘要前50字 prev history[-1] prev_intent prev.get(intent, ) answer_suffix prev.get(answer, )[:50] return f[上轮意图:{prev_intent}] [上文摘要:{answer_suffix}] 当前问题:{query}做法不算高明但实测下来能解决八成指代问题。更精细的方案是维护实体状态表记录“当前会话正在讨论的产品/订单号”意图识别同时参考实体表适合需要强上下文记忆的场景。对多数RAG应用来说快照拼接法已足够。4.3 检索参数自动配置与Prompt模板分派意图识别不只是“要不要检”的二选一。对不同的意图检索参数和Prompt组织方式应该有差异。我的做法是把意图映射到一组检索配置意图类型检索开关Top-K值相似度阈值Prompt模板直答型关--无文档模板常规检索开50.25文档辅助模板对比分析开100.20对比分析模板操作指南开30.30步骤引导模板Top-K值和相似度阈值的组合决定了召回效果。FAQ多且答案较短的业务Top-K可以偏大让模型有足够上下文筛选技术文档长且结构复杂的业务Top-K反而要调小否则塞太多长文档进Prompt输出质量会下降token成本也飙升。Prompt模板的分派同样由意图字段完成。对比分析类意图的模板会明确要求“请对比以下文档中的异同”操作指南类则要求“请按步骤列出操作流程”这种模板差异化是让答案风格贴合用户期望的关键。下面是检索配置构建的伪代码RETRIEVAL_CONFIG { retrieval: {top_k: 5, threshold: 0.25, scope: all}, operation: {top_k: 3, threshold: 0.30, scope: manual}, comparison: {top_k: 10, threshold: 0.20, scope: all, prompt: comparison}, } def retrieve_docs(query, intent): cfg RETRIEVAL_CONFIG.get(intent, RETRIEVAL_CONFIG[retrieval]) # 先确认该意图是否需要检索 if intent direct: return [] # 在scope指定的库中召回再按阈值过滤 docs vector_search(query, top_kcfg[top_k], scopecfg[scope]) return [d for d in docs if d.score cfg[threshold]]这里scope字段和4.1里的retrieval_scope对应可以实现“不同意图查不同知识库”。比如操作指南意图只在用户手册库中检索不碰产品政策库既提高检索精度又减少无关信息干扰。4.4 完整的判别日志设计问题追踪不再靠猜接入了意图识别之后最容易被忽视的是日志设计。没有日志模型判错时你完全不知道卡在哪一层只能靠肉眼猜。我在项目里的做法是写全量判别日志每个请求输出一条结构化记录包含原始问题、最终意图、判定来源、每层置信度、检索是否触发、检索命中的前三条文档名和得分。这些数据是意图识别持续优化的燃料。排查一个误判时日志能直接告诉你是规则命中了错误模板是小模型置信度不够被强行舍弃还是大模型输出了非法格式。有了这些信息调整方向非常明确。日志字段结构化输出按月归档分析模型换版时也能靠它对比AB测试的效果。5. 线上运行的坑我帮你踩过的都列这里5.1 常见问题速查表意图识别上线后的坑不少把典型的整理成一张表直接对照自查问题现象常见原因解决方案闲聊被送去检索用户说“哈哈”系统回了文档内容规则层没有闲聊词表小模型训练数据缺闲聊负样本扩充直答类关键词哈哈、好的、辛苦了加负样本训练检索型被判成直答“入库单怎么填”被当成闲聊短问题特征不足模型置信度摇摆加操作动词规则扩展术语表绑定检索意图多轮指代判错“那客服呢”直接跳直答上下文没接上启用多轮快照机制参考上一轮意图输出格式校验失败大模型分类输出了“retrival”错拼或夹带解释提示词约束不够强解析逻辑太脆加强提示词“只输出一个单词”解析时做容错映射检索配置一刀切复杂对比问题召回文档太少所有意图共用一套Top-K和阈值按意图分组配置检索参数规则优先级颠倒“怎么登录”被识别成闲聊“怎么”命中了直答规则调整规则判定优先级检索型先于直答型这张表里第一条是我遇到最多的情况。用户随口一句“辛苦啦”系统哗啦啦拉出一堆文档回复里还一本正经引用内容看得人尴尬。规则层加一个高频闲聊词表就能过滤掉大部分这类问题。5.2 四个亲测有效的避坑技巧第一个技巧兜底分支选检索型不选直答型。拿不准用户意图时让系统去查。检索型可能产生一次多余的查询开销但检索生成的结果一定包含参考信息质量不会太差直答型如果判断错误系统要用通用能力硬答歪题容易出现看起来一本正经实际完全跑偏的回答。两相比较检索型更安全。第二个技巧规则层多放“行为动词”少放“疑问词”。“怎么”“如何”“为什么”这些词跨界严重聊天里也常说“怎么这样啊”。相比之下“导出”“修改”“上传”“删除”这类行为动词命中的基本都是操作相关意图正命中率高得多。规则设计时优先选行为动词疑问词只作为辅助信号。第三个技巧小模型训练数据的负样本不要只从知识类问题里取。很多团队构造训练集时只挑业务问题忽略了从真实对话日志里抽闲聊和无意义表达导致模型上线后遇到“哈哈哈”这类文本直接将其判定为“检索”。我建议每个意图的训练数据里至少混入10%-15%的无关负样本模型才能学会拒绝。第四个技巧设定“意图变化”告警。意图识别上线后持续统计各意图的占比分布。正常情况下这个分布比较稳定。某天如果操作型意图占比忽然从20%跳到40%大概率是线上规则或模型被误改了而不是用户行为突变。加一道告警能在用户明显感知到异常之前发现问题。5.3 索引里的文档质量也会反噬意图识别效果这个坑比较隐蔽。很多团队把精力花在意图模型上却忽视文档切片质量。当一个用户问题是“保修期多久”检索出来的文档片段全是“保修范围不包括人为损坏”这种含有“保修”但无“时间”的碎片时RAG答非所问就在所难免。这里有个现象值得注意用户反复追问同一个问题往往不是因为意图识别出了问题而是因为文档粒度太粗、切出来的片段不含关键答案。建议排查异常意图的时候先看命中文档本身。如果检索命中的Top3文档相关度得分都很低问题可能不在意图层而在文档预处理层——切片没有按语义边界切导致答案被切断。这时候先把文档切成更小的语义块比调模型参数有效得多。RAG系统里文档质量永远是地基意图识别只是地上建筑地基不稳再好的模型也没用。写在最后的几句体会做了这么多RAG项目之后我愈发觉得意图识别不是那种“高深的技术模块”反而是最贴近人性的环节——想办法理解一个人想问什么、需要什么。它的每个设计决策背后都是在为系统的使用者减少摩擦。我从这个模块里收获最大的一课是任何分类模型都会有误判的可能但好的系统设计能让误判变得无感。无论是加规则层做前置过滤还是兜底走检索型都是在给模型的错误留缓冲垫。技术方案的选型固然重要但比它更关键的是你愿意花多少时间去观察真实用户的提问习惯。如果你正在为自己的RAG系统设计意图识别先把日志和监控搭好然后从规则方案起步跑通链路再加小模型最后视能力决定要不要引入大模型。这套路径的每一步都扎实不会白费功夫。希望这些实操经验能帮你少踩些坑做出一套真正“懂用户”的RAG系统。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑