ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

RAG查询重写实战:弥合用户Query与检索器的表达鸿沟

RAG查询重写实战:弥合用户Query与检索器的表达鸿沟 做RAG做到一定阶段你迟早会遇到一个特别憋屈的问题检索器配置没毛病、知识库质量也说得过去可用户问出来的话检索结果就是南辕北辙。我第一次认真把查询重写Query Rewriting当成RAG管线里的独立环节是在某知识库问答项目的线上反馈里用户问“那个权限申请的流程现在还能线上走吗”召回的全是旧版线下流程文档新版内容一篇都没进top 5。当时我第一反应是换embedding模型后来把query和文档拉出来逐条对比才意识到问题根本不在模型而在用户这句口语化、带着指代和时态的话压根不是检索系统该直接吃的输入。从那时起我开始系统性地研究查询重写把用户query转成更适合检索的形式再进入向量检索和重排环节。这一篇是Advanced RAG系列里关于查询重写的探索记录覆盖策略选型、提示词设计、工程改造和上线评测适合已经跑通RAG基础链路、想继续拉高检索上限和答案质量的开发者。下面的内容都来自我自己的项目踩坑经历方案未必放之四海皆准但思路和排坑路径是通用的。1. 先定位问题查询重写到底在弥合哪条“表达鸿沟”要理解查询重写首先要接受一件事检索器不是理解器。很多人觉得“我已经用了很强的embedding模型语义检索应该很聪明”但实际跑起来就会发现embedding模型对“表达方式”的敏感程度远超你的预期。1.1 检索器不是理解器原始query直接进检索的隐患embedding检索的本质是把query和文档都映射进同一个向量空间然后用余弦相似度或内积排序。这套机制对“表达方式”极其敏感。同一个意思换一种说法向量可能就偏到另一个区域。最典型的例子是电商场景里用户说“不要白色的”文档里写的是“黑色/蓝色”否定表达在embedding里几乎不起作用。技术文档问答里更常见用户习惯说“这功能咋关闭”文档正文写的却是“如何停用该模块”两者之间不是简单的同义词关系而是句式和语域的差异。短query是另一个麻烦。用户只输入四五个词向量被压到一片模糊区域top k里混进大量主题沾边但不精确的内容。再加上用户的query多是聊天式口语而知识库文档是结构化书面语两者天然存在分布差异。这种鸿沟不是靠换一个更强的embedding模型就能完全解决的因为问题发生在输入端——query这个“句子”本身信息密度不够。所以查询重写的角色可以理解成在用户query和检索器之间加一个适配层把一句口语化、短小、带指代的话翻译成更接近文档表达习惯、信息更完整的检索式。它不是为了让query变得更“像人话”而是为了让query在向量空间里离目标文档更近。这个视角一旦建立后面所有策略的选择逻辑都会变得很清晰。1.2 三类最典型的“问非所想”场景我做过的项目里检索效果崩掉的query基本能归成三类。第一类是指代与口语化。比如用户在先前的对话里问过“上传发票的步骤”下一句紧跟“那上传之后多久能审核”系统如果只看后一句根本不知道“上传”指什么。这种query重写必须结合对话历史把指代还原成“上传发票之后审核需要多长时间”。这类问题在客服机器人、企业内部门户里最普遍也是大家最容易忽略的一类——因为单看每一句query你总觉得“人话没问题啊”但检索器眼里全是漏洞。第二类是复合问题。一个提问里揉了两三个意图比如“怎么查日志以及怎么通过日志定位超时”前半句在问操作后半句在问诊断方法。直接检索时两个意图互相干扰召回结果哪边都不精准。这类query要做子问题分解拆开检索再合流。比较麻烦的是有些复合问题拆开之后单独看都成立但用户真正想要的是两件事之间的关联后续生成环节需要额外处理。第三类是具体问题背后其实是抽象需求。用户问“为什么我们召回率比之前低了3个点”直接拿“召回率 低 3个点”去检索什么都查不到。他真正需要的是“RAG评测指标有哪些、召回率下降通常由哪些环节导致”的背景知识。这类query适合做step-back式抽象重写。还有一类隐藏场景是query本身包含术语变体比如用户说“卡顿”文档里写“延迟”和“性能劣化”这种同义改写问题用多查询扩展通常能覆盖。1.3 重写的前提约束别把原意也改没了查询重写不是让LLM自由发挥不加约束就上生产等于往系统里埋雷。我自己定了四条铁律每一句都会写进提示词里。第一保留原始查询中的专有名词、数字、缩写绝不允许替换。“ES”不能改成“搜索引擎”“V2”不能改成“第二版”。第二不添加外部知识重写只能基于query本身和对知识库结构的理解不能擅自塞入query里没有的事实。第三保持问题类型疑问句还是疑问句陈述式需求还是陈述式。第四也是我后来加上的一条如果原始查询已经足够清晰直接原样输出。这条“如果足够清晰就原样输出”特别重要。不加这条约束LLM会把很多本来没问题的query也重写一遍改完反而引入噪声。我在提示词里加了这一句之后负面改写率肉眼可见地降了下来。所谓负面改写就是重写结果比原文更差、检索效果反而下降的情况。别小看这个问题它在真实线上环境里出现的频率远比你想象得高。2. 五种主流查询重写手段的适用边界与提示词设计重写不是只有一种做法。我按自己的项目经验把实践下来真正有效的手段分成五类Multi-Query扩展、子问题分解、Step-Back抽象、HyDE假设文档以及不依赖LLM的轻量规则重写。每一类都有明确的适用场景用错了地方还不如不重写。2.1 Multi-Query扩展一路变多路召回率最先见效Multi-Query是最好上手、收益最稳的一种。思路很简单让LLM基于原始query生成多个不同角度的检索query每路query跑一次召回最后合并。举个例子。用户问“平台支持哪些登录方式”原始query在向量空间里只对应一个点。扩展成“邮箱登录的说明”“手机验证码登录方式”“第三方统一认证登录流程”之后三路query分别落在文档库不同的语义区域召回互补性很强。我自己的测试里很多原本只能命中一两篇相关文档的问题用3路Multi-Query之后可以稳定覆盖到三四篇。提示词我通常这样写请基于用户的原始查询生成3-5个从不同角度表述的搜索查询用于在知识库中检索相关资料。 要求 1. 保留原始查询中的专有名词和实体 2. 每个查询是独立、完整的疑问句 3. 覆盖同义改写、具体化、场景化等不同角度 4. 如果原始查询已经清晰可以只输出原始查询的一个加强版本 5. 直接输出JSON数组不要解释。 原始查询{query}N取多少我的经验是3-5路比较合适。中小规模知识库里5路以上收益明显递减而且多路召回合并时的去重成本会上升。Multi-Query最适合query短、实体抽象、同一个意思有多种说法的场景。如果你的query本身已经是精确的术语或编号比如“KT-2024-0912”这种就别多加精确匹配反而更快更准。2.2 子问题分解多跳问题拆成单跳再合流子问题分解针对的是复合型、多跳型问题。典型例子是“A人员的申请和B人员的申请在同一个流程里为什么状态不一致”直接检索这句话向量检索大概率会召回流程状态流转的通稿但回答不了“为什么不一致”。我的做法是先让LLM判断能否拆分、怎么拆再吐出一个结构化的拆分结果。提示词核心部分判断下面的用户问题是否可以拆分为多个可以独立检索的子问题。 如果可以输出JSON数组每个元素包含 - sub_query独立的检索问句 - reason为什么需要这个信息 如果无法拆分直接输出原始问题。 问题{query}上面那个例子拆出来就是三条A人员申请的流程状态流转规则是什么B人员申请的流程状态流转规则是什么同一流程下不同人员状态不一致通常由哪些原因导致。三路检索分别命中不同文档生成时再按主问题汇总。这里有个工程细节值得注意子问题并行检索通常更快但有些子问题之间存在依赖。比如先要知道“日志文件在哪个目录”才能检索“怎么分析这个目录里的日志”。我的做法是给子问题拆解加一个最大深度限制默认两层如果第一轮检索到的新文档里没有足够信息再基于已有信息补一次子问题检索。拆解不是越细越好拆到单个子问题无法独立检索就该停。2.3 Step-Back抽象先抽象一层再回到细节Step-Back和Multi-Query的区别在于方向。Multi-Query是横向撒网Step-Back是纵向抽象先从具体问题退到一个更一般化的问题检索原理性、概念性文档再回到原始细节问题补一轮精确召回。我遇到的一个真实场景是用户问“为什么我们切换模型之后首字延迟变高了”。直接检索“首字延迟 切换模型”效果很差因为文档里根本没有这么具体的组合描述。Step-Back之后变成“LLM服务延迟评估有哪些指标、模型切换对推理性能的影响因素有哪些”一下子就命中了方法论文档。拿到这些背景之后再拿原始query做第二轮精确检索两轮结果合并给生成答案完整度明显更高。Step-Back的prompt不用太复杂请把下面的具体问题改写成一个更一般化、更抽象的搜索问题 以便在技术知识库中找到相关背景知识。 保持问题的核心主题不变抽象到领域概念或方法论层面。 问题{query}这个策略最适合那种“问得太具体、背后依赖领域概念文档”的场景。但如果你的知识库里只有操作手册、没有原理文档Step-Back收益会比较有限因为它检索的是抽象概念层。另外要注意Step-Back通常不能单独用它产出的抽象query结果更偏背景资料需要再配合原始query的精确检索两轮结果一起喂给生成器才完整。2.4 HyDE用“假设答案”去撞向量相似度HyDE的思路很有意思让LLM不检索就先生成一个假设性答案然后用这个假设答案的embedding去检索相关文档。它跟Multi-Query、Step-Back不一样后两者改的是“问法”HyDE改的是“和检索目标之间的句子形态”。为什么有效用户query往往很短短句的embedding和长文档的embedding天然有形态差。但LLM生成的假设回答是一段完整的、类似文档口吻的文字在向量空间里和真实文档距离更近。等于先把用户意图“翻译”成文档的语言再去做相似度匹配。但必须清晰一点HyDE的假设答案是不可信的内容它只用来做检索的key绝对不能混进生成阶段的上下文。我让LLM按“如果我在文档里看到这个问题的答案内容可能会这样写”的口吻生成一段100-150字的段落然后取它的embedding走一遍召回。实测下来HyDE对概念性、综述性问题效果明显对强实体、强事实型查询反而可能帮倒忙。比如用户问“今年预算审批截止日期”假设答案可能写出一个错误的日期把检索结果带偏。这类查询用轻量关键词扩展更稳。所以在我的方案里HyDE不是默认开启的而是只针对检测到“综述性、比较性、概念解释”意图时才启用。2.5 轻量级重写不调LLM也一样能救召回不是所有场景都值得为查询重写多调一次LLM。数据量大、延迟敏感、离线批处理成本受限的时候规则与NER结合的轻量重写方案同样有效。我能落地的轻量重写包括同义词词典替换比如“卡顿”替换为“延迟 性能问题”语气词清理“咋”“呢”“那个”直接删除实体与缩写标准化把“权限系统V2”这种说法归一成标准写法基于对话历史的指代消解先用规则把“它”“这个功能”替换成前文实体。这套做法的好处是零幻觉、稳定、可控、几乎没有额外延迟缺点是覆盖面窄同义词词典需要长期维护。实际生产里我更推荐“两级重写”第一级用规则先清理和标准化第二级只对规则处理不了的复杂query调用LLM做扩展或分解。这样既控制成本又保留了LLM重写的弹性。很多人一上来就上LLM重写忽略了规则层导致每一条query都多一次模型调用成本上去不说那些本来很清晰的query反而被无谓地改了一遍。3. 重写链路嵌入RAG管线后的工程改造点策略选好后重写不是一个独立模块它会牵动检索、合并、生成、缓存一整条链路。这里说几个我改管线时踩过的坎。3.1 多路召回合并RRF与去重要一起做多查询和子问题分解都会产生多路召回于是合并策略就变成了一个不能绕开的问题。最简单直觉是把各路分数相加排序实测会发现行不通不同路线的分数分布差异巨大embedding的余弦相似度和稀疏检索的score根本不可比直接相加等于默认它们权重相同。我用的是RRFReciprocal Rank Fusion加去重。RRF不看原始分数只看文档在每路结果里的排名def rrf_fusion(rankings, k60): scores {} for ranking in rankings: for rank, doc_id in enumerate(ranking): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) return sorted(scores.items(), keylambda x: x[1], reverseTrue)k取60是常见默认值实验效果也比较稳。RRF天然对“多路里都排得靠前”的文档友好这正是我们想要的结果一个文档如果在三路query里都出现在前10它大概率真的相关。但RRF不去重。多路query本身语义接近召回的文档大量重叠我会先做文本归一化后的精确去重再用embedding相似度做一遍相似去重阈值大概在0.9左右。不然高频重叠内容会挤占上下文窗口生成时全在复读同一段话。合并之后的排序结果还会再进一轮重排模型但那是另一个话题这里不多展开。3.2 重写后的query拿去检索还是也进生成这是我在项目里被反复问到的问题。我的实验结论是重写query应当参与检索但生成阶段要以原始query为主重写query为辅。如果只用重写query做生成输入风险是LLM给出一个改得面目全非的query答案跟着跑偏。我有一次把“怎么申请服务器资源”重写成了“服务器资源申请流程和审批细则”检索没问题但生成回答变成了审批细则科普偏离用户真正想要的操作步骤。这种“检索对了、回答偏了”的case端到端体验非常奇怪。折衷方案是生成提示词里同时放“原始query”和“重写后的query”并明确告诉模型“优先按原始问题回答重写问题仅作为补充上下文”。这样既享受了重写带来的更全召回又不会被重写方向带偏。这个方案在评测里忠实度分数比“只喂重写query”高出一截。3.3 延迟、成本与兜底策略查询重写本质上是多了一次LLM调用延迟和成本必须纳入设计。延迟敏感的线上服务我的做法是“异步重写加兜底”请求进来先用原始query马上跑一版召回同时后台把query送去重写重写结果回来后再做一轮补充召回两轮结果合并。用户首包延迟几乎不增加只有最终答案会因为补召回稍微变好。如果重写环节超时或者失败直接放弃补充召回绝不阻塞主流程。这个兜底逻辑一定要写死否则重写一旦抖动整个RAG服务都会跟着雪崩。成本控制上重写任务比生成简单没必要每次都上大模型。一个专门蒸馏的小模型完全够用我自己用7B量级模型跑重写质量和100B以上模型在生产场景里差异很小主要差距在复杂子问题分解上。另外重写结果可以做缓存相同query或者高相似度query直接命中缓存可以省掉大量重复调用。对于企业知识库这类query分布相对稳定的场景缓存命中率能到三成以上成本优化效果很明显。4. 从指标到上线查询重写的评测方法与排坑心得查询重写很容易陷入“体感有效但说不上来有效在哪”的状态。我后来用了一套两段式评测一段看检索层一段看端到端答案质量两边一起定策略。只做检索层评测容易自嗨只做端到端评测又很难定位问题出在重写还是生成。4.1 检索层指标命中率、Recallk与MRR评测检索层需要先准备一组query和对应的gold文档。从线上日志抽几百条真实query人工标注每条query应该在知识库里命中哪些文档然后分别跑三条链路原始query、重写后query单路、重写后多路召回合并。关注三个指标。命中率看gold文档有没有进top kRecallk看gold文档在top k里的覆盖率MRR看gold文档首次出现的位置它比命中率更能反映排序质量。为了说清楚趋势我放一张自己项目里整理出来的示意数据数字不一定适用于你的场景但趋势是典型的链路/策略命中率Recall3MRR适用query类型原始query直接检索52%0.410.38全量Multi-Query三路扩展68%0.550.47短query、口语化子问题分解76%0.620.53复合/多跳问题Step-Back抽象61%0.480.44概念依赖型问题HyDE假设文档58%0.460.42综述/概念型问题关键是要分场景看而不是只看整体均值。整体均值会掩盖“这个策略只对某类query有效”的事实。我建议按query类型分桶统计否则你会在一个不合适的桶里得出错误结论。比如子问题分解在全量query上平均提升有限但在复合问题子集上优势拉开得非常大。4.2 端到端评测答案质量才是最终裁判检索指标好不代表最终答案好。我见过命中率从50%拉到70%、但用户主观回答满意度反而下降的情况原因是多路召回引入了大量相关但不关键的片段生成模型被碎片信息带偏。所以端到端评测不可省。我从业务侧收集了一条200条的评估集每条包含query、gold答案和“策略标签”比如这个query适合Multi-Query还是适合Step-Back。评测时用LLM裁判按相关性、忠实度、完整性三项打分每批再抽20%做人工复核防止LLM裁判自带偏好。LLM裁判的绝对分数参考价值有限但同一批数据跑不同链路之后的相对差异可信度还是够的。这里必须提醒一点单独看“用重写平均分更高”不够我会额外看“重写后分数下降的query”有多少也就是负面率。正向率和负面率一起看才公平。如果一个策略把某些query的分从9拉到10同时把另外一些从7拉到4那负面样本里的问题必须优先修。与其追求平均分最高不如追求“没有明显变差的case”。4.3 上线后被现实教育出来的三个坑最后说三个我实际踩过、修复成本都不低的坑希望你能绕开。第一个坑是实体丢失。我在重写prompt里没写“不得替换专有名词和缩写”结果LLM把“ES”改写成“搜索引擎”把“Grafana”改写成“监控面板”检索结果全部偏到通用文档上。修复方案就是我在1.3节写的四条铁律把它写死在prompt顶层。这个坑之所以隐蔽是因为单看重写后的query每一句都通顺合理只有对照检索结果才发现路径全歪了。第二个坑是重复片段挤占上下文。多路召回合并之后忘了去重top 5里出现三篇内容几乎一样的文档整个上下文窗口有一半在复读。修复方案是RRF排序之前先做一轮精确去重和相似去重。这个坑在Multi-Query场景里特别容易出因为三路query本身讲的是同一件事的不同说法。第三个坑是子问题分解完成后丢失问题间关联。子问题各自检索都很精准但生成阶段模型只看到三堆子答案回答逻辑是断的。修复方案是生成提示词中同时重述完整主问题然后把子问题之间的逻辑关系按顺序写清楚让生成器知道自己在回答一个整体性问题。这个问题我一开始没意识到直到有用户反馈“你们回答里每个部分都对但连起来不像在回答我的问题”才回头去看生成链路。另外一个小习惯上线后持续抽检线上query的重写结果看有没有出现“把query改得更糟”的case。这类case攒下来反向补充到prompt约束或规则层里重写效果会越滚越好。我每个月会导出一次重写日志随机抽200条人工过一遍成本不高但能拦住好多模型升级后突然冒出来的行为漂移。最后留一个我沿用了很久的小习惯重写prompt里永远放一句“如果原始查询已经足够清晰请原样输出”。这一句让负面改写率掉了不少算是全篇里性价比最高的一行字。查询重写不是一次性做完就结束的组件它的策略组合会跟着知识库内容、用户query分布的变化而漂移。我目前的存量配置是规则清理打底Multi-Query做通用扩展复杂query走子问题分解概念依赖型query补一次Step-BackHyDE只在综述型场景下单独开启。如果你也想给RAG提上限我建议别一上来就铺开所有策略先跑通Multi-Query加RRF加去重这一组最小闭环拿线上真实query对比命中率。确认有增益之后再逐步加入别的策略——每一步都能单独回滚才不会改乱了没法收场。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表