☰
RAG系统模糊查询治理:从改写、澄清到混合检索的完整实践
2026/10/21 7:46:26 网站建设 项目流程

上个月在给某公司的知识库问答系统做效果调优,翻了一下午日志,发现40%以上的失败案例都指向同一个源头:用户压根没把问题问清楚。输入框里躺着“怎么开”“为什么不行”“这个能用吗”这类半句话,检索器拿不到好上下文,生成器再聪明也只能硬编。RAG系统最怕的不是知识库内容少,而是用户查询太模糊——这句话是这两年被反复验证的切身体会。

这篇文章想聊的,就是“模糊查询”这件事本身:它长什么样、为什么杀伤力这么大、有哪些治理思路,以及我实际落地过的一条完整管线。适合正在做RAG应用、被检索质量折磨过的朋友参考;哪怕你是刚入门,看完也能理解为什么有时候傻傻追问一句“您说的‘这个’具体指哪项功能”,比让模型瞎猜强一万倍。

1. 模糊查询为什么会拖垮整个RAG链路:检索不准,生成必偏

很多人有个误解,觉得RAG的效果瓶颈在生成模型不够聪明。但把链路拆开看就会发现,query进来到生成结果出去,中间最脆弱的一环是检索。模糊查询真正影响的不是“最后那句回答”,而是从embedding开始每一步都在将错就错,最后错误被放大到不可接受。

1.1 从一次日志排查说起:用户问的是“那个文件”

当时我们做的是一个企业内部的规章制度问答系统,知识库里沉淀了几百份制度文件。上线第一周,用户提了一堆“那个文件”“上个月说的那个”“首页上写的”之类的查询。从日志看,这些query的embedding向量散落在向量空间的各个角落,跟库里任何一条制度文本的相似度都不到0.45,检索器硬着头皮返回了top-10段落,但相关段落排在第六七位,重排序模型也没能捞回来,最终生成器对着几段不相关的“考勤制度”“报销流程”拼了一堆正确但无用的废话。

这类案例最坑的地方在于:系统没有报错,没有超时,甚至生成结果还很流畅。用户看到的是“车轱辘话”,感知却是“这系统不行”。所以我后来定了一条规矩:排查RAG质量问题,先看query,再看召回,最后才看生成。

1.2 模糊查询的三个典型缺口:主体缺失、意图漂移、限定条件为空

把大量模糊query归归类,其实就三类缺口。

第一类是主体缺失。指代不清是最常见的,“这个”“那个”“它”满天飞。用户问“它怎么设置”,背后的“它”可能在上一条对话里,也可能根本不在同一会话里。对检索器来说,“它”是一个没有语义锚点的代词,embedding模型计算相似度时等于在裸奔。

第二类是意图漂移。口语化表达里动作和对象往往粘连不清。“怎么弄一下”到底是想咨询操作步骤、遇到故障想报修、还是单纯问问有没有这个功能?同一个短句,在不同业务场景下的检索意图千差万别。embedding模型不会读心,它只会忠实地把“怎么弄一下”映射到一个平均化的语义位置,结果就是和谁都不近。

第三类是限定条件为空。用户知道要问什么,但忘了给时间、部门、产品线等关键限定词。比如问“审批流程有变化吗”,知识库里可能有2021版、2023版、不同事业部的多套流程,缺少限定词直接导致召回结果混杂,重排也无法判断该偏向哪条。

我建议所有做RAG的同学都先做一次类似的“缺口归类”,因为后面选治理方案,完全取决于你面对的主流模糊类型。

1.3 为什么不能指望Embedding模型自己“理解”模糊表达

有一个很流行的误区:觉得大模型都这么强了,embedding模型应该也能自动理解模糊query吧?很遗憾,这纯粹是幻觉。

