ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

重排、压缩、校正:构建RAG后处理链路,让检索结果真正可用

重排、压缩、校正:构建RAG后处理链路,让检索结果真正可用

先把话说在前头:检索结果“看起来相关”和“真正能用”,中间隔着一条很宽的沟。我做过不少知识库问答和搜索排序相关的项目,最深的体感是——召回阶段再努力,给大模型或用户的那几段文本只要夹着噪声、落着错位、带着冗余,后面所有环节都会被拖垮。重排、压缩、校正这三板斧,干的就是“让检索结果从能用变成好用”的脏活累活,也是决定检索增强系统上线后是惊艳还是翻车的关键分水岭。

这篇文章不聊虚的,我会把这三步拆开讲透:每一步解决什么问题、背后的原理是什么、实操时怎么配置、有哪些文档里不会写的坑。我已经在这类链路上踩过不少次雷,有些经验是用线上事故换来的,希望能帮你少走点弯路。

1. 为什么检索完不能直接拿去用:先看懂“初排”的局限

先说一个可能有些反直觉的结论:大多数检索系统返回的前几屏结果,真正直接可用的比例往往不到一半。这不是某个具体引擎的锅,而是“召回—排序”这套经典架构天然留下的空间。

1.1 召回阶段的目标本来就只是“别漏掉”

无论是基于稀疏词项的倒排索引,还是基于稠密向量的语义检索,第一轮召回的使命都很纯粹:在尽量大的候选池里把可能相关的内容捞出来,宁滥勿缺。这个阶段打出来的分数,本质上是“粗略的相关性估计”,并不擅长做精细的质量判断。

比如传统倒排索引计算的相关性,主要看词频、逆文档频率、文档长度这些统计量。一个文档里反复出现“苹果”这个词,搜索引擎会很诚实地给它高分,但它可能只是在讲水果种植,跟用户想找的“苹果公司财报”完全不搭。这不算bug,而是统计方法本身就不理解语义。

稠密向量检索看起来高级一些,通过双塔编码把文本映射到语义空间,用向量距离衡量相似度。但它也有自己的盲区:同一个词在不同语境下的含义差异、否定表达、实体之间的复杂关系,向量空间未必能精细刻画。我实测过一个案例,用向量检索找“不含酒精的护肤品”,返回结果里居然有大量主打酒精成分的产品,因为“酒”相关向量在空间上离得太近了。

1.2 第一轮排序的分数不能直接信任

初排分数还有一个很微妙的问题:不同来源的分数不具备可比性。现在很多系统走的是混合召回,倒排索引和向量索引各回一堆结果,然后合并排序。问题来了,这两个分数一个基于统计分布,一个基于几何距离,量纲完全不同,直接相加或者比较等于把苹果和橘子放在一起称重。

更麻烦的是,初排分数往往存在“局部拥挤”现象。尤其在向量检索里,大量相似文档的分数会挤在一个很窄的区间里,彼此之间只差小数点后三位的距离。此时top1和top10的分数几乎没差别,但内容质量可能天差地别。要是直接把这个排序结果喂给下游,等于把排序的决策权交给了噪声。

1.3 “可用”的标准跟“相关”不一样

我觉得要理解后处理的必要性,最核心的是区分两个概念:相关性和可用性。相关性回答的是“这个话题是否有关”,可用性回答的是“这段话能否直接支撑回答”。知识库问答场景里,检索出来的段落可能相关,但信息已经过时;或者内容自相矛盾;或者关键结论藏在第三段里,前面全是背景铺垫;又或者同一页里混杂了关于多个产品的描述,难以直接定位。

这些情形下,检索结果都“相关”,但都不“可用”。重排解决“排序不够准”的问题,压缩解决“篇幅和信息密度不匹配”的问题,校正解决“内容质量和一致性存疑”的问题。三者的共同指向,是把检索结果加工成可以直接支撑下游使用的形态。

2. 重排:让真正有用的内容浮到最前面

重排是后处理链路里投入产出比最高的一步,也是大多数人最先接触的一步。它的目标很明确:用更精细的模型,把召回阶段粗排出来的候选集重新排一遍,让真正高质量、高相关的内容进入视野。

2.1 双塔编码器再牛,也干不过交叉编码器

