ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Rerank 重排模型怎么用:让 RAG 回答更准的最小实现

2026/9/6 13:06:55 拓冰建站 浏览量
Rerank 重排模型怎么用:让 RAG 回答更准的最小实现 向量检索已经能找回“差不多”的内容为什么回答还是不准一个典型现象是问题问“远程办公申请需要谁审批”Top-5 里既有远程办公制度、考勤制度、出差制度也有一段旧 FAQ正确段落确实出现了但排在第四。把这五段全交给模型它可能被第一段相似但不适用的文字带偏。把k从 5 提到 20 通常也不是答案只会把更多噪声和重复文本塞进上下文。Rerank 的角色正好在这里第一阶段用向量检索快速从大量文档中召回几十个候选第二阶段对“问题—候选片段”逐对做更细粒度的相关性判断再把少量最相关的片段交给生成模型。它不是替代向量数据库也不是让模型凭空知道新知识而是把有限的计算花在已经缩小的候选集上。最重要的一句话是先确认正确证据能被召回再谈重排。若正确片段连 Top-50 都进不来重排模型没有机会把它变到第一名。Rerank 解决的是候选排序问题不是文档解析、权限过滤、切分失真或知识缺失的万能药。一、双阶段检索到底发生了什么embedding 检索常用双塔bi-encoder模型问题单独编码成一个向量文档单独编码成一个向量离线索引后可以用近邻搜索快速比较。它适合海量候选但为了让文档向量可预计算模型无法在计算文档向量时看到当前问题的每个词。Cross-Encoder 则把问题和候选文本一起输入输出一个相关性分数。由于两段文本可以在模型内部交互它通常在精细排序上更强代价是每个候选都要再做一次推理。因此正确架构不是用 Cross-Encoder 扫全库而是“先广召回、后精排序”。Sentence Transformers 的官方文档也明确指出 Cross-Encoder 更慢通常用于重排 bi-encoder 的 top-k 结果。用户问题Bi-Encoder 编码向量索引召回 top_n 候选ACL/元数据过滤Cross-Encoder: 问题每个候选按相关分数排序截取 top_m 上下文LLM 基于证据回答从这张图还能看出权限的位置。权限和文档状态过滤必须早于重排不能为了获得“更好排序”把无权文档送给重排服务。即使 reranker 不输出文本敏感文本已离开了本该受控的边界。若重排调用的是第三方 API还要确认其数据保留、地域和合规要求。二、开始前不要把相关性分数误读成概率许多 Cross-Encoder 模型输出的是 logit 或未校准的相关性值可能是负数也不一定在 0 到 1 之间。这个数最可靠的用法是对同一个问题的候选排序而不是解释成“87% 正确”。不同模型、不同版本甚至同一模型不同任务的分数分布都可能不同。若要设拒绝阈值必须在自己的已标注问题集上校准而不是从别人文章里复制一个 0.5。还要区分“相关”与“支持”。一段关于“差旅审批”的文字对“远程办公审批”很相关但不支持答案。Reranker 的训练目标通常是相关性不会自动理解企业制度中的时间有效性、部门权限或版本优先级。这些条件应通过 metadata 过滤和文档治理先保证再交给语义排序。本文示例用sentence-transformers的CrossEncoder展示本地重排。模型名称只是一个能运行的示例中文业务文档应优先选择在中文/多语数据上适配、许可证允许使用的模型并在自己的评测集验证。第一次部署时下载权重需要网络和磁盘空间离线服务器则应通过受控制品库预置模型别在生产进程启动时临时下载。python-mvenv .venv# Windows PowerShell.venv\Scripts\Activate.ps1 pipinstallsentence-transformers torch这里假定你的第一阶段检索器已经返回字典列表每项具有id、text、source和metadata。无论底层是 Chroma、Elasticsearch、pgvector 还是托管向量库这个边界都足够小重排函数不应关心数据库 SDK 的细节。三、一个最小且可运行的本地重排函数下面代码加载一个公开 Cross-Encoder 模型对问题和候选段落组成的 pairs 调用predict按得分降序返回前若干项。为了可检查性函数不修改输入字典而是生成包含rerank_score的新字典。对空候选、过长问题、过长候选作了防御性处理长度截断并不是语义完美方案却比让异常输入把请求拖垮要好真实上限应结合所选模型的最大序列长度设定。# rerank.pyfrom__future__importannotationsfromcollections.abcimportSequencefromtypingimportAnyfromsentence_transformersimportCrossEncoder MODEL_NAMEcross-encoder/ms-marco-MiniLM-L6-v2defrerank(query:str,candidates:Sequence[dict[str,Any]],model:CrossEncoder,top_n:int5,max_chars:int3000,)-list[dict[str,Any]]:ifnotquery.strip():raiseValueError(query 不能为空)iftop_n0:raiseValueError(top_n 必须大于 0)ifnotcandidates:return[]normalized[]foritemincandidates:textstr(item.get(text,)).strip()ifnottext:continuenormalized.append({**item,text:text[:max_chars]})ifnotnormalized:return[]pairs[(query[:1000],item[text])foriteminnormalized]scoresmodel.predict(pairs,show_progress_barFalse)rankedsorted(({**item,rerank_score:float(score)}foritem,scoreinzip(normalized,scores)),keylambdaitem:item[rerank_score],reverseTrue,)returnranked[:top_n]if__name____main__:rerankerCrossEncoder(MODEL_NAME)rows[{id:a,text:远程办公申请由部门负责人审批并同步人力资源部门。},{id:b,text:出差前应在系统提交出差申请。},]resultrerank(远程办公需要谁批准,rows,reranker,top_n1)assertlen(result)1andresult[0][id]aprint(result)这个断言只验证示例数据的排序不能代替业务效果评测。还要注意模型分数某些模型原始输出是 logits加入 sigmoid 只会改变数值尺度不会改变同一批候选的排序。不要为了让分数“好看”而擅自把它展示为置信度。四、接到向量检索后而不是替换向量检索以下代码用一个小型内存候选集模拟向量库返回值。在项目里把vector_search替换为实际集合的query即可。真实查询建议先按租户、部门、文档状态、有效期过滤再向量召回candidate_k20到100的候选具体数值必须由延迟预算和离线评测决定不能把这里的数字当成通用参数。# pipeline.pyfrom__future__importannotationsfromsentence_transformersimportCrossEncoderfromrerankimportrerank,MODEL_NAMEdefvector_search(question:str,allowed_departments:set[str],candidate_k:int20)-list[dict]:# 用来演示接口形状生产实现应由向量库完成相似度检索和服务端 ACL 过滤。all_rows[{id:policy-1,department:hr,source:hr/remote.md,text:远程办公申请须经直属部门负责人审批并抄送人力资源部门。},{id:policy-2,department:finance,source:finance/travel.md,text:出差申请应在出发前提交费用按差旅标准报销。},{id:policy-3,department:hr,source:hr/attendance.md,text:员工应按规定完成每日考勤打卡异常考勤在三个工作日内说明。},]return[rowforrowinall_rowsifrow[department]inallowed_departments][:candidate_k]defretrieve(question:str,departments:set[str],model:CrossEncoder)-list[dict]:candidatesvector_search(question,departments,candidate_k20)# ponytail: 先用固定候选池只有评测显示正确证据常在池外时再提升 candidate_k 或增加召回路。returnrerank(question,candidates,model,top_n4)if__name____main__:modelCrossEncoder(MODEL_NAME)hitsretrieve(远程办公由谁审批,{hr},model)asserthitsandhits[0][source]hr/remote.mdprint(hits)在代码里把allowed_departments作为函数参数是为了强调它来自认证后的服务端身份而不是请求体中的一个任意字符串。生产接口也不要把所有候选文本打印到日志。调试时常有人把完整候选和 prompt 输出到 APM后来才发现日志平台的访问范围比文档库宽得多。五、候选数、返回数与延迟预算双阶段的两个数字经常被混为一谈。candidate_k是第一阶段召回多少条交给 rerankertop_n是重排后保留多少条给生成模型。前者越大正确证据留在池中的机会越高但 Cross-Encoder 推理越慢后者越大生成模型能看到更多证据却可能被冗余上下文干扰并增加 token 成本。合理的做法是画出候选数—Recallk 和候选数—P95 延迟两条曲线。假设正确证据在 top-20 的召回已接近 top-50而 P95 在 50 时明显上升就没有理由默认传 50 条。又假如 rerank 后 top-3 的证据已足够支持大多数问题top-8 只增加重复则优先传 top-3。这里的“假如”是评测设计的例子不是未经测量的性能承诺。重排可以做批处理。CrossEncoder.predict接受 pairs 列表避免为每个候选单独发起 Python 调用。并发较高时要为模型推理设置队列、最大批量和超时CPU 上跑大模型会显著拉长尾延迟GPU 也可能因显存竞争抖动。不要在每一次请求里重新CrossEncoder(MODEL_NAME)权重加载既慢又占内存应在进程生命周期内加载一次并通过健康检查确认模型已就绪。若服务端跑多个 worker每个 worker 都会加载一份模型。对显存敏感的部署可先用单模型服务应用以 HTTP 或 RPC 调用它这增加了一个服务但比每个 Web worker 各自抢 GPU 更容易控制。是否拆服务不该从架构图开始而应由实际并发、显存和故障隔离需求决定。六、评测要证明“排序变好”不是只看回答更像人建立对照非常简单保持文档、切分、embedding、过滤规则和 candidate_k 不变只改变是否使用 rerank。对每个问题保存第一阶段候选 ID 和重排后 ID计算 Recallk、MRR、nDCG或者至少记录正确证据的排名变化。若你没有分级相关性标注MRR 往往比 nDCG 更容易解释。以下是一个最小的 MRR 对比脚本。它输入两份结果文件比较同一个金标准集合没有足够标注时它不会替你“自动判分”。gold_ids可包含多个同等有效的证据 ID。# compare_rerank.pyfrom__future__importannotationsimportjsonfrompathlibimportPathdefrows(path:str)-dict[str,dict]:return{r[id]:rforrinmap(json.loads,Path(path).read_text(encodingutf-8).splitlines())}defmrr(gold:dict[str,dict],ranked:dict[str,dict],k:int10)-float:total0.0forqid,labelingold.items():targetsset(label[gold_ids])idsranked.get(qid,{}).get(ids,[])[:k]positionnext((ifori,item_idinenumerate(ids,1)ifitem_idintargets),None)total0.0ifpositionisNoneelse1.0/positionreturntotal/len(gold)ifgoldelse0.0if__name____main__:goldrows(gold.jsonl)before,afterrows(vector.jsonl),rows(reranked.jsonl)vector_mrr,rerank_mrrmrr(gold,before),mrr(gold,after)assert0vector_mrr1and0rerank_mrr1print(fvector MRR10{vector_mrr:.3f}; reranked MRR10{rerank_mrr:.3f})除了数值还要保留失败样本。一个非常有价值的失败模式是“词面相同、语义不同”问“审批人”候选里出现了“审批流程说明”却没有实际角色另一个是“时间冲突”旧制度因词汇更接近而被排第一。后者不该单纯靠 reranker 解决应在候选过滤中排除失效版本。还有“跨段证据”正确答案需要两段同时出现reranker 把其中一段升到第一却把另一段挤掉最终生成仍会遗漏条件。生成层也应单独评估。随机抽取重排前后相同问题要求评审只依据原始资料判断答案是否忠实、引用是否支持每个结论、无答案时是否拒绝而不是凭文风好坏评分。为了减少期望效应可以隐藏“是否使用重排”的版本标签。不要让模型自评后把分数当作唯一事实特别是制度和合规场景。七、分数阈值、拒答与多样性常见需求是“分数低于某值就不要回答”。这里有两个风险。第一Cross-Encoder 的原始分数未必可跨问题比较同一模型对短问题和长问题的尺度可能不同第二阈值通过后也不意味着候选完整支持结论。更稳妥的第一步是以标注集绘制正负样本的分数分布再选择满足业务风险的阈值并把“低分拒答”视为可测量的分类规则而非一句 prompt。另外排序最前面的三条很可能来自同一节相邻窗口。可以在 rerank 之后加入一个轻量的来源多样性约束同一(source, section)最多保留两条其余从后续候选补齐。它不适用于所有问题——有的问题确实需要同一章节的连续三段——所以应该在评测中验证而不是强行套用。defdiversify(rows:list[dict],limit_per_section:int2)-list[dict]:selected,counts[],{}forrowinrows:key(row.get(source),row.get(metadata,{}).get(section))ifcounts.get(key,0)limit_per_section:continuecounts[key]counts.get(key,0)1selected.append(row)returnselected这段函数故意简单它只减少明显重复并不声称实现了最优的多样性排序。如果离线结果显示某类跨段问题被它伤害就关闭它或只针对文档窗口高度重叠的集合启用。工程上最危险的不是简单规则而是没有指标却把规则当成普遍真理。八、模型选择与安全边界选择 reranker 时至少检查四件事语种/领域是否匹配最大输入长度是否覆盖你的 chunk许可证是否允许商业或内部使用推理资源是否满足延迟目标。通用英文 MS MARCO 模型可以验证代码路径不应自动被当成中文企业制度的最佳模型。若候选文档混合中英最好在相同中文问题集、相同切分条件下比较少量候选模型而非只看模型榜单。模型下载同样是供应链问题。生产部署应固定模型 revision 或校验哈希使用受控镜像或模型仓库不要让线上服务在启动时按一个浮动名称联网拉取最新权重。对于远端重排 API除了 API key 管理还需评估是否允许将候选原文传出。对包含个人信息、合同、源代码的资料答案不是“脱敏一下再说”而是先由数据负责人确认处理边界。对输入要有长度、频率和格式限制。攻击者可以提交超长问题迫使系统构造大量 pairs也可以用大量低价值请求耗尽推理队列。接口层需要认证、限流、超时和请求体大小上限业务层需要按租户隔离索引日志层需要对问题和片段做最小化记录。Reranker 只是模型组件不应绕开现有的安全控制。九、何时不该加 Rerank如果知识库只有几百个短且结构良好的 FAQembedding 检索的 top-3 在评测中已经稳定命中加入 rerank 只会增加部署、延迟和排障面。若主要问题是文档 OCR 错误、过时版本未清理或权限 metadata 缺失重排也是在给脏数据排序。若正确答案必须跨多个系统实时计算例如库存余额或审批状态应调用受权限控制的业务 API而不是把昨天导出的报表交给 reranker。一个更朴素的替代方案是先改进切分与 metadata再取稍大的候选池并在 UI 中显示来源片段让用户判断。只有当离线日志显示“正确证据经常在候选池内但名次不够前”并且延迟预算允许第二阶段推理时rerank 才是有证据的投资。十、从分数到决策离线校准比硬编码阈值更可靠如果业务确实需要“没有足够证据就拒答”就把它当作一个分类问题做校准。收集一批已标注的问题—候选对标注每个候选是“可直接支持”“相关但不能支持”还是“不相关”。对每个问题记录重排最高分、第一与第二名的分差、前几名来源是否一致、候选的文档状态。然后分别统计不同阈值下的误答率和拒答率而不是只挑一个能让准确率看上去最高的点。阈值取舍必须由场景决定。内部搜索助手可以接受更多“请查看来源后确认”的回答因而阈值不必过严涉及报销、合规或安全操作的机器人则宁可多拒答也不能把相关片段当作依据。更稳妥的输出可分三级证据充分时给简要结论和引用证据相关但不完整时仅展示相关条款并提示确认无证据或冲突时明确拒绝下结论。把这些状态变成响应字段而非让前端从一句自然语言猜测模型态度。第一名与第二名的分差有时也很有用。如果最高分很高但第二名紧随其后且来源冲突可能表示问题存在歧义如果最高分远高于其余候选且文档有效证据通常更集中。但分差仍受模型和候选池影响只能作为特征的一部分。不要把“分差大”直接写成“模型 99% 确定”。十一、跨语言、缩写与专有名词先确认召回再选模型企业资料常混有中英文产品名、工号、接口字段、缩写和部门别称。问题写“VPN 申请”文档写“远程接入权限”问题写“报销单”文档写“费用申请单”。这类差异有两层向量召回是否能把两种说法放到同一候选池重排模型是否能在上下文中判断哪个概念真正对应。若第一层已经漏掉替换 reranker 不会有任何效果。可以按真实问题建立一个小型同义表达集但不要在客户端悄悄把用户问题替换成某种推测。更可控的做法是把受治理的同义词作为额外检索查询或 metadata 别名记录展开后的查询然后保留原问题进入 rerank避免改写带来意外语义。对于型号、合同号、错误码等精确标识向量相似度未必优于关键词检索常见做法是把关键词召回和向量召回合并、去重再统一重排。这也解释了为什么“只用向量库”有时很脆弱。关键词匹配擅长精确 token向量擅长同义表达二者在候选阶段互补reranker 负责在合并后的几十条里做细排。是否需要混合检索依然要靠失败样本证明若评测中专有名词问题频繁漏召回它是低风险、可解释的下一步若问题全是自然语言制度问答先保持简单更好。十二、与文档版本和过滤规则协同排序模型通常只看问题和文本无法天然知道“本制度已失效”或“该条只适用于上海分公司”。把版本、生效日期、地域、产品线、部门和保密级别当作 metadata在第一阶段完成硬过滤。硬约束不应该交给 soft score一段非常相关但已失效的旧制度分数再高也不应进入结果。有些规则不是简单过滤例如“当前版本优先但用户明确询问历史版本”。这类需求可以把查询模式写成受控参数默认仅 active审计角色可以指定历史日期界面明确展示版本。不要让 LLM 从问题中自行决定可以越过版本边界更不要用“请优先最新文档”这样的提示词代替数据库条件。模型会犯错过滤规则应该可测试、可审计。重排输出也应保留第一阶段分数与所有 metadata。排查一次错答时若只能看到最后三段文本无法知道它是因为正确内容没召回、被权限过滤、在重排中降级还是被最终上下文截掉。按请求 ID 保存最小必要诊断记录既能支持评测也能发现一个新版本发布后出现的排序回归。十三、服务化时的资源管理与降级策略本地调用很适合验证逻辑线上则要面对模型加载、显存、并发和升级。推理服务启动后先完成权重加载和一次健康检查未就绪就不要接收流量设置输入最大长度、候选最大数、批量大小和超时记录队列等待时间与推理时间避免把排队误判为模型变慢。CPU 方案成本低但尾延迟可能不稳定GPU 方案吞吐高但要避免多个进程各自复制权重。遇到模型服务不可用时最安全的降级一般是回退到“第一阶段检索 明确来源”并在响应中标记排序服务暂不可用不要自动扩大候选并让生成模型在更多噪声中自由发挥。是否允许这种降级要由业务风险决定高风险领域可以直接返回暂不可用低风险知识搜索可显示原始候选供用户自行判断。无论哪一种监控都应将降级次数单独统计防止它长期悄悄成为常态。模型升级也应走影子验证。新模型对同一批匿名问题生成排序但暂不影响用户比较它与线上模型的 MRR、拒答行为、延迟和关键失败样本再决定是否灰度。固定模型 revision、容器镜像和依赖版本能让回滚真正可行。只记录一个浮动模型名发生回归时很可能连上一个可复现的权重都找不到。十四、如何读懂“重排没有提升”的结果没有提升并不意味着实验失败。它可能说明当前 embedding、切分和候选数量已经足以完成任务新增一个模型只是增加成本也可能说明评测集主要是精确关键词问题Cross-Encoder 的优势没有被触发还可能是你选用的模型语种不匹配、输入被过度截断、金标准未标注跨段证据。与其为了证明投入合理而继续调参不如把原因写进实验报告。尤其警惕平均数掩盖伤害。整体 MRR 略升但“例外条款”“否定问题”或“最新版本”类别明显下降可能比均值更重要。按问题类型、文档类型、语言和权限范围切分结果才能发现模型偏好。对于关键问题建立回归集每次改 embedding、rerank、chunk 或 prompt 都跑一次任何关键条目退化都需要解释或阻止发布。同样不要把生成答案的文采变化误当作检索提升。重排评估首先看证据排名生成评估再看是否忠实引用两者分开记录。这样即使某个模型让答案更简洁你也能知道它没有偷偷丢掉关键条件。十五、最小发布清单把 reranker 接进服务前可以用一张很短的清单收尾确认正确证据在第一阶段候选池的召回率足够确认 ACL、租户和有效版本在任何模型调用前过滤确认模型许可证、权重来源、语言能力和最大长度已审核确认候选上限、输入上限、超时、限流和降级路径可观测确认评测同时记录排序、引用和高风险问题的拒答确认日志不写入不必要的全文和密钥。清单并不花哨却能避免最常见的“本地很准上线很危险”。如果还没有一份标注问题集先不要着急把模型服务部署到 GPU。用几十个有依据的真实问题建立基线通常比连续试十个开源模型更快告诉你是否需要重排。若候选池内的正确证据已经稳定排第一最好的 reranker 就是暂时不加若正确证据常在第十名附近才值得为它分配延迟预算。需要解释每次排序的原因时不要编造模型的“思考过程”。可解释的事实是该候选来自哪份有效文档、通过了哪些过滤、在向量阶段和重排阶段分别排第几、最终是否被截断这些记录足以支持工程复盘也不会把不可验证的内部推理伪装成证据。对同一个问题重复请求时候选顺序若偶尔变化也要先检查索引是否在更新、服务是否使用了不同模型 revision、并发批处理是否影响了浮点计算再判断是模型不稳定。把请求时的索引版本与模型版本写入诊断记录才能把偶发问题从“感觉不稳定”变成可定位的事实。还应为关键查询保留一次人工复核入口。技术指标可以告诉你排名变了却不能替业务负责人确认“这条制度解释是否该由系统自动给出”。人机协同不是失败兜底而是高风险信息系统应有的最后一道边界。十六、小结Rerank 的价值在于把“语义大致相近”的候选重新排成“最能回答当前问题”的少量证据。它应位于权限过滤之后、LLM 生成之前第一阶段负责召回Cross-Encoder 负责精排最终只传递有限且多样的上下文。把分数当作排序信号不要贸然解释成置信度把改进建立在固定数据集的排名对照上不要凭一两个演示问题下结论。最小实现只需要一个批量predict、一个不可变的排序函数和一份问题—证据评测集。后续是否扩大候选池、加多样性、做阈值拒答、拆独立推理服务都应该由失败样本、P95 延迟和合规边界推动。这样做的好处不仅是“答案更准”还在于你清楚它为什么变准以及何时不值得继续加复杂度。参考资料Sentence TransformersCross-Encoder UsageSentence TransformersRerankersSentence Transformers CrossEncoder APIChromaCollection Query APIOWASP Top 10 for LLM Applications