Embedding模型做的事情,是把文本映射到一个高维向量空间,让语义相近的文本向量距离更近。它的训练目标决定了它擅长区分“苹果”是水果还是公司(在上下文足够时),但如果你只丢给它一个“那个”,它的向量仍然会落在“那个”这个泛化词的位置,不会神奇地变成“那个文件的向量”。这个问题不是模型能力不够,而是信息量不足——模型可以聪明,但它不能无中生有地知道你没说出口的限定条件。

这也是我反复跟团队强调的:模糊查询是输入侧缺陷,不是检索侧性能问题。试图靠换一个更大的embedding模型来解决问题,基本是南辕北辙。

2. 治理模糊查询的路线选择:改写、澄清还是扩展

既然模糊查询是输入侧的信息缺口,思路就清晰了:要么把缺口补上,要么让缺口不致命。补缺口的方式主要有两条——查询改写和主动澄清。扩展呢,则是把检索范围适当放宽,让模糊query不至于彻底漏检。这三条没有绝对的优劣,取决于你的产品形态和用户耐心。

2.1 查询改写:把口语“翻译”成检索器喜欢的话

查询改写,核心动作是让LLM充当翻译官,把一句口语化的模糊query改写成一句更结构化、更完整、更接近知识库描述习惯的query。

比如用户输入“那个兵役登记表咋弄”,改写系统可能输出两个版本:一是“兵役登记表填写流程”,二是“如何在网上进行兵役登记”。这两个改写后的query和知识库里的制度文本向量相似度会显著高于原句。说白了,改写的本质是用大模型的常识知识做了一次小小的推理补全。

改写的最大好处是无感——用户不需要多打字,系统在内部悄悄完成了补全,体验损失为零。但它有一个天然边界:如果缺的信息是用户私有背景(比如他所在部门、他的职级),模型推理不出来,改写也只能瞎蒙。所以改写适合处理“没有额外信息也能猜个大概”的模糊,比如口语转书面、补常见同义词。

2.2 主动澄清:让系统反问,而不是硬猜

主动澄清是另一种思路:检测到query信息量不足时,系统不急着检索,反过来追问用户一句。比如“您说的‘那个文件’,是指上个月发布的XX管理办法吗?”

澄清的优点是精准,能拿到用户脑子里才有的私有信息;缺点是打断体验。每多一次追问,就多一次用户流失的风险。尤其在内网工具场景下,很多用户根本没有耐心跟机器人对话,宁愿直接关掉去问旁边的同事。

所以我的建议是:澄清要克制。只有满足两个条件才值得追问——第一,信息缺口严重到改写也无法补救;第二,错误回答的代价高于追问的代价。对于“问题的答案错了顶多多点一次”的轻量场景,不如直接猜+给推荐问法。

2.3 场景取舍:什么时候改,什么时候问

实操中我习惯画一个简单的决策矩阵。信息缺口大不大、误答成本高不高,两个维度一组合就有答案:缺口大且误答成本高(政策、法务、医疗类),必须澄清;缺口大但误答成本低(猜错再问一次就行),优先改写加多候选召回;缺口小、改写就能补,直接改;缺口小但改写可能补错方向(比如账户名只有一个简写),可以澄清一次确认。

这套取舍说起来简单,做起来难的是“识别缺口大不大”。我的经验是先跑一批日志,把query人工标成“足够”“轻微模糊”“严重模糊”三类,再拿改写器在中等模糊数据集上跑效果,优先把“轻微模糊”的改写做好,因为这类占比最高、最容易出成绩。“严重模糊”的干脆走澄清或者兜底话术,不要在检索侧死磕。

3. 落地一套查询改写管线:组件拆解与完整步骤

到具体实现这一步,我分享一条我实际在用的查询改写管线。它不复杂,核心就四个环节:基于LLM的改写生成、结构化输出、候选融合、效果评估。下面拆开说。

3.1 整体流程:改写不是“一步到位”,而是生成多条再择优