既然召回阶段已经用双塔编码器(把查询和文档分别编码成向量,再算相似度)做过粗排了,为什么重排还要再跑一遍模型?因为双塔编码器为了性能,牺牲了查询和文档之间的深度交互。

我打个比方:双塔编码器像两个人分别看完资料,然后各自写摘要,最后再比对摘要的相似度;交叉编码器则像两个人坐在一起,逐句对照讨论,任何一点互动的细节都不会漏掉。后者的精准度自然要高一个层级,代价是计算量更大,所以不适合对全量候选执行,只能作为重排阶段针对top-k结果的精排手段。

交叉编码器的标准做法是把查询和文档拼成一对文本,一起送进编码器,直接输出一个相关性分数。因为模型能看到查询和文档每个token之间的注意力交互,所以能捕捉到“字面不相似但语义相关”或者“表面相关但实际不对路”这些细腻信号。

业界在小规模重排场景,从早期的开源双塔底座蒸馏、到各类cross-encoder模型,再到如今直接用大模型做重排器,路线其实一直在演进。我的经验是:如果没有特别强的领域特殊性和标注数据,直接用开源的交叉编码器做zero-shot重排,大多数情况下已经能明显改善排序质量。只有当你发现模型在某个专业术语上频繁误判时,才需要考虑用领域数据微调或者升级方案。

2.2 用大模型重排:从pointwise到listwise

大模型参与重排,是现在比较受关注的方向,大致分两种玩法。

第一种是pointwise打分,把查询和文档交给大模型,让它输出一个0到10的打分并附理由。这种做法的效果通常不错,但有个明显问题:大模型打分往往是“绝对评分”,不同请求之间的分数波动可能很大,而且逐条调用成本较高。另一个潜在风险是,大模型可能忠实写出了分析理由,但最终给出的分数却受上下文影响而漂移,这在实践中并不罕见。

第二种是listwise排序:把top-k个文档一次性塞给大模型,让它直接输出一个排序结果。这种玩法有个诱人的好处——模型能全局对比所有候选,理论上排序更稳定。但实际踩过的坑是,当文档数量超过5-6个时,大模型的注意力会被冗长内容稀释,排序质量明显下滑,甚至会出现“选择困难症”。所以我更推荐的做法是,先用交叉编码器把候选剪到个位数,再用大模型做精细排序,而不是让大模型一上来就处理几十个候选。

2.3 关键技术细节:分数归一化与阈值校准

重排模型输出的是原始分数,不同模型的分数分布形态差异很大。有的输出0到1的概率值,有的输出logit,跨度能到负十几到正十几。如果不做归一化,直接把不同模型的分数拿去融合或者设定阈值,很容易出问题。

实践中常用的是min-max归一化或者softmax温度缩放。我建议额外做一步:先拿一小批你已经知道答案的查询,看重排模型给“正确答案”打的分数分布区间,然后据此设一个可用性阈值。这步操作成本很低,但可以显著减少后续环节被低分噪声干扰的情况。

归一化还有一个作用,是让重排分数可以和其它信号融合。比如检索系统里除了相关性,还想纳入时效性、来源权威度、用户点击行为等信号,就需要把重排分数和其它分数拉到一个量纲上再做加权融合。我自己常用的融合权重是相关性占大头,但不至于垄断排序,其它信号作为调节项防止排序脱离业务偏好。

2.4 混排策略:多路召回如何合并

混合检索架构下,倒排命中的结果和向量检索命中的结果各有千秋,合并排序时不能简单交叉排列。一种稳妥的策略是分组重排:对两路结果分别做重排,再按一定比例交替合并。这样做的好处是保留了多路召回的多样性,避免某一路把另一路的优质结果全部挤掉。

实际操作的时候,我会先各取一定数量的候选,各自做归一化,再按照“倒排与向量的加权融合”规则汇总排序。这个权重很多时候不是拍脑袋定的,而是结合验证集试出来的,比如离线评测或线上反馈。

重排阶段的经验总结:第一,不要对全部召回结果重排,掐住top100或更少即可;第二,交叉编码器和大模型各有用武之地,先粗后精两级走;第三,分数归一化和阈值校准是必须做的工程步骤,别偷懒。

3. 压缩:在保留关键信息的同时给上下文瘦身

重排之后,我们手里有了一个排序更可信的候选集合。但如果把这些候选原封不动地拼给大模型,很快会遇到第二个麻烦:上下文太长。大模型虽然有长窗口,但窗口不等于质量——内容越长,噪声越多,模型对关键信息的注意力反而会被稀释。

