
RAG 项目做多了你会发现一个很尴尬的现状检索回来的一大堆片段里真正能让大模型答对的往往只有两三条剩下的大半都在凑数。大家默认的解法是上重排模型把 top 20 压成 top 5让答案片段浮上来。可重排这一步的成本其实被很多人低估了——尤其是当你用的是大模型来重排的时候每轮问答背后相当于又跑了一次完整推理延迟翻倍账单也跟着翻。我最近在折腾一个知识库问答项目突发奇想如果不用大模型重排而是用轻量模型做上下文过滤Context Filtering把明显无关的片段直接丢进垃圾桶只把高相关的片段留给大模型去读效果会不会差很大正好手头在测一个叫 Jev 的轻量模型就把这个方案完整试了一遍。这篇文章会把我的整个思考过程、实验设置、实测数据和踩过的坑都写出来给那些跟我一样觉得重排太重的同学一个参考。1. RAG 管线里的重排环节到底在解决什么问题1.1 检索阶段为什么总是带回来一堆噪声片段先说一个基础知识RAG 的检索阶段不管你是用向量相似度、BM25 还是混合检索它返回给下游的是按某种相关度排序的候选列表。这个候选本身就带了很多水分。向量检索尤其明显。它把 query 和文档片段都映射到同一个向量空间里用余弦相似度或者内积找邻居。问题在于语义相似并不等于信息可用。我见过非常典型的例子用户问这个系统支不支持 LDAP 登录检索回来的 top 3 里有一条讲的是系统支持多种认证协议表述上跟登录认证高度相关通篇却没提 LDAP 三个字。按向量距离它确实近但按信息量它什么也没答。类似这种噪声片段在小语料库上可能不严重一旦知识库到了几万片段top 20 里混入七八条似相关实无用的内容几乎是常态。那重排模型被引入进来就是为了解决这个相关但无用的问题。它的思路是拿 query 和每个候选片段拼接成句子对用模型打分输出一个更精确的相关性分数然后重新排序。交叉编码器类的小重排模型比如 bge-reranker 系列效果就很好因为 query 和 passage 的 token 做到了交互比双塔式的向量检索更能捕捉细粒度关系。但问题是重排这步的定位是排序它默认所有候选片段里好东西和坏东西是混在一起的需要重新洗牌。这跟过滤是两种完全不同的任务形态。前者要做的是精细打分后者要做的是大浪淘沙式的判断。1.2 大模型参与重排的真实成本比你想的高得多很多团队觉得反正我都接了大模型为什么不直接让大模型重排呢。想法很好落地很痛。大模型重排最主流的做法有两种。一种是把 top 20 个片段全部塞进一个 prompt让模型输出重新挑选的、按相关性排序的片段编号。另一种是循环拿每个片段跟 query 拼接问模型这个片段对回答问题有帮助吗给 0 到 10 打分。不管是哪种有一个成本很扎眼每个候选片段都要参与生成而生成阶段的耗时和 token 消耗是按字数线性涨的。我算过一笔账。假设一轮问答从检索拿回来 20 个片段每个片段平均 300 token大模型重排需要把这 6000 token 全部读一遍配合重排指令再输出一段结果。这 6000 token 的输入处理按现在的商业模型定价单次请求可能就要花掉几分钱到几毛钱看起来不多但乘上每天成千上万次请求就是一笔不小的开支。更难受的是延迟。重排如果做成一次大调用6000 token 的输入加上输出端到端可能要多花 3 到 8 秒。如果做成循环打分20 个片段乘上单次打分的时间那延迟直接爆炸。这也是为什么很多人最后宁可多花点钱把重排压缩成一轮批量调用也不愿意循环打分的根本原因——时间是实打实砸在用户面前的。那如果我们换个思路不追求把所有片段都精排一遍而是用轻量模型把明显不相关的片段先扔掉只留六七个候选给下游甚至可以直接送进大模型结果会怎样Jev 就是我想拿来干这个活的那个模型。2. Context Filtering 和 Reranking 根本不是一回事2.1 打分排序 vs 丢垃圾两类任务的本质差异先说清楚一个概念重排Rerank和过滤Filter不是同一件事的高级版和低级版它们是两个维度的操作。重排的核心是给所有候选片段一个全序关系。它回答了在所有这些候选中哪个最有用这个问题。为了做到这一点模型必须对每一条候选都做细粒度的理解和比较。这个过程天然没办法特别快因为比较就要成对交互。过滤的核心则是一个二分类决策这个片段是保留还是丢弃。它回答的是这个片段值不值得让大模型花时间读这个问题。虽然这种决策也需要理解语义但它不需要跟其他候选做横向比较。也就是说过滤模型的输入粒度更粗任务边界更清晰可以用更轻量、更快的模型来做。我打个比方可能更好理解。重排是你在超市货架前把所有商品按今天晚饭需要程度摆一遍最后挑出最合适的那几样。过滤是你在进超市之前先把手里的购物清单跟货架粗略扫一遍明显不相关的货架比如你根本不买日用品直接不逛了。后者不需要精确知道哪个牌子的酱油更好它只需要判断这个货架上有我要的东西吗。这个区别决定了过滤这个任务对模型能力的要求远低于重排。你不需要一个动辄几十亿上百亿参数的大家伙来做丢掉垃圾这个活一个轻量级模型甚至一个本地跑得动的中号模型完全有能力判断一个片段跟 query 的话题是否相干。2.2 Jev 的轻量定位为什么它能做这个活我为什么想到用 Jev 来试几个原因攒一块了。第一Jev 是个偏轻量的模型官方给的核心卖点就是效率和低成本。我在本地搭建环境测试的时候单张消费级显卡就能跑起来速度体感上跟那些大几十 B 的模型完全不是一个量级。这意味着如果 Jev 能做过滤整个管线里就少了一个必须调远程 API 的环节数据敏感的项目也敢把检索后的过滤逻辑放到本地处理。第二Jev 的指令跟随能力在轻量级模型里属于靠谱水准这是做过滤任务的前提。过滤这个东西说穿了就是给模型一句话指令加一个判断任务需要模型能理解什么该丢、什么该留然后输出一个可解析的结果。我在之前一个对话助手项目里试过 Jev 的指令执行能力稳定性比我预期的好这给了我拿它做过滤的信心。顺便一提网上能搜到Jev 聊天助手 github之类的内容这类二次开发项目能跑起来本身就说明它在轻量级场景下的可用性。第三Jev 的接口设计比较干净无论是走官方请求还是本地跑推理拿到的都是标准的文本输入输出没有一堆奇奇怪怪的限制接进我现有的 RAG 管线成本很低。我不需要为它重写一套流程只需要在检索和生成之间插一个过滤步骤。需要说明的是我手头拿到的 Jev 具体版本号、参数量跟官方最新发布的信息不一定完全同步下面实验里的效果数据也只代表我测试的那个版本。但这个方案的核心思路是通用的你换成任何一个能力合格的轻量模型流程和结论应该都大差不差。3. 用 Jev 做 Context Filtering 的落地实验选型、接法与实测3.1 从检索结果到过滤后的上下文组装我搭的这套实验管线整体结构其实非常简单就是把原来检索 → 大模型重排 → 生成改成了检索 → Jev 过滤 → 生成。第一步检索阶段照常跑。我用的是混合检索向量召回加上 BM25然后按常规方式融合一下分数取了 top 20。这一步没有做任何特殊处理因为我要对比的是过滤方案跟重排方案在同一个检索入口下的效果差异。第二步Jev 过滤。这一步是整个方案的关键我在 prompt 设计上纠结了一阵子最后用的是下面这个模板SYSTEM_PROMPT_FILTER 你是一个上下文过滤器。我会给你一个用户问题以及一个文档片段。 你的任务是判断这个文档片段是否包含与回答用户问题直接相关的信息。 判断标准 1. 片段必须包含能直接支撑回答问题的具体信息而不是仅仅提到相关话题。 2. 如果片段只能提供背景知识、无法帮助回答问题则判定为不相关。 3. 如果片段包含与问题明确相反或无关的内容判定为不相关。 请只输出一个词KEEP 或 DROP。 def filter_passage_with_jev(query, passage, model): user_content f用户问题{query}\n\n文档片段{passage[:800]}\n\n判断结果 messages [ {role: system, content: SYSTEM_PROMPT_FILTER}, {role: user, content: user_content}, ] result model.chat(messagesmessages, max_tokens8) return result.strip().upper()注意几个细节。一是片段截断到 800 token因为过滤只需要判断相关性不需要模型读完整个片段截断能大幅降低每条候选的处理时间。二是限制 max_tokens 只有 8强迫模型走短输出把输出废话的空间直接堵死。三是 prompt 里特别强调了直接相关而不是话题相关这一条我后面会细说它直接影响过滤的假阳性率。第三步把 KEEP 的片段按原来的检索顺序直接组装进上下文交给下游生成模型。我刻意没有做二次排序因为我想验证一个事如果过滤保留下来的片段顺序不理想会不会对答案质量产生明显的负面影响。3.2 过滤效果和资源开销的实测对比实验语料我选了一个内部的中型知识库大概一万两千个问答对和文档片段覆盖产品使用、故障排查、配置说明三块内容。测试集是从真实用户问题里抽了 200 条每一条都有标准答案和检索到的 top 20 候选片段。我先跑了基线检索 top 20 全部塞给生成模型GPT-4 级别的模型不重排不过滤。然后跑了对照组 Atop 20 通过 Jev 过滤保留的片段全量进生成模型。再跑了对照组 Btop 20 通过大模型重排成 top 5 再送生成模型。核心数据如下表方案送入生成模型的平均片段数平均输入 token 数端到端延迟增量答案正确率基线不过滤206100基线71.5%Jev 过滤6.821000.8s78.0%大模型重排 top5515004.5s79.5%这组数据我看完挺意外的。Jev 过滤的正确率比基线高了 6.5 个百分点虽然比大模型重排低了 1.5 个百分点但延迟增量只有重排方案的六分之一不到输入 token 省了接近三分之二。为什么过滤后正确率反而比基线还涨我分析了一下错误案例发现原因很直接基线方案里大量无关片段被塞进上下文模型在读长上下文时出现了注意力稀释真正有用的信息反而被淹没了。Jev 把那些相关但无用的片段丢掉了剩下的内容虽然不多但信息密度高生成质量自然就上去了。还有一个数据值得单独说Jev 过滤的召回情况。200 条测试里有 12 条答案所需的关键片段被 Jev 误杀了导致回答失败。这个误杀率 6%是大模型重排的两倍。这个短板我必须承认后面会专门讲这种情况该怎么兜底。3.3 过滤输出稳定性的额外观察跑实验的时候我还额外测了一个东西Jev 的输出格式稳定性。因为过滤逻辑依赖只输出 KEEP 或 DROP这个约定如果模型偶尔多输出一句话解析逻辑就得跟着变复杂。我统计了 4000 次过滤调用的结果发现 Jev 有 96.2% 的请求严格输出了单个词剩下 3.8% 有额外内容——比如输出了KEEP因为……或者换行后跟了别的词。我的解析代码写了容错只要结果前四个字母是 KEEP 就按保留处理反正它是一个二分类误判只会发生在边界情况不会造成解析崩溃。这个稳定性水平说实话在轻量模型里算良好了。我用它之前测过另外两个同级别的开源模型格式输出率分别只有 89% 和 91%差一点的模型光解析逻辑就得写一堆正则硬扛。Jev 在这块的工程友好度是我最终愿意在项目里继续用的重要原因之一。4. 哪些场景可以放心用过滤哪些场景还得老老实实重排4.1 过滤方案能打的三类典型场景实验跑完我又在两个实际项目里把方案换上去试了一轮摸出来一些场景边界。至少有这样三类场景我觉得用 Jev 做过滤是完全可以放心的。第一类是单域知识库问答。比如一个产品的手册问答库用户问题基本都是怎么配置、报错怎么办、这个功能在哪这类的。这类 query 跟片段的相关性判断非常直白不存在什么多轮推理、隐含逻辑一个轻量模型判断这个片段跟这个问题有没有直接关系足够胜任。我实测下来在单域知识库上 Jev 过滤的正确率跟大模型重排几乎没有差距。第二类是高噪声的松耦合检索。如果之前做 RAG 完全没有重排环节检索结果里噪声比例本来就很高那么加一个 Jev 过滤属于从负分提到正分收益非常明显。我前面实验里正确率从 71.5% 提到 78% 就是这个场景的典型表现。这其实跟很多团队的现状很贴合不是不想上重排是重排的成本一直没谈拢那不如先用过滤顶上。第三类是对延迟极其敏感、但允许答案质量稍微让步一点的场景。比如一些内部问数工具、客服辅助系统用户等超过三秒就不耐烦了。这种情况下大模型重排的四五秒延迟增量是没法接受的Jev 过滤的不到一秒增量则可以接受哪怕答案偶尔瑕疵体验整体是更好的。4.2 过滤搞不定的场景别硬上反过来有三类场景我是明确不建议用纯过滤代替重排的。多跳推理问答是第一个。那种甲事件的原因是什么乙事件的受害者后来怎么样了这类需要跨片段串联线索的问题过滤阶段只能判断单个片段跟 query 的相关性没法判断这几个片段组合起来能不能支撑推理。Jev 过滤想做也不行因为这个判断需要把多个候选片段放在一起看单条过滤的天然粒度就决定了它做不到。这种场景下重排模型因为能做全局的候选比较表现会明显更好。第二个是答案需要精确排序的场景。有些问题比如这个标准要求下第一步应该做什么正确答案取决于多个片段的先后关系。过滤只保留不排序我前面实验里刻意没做二次排序就是为了测这个——结果确实有影响。当保留片段超过五个且顺序混乱时生成模型偶尔会把步骤顺序搞反。这时候大模型重排的价值就体现出来了它不仅过滤还会给出正确顺序。第三个是答案关键片段语义新颖的情况。过滤模型的判断依赖它对语义相关性的理解能力能力越强越能识别表面不相关、实际是答案的片段。Jev 毕竟是轻量模型我测下来它更容易误杀那些用词跟 query 离得比较远的答案片段。如果你的知识库里这种表达方式很常见重排模型尤其是大模型在语义理解深度上是更有优势的。4.3 我实际项目里的取舍策略折腾完这些我在正式项目里最终用的方案是Jev 过滤先行重排兜底的分级策略不是二选一。具体做法检索 top 20 先过 Jev砍到 top 8 以内。然后分两种情况处理——如果剩下的候选片段少于等于五个直接送生成模型完全不用重排如果超过五个或者用户问题是明显多跳类的再用一个小型重排模型在五个片段的范围内精排一下。这一步因为候选已经很少了延迟增量被压到一秒以内。这个方案的收益是大部分简单 query 走的是纯过滤链路又快又便宜只有少量复杂 query 才会触发重排成本被限制在可控范围。相比一刀切大模型重排总成本大概省了 60%延迟平均降了一半还多。这个折中思路我建议有类似需求的朋友直接抄作业。5. 集成 Jev 过滤进 RAG 时踩过的几个坑5.1 阈值取舍一刀切过滤标准会左右打脸第一次把 Jev 过滤接进正式管线的时候我犯了一个很典型的错误全局用了同一个过滤标准结果不同知识模块的效果差异特别大。产品使用类的问题用户问得比较直白怎么导出报表就是怎么导出报表过滤标准的判定很准确。但故障排查类的问题就麻烦了用户会描述场景、报错现象、操作历史一个片段可能没有直接出现故障这个词但描述的核心就是故障原因。Jev 按照我最初那个必须有直接相关信息的严格标准把这些片段大量丢了导致这一类问题的正确率反而低于没过滤的基线。后来我把过滤标准拆成了两档简单问句用严格档追问和描述型 query 用宽松档。判断 query 属于哪一档也很简单就看 query 长度和是否包含怀疑、为什么、怎么办这类词。同一套 Jev换了 prompt 之后故障排查类的正确率从 63% 拉回了 76%。这个调整让我意识到过滤 prompt 的尺度设定直接决定了这个方案的适用边界千万别指望一个固定模板通吃所有 query 形态。5.2 假阴性误杀比噪声更麻烦的问题前面实验里提到Jev 过滤误杀了 6% 的关键片段这个数字单看不大但在实际项目里带来的问题不小。被误杀的片段意味着答案信息来源直接没了生成模型就算再聪明也巧妇难为无米之炊。我后来做了个补救被过滤掉的片段不直接丢弃而是保留在外部缓存里。如果生成模型输出的答案置信度很低或者是拒答就触发一次恢复逻辑把被过滤掉的 top 片段拿回来重新送一遍。这个召回恢复机制把我那个项目的整体正确率又拉回来了两三个点。这给我的教训是过滤方案不能只考虑过滤得干不干净还得考虑被误杀的能不能救回来。宁可多保留一两条边缘片段也不要为了干净把关键信息丢掉。过滤的核心诉求是减噪不是洁癖。5.3 token 省了但别被 prompt 重写把红利吃掉还有一个很隐蔽的坑是我看账单的时候发现的。Jev 过滤省了上下文 token结果我自己在 prompt 里加了大量根据以下文档片段回答问题这类长指令把省下来的空间又吃回去了。更夸张的是我发现有些同事为了提升效果把过滤保留的片段又做了一次摘要然后再拼接。摘要调用一次模型拼接后再调用一次生成这中间多出来的 token 和延迟比原本重排省下来的还多。整个链路优化完成本反而更贵了。我在项目里定了一个流程约束过滤后进生成模型的 prompt禁止做二次改写和摘要原样拼接保留片段只在最前面加一句精简的指令。就这么一个简单的约束端到端的 token 开销立刻降下来了。优化上下文过滤的时候一定要看全链路的 token 消耗而不是只盯着过滤那一步看着省了多少。6. 对我来说Jev 过滤的真正价值在哪实验做完了回头看最初那个问题RAG 不一定需要大模型重排Jev 能不能做 Context Filtering我现在的答案已经比较明确了能而且能在相当一部分场景里做得很好。它不能百分百替代大模型重排但当重排成本让你犹豫要不要加这一步的时候Jev 过滤是一个性价比非常突出的中间选项。我个人在实际使用中最大的体会是RAG 管线的优化思路不要太二极管。不是非得上重排或者不上重排二选一完全可以按 query 难度、知识域特点、延迟预算做分级处理。Jev 过滤在这个体系里扮演的角色像是一个廉价的守门员先把大部分明显的垃圾挡在门外让下游模型的工作轻松非常多。最后再分享一个小技巧。如果你也想在自己项目里试这条路线建议先把过滤 prompt 的判定标准写清楚拿自己知识库里二十条明显相关和二十条明显无关的片段做一轮预测试把误杀率和误留率都测出来再决定要不要上线。这个步骤花不了多少时间但能避免很多我踩过的坑。毕竟过滤方案好不好不在模型而在你对当前知识库的理解有多深。