很多人的第一版改写器写了这样一个prompt:“请把用户问题改写成更完整清晰的表述,然后直接去检索。”结果发现时好时坏,原因在于:一个改写结果等于把所有鸡蛋放一个篮子里,一旦改写方向错了,检索必歪。

我的做法是让LLM一次输出2到3个不同侧面的改写版本。比如原query“安卓的APP闪退”,版本一补主体“Android版APP启动时闪退问题排查”;版本二补意图“如何解决Android版APP闪退”;版本三补限定“Android最新版本APP闪退反馈处理”。三个版本分别去检索,最后把召回结果合并去重,再交给重排序模型统一打分。

这条管线的伪代码大致长这样:

def rewrite_query(raw_query: str, history: list[str]) -> list[str]: prompt = build_rewrite_prompt(raw_query, history) result = llm_client.complete(prompt, temperature=0.3) candidates = parse_json_array(result) # ["版本1", "版本2", "版本3"] return candidates def retrieve_with_candidates(candidates: list[str], top_k: int = 10): all_hits = [] for cand in candidates: hits = vector_store.search(cand, top_k=top_k) all_hits.extend(hits) # 按文档ID合并去重,保留每个命中的分数 return merge_hits(all_hits) def rag_answer(raw_query: str, history: list[str]): candidates = rewrite_query(raw_query, history) hits = retrieve_with_candidates(candidates) reranked = reranker.rerank(raw_query, hits) return generate(raw_query, reranked)

这条管线最核心的思维转变:改写器的输出不是“唯一正确答案”,而是“一组可能的检索入口”。把宝押在多条改写上,结合合并去重,模糊query的召回稳定性会明显提升。

3.2 改写Prompt的具体写法:结构化输出的价值

提示词我用的是英文模板加中文例子,效果比较稳定。分享一版简化的:

You are an expert query rewriting assistant for a knowledge base. Given the raw user question and conversation history, generate 3 query variants that: 1. Resolve ambiguous pronouns where possible. 2. Add missing entity types inferred from the user's wording. 3. Convert casual spoken language into formal knowledge-base search language. 4. Each variant should be a self-contained search query. Return ONLY a JSON array of strings, no explanation. Raw question: {raw_query} Conversation history: {history}

注意这里有两个细节:一是要求“self-contained search query”,也就是每条改写都能独立成query;二是强制JSON结构化输出,方便后续解析。前者保证检索器拿到的不是半截话,后者保证管线能自动化跑通。如果你发现模型偶尔不输出合法JSON,可以在解析失败时快速切到简单字符串拆分逻辑,而不是让整个流程报错。

3.3 改写数量与温度系数:多样性从哪里来

多版本改写最大的问题是“多样性”和“稳定性”打架。温度设到0.7,确实能产生更多样的改写,但也更容易出现离谱结果;设到0.1,稳定是稳定了,三个版本可能意思一模一样,等于白做。

我实际调下来,温度0.3到0.4是个甜点区,配合prompt里“请从不同角度改写”的指令,能产生既有差异又不跑偏的候选。另一个技巧是给改写模型提供1到2条历史对话作为上下文,比如上一轮用户说“申请了调休”,这一轮问“那边批没批”,有历史就能把“那边”补成“调休审批部门”,没历史就只能瞎猜。

3.4 改写效果怎么评估:不要只看“改写得好不好”

评估改写器有两个层次,很多人只做了第一层。第一层是直接看改写的语句是否通顺、信息是否补全,这可以用少量人工标注来抽检。第二层才是关键——看改完之后的检索命中率有没有提升,具体指标是召回率@10和MRR(平均倒数排名)。换句话说,改写得再漂亮,如果检索器top10里没有相关段落,就是白改。

我建议搭一套离线评测集:取200条历史真实模糊query,标注好每条对应的“标准答案文档ID”,分别测“原始query直接检索”和“改写后多路检索”的召回率。如果两版差异不大,说明你的改写器还在自嗨,需要回头调prompt或换更大的模型。