3.1 为什么不能无脑截断:关键信息往往躲在中后段

压缩最容易想到的方案,是“取每个文档的前N个字”。这个方案在有些场景下确实有效,但也可能带来不小风险。我遇到过不少案例,段落开头是背景介绍或铺垫,核心结论和答案反而藏在后段。一次知识库问答里,某篇文档的前半部分都在讲技术背景,真正回答用户问题的方案对比出现在第几百字之后,如果只截前N字,等于把答案扔了。

所以压缩的第一步不是确定“截到多少字”,而是先做“哪些句子值得保留”的判断。这本质上是把段落切成句子,然后按某种规则评估每个句子的重要性。

3.2 两种主流的压缩姿势:抽取式和生成式

抽取式压缩比较好理解:从原段落里抽取出最重要的句子,拼成一段浓缩文本。关键问题是怎么判断“重要”。简单做法是看句子跟查询的相关性,用交叉编码器逐句打分;进阶做法是把句子位置、长度、核心实体密度这些特征加进去做加权。

我实践下来效果不错的一种配置是:先用规则(比如是否包含查询关键词、是否出现在段首段尾)给句子加一些额外权重,再用轻量级的相关性模型对每句打分。最终筛选出累计得分最高的若干句子,同时控制总长度。这种做法的好处是速度快、可解释,坏处是抽取出来的句子之间可能没有逻辑衔接,读起来像断线的珠子。

生成式压缩则更进一步,让大模型基于原段落和查询,重新生成一段更紧凑的摘要。它的优点是信息密度高、表述连贯,能够将分散在多个句子里的关键信息合并成一句。缺点也很明显——生成过程可能引入原文没有的信息,也就是幻觉;同时增加了延迟和成本。

两种方案怎么选?我个人的判断标准是:如果下游对事实准确性要求极高,比如医疗、法律、金融领域,优先用抽取式,因为每一句话都能溯源到原文;如果对流畅度和信息密度要求更高,且你能接受偶尔出现的信息偏差,可以上生成式,但对生成的“忠实度”要做额外校验。

3.3 压缩窗口设计与token预算控制

压缩并不只是“缩得越短越好”,而是在信息损失可接受的前提下,把长度压到够用。不同下游任务对上下文长度的需求差异很大。比如事实性问答可能只需要几个支撑段落;但做多文档综述时,反而需要保留更广泛的背景信息。

一种可复用的做法是:先按重排分数给文档排序,给排在前面的文档分配更多预算,排在后面的文档压缩得更为激进。比如整体预算限定在总上下文的一定比例内,头部文档可保留较大比例原文,尾部文档则采用更激进的截断或摘要策略。这相当于做了一次“信息密度的分级管理”。

再补充一个容易忽略的点:压缩之后要检查信息完整性。压缩文本里如果出现了指代词,比如“该方案”“上述结论”,却没有对应的前文支撑,这就是信息链条断裂。常见的补救办法是压缩时保留核心实体和关键限定词,或者干脆在压缩模版里强制要求“不得使用指代词”。这个细节看似琐碎,但在大模型消费压缩结果时影响极大。

4. 校正:消除事实性硬伤和文本矛盾

重排和压缩解决的是“哪段有用、哪段可省”的问题,但检索结果里还可能埋伏着更危险的一类问题:内容本身是错的、过时的、或者自相矛盾的。直接把这类材料交给下游,等于让模型在沙地上盖楼,再强的推理能力也会被错误信息带跑。所以我说的“校正”,核心是事实一致性校验,而不是简单的文字校对。

4.1 三类高频病:幻觉式联想、时效性失效、自相矛盾

先说幻觉式联想。常见于生成式压缩环节,模型在压缩时自行脑补了原文没有的信息;也可能来自知识库本身,某些文章里存在无依据的推测性描述,看着像事实,其实是作者的主观判断。这类问题最麻烦,因为文字通顺、逻辑自洽,但关键数据经不起推敲。

再来说时效性失效。检索库如果更新不及时,可能返回过时的政策、价格、联系方式等信息。用户问“当前最新价格”,结果检索到的是半年前的数据,相关但不准确。处理时效性问题的通用方案是给文档标注“发布时间”或“有效期限”,在重排或校正阶段把时效性作为一个重要的过滤维度。

