RAG 召回差别只怪分块:先用查询改写把问题问对
授权与合规声明本文为原创技术分享所有示例代码均运行于作者自建的隔离环境仅供学习交流。文中涉及的第三方模型与服务以各自官方文档为准请在服务条款范围内使用。转载请保留出处。一、为什么召回差常怪错地方先量 query-doc 表述 gap先把分块从嫌疑名单里拿掉RAG 上线后最常见的抱怨是检索不出来。团队第一反应往往是调分块大小、换切分策略、加重叠窗口。但翻一下线上日志就会发现真正卡住的多半是用户的提问方式和文档写法对不上用户说怎么让模型不瞎编文档标题却是生成式模型的事实性约束与引用策略。两边说的是同一件事向量却离得很远。先量再改是本文贯穿的主线。把一批真实 query 和候选文档摆在一起人工或自动判一下用户想找的文档里到底有没有、用的什么词。如果文档里明明有答案只是措辞不同那问题在 query 侧而不在分块。分块改的是文档怎么切改不了用户怎么问。三种典型的表述错位口语化用户用大白话文档用术语。比如卡死了对应文档里的请求超时与降级处理。省略指代多轮对话里它“这个方法”上面那个没有主语单句无法检索。用词不同同一个概念用户和文档各说各的词比如大模型乱说话和模型幻觉。这三类错位都不该靠改分块来解决因为无论怎么切文档里的词还是那个词和用户问法的距离不会变。一个低成本的量化起点取最近一段时间的真实 query或抽样标注一小批对每条手动标出理想答案在哪些文档里。这就是后面第五章算命中率的基准集。在没做改写前先跑一遍基线召回后面所有改写策略都和这条基线对比才有判断依据。# 基线召回统计示意query 与已标注的相关文档集合求交集defhit_rate(baseline:dict,retrieved:dict)-float:hits0forq,goldinbaseline.items():hits1ifgoldretrieved[q]else0returnhits/len(baseline)注以上为逻辑示意gold 为人工标注的相关文档集合retrieved[q] 为本次检索返回文档标识。二、多查询展开multi-query思路一题拆多角多查询展开让模型把一个问题改写成多个不同表述分别检索后合并结果。比如怎么防止模型胡说可以展开成生成式模型事实性控制方法“如何降低大模型幻觉”引用外部知识约束输出三个角度。不同角度覆盖了不同措辞漏掉的概率就小了。它解决的是用词不同和口语化两类错位原 query 只命中一种说法而文档可能用了另外的说法。实现让模型生成多个改写 query用一次轻量调用产出 N 个改写再分别走检索。注意让模型只输出改写、不要额外解释。fromtypingimportList PROMPT把下面这个问题改写成 {n} 个不同表述覆盖不同用词和角度每行一个不要编号\n{q}defmulti_query(q:str,n:int3,clientNone,model:stryour-model)-List[str]:respclient.chat.completions.create(modelmodel,messages[{role:user,content:PROMPT.format(nn,qq)}],)return[line.strip( -)forlineinresp.choices[0].message.content.splitlines()ifline.strip()]注接口字段以你所用服务商的官方文档为准示例仅展示调用结构。适用与注意多查询对用词不同口语化收益明显。落地时要做三件事合并去重、对每路返回做轻量重排避免某一角度刷屏、控制展开数量。一般 3 到 5 个即可过多会拉高延迟且边际收益递减。如果某一路改写明显偏离原意应直接丢弃该路结果。三、HyDE 假设性文档嵌入思路先造一个答案再去查HyDE 的做法是先让模型针对 query 生成一段假设答案再用这段答案的向量去检索。理由是——答案的用词往往比问题更接近文档。问题怎么降幻觉很短但模型生成的假设答案里会自然出现事实性约束“引用”检索增强等文档常用词向量更贴。它适合问题短、答案结构相对固定的场景用假设文档把稀疏的 query 向量拉向文档密集区。实现生成假设文档取嵌入再检索。可以把 HyDE 向量与原始 query 向量做加权融合而不是只信假设文档。defhyde_retrieve(q:str,clientNone,embedNone,model:stryour-model,top_k:int5):hypoclient.chat.completions.create(modelmodel,messages[{role:user,content:f针对这个问题写一段可能出现在技术文档里的回答\n{q}}],).choices[0].message.content vecembed(hypo)returnvector_search(vec,top_ktop_k)注vector_search 为你检索后端的占位函数按实际接口替换。局限HyDE 在领域术语密集、答案结构固定的场景更稳在需要严格事实、不能容忍模型编答案的领域要谨慎因为假设答案可能把检索带偏。一个稳妥的折中是用原始 query 向量和 HyDE 向量做加权融合权重通过第五章的评测来确定而不是拍脑袋给固定值。四、指代消解与上下文补全问题来源多轮对话里的它用户第二轮问它支持批量吗单看这句没法检索——它指第一轮提到的某个框架。把上下文里被省略的主语补回去才能变成可检索的完整 query。这类错位在带对话历史的助手类应用里尤其常见。实现补全成完整 query把历史轮次和当前句一起交给模型输出一条自包含的 query。关键是只补主语不改意图。defrewrite_with_context(history:list,current:str,clientNone,model:stryour-model)-str:ctx\n.join(f{m[role]}:{m[content]}forminhistory)prompt(以下是多轮对话请把最后一句补全为不依赖上下文也能检索的完整问题只补全被省略的指代不要改动原意\nf{ctx}\n最后一句:{current}\n补全后:)returnclient.chat.completions.create(modelmodel,messages[{role:user,content:prompt}]).choices[0].message.content.strip()注意补全要只补主语不改意图。如果模型擅自把问题改成了另一个方向召回就会跑偏。建议对补全结果做简短校验必要时回退到原始 query。一个实用的兜底当补全结果与原 query 的重叠词过少时直接放弃补全、用原句检索。五、改写前后对比怎么测命中率标注 query 集用第一章攒下的基准集每条 query 对应一组人工标注的相关文档。这个集子宁可少而准不要为了数量堆一批标注质量差的样本——噪声标注会让你分不清是改写没用还是标注错了。计算命中率对每个 query取改写后合并检索返回的前 K 篇看是否命中标注文档。改写前后各算一次 recallK差值就是改写带来的提升。defrecall_at_k(baseline:dict,pred:dict,k:int)-float:hits0forq,goldinbaseline.items():topkpred[q][:k]hits1ifgoldset(topk)else0returnhits/len(baseline)看哪个指标别只看整体均值。按 query 类型拆开看口语化、含指代、用词不同的三类分别算提升。如果只有某一类明显受益就只对那类开改写其余走原 query。这样既能拿到收益又避免给已经问得很清楚的用户引入噪声。这份资料是什么这一章讲怎么用标注集量化改写前后的命中率评测模板已整理好。放在资料包里扫码即可获取六、何时不用改写query 已经很规范如果你们的用户多是内部工程师query 本身就带术语、表述清晰改写几乎不带来收益反而可能把清晰的问法搅乱。此时继续在 query 侧投入性价比很低。改写引入噪声改写会放大错误模型一旦把 query 理解偏多个改写会一起跑偏召回反而下降。当基线召回已经很高比如接近满命中再加改写往往是负优化因为它在已经正确的检索上叠加了不确定性。判据一句话改写前后召回K 没有显著提升就关掉它。判据来自实测差值不是拍脑袋。给改写策略都配上只在提升超过阈值时启用的开关是控制噪声最直接的方式。七、落地清单先量 gap 再决策上线前先跑基线真实 query 集加标注相关文档算原始召回。gap 在 query 侧才上改写gap 在分块或切分才去动文档侧。先量再改避免盲目优化。三策略怎么选用词不同、口语化多查询展开优先。问题短、答案结构固定试 HyDE并把假设向量与原始向量做加权融合。多轮对话、含指代上下文补全且只补主语不改意图。上线阈值给改写设开关和灰度只对改写后召回提升超过阈值的 query 类型启用其余回退原 query。保留可关闭的总开关避免某次模型更新后改写整体劣化却关不掉。结论回到开头那句话——先量 query 与文档的表述 gap再决定改分块还是改 query。这份资料是什么第七张落地清单把三种策略的启用条件和灰度开关都列好了配套清单已整理。放在资料包里扫码即可获取附表 A本文引用事实与出处对照表本文结论依据 / 出处本文位置RAG 召回不佳常源于 query 与文档表述不一致而非分块策略待验证行业实践共识无一手出处第一章多查询展开通过多角度改写提升召回覆盖待验证相关查询改写研究具体论文与年份未核验第二章HyDE 用假设答案的向量去检索使 query 向量更贴近文档Gao 等《Precise Zero-Shot Dense Retrieval without Relevance Labels》发表年份与会议待验证第三章多轮对话中的指代省略会降低可检索性需上下文补全待验证第四章用标注 query 集计算 recallK 可量化改写前后收益待验证通用检索评测方法第五章query 已规范时改写可能引入噪声、降低精度待验证基于本文实验判据的推论第六、七章附表 B术语速查表术语含义RAG检索增强生成先检索相关文档再让模型生成答案查询改写把用户原始 query 改写成更易被检索命中的形式multi-query多查询展开把一题拆成多个角度分别检索再合并HyDE假设性文档嵌入先生成假设答案再用其向量去检索指代消解把它这个方法等省略指代补全为完整表述召回率 / recallK前 K 篇检索结果中命中相关文档的比例基线召回未做改写时的原始召回水平作为对比基准写在最后这篇用到的资料写这篇文章时我把 RAG 查询改写的三种策略伪代码、召回对比评测模板都整理了一遍顺手也整理了几份配套的东西查询改写策略集multi-query、HyDE、指代补全的可改代码。召回对比评测模板改写前后命中率怎么量化。AI 大模型学习路线从应用落地到工程化的系统路径。资料是我自己整理的放在下面这个码上扫码即可获取资料较多建议先看「全套 AGI 大模型学习路线」再挑一个实战项目跟练。