4. 检索侧的三层兜底:混合检索、HyDE与重排序

改写做得再好,难免有漏网之鱼。所以在检索侧我也加了兜底机制——这三层不负责“理解模糊query”,它们负责让模糊query在检索阶段不直接崩盘。

4.1 混合检索:向量召回+关键词召回,双通道互补

纯向量检索的毛病是:模糊query的向量本身就不靠谱,但同一个query里的几个关键词往往是可靠的。比如“档案借阅流程”哪怕整个句子语义平均化得厉害,“借阅”和“流程”两个词还是实打实的地点。

所以我在向量检索之外,并行加了一层BM25关键词检索,再把两路结果合并。合并策略我在生产环境用的是“加权融合”,向量命中给0.7权重,关键词命中给0.3权重,权重参数在测试集上调优。实际效果:在模糊query上,混合检索的召回率比纯向量检索普遍高5到8个百分点,最关键的是关键词路偶尔能把“向量路”完全错过的正确文档捞回来。

4.2 HyDE:让模型“先猜答案再去查”,适合缺限定的场景

HyDE(Hypothetical Document Embeddings)的思路有点反直觉:模糊query信息不够,那就让LLM先根据query“伪造”一段可能存在的文档内容,把这段虚构文档拿去embedding再检索。因为虚构文档比原始query包含更多上下文词汇,向量位置更容易命中真实文档。

打个比方:用户问“今年的政策变了吗”,原文query信息量太低,但让LLM先写一段“某公司2025年XX政策调整说明,包括年度申报时间、材料要求、最新变化”,这段虚构文档的embedding就可以和知识库里真正讲政策变化的文档拉近距离了。

HyDE也不是没有代价。虚构文档如果方向猜错,会把检索结果带偏。我用的策略是:只用HyDE生成一个额外的检索入口,不取代原始query,也不取代改写候选。四个入口(原始query、3个改写版本、1个HyDE文档)一起打,反正最后还有重排序把关。

4.3 重排序:把混在一起的结果重新洗牌

多路召回之后,候选池里混着不同来源、不同质量的段落。这时候必须有一个重排序模型统一起跑线。重排序不依赖query的向量表示,它直接把“原始query+段落”拼在一起算相关性分数,所以对模糊query更宽容——它能看到整个句子的完整语义,而不是被压缩成向量后的平均位置。

我的经验是:重排序这层一定要上,它能捞回不少“向量召回前五位之外但确实相关”的段落。预算紧张的时候可以先用小模型,效果也够用;预算充足的再上更重的排序模型,延时会增加,但离线评测里MRR确实更漂亮。

5. 上下文记忆与多轮追问:把模糊问题“问”成具体问题

改写和检索兜底解决的是“系统在后台使劲”的问题。但有些模糊,后台再怎么使劲也没用——因为缺的关键信息只有用户自己知道。这时候要做的是把多轮能力接进来,让系统学会“看着上下文猜”,以及“实在猜不中就礼貌地问”。

5.1 缺主语时,如何从历史对话里补齐

继续用“那个文件”举例。如果开启对话记忆,系统可以把当前query和最近两轮的历史拼在一起去改写。上一轮用户说过“我想申请加班调休”,当前轮说“那个流程走哪了”,改写器看到历史就能把“那个流程”补成“加班调休审批流程”。这个效果是单轮查询永远做不到的。

具体做法不复杂:在改写prompt里加入history字段,让模型自行判断哪些指代需要从历史中解析。但要注意别把太长的历史堆进去,token有限而且重点会被稀释。我一般只取最近2轮,最多3轮,超过的直接截断。

5.2 追问一次的价值:用一次交互换检索精度

当系统认为改写和HyDE都救不回来时,我倾向触发一次澄清追问。比如用户问“审核要多久”,系统可以反问:“您是指哪个流程的审核?调休申请、采购审批还是合同用印?”用户点一下就能把限定条件补齐。