最后是自相矛盾。同一份文档的前半段和后半段说法不一致;或者多个检索片段之间互相冲突。这种情况下,下游模型容易无所适从,回答可能摇摆甚至融合出一个“两头都不对”的结论。

4.2 事实校验的常用方法:从外部验证到内部互证

做事实校验,最粗暴也最高成本的方式是引入外部知识库或API去核对文档中提到的数值、日期、实体关系。这种方式精确度高,但局限性也不小——外部知识库覆盖范围有限,长尾领域的实体根本查不到。

另一种可行思路是内部互证:检索系统本身已经召回了多个相关文档,如果其中若干篇描述一致,那么可信度会高于孤证。可以基于多文档间的相互印证关系,提取关键子句的证据链。如果一篇文档的关键说法在其他文档里找不到支撑,甚至被另外的文档直接反驳,那就要给这个说法打低分,甚至过滤掉。

还有一种做法是使用NLI模型做“蕴含关系校验”:把原文作为前提,把压缩/生成后的句子作为假设,判断两者是蕴含、矛盾还是中立。这套方法在学术上研究得比较多,工程落地上也有不少轻量级模型可用。实际使用中,我会设定一个保守的策略:只保留被判定为“蕴含”的句子;遇到“矛盾”或“中立”的句子,要么回退到原文,要么直接丢弃。

4.3 校正的边界:别把正常表述改坏

校正环节的错误有一些比较典型的模式,比如过度校验:模型把原文里合理的推测性措辞当成事实错误给改掉,反而破坏了原本的语用。或者过度纠正:试图将文章风格统一化,结果把作者刻意强调的语气给消平了。校正的初衷是保证事实一致性,不是把内容改得面目全非。

我给自己定了两条原则:第一,能不改写就不改写,优先做“标记和过滤”而不是“改写”;第二,必须在保留原文信息和修正事实错误之间划定清晰边界——校正动作只针对可验证的错误,比如实体名不一致、数值错误、时效性失效、明显矛盾,不针对行文风格和表达习惯。这样既控制了风险,也让整个过程更可控。

5. 实操指南:搭建一套可落地的后处理链路

前面聊了不少原理,这个部分直接给配置和流程。我把自己比较常用的一套后处理链路搭配拆出来,你可以当作模板来用,再根据自身场景调整。

5.1 链路总览与每个阶段的输入输出

完整链路通常长这样:召回阶段产出候选集合后,先进入重排模块;重排后的头部结果进入压缩模块;压缩结果再进入校正模块,最终输出给下游大模型或直接展示给用户。三个模块在管线上是串联的,但在实际工程上,我会刻意做成可以旁路的组件,方便单独调试。

  • 重排模块输入:查询文本与召回候选文档列表;输出:排序后的文档列表附带相关性得分。
  • 压缩模块输入:重排后的头部文档;输出:压缩后的精简片段。
  • 校正模块输入:压缩片段及其证据来源;输出:校验通过、过滤异常后的最终片段。

5.2 关键参数配置参考

下面这份配置来自我项目里的常用基线,不同资源和场景可以灵活调整。

模块参数项推荐配置说明
重排候选集合规模取top 100以内超出这个规模,重排延迟和成本会快速上升
重排重排模型输入长度按模型上限,通常512-1024超长文档先分块再重排,避免被截断影响判断
重排最终保留数取top 10以内具体看下游窗口和任务复杂度
压缩句子重要性模型末层CLS向量计算余弦相似度即可不一定要上大模型,轻量级也能跑出不错的效果
压缩保留比例头部文档保留60%-80%,尾部保留30%-50%信息密度分级,核心是保住关键实体和结论
压缩最大片段长度512 token以内超长片段会显著增加下游模型跑偏的概率
校正校验方式内部互证+NLI辅助充分证据优先,孤证降低权重
校正矛盾处理策略矛盾则丢弃,中立即回退原文宁可少给,也别给错

5.3 一个具体的配置流程示例

假设我要搭建一个面向内部知识库的问答增强链路,大致步骤如下:

第一步,混合召回候选集,倒排索引和向量检索各取top100; 第二步,两组候选合并后做归一化,送入重排模块,取top10进入压缩; 第三步,压缩模块先按句子打分保留关键句,控制总长度在几百token内; 第四步,校正模块对压缩结果做事实一致性检查,标记出无法被证据支持的句子; 第五步,最终将检查通过的片段拼接,并把标记的低置信度片段排到末尾,供大模型结合上下文自行取舍。

这套流程跑下来,整体延迟增加通常在可接受范围内,但结果质量有肉眼可见的提升。

5.4 优化延迟的几条思路

后处理三个模块都有额外耗时,如果对延迟敏感,有几种优化思路可以单独或组合使用。一是缓存:对高频查询和相似文档,把重排和压缩结果做缓存,命中缓存直接跳过后处理。二是跳级:当粗排分数已经非常高且无争议时,可以跳过重排直接进压缩;当文档本身很短且信息密度高时,可以跳过压缩直接进校正。三是并行:校正模块里的多文档互证可以并行计算,不必串行等待。四是模型蒸馏:用大模型产出的精排结果蒸馏一个小模型出来,线上用小模型,延迟能压到很低。

6. 常见问题与排查技巧实录

这块内容的价值很多时候不体现在架构设计上,而是体现在踩坑之后的修复过程。我把几个高频问题整理成一份速查表,再挑几个展开讲讲。

6.1 问题速查总表

现象可能原因排查思路
重排后结果和粗排差不多重排模型能力不足或领域不匹配检查重排分数分布是否区分度低;尝试换更强的重排模型或做领域微调
重排分数整体偏高/偏低模型输出未归一化做min-max归一化或温度缩放;校准可用性阈值
压缩后回答丢失关键信息截断位置不合理或重要性打分不准检查压缩前后的关键实体覆盖率;调整句子重要性权重
压缩片段读不通顺抽取式压缩句子缺乏衔接在抽取结果前补一句引导语;或者升级为生成式压缩
校正误杀过多正常片段校验模型阈值过于激进放宽阈值;对“矛盾”和“中立”分类区别对待
链路延迟过高三个模块串行太重加缓存、跳级或并行化;小模型替代大模型

6.2 重排没效果?先看分数分布,别急着换模型

很多人遇到重排效果不明显,第一反应是“换更强的模型”。但我建议先做一件事:把重排模型在典型查询上的分数打印出来看分布。如果所有候选文档的分数都挤在一个特别窄的区间里,说明模型没拉开差距,此时换更强的模型可能有用,但也有可能是输入格式有问题,比如查询和文档拼接顺序不合理,或者候选文档被截断得太狠。

如果分数分布正常,但排序依然不对,才考虑模型能力或领域匹配问题。一个快速验证方法是人工拿几条“明明高度相关但排到后面”的bad case,去查一下模型给它们的分数,以及和top1之间的差距。这样定位速度快得多。

6.3 压缩之后的文本,如何用最小成本查漏

我常用的成本很低的方法是“关键实体覆盖率检查”。具体做法是先从原始文档里抽取出人名、地名、机构名、时间、数值、术语等关键实体,再统计压缩后这些实体的保留比例。如果一次压缩把大量核心实体弄丢了,说明压缩策略过于激进,就需要调低压缩比例或修正句子打分机制。这套方法不需要人工逐句阅读,跑起来非常快,而且能提前拦截信息丢失的问题。

6.4 校正被“带节奏”的防法:保留证据链

校正模块最容易陷入的局面,是它自己也被错误信息带节奏。当多篇文档都含有同一个错误描述时,内部互证会把错误放大,因为互证只认一致性、不认真实性。要破解这个困局,最核心的手段是给每个校验结论留下完整的证据链,同时在可信度计算时引入“来源质量”和“信息层级”作为调节信号——高层级来源的权重高于低层级来源,官方或一手数据的权重高于个人解读。没有证据链支撑的校正结论不能直接输出。这套“带证据校验”的机制,能让校正不变成第二个制造问题的源头。

后处理链路之所以是 RAG 和搜索系统的分水岭,是因为它把“找到一堆东西”真正变成了“拿到能用的东西”。我做这类系统最大的收获是:重排、压缩、校正三件事一定要分得清清楚楚,各司其职,不要混在一起;同时每一层都要能单独评估、单独回退,否则出了问题极难调试。如果你的系统现在还只是“召回直接拼给大模型”,强烈建议先把重排和压缩加上,效果会立刻不一样;校正可以后续迭代慢慢打磨。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进