用户问:“夜间打车报销要先审批吗?”知识库里明明有一句“夜间打车报销必须先取得主管审批”,RAG 最后给出的答案却没有这条要求。
看到这种结果,很多人会先怀疑 Reranker:是不是模型太小,相关性判断不准,换个更强的重排模型就好了?这个方向并非完全错误,但它跳过了两个更早的问题:正确段落有没有进入候选名单?进入以后,关键句有没有真的出现在模型输入里?
RAG 的检索链路通常先从知识库找出一批候选,再让 Reranker 对问题和候选逐对打分,最后保留少量内容交给生成模型。Reranker 擅长在已经拿到的材料里重新排序,却不能替前面的召回补回一条根本没交给它的段落。
下面用一份合成的报销资料,把候选缺失、输入截断和排序错误分开。名单、问题与证据位置都是人工设定的,只用来说明排查方法,不假装是真实公司的制度或线上事故。
Reranker 为什么找不回未入场的 D?
假设粗召回按相关性给出六个片段,顺序固定为 A、B、C、D、E、F:
系统配置 TopN=3,意味着只有 A、B、C 会进入重排。Reranker 无论怎样调整这三条的顺序,都不可能选出 D。它能改变的是候选之间的相对位置,不能凭空创造一条没有入场的候选。
这也是为什么“知识库里有答案”和“这次请求召回了答案”是两件事。文档已经入库,只能说明材料存在;过滤条件、切块方式、召回模型以及 TopN 都可能让目标片段停在候选集之外。
对于标准的重排步骤,最终保留的 TopK 应当来自输入的 TopN。如果系统在重排阶段又调用搜索补充新资料,那是另一项召回能力,需要单独记录新候选从哪里来,不能把它混成 Reranker 自己找到了证据。
开发者拿着目标D站在候选入口外,评审台上只有A、B、C
遇到“正确证据没进最终答案”,第一步不是看重排分数,而是拿着目标证据的标识往前查:它是否完成入库,是否通过元数据过滤,在粗召回中排第几,最后有没有进入本次 TopN。D 没入场时,应修召回、过滤或候选数量;直接换重排模型,解决不了入口缺失。
D 入场了,模型实际看到了什么?
把 TopN 从 3 扩大到 4,D 终于进入候选名单。此时日志里能看到 D 的片段 ID、来源页码和完整原文,看起来条件已经满足。但 Reranker 实际打分的并不一定是日志里那份完整原文,而是经过输入模板拼接和长度处理后的文本。
为了单独观察这个差别,我们给“问题 + 分隔符 + 候选正文”设置一个人工字符预算。问题和分隔符占 17 个字符,总预算为 64,于是正文只剩 47 个字符。D 的原文有 103 个字符,真正回答问题的句子位于 [87,103),正好被裁在窗口外。
此时系统同时拥有两种看似矛盾的记录:候选日志显示 D 已命中,实际成对输入却只保留了 D 开头的资料归档说明。Reranker 看不到“必须先取得主管审批”,自然无法依据这句话给 D 打分。把这种结果直接归因为模型不理解审批规定,会修错层。
开发者核对D文件,尾部审批句落在人工输入窗口外
保持 A、B、C、D 和原文完全不变,只把人工预算扩大到 256,D 的成对输入变为 120 个字符,审批句才完整进入可见范围。这个变化只证明模型有机会看到关键句,还没有证明它一定会把 D 排到第一。
三种状态放在一起,断点就很清楚:
状态:TopN=3
D 是否在候选中:否
审批句是否在实际输入中:否
当前能判断什么:先修召回入口
状态:TopN=4、预算 64
D 是否在候选中:是
审批句是否在实际输入中:否
当前能判断什么:先修输入截断
状态:TopN=4、预算 256
D 是否在候选中:是
审批句是否在实际输入中:是
当前能判断什么:才能继续评价排序
实际模型通常按 token 而不是中文字符计算长度。问题、候选、特殊标记各占多少预算,截断较长的一侧还是只截第二段,也取决于具体模型、封装和版本。因此,排查时要保存或重建真正送入模型的 tokenized 输入,而不能拿截断前的原文代替。
来源元数据也不能冒充模型输入。日志里保存了“第 4 页、夜间出行审批”,只说明系统知道片段来自哪里;这些字段没有拼入成对文本时,Reranker 就不会自动看到它们。同样,问题本身出现“审批”两个字,也不能算来源证据已经可见。
多证据问题,召回率究竟在数什么?
前面的问题只需要一条规定:夜间打车是否需要审批。把这条规定标成一个证据单元,D 进入候选且关键句可见,才算这一项材料到位。
再把问题改成:“跨城夜间打车的审批和附件要求是什么?”答案同时依赖两条规定:D 中的主管审批,以及 F 中的“跨城夜间打车还需附行程说明”。这时分母是两个证据单元。
当 TopN=4 时,候选里只有 D,没有 F。若按文档命中来汇报,可能会说“报销制度已经召回”;若按问题实际需要的证据来算,只找回了两条中的一条。两种口径回答的不是同一个问题。
证据单元应该跟任务要求对应。同一条规定因重叠切块出现在两个片段里,只算一项证据;两个不同规定即使来自同一份文档,也不能因为文档 ID 相同就合并成一项。这样计算,才能知道最后答案缺的是哪条事实,而不是只知道某份文件来过。
候选覆盖还要与可见覆盖分开。D 已在 TopN 中,但审批句被截掉时,候选覆盖可以记为命中,实际可见证据仍是缺失。二者一起记录,才能区分“没有召回片段”和“召回了片段却没把关键内容交给模型”。
开发者拉开同一D文件的阅读窗口,保留关键句与原文位置,但没有模型评分
最终保留数量 TopK 也会形成新的上限。跨城问题需要 D 和 F 两条证据,而两条证据分别落在两个片段里;如果 TopK=1,即使它们都进入候选且完整可见,最后也只能保留其中一个。若一个片段本来就同时包含两条规定,结论又会不同,所以不能把“两条事实”机械理解成“必须保留两个片段”。
必要证据全部进入上下文,也仍不等于答案必然正确。生成模型还要理解条件、组合信息并形成回答。候选覆盖、输入可见、最终保留和回答正确,是四个相邻但不同的检查点。
TopN 扩大有什么代价,收益从哪儿看?
既然 D 在第 4 位、F 在第 6 位,直接把 TopN 调大似乎就能解决问题。但候选数量越大,Reranker 需要处理的问题与候选对也越多。一次请求有 N 条候选,就要形成 N 个成对输入;两个问题分别测试 N=3、4、6 时,对应 6、8、12 条输入。
在这份固定名单中,N 从 3 增加到 4,审批问题从 0/1 变成 1/1,跨城问题从 0/2 变成 1/2;N 增加到 5,只多出餐费片段 E,覆盖没有提升;增加到 6,F 才进入,跨城问题达到 2/2。
这个过程说明,调大 N 的收益不能只看数字变大,而要看新增候选补回了哪些必要证据。第 5 条候选只增加重排工作量,没有帮助这两道问题;第 6 条才补齐附件要求。
成对输入数量只是最容易核对的成本。真实部署还要看 token 长度分布、批大小、设备利用、排队时间和吞吐。十条很短的候选与十条接近长度上限的候选,开销不会相同。批处理可能提高整体吞吐,也可能为了凑批增加等待,不能把“支持 batch”直接翻译成单次请求更快。
选择 N 时,可以在验证集上扫描几个候选值,同时记录两类结果:必要证据覆盖增加了多少,以及实际计算、延迟和资源增加了多少。参数选定后,再用未参与调参的问题报告结果,避免在同一批题上反复调到好看。
如何沿 ID 找到证据消失的位置?
重排失败若只保存最终分数,后面很难判断该修哪一层。更实用的做法是给候选保留稳定 ID,把这些信息串起来:粗召回位置、文档来源、截断前原文、实际成对输入、重排分数、重排后名次和最终是否保留。
继续看 D。如果粗召回列表中没有 D,问题发生在入库、过滤或召回;列表里有 D,实际输入没有审批句,问题发生在切块、拼接模板或长度处理;D 的关键句完整可见,分数仍低,才进入模型适配和训练数据的讨论;D 已经排到前面,最终上下文却没有它,还要检查阈值、去重和上下文预算。
TopN | |
TopK 与上下文预算 |
批量打分时还要核对分数与候选 ID 是否对齐。输入顺序变了,输出分数却按旧顺序贴回去,也会制造“模型排错”的假象。保存一串脱离身份的分数不够,输入 ID 序列和输出 ID 序列需要能互相对应。
开发者与同事分别核对候选入场和实际输入,之后才进入重排比较
这套定位方式的价值,在于每种现象都有对应修复层。它不会把所有失败都推给 Reranker,也不会因为召回到了某份文档,就提前宣布证据已经齐全。
面试里,把上限和验证讲完整
回答“RAG 重排漏证据怎么办”,可以这样组织:
我不会先换模型,而会先沿检索链路确认目标证据第一次在哪里消失。Reranker 只能对传入的 TopN 候选排序,所以先检查目标片段是否入库、是否通过过滤并进入候选。候选命中后,再核对真正送入模型的成对输入,确认关键句没有被切分或长度预算截掉。
对于需要多条事实的问题,我会按问题定义证据单元,分别记录候选覆盖、实际可见覆盖和最终 TopK 保留情况。调大 TopN 时,同时比较新增证据与成对输入、token 和延迟成本,而不是只看召回数字。
只有目标证据在固定候选中完整可见、候选 ID 与分数对应正确,却仍然排名靠后时,我才会在同一输入条件下比较 Reranker 模型。这样换模型是在解决排序能力,而不是替前面缺失的证据背锅。