这次追问会产生三个好处:限定条件明确后retrieval精度大幅提升;用户感受到“系统在努力理解我”,而不是机械地吐出毫不相干的答案;后续生成时上下文里有用户自己提供的关键词,生成结果的可靠性更高。一个细节是追问话术必须给选项,不要开放式问“您能再详细说说吗?”——开放问题会把球踢回给用户,交互成本瞬间翻倍。

5.3 兜底回复模板:当所有手段都失效时如何体面收场

即便改了、问了、混合检索了,依然有少数query无法命中。这时候最忌讳的是让生成器对着空空如也的上下文硬编一段“可能……”出来。我现在的策略是分级兜底:第一级,把检索到的置信度低但非零的段落压缩成“相关但不完全匹配”的摘要,并配一句“您可以补充关键词再试”;第二级,如果完全没召回,直接返还固定引导语,告诉用户换一种描述方式,并给出业务侧的热门问题入口。

这个兜底模板看似不起眼,但对用户体验影响很大。用户收到“抱歉没找到相关内容,试试这样说:‘XX部门的调休审批需要多久’”和收到一段幻觉编造的流程说明,前者是失望,后者是彻底失去信任。

6. 实测中的坑与我的调优心得

最后说几个我们自己在生产环境里踩过、调过的细节问题。这些东西单看文档几乎不会有人写,但遇到的概率一点都不低。

6.1 改写过度:把“模糊”改成了“另一件事”

有个典型案例:用户问“表格里那个公式怎么弄”,改写器把它补成了“Excel中VLOOKUP函数使用方法”,因为模型看到“表格”“公式”就自动联想到Excel。但用户其实是在问OA系统里自带的统计报表公式。这种“看似合理的错误改写”比不写更糟,它会带着检索器往完全错误的方向跑。

我们在问题列表里遇到多少次都是这种“自信的误解”。对策是控制改写器的联想边界:在prompt里明确“只能基于用户原文本有的信息进行补全,不得引入未出现过的具体软件名、产品名”,并且把改写后的候选和原query同时送去检索,保证原query这个“纯真信号”不会被覆盖。

6.2 澄清过多:用户流失的隐性成本

有一版我们特别激进,只要检测到query缺少主语就追问一遍“您指的是……”。结果内部试用时,一位同事问了一句“打印机卡纸了”,系统回了一句“您好,您说的‘卡纸’具体指哪台打印机?”这位同事当场就骂了,直接打电话给IT支持,比机器人快多了。

后来我们重新定了一条规则:只有在改写补全和混合检索双重失败后,才进入追问流程。并且追问出现频率被控制为所有session的15%以内。超了说明之前的改写太弱,优先调的是改写,而不是继续加强追问。

6.3 三条经验清单:可以直接抄走的

第一,模糊query治理不是“加一个模块”的事,而是从输入到检索的整体工程。我见过不少人只加了个改写器,不加混合检索也不加重排序,效果还不如不加。因为单点改造会让每个环节的标准不统一,最后全链路还是崩的。

第二,永远保留原始query一路。不管改写多香、HyDE多聪明,原始query都是一个“用户真实意图的锚点”。多路召回合并时,原始query的命中结果应该拿到一定的分数加成,防止系统在改写阶段带偏方向。

第三,建立你自己的模糊query评测集。不要相信任何人宣称的通用指标,收集你自己业务里100到200条真实模糊query,标好标准答案,每次改动都在这套集上回测。有这套集,你才敢放心上新的prompt模板、换更大的模型、调融合参数。

说到底,RAG系统里用户没耐心把问题打完整,这可能是最稳定的“用户行为定律”之一。我们能做的不是教育用户好好说话,而是在后台多替他们想一想。把模糊query治理好,知识库问答的体验上限才能真正打开。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询