资讯动态

RAG中段演进:从能查到查得准的检索优化实战

发布时间:2026/10/13 13:03:14 来源:尧图企业网站定制
1. 从“能查到”到“查得准”RAG 第二阶段到底在解决什么如果你已经动手搭过最基础的 RAG 流程大概率经历过这样一个阶段文档切好了向量库也灌进去了问一个问题系统确实能吐出点相关内容但仔细一看要么答非所问要么把三段不相干的文字拼在一起要么明明知识库里有正确答案检索出来的却是隔壁那份文档里的相似句子。这个阶段我称之为“能查到但查不准”而 RAG 技术演进的中段核心任务就是解决这个问题。基础 RAG 的链路非常直白把文档按固定长度切块用嵌入模型把每块转成向量存进向量数据库用户提问时把问题也转成向量然后做最近邻搜索取回 Top-K 个片段塞进大模型的上下文里让它生成答案。这条链路能跑通但它有一个隐含假设语义相似度等于内容相关性。实际用下来你会发现这个假设在很多场景下根本不成立。举个我实际遇到的例子。当时我在做一个内部技术文档的问答系统知识库里同时存在“接口鉴权流程”和“接口限流策略”两份文档。用户问“鉴权失败会怎样”基础 RAG 检索回来的片段里有一半是限流相关的因为这两份文档都大量出现“接口”“请求”“失败”“返回码”这些词向量空间里它们离得很近。但用户要的是鉴权失败后的错误码和处理建议不是限流触发后的降级方案。这就是典型的“语义相近但意图不匹配”。中段演进要解决的核心矛盾可以归纳为三个层面。第一是检索粒度的矛盾切得太粗一个片段里混了多个主题向量表示被平均化什么都像又什么都不精切得太细片段丢失上下文检索回来一堆碎片大模型拼不出完整答案。第二是查询与文档的表达鸿沟用户用口语提问文档用书面语书写两者在向量空间里的分布天然有偏差。第三是单次检索的局限性一次 Top-K 检索只能拿到一个视角的结果无法覆盖复杂问题的多个子需求。我个人的判断是RAG 中段的所有技术手段本质上都在做一件事在“检索”和“生成”之间插入更多的判断和转换步骤让传给大模型的内容不是“最相似的”而是“最相关的”。这个思路的转变是从“向量搜索工程师”思维转向“信息检索系统”思维的关键一步。接下来我会把中段演进中最核心的几类技术拆开讲包括它们各自解决什么问题、怎么落地、以及我在实操中踩过的坑。2. 切块策略的精细化固定窗口为什么不够用2.1 固定长度切块的三个致命伤最基础的切块方式是按字符数或 token 数做固定窗口滑动比如每 500 个 token 切一块相邻块之间重叠 50 个 token。这个方法实现简单在早期 Demo 里够用但一旦知识库稍微复杂一点问题就暴露了。第一个致命伤是语义截断。一个完整的操作步骤可能横跨 800 个 token固定切块会在中间一刀切下去前半段在块 A后半段在块 B。用户问“第三步怎么做”检索到块 A里面只有前两步和第三步的开头大模型拿到这个片段要么编一个第三步要么告诉用户信息不完整。我见过最离谱的情况是一个表格被从中间切开表头在上一块数据在下一块检索回来只有数据没有表头大模型完全不知道每列代表什么。第二个致命伤是主题混杂。一份文档里相邻的两个段落可能讲的是完全不同的东西固定切块把它们塞进同一个向量这个向量的语义表示就被稀释了。比如一份产品需求文档上一段讲用户登录流程下一段讲数据导出格式切在同一块里向量既像登录又像导出检索时两边都能匹配上一点但两边都不精确。第三个致命伤是层级信息丢失。很多文档有标题层级结构H1 下面是 H2H2 下面是正文。固定切块完全无视这个结构切出来的片段脱离了标题语境。用户问“缓存配置里的过期时间怎么设”检索到一个片段写着“默认 3600 秒”但不知道这是哪个模块的缓存配置大模型只能猜。2.2 基于文档结构的切块方案解决固定切块问题最直接的办法是利用文档自身的结构信息。Markdown、HTML、Word 这些格式都有明确的标题层级和段落边界按结构切块比按长度切块合理得多。具体做法是先解析文档的标题树把每个叶子节点下的内容作为一个基础块。如果某个叶子节点内容太长再在内部按段落切分如果太短就向上合并到父节点。这样切出来的每个块都自带标题路径比如“产品文档 用户模块 登录流程 鉴权失败处理”这个路径本身就是极强的上下文信息。我在实操中会把这个标题路径拼接到块内容的前面一起做向量化。这样即使用户的提问没有提到“登录流程”只要提到了“鉴权失败”向量匹配时标题路径里的关键词也能贡献相似度。实测下来这种做法的检索准确率比纯固定切块高出不少尤其是在文档结构清晰的场景下。但这里有个坑需要注意标题路径不能太长。如果文档层级很深拼出来的路径可能有五六层把块内容本身淹没了。我的经验是路径控制在三层以内超过三层的部分做截断只保留最靠近叶子节点的两层。2.3 语义切块用模型判断句子边界当文档没有清晰结构或者结构混乱时语义切块是另一个选择。它的思路是不按固定长度切而是按语义连贯性切。具体实现方式有多种我常用的是基于句子嵌入的相似度突变检测。做法是先把文档拆成句子然后计算相邻句子的嵌入向量相似度。如果两个相邻句子的相似度低于某个阈值说明这里发生了主题转换就在此处切一刀。阈值需要根据文档类型调技术文档通常主题转换比较明显阈值可以设高一点叙事类文档主题渐变较多阈值要设低一点。这个方法的优点是切出来的块语义内聚性好缺点是计算量大而且阈值不好定。我试过在一个混合了技术文档和会议纪要的知识库上用同一个阈值结果技术文档切得太碎会议纪要切得太粗。后来改成按文档类型分别设阈值效果才稳定下来。还有一种更轻量的做法是利用文本里的自然边界标记比如空行、分隔线、列表项之间的换行。这些标记在写作时本身就代表了作者的语义分段意图直接拿来用比模型判断更可靠。我在处理 Markdown 文档时优先按空行和标题切只有在连续大段文字没有自然边界时才启用语义切块作为兜底。2.4 小块检索、大块生成父子块策略切块粒度有一个根本矛盾检索时希望块小一点这样向量表示更精确生成时希望块大一点这样上下文更完整。父子块策略就是用来调和这个矛盾的。具体做法是把文档切成两层父块较大比如 2000 token子块较小比如 300 token子块从父块中切分而来。检索时用子块做向量匹配命中后返回对应的父块给大模型。这样检索精度由子块保证生成质量由父块保证。我在一个法律文档问答项目里用过这个策略。法律条文经常是一条大款下面有若干小项用户问的是某个小项的具体规定但大模型需要看到整个条款才能准确理解。用父子块之后检索命中率明显提升大模型也不再断章取义了。实现上需要注意一点子块和父块的映射关系要存好检索到子块后要能快速找到父块。我一般会在向量库里存子块向量同时用另一个键值存储维护子块 ID 到父块内容的映射。查询时先向量检索拿到子块 ID再查映射拿父块内容两步操作延迟增加不多但效果提升明显。3. 查询侧的改造让用户的问题更适合被检索3.1 查询改写把口语问题翻译成文档语言用户提问的方式和文档书写的方式往往差距很大。用户问“为啥我登录不上去”文档里写的是“鉴权失败错误码及处理方案”。这两句话在向量空间里的距离可能很远直接检索效果很差。查询改写的目标就是把用户的口语问题转换成更接近文档语言的表达。最简单的改写是关键词提取。把用户问题里的核心名词和动词抽出来去掉“为啥”“怎么”“能不能”这些口语化修饰用提取出的关键词去做检索。这个方法实现简单对短问题效果不错但对复杂问题会丢失意图信息。更进一步的做法是用大模型做查询改写。给大模型一个提示让它把用户问题改写成多个不同角度的查询语句。比如用户问“登录不上怎么办”大模型可能改写出“登录失败错误码”“鉴权异常处理”“用户认证失败原因”等多个查询。然后对这些查询分别检索合并结果。这个做法叫多查询检索能显著提升召回率。我实测下来多查询检索的召回率比单查询高出不少但也不是没有代价。每个查询都要做一次向量检索延迟会线性增加。我的折中方案是先用原始查询检索一次如果 Top-K 结果的相似度分数都低于某个阈值说明原始查询效果不好再触发多查询改写。这样大部分简单问题走快速路径只有难问题才走慢速路径。3.2 HyDE用假设答案来检索HyDE 是一个反直觉但很有效的思路。它的做法是不让用户问题直接去检索而是先让大模型根据用户问题生成一个假设性的答案然后用这个假设答案去做向量检索。为什么这样有效因为假设答案的文本形态更接近文档它包含了文档里可能出现的术语和表达方式。用户问“登录不上怎么办”大模型生成的假设答案是“登录失败通常由鉴权令牌过期或凭证错误导致需要检查令牌有效期并重新获取”这段话和文档里的“鉴权失败处理方案”在向量空间里距离很近检索效果自然好。HyDE 的代价是每次查询都要调用一次大模型生成假设答案延迟和成本都会增加。而且如果大模型对领域不熟悉生成的假设答案可能偏离实际文档内容反而引入噪声。我的经验是HyDE 适合领域知识比较通用、大模型有一定了解的场合如果是非常垂直的专业领域大模型生成的假设答案可能不靠谱这时候不如用查询改写。3.3 查询分解复杂问题拆成子问题有些用户问题本身就是复合的比如“对比一下方案 A 和方案 B 在性能和成本上的差异”。这个问题包含四个子需求方案 A 的性能、方案 A 的成本、方案 B 的性能、方案 B 的成本。直接用整句去检索很可能只召回其中一两个方面的内容。查询分解的做法是用大模型把复合问题拆成多个子问题每个子问题单独检索最后把检索结果合并。这样每个子问题都能拿到针对性的内容生成答案时信息更完整。我在做竞品分析类问答时经常用这个策略。用户问“X 产品和 Y 产品哪个更适合小团队”拆成“X 产品小团队适用性”“Y 产品小团队适用性”“X 产品定价”“Y 产品定价”等子问题分别检索后再让大模型综合判断。效果比直接检索整句好很多。但查询分解也有边界。如果拆得太细子问题之间失去关联生成答案时可能拼凑感很强。我的做法是拆到两到三层为止每个子问题保持一定的独立性同时在生成阶段把原始问题也传给大模型让它知道这些子问题是为了回答什么。4. 检索后的精排与过滤Top-K 不是终点4.1 为什么向量相似度排序不够可靠向量检索返回的 Top-K 结果是按向量相似度排序的。但向量相似度高不等于内容相关。我见过太多案例排第一的片段和问题只是用词相似实际内容完全不相关排第五的片段才是真正有用的但因为相似度分数低被淹没了。造成这个问题的原因有几个。一是嵌入模型的训练目标和检索任务不完全一致它优化的是通用语义相似度不是问答相关性。二是向量表示会丢失细粒度的信息比如否定、条件、时间限定这些在向量空间里可能被平滑掉。三是用户问题的意图往往比字面意思复杂向量只能捕捉字面语义。所以 Top-K 检索之后需要一个精排步骤用更精细的模型对候选片段重新打分排序。这个步骤叫重排序是中段 RAG 里性价比最高的优化手段之一。4.2 交叉编码器重排序的落地细节重排序最常用的方案是交叉编码器。和双编码器不同交叉编码器把查询和文档拼在一起输入模型直接输出一个相关性分数。因为它能看到查询和文档之间的细粒度交互所以判断更准确。代价是计算量大不能像双编码器那样预计算文档向量只能在线计算。落地时的标准流程是向量检索先召回较多候选比如 50 个然后用交叉编码器对这 50 个逐一打分取分数最高的 5 到 10 个传给大模型。这样既保证了召回率又保证了精度。我实操中会注意几个点。第一召回数量要够50 是起步如果知识库很大、问题很复杂可以放到 100。第二重排序模型的选择很关键通用模型在垂直领域可能表现一般有条件的话用领域数据微调一下效果提升明显。第三重排序的延迟要算进整体响应时间里如果对延迟敏感可以用轻量级重排序模型或者只对 Top-20 做重排。还有一个容易被忽略的点重排序之后传给大模型的片段顺序也要调整。我一般把重排序分数最高的放在最前面因为大模型对上下文开头的内容注意力更集中。这个细节对最终答案质量有影响但很多人不注意。4.3 基于规则的过滤处理元数据和时间向量检索和重排序都是基于内容的但很多场景下元数据过滤比内容匹配更有效。比如用户问“最近三个月的销售数据”如果知识库里的文档都有时间戳元数据直接按时间过滤比向量检索靠谱得多。我在实操中会尽量给每个块打上丰富的元数据文档来源、创建时间、更新时间、文档类型、所属部门、权限标签等。检索时先用元数据做粗筛再做向量检索。这样既能缩小检索范围提升速度又能保证结果符合用户的硬性条件。元数据过滤的坑在于过滤条件太严会导致召回不足太松又起不到筛选作用。我的经验是把过滤条件分成硬性和软性两类。硬性条件是用户明确要求的比如时间范围、文档类型必须严格过滤软性条件是推测的比如用户问技术问题就优先检索技术文档这种可以作为加权项而不是过滤项。4.4 去重与多样性避免信息冗余Top-K 检索还有一个常见问题返回的片段高度重复。因为向量空间里相近的内容会聚在一起Top-10 里可能有 6 个片段都在说同一件事只是措辞略有不同。这样传给大模型既浪费上下文窗口又可能导致大模型过度关注某一个点而忽略其他方面。解决方法是做去重和多样性控制。去重比较简单计算片段之间的相似度超过阈值的只保留一个。多样性控制稍微复杂一点常用的是 MMR 算法它在选择下一个片段时既考虑与查询的相关性又考虑与已选片段的差异性避免选出一堆雷同内容。我在做多文档问答时特别依赖多样性控制。用户问“这个问题的不同解决方案有哪些”如果检索回来的都是同一种方案的变体大模型就答不出多样性。用 MMR 之后检索结果能覆盖不同文档、不同角度的内容生成答案的全面性明显提升。5. 嵌入模型的选型与微调检索质量的地基5.1 通用嵌入模型在垂直领域的局限嵌入模型是 RAG 检索质量的地基。地基不稳后面的重排序、查询改写都是修补。我见过很多项目检索效果不好第一反应是换向量数据库、调 Top-K 参数折腾一圈发现是嵌入模型不行。通用嵌入模型是在大规模通用语料上训练的对日常语言理解很好但到了垂直领域就力不从心。比如医疗领域的“心梗”和“心肌梗死”是同一个意思但通用模型可能认为它们相似度不高法律领域的“不可抗力”有特定含义通用模型可能把它和“意外事件”混为一谈。我做过一个对比测试在同一个技术文档知识库上用通用嵌入模型和领域微调后的模型分别做检索Top-5 命中率差了将近一倍。这个差距在简单问题上不明显但一到专业术语密集的问题上就暴露无遗。5.2 微调嵌入模型的实操路径微调嵌入模型没有想象中那么难关键是构造训练数据。最常用的数据格式是三元组一个查询、一个正例文档、一个负例文档。训练目标是让查询和正例的向量距离更近和负例的距离更远。构造三元组数据有几种方式。如果知识库里有现成的问答对可以直接用问题作为查询对应答案所在的文档块作为正例随机采样的其他块作为负例。如果没有问答对可以用大模型生成合成数据给大模型一段文档让它生成几个可能的问题然后用这些问题和文档块组成正例对。负例的采样很关键。随机负例太简单模型学不到东西需要加入一些“困难负例”也就是和查询语义相近但实际不相关的文档块。困难负例可以用基础检索模型先召回一批然后人工或模型判断哪些是真正不相关的挑出来作为负例。我微调嵌入模型时一般用几千到一万条三元组就能看到明显效果。训练时注意不要过拟合在验证集上监控检索指标一旦指标不再提升就停。微调后的模型要重新灌库因为向量空间变了之前存的向量不能用了。5.3 多向量与混合检索的取舍除了微调还有一种提升检索质量的思路是混合检索同时用稠密向量和稀疏向量比如 BM25做检索然后合并结果。稠密向量擅长语义匹配稀疏向量擅长关键词匹配两者互补。我在实操中发现混合检索对专有名词、型号、代码这类内容的检索效果提升特别明显。因为这些词在稠密向量空间里可能表示不好但稀疏向量能精确匹配。合并时可以用加权求和也可以用一个重排序模型统一打分。多向量是另一个方向指的是给同一个文档块生成多个向量比如用不同嵌入模型生成或者对文档块做不同摘要后生成。检索时多个向量分别匹配取并集或加权。这个做法能提升召回率但存储和计算成本翻倍需要权衡。我的建议是如果知识库以自然语言为主稠密向量加微调就够了如果知识库里有大量专有名词、代码、型号优先上混合检索多向量适合对召回率要求极高、且资源充足的场景。6. 中段演进的工程化代价与我的取舍经验6.1 延迟、成本与效果的三角权衡RAG 中段的每一项优化都不是免费的。查询改写要多调一次大模型HyDE 要多调一次大模型重排序要多跑一个模型混合检索要多做一次检索。这些加起来端到端延迟可能从基础 RAG 的 1 秒涨到 5 秒甚至更多成本也成倍增加。我在实际项目里的取舍原则是先定位瓶颈再针对性优化。如果检索召回不够优先做查询改写和混合检索如果检索回来了但排序不对优先做重排序如果答案不完整优先做查询分解和父子块。不要一上来就把所有技术都堆上去那样既浪费资源又难以定位问题。还有一个经验是把优化分成离线和在线两部分。嵌入模型微调、文档切块优化、元数据标注这些是离线做的不影响在线延迟查询改写、重排序、混合检索是在线做的直接影响响应时间。离线优化优先做在线优化按需做。6.2 评估体系的建立没有度量就没有优化中段演进最容易犯的错误是凭感觉优化。改了一个参数觉得答案好像好了一点但到底好了多少、有没有副作用说不清楚。所以建立评估体系是前提。我一般会构造一个评估集包含几十到几百个问题每个问题标注好正确答案所在的文档块。评估指标用召回率正确答案有没有被检索到和准确率检索到的内容有多少是相关的。每次改动后跑一遍评估集看指标变化。评估集的构造也有讲究。问题要覆盖不同类型简单事实型、复杂推理型、多文档综合型、否定条件型等。只有覆盖全面才能发现优化带来的副作用。比如查询改写可能提升了复杂问题的召回但让简单问题的延迟增加了如果没有分类评估就发现不了。6.3 什么阶段该上什么技术一个务实的路线图根据我的经验RAG 中段的落地可以分三步走。第一步先把切块和元数据做好。按文档结构切块给每个块打上来源、时间、类型等元数据。这一步不需要额外模型纯工程活但对检索质量的提升立竿见影。很多项目跳过这一步直接上重排序效果提升有限因为地基没打好。第二步上重排序和混合检索。这两个是性价比最高的在线优化实现相对简单效果提升明显。重排序解决排序不准的问题混合检索解决专有名词匹配的问题。这两个做完大部分场景的检索质量已经够用了。第三步按需上查询改写、HyDE、查询分解。这些技术针对的是特定类型的问题不是所有场景都需要。如果评估发现复杂问题召回不足再上查询分解如果发现口语化问题检索效果差再上查询改写。不要为了技术而技术。嵌入模型微调可以贯穿始终。如果发现通用模型在领域术语上表现不好尽早微调因为微调后的向量空间会影响后续所有优化步骤的效果。这套路线图不是绝对的但核心思路是先做确定性高、副作用小的优化再做复杂、有副作用的优化。每一步都用评估数据说话不凭感觉决策。

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

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

免费获取报价 →
↑