搞定京东面试官:RAG Rerank 核心技术全攻略(非常硬核),召回质量优化从入门到精通,收藏这一篇就够了!

张开发
2026/4/10 17:04:33 15 分钟阅读

分享文章

搞定京东面试官:RAG Rerank 核心技术全攻略(非常硬核),召回质量优化从入门到精通,收藏这一篇就够了!
上周有个学员面京东项目介绍里提到自己做的 RAG 系统用了 Top-20 召回。面试官问20 条里召回质量怎么样学员回答说挺好的语义相似度都挺高。面试官接着追问相似度高等于相关度高吗学员沉默了一下说……差不多吧。面试官又问那你知道 Bi-Encoder 和 Cross-Encoder 的区别吗如果 Bi-Encoder 打分高但 Cross-Encoder 打分低说明什么问题学员答不上来。面试官直接连环问你召回了 20 条其中有 15 条是语义相近但答案无关的噪声LLM 拿着这 15 条垃圾在脑补答案——你还以为召回质量挺好的最后面试官说召回只是第一步Rerank 才是决定送给 LLM 什么内容的关键。你连这道门都没做。这个问题绝对不是个例。在我们训练营里很多学员刚开始做 RAG 项目时都把大量精力放在向量检索调参上却对 Rerank 这一环节几乎没有认知。今天这篇文章我们就系统地把 RAG 中的 Rerank 环节讲清楚包括原理、架构、阈值过滤、领域微调以及如何在面试中完整地回答这个问题。一、召回多不等于召回准Bi-Encoder 的固有局限理解 Rerank 为什么必要首先要理解 Bi-Encoder 的工作方式和它的根本性局限。Bi-Encoder 的工作方式是把 query 和 document 分别独立地送入同一个编码器各自得到一个向量表示然后用余弦相似度或点积来衡量二者的距离。这是一种双塔结构query 塔和 document 塔在编码过程中完全没有交互。正因如此document 的向量可以离线预先计算好并建索引检索时只需要对 query 向量做一次近邻搜索速度极快百万量级的文档可以在毫秒内完成。但这个架构有一个根本性的问题query 和 document 在编码阶段没有任何交互模型只能学到这两段文本的话题是否相近而无法判断这段文档是否真正回答了用户的问题。举一个具体的例子。我们训练营的金融保险实战项目使用了 5000 份合同文档里面有大量关于等待期的条款描述。当用户问重疾险的等待期是多少天时向量检索可能召回 20 条文档其中有“等待期内发生的疾病不在保障范围之内”“等待期是保险合同的重要组成部分”“等待期的计算方式从合同生效日起计算”“本产品等待期为 180 天”这才是真正有用的前三类文档都含有等待期这个词语义向量距离也很近Bi-Encoder 会给出较高的相似度分数。但它们根本没有回答用户的问题只有最后一类才真正包含答案。在 20 条召回结果里这样的话题相关但答案无关的噪声文档可能占到一半以上。这就是召回精度低的根本原因Bi-Encoder 衡量的是话题相似性而 RAG 真正需要的是答案相关性。这两者之间有本质的鸿沟而 Rerank 就是用来填补这道鸿沟的。二、Cross-Encoder Reranker 的工作原理Cross-Encoder 的设计思路和 Bi-Encoder 完全相反它把 query 和 document 拼接在一起作为一个整体送入 Transformer 编码器让二者在每一层注意力计算中都充分交互最终输出一个 0 到 1 之间的相关性分数。这种方式的核心优势在于 token 级别的深度交互。模型可以看到用户问的是天数也可以看到文档里写了具体的数字从而判断这个文档是否真正回答了这个问题。它不再只是比较两段文本的话题距离而是在做真正意义上的相关性判断。代价也很明显Cross-Encoder 无法预先建索引因为每次都必须把 query 带入对每一对 (query, doc) 单独进行前向计算。如果文档库有 100 万条在线对每条都跑一次 Cross-Encoder延迟是完全无法接受的。这就决定了 Cross-Encoder 只能用在精排阶段对少量候选文档比如 20 条做精细打分而不能替代向量检索做初筛。目前常用的 Reranker 模型有BGE-Reranker 系列BAAI/bge-reranker-v2-m3中文效果好Cohere Rerank API闭源效果强JinaAI Reranker下面是一个使用 BGE-Reranker 对召回结果重排的代码示例from transformers import AutoTokenizer, AutoModelForSequenceClassification import torch # 加载 BGE-Reranker 模型 model_name BAAI/bge-reranker-v2-m3 tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained(model_name) model.eval() def rerank(query: str, documents: list[str], top_k: int 5) - list[dict]: 使用 Cross-Encoder 对候选文档重排序 Args: query: 用户问题 documents: Bi-Encoder 召回的候选文档列表 top_k: 重排后保留的文档数量 Returns: 按相关性分数降序排列的文档列表每条包含文本和分数 # 构造 (query, doc) 对 pairs [[query, doc] for doc in documents] # tokenize注意 Cross-Encoder 把两段文本拼在一起 inputs tokenizer( pairs, paddingTrue, truncationTrue, max_length512, return_tensorspt ) with torch.no_grad(): # 模型输出 logits形状为 (num_docs, 1) scores model(**inputs).logits.squeeze(-1) # sigmoid 转换为 0-1 的相关性概率 scores torch.sigmoid(scores).tolist() # 将文档和分数打包按分数降序排列 scored_docs [ {text: doc, score: score} for doc, score in zip(documents, scores) ] scored_docs.sort(keylambda x: x[score], reverseTrue) # 返回 Top-K return scored_docs[:top_k]这段代码的核心逻辑清晰把 query 和每条文档配对一起送入 Cross-Encoder得到精细的相关性分数按分数重排后取 Top-K 送给 LLM。三、召回-精排级联架构效果和性能的最优解理解了 Bi-Encoder 和 Cross-Encoder 各自的特点最优的架构设计就很自然了把二者组合成一个级联管道。整体流程如下用 Bi-Encoder向量检索从全量文档库中快速召回 Top-20 候选文档毫秒级用 Cross-EncoderReranker对这 20 条候选文档精细打分取 Top-5几十毫秒把这 Top-5 条高质量文档送入 LLM 生成回答为什么不直接用 Cross-Encoder 做全量检索原因很简单假设文档库有 10 万条文档每次查询都要对 10 万个 (query, doc) 对跑 Cross-Encoder即使每对只需要 1 毫秒总延迟也高达 100 秒完全无法用于线上推理。而先用向量检索缩减到 20 条候选再用 Cross-Encoder 精排总延迟只增加几十毫秒工程上完全可接受。我们训练营的金融保险实战项目5000 份合同文档覆盖重疾险、医疗险、寿险等多个险种在引入 Reranker 前后做了系统性的对比评测。评测方式是人工标注 200 个典型问题的相关文档然后比较系统送给 LLM 的文档中噪声占比和最终回答的幻觉率。结果如下指标仅 Bi-Encoder 召回 Top-5Bi-Encoder Top-20 Reranker Top-5送给 LLM 的 Chunk 中噪声比例42%11%LLM 回答幻觉率18.3%6.4%平均检索延迟12ms58msPrecision50.610.84Recall50.580.79数据非常直观引入 Reranker 后噪声比例从 42% 降到 11%幻觉率从 18.3% 降到 6.4%代价是延迟从 12ms 增加到 58ms。对于一个问答系统来说这个延迟完全可以接受而幻觉率降低近 2/3 带来的体验提升是显著的。配图1四、相似度阈值过滤宁缺毋滥有了 Reranker 之后还有一个常被忽略的细节阈值过滤。Reranker 给每条文档打了一个 0-1 的相关性分数并按分数取 Top-K。但如果所有候选文档的相关性分数都很低即使取了 Top-5这 5 条文档也可能是噪声。此时强行把它们送给 LLMLLM 会基于这些低质量的上下文生成回答结果往往是幻觉。正确的做法是在 Reranker 打分之后再设一个绝对阈值比如 0.5。低于阈值的文档直接丢弃即使 Top-K 里最终只剩 1 条甚至 0 条也不要凑数追加低质量文档。如果所有文档都低于阈值应当直接告诉用户在知识库中未找到相关内容而不是让 LLM 瞎编一个答案。宁缺毋滥这是 RAG 系统工程实践中非常重要的一个原则。def filter_by_threshold( scored_docs: list[dict], threshold: float 0.5 ) - list[dict]: 对 Reranker 打分结果做阈值过滤低于阈值的文档直接丢弃 Args: scored_docs: rerank() 返回的文档列表每条包含 text 和 score threshold: 相关性分数阈值低于此值的文档不送给 LLM Returns: 过滤后的文档列表可能为空列表 filtered [doc for doc in scored_docs if doc[score] threshold] return filtered def rag_retrieve( query: str, vector_store, reranker_model, top_k_recall: int 20, top_k_rerank: int 5, threshold: float 0.5 ) - list[dict]: 完整的 RAG 检索流程向量召回 → Reranker 精排 → 阈值过滤 Args: query: 用户问题 vector_store: 向量数据库客户端 reranker_model: 已加载的 Reranker 模型 top_k_recall: 向量检索召回数量 top_k_rerank: Reranker 保留数量 threshold: 相关性阈值 Returns: 最终送给 LLM 的文档列表可能为空表示知识库中无相关内容 # 第一步向量检索召回候选文档 recall_results vector_store.similarity_search(query, ktop_k_recall) recall_texts [doc.page_content for doc in recall_results] # 第二步Reranker 精排取 Top-K reranked reranker_model.rerank(query, recall_texts, top_ktop_k_rerank) # 第三步阈值过滤宁缺毋滥 final_docs filter_by_threshold(reranked, thresholdthreshold) return final_docs阈值如何标定不建议直接拍脑袋设 0.5。正确的做法是用已标注的问答对做离线实验准备一批有标准答案的问题以及人工标注的相关/不相关文档然后遍历不同的阈值比如 0.3 到 0.8 步长 0.05计算每个阈值下的 Precision 和 Recall找到 F1 分数最高的阈值点作为线上使用的阈值。我们训练营的保险项目通过这种方式找到的最优阈值是 0.52在此阈值下 F1 从 0.74 提升到了 0.81。配图2五、Reranker 的领域微调通用的 BGE-Reranker 在通用场景表现不错但在专业垂直领域它的表现可能明显下降。原因在于通用 Reranker 在训练时见到的主要是通用语料对专业领域的表达方式、术语体系、答案形式理解不够深入。以我们训练营的保险项目为例用户会问轻症赔付比例是多少知识库里有一段文字写着轻度恶性肿瘤按基本保额的 20% 给付。通用 Reranker 可能给这段文字打出较低的分数因为轻症和轻度恶性肿瘤在通用语料中并不是高频的对应关系而保险从业者都知道这是标准对应。解决方案是对 Reranker 进行领域微调。微调的数据格式是三元组(query, positive_doc, negative_doc)即对于每个问题需要一条真正相关的文档和一条看起来相关但实际不相关的文档。难负例Hard Negative的质量是微调效果的关键。所谓难负例是指那些语义上与 query 很接近但实际上没有包含答案的文档——正是 Bi-Encoder 最容易误判的那类文档。如果负例太容易区分比如完全不同话题的文档模型学不到有价值的判别能力。import json def prepare_finetune_data( qa_pairs: list[dict], vector_store, reranker_model, output_path: str ) - None: 为 Reranker 微调准备训练数据三元组格式 Args: qa_pairs: 已标注的问答对列表每条包含 query、positive_doc、answer vector_store: 向量数据库用于挖掘难负例 reranker_model: 当前 Reranker 模型用于找出它打分最高但实际错误的文档 output_path: 输出的 jsonl 文件路径 training_examples [] for qa in qa_pairs: query qa[query] positive_doc qa[positive_doc] # 人工标注的正确相关文档 # 用向量检索召回候选从中挖掘难负例 # 难负例Bi-Encoder 打分高但实际不包含答案的文档 recall_candidates vector_store.similarity_search(query, k50) hard_negatives [] for candidate in recall_candidates: # 排除正例本身 if candidate.page_content positive_doc: continue # 选择语义相近在召回结果里但不包含正确答案的文档 # 这里需要人工标注或启发式规则判断是否含答案 if not contains_answer(candidate.page_content, qa[answer]): hard_negatives.append(candidate.page_content) if not hard_negatives: continue # 取最难的一个负例向量检索排名最靠前的不含答案文档 hardest_negative hard_negatives[0] training_examples.append({ query: query, pos: [positive_doc], # 正例列表 neg: [hardest_negative] # 难负例列表 }) # 按 BGE-Reranker 微调格式保存为 jsonl with open(output_path, w, encodingutf-8) as f: for example in training_examples: f.write(json.dumps(example, ensure_asciiFalse) \n) print(f共生成 {len(training_examples)} 条微调数据已保存至 {output_path}) def contains_answer(doc_text: str, answer: str) - bool: 简单的启发式判断文档是否包含答案的关键词 实际项目中可以用更复杂的语义匹配方式 answer_keywords answer.split() return any(kw in doc_text for kw in answer_keywords if len(kw) 1)在我们训练营的保险项目中我们对 BGE-Reranker-v2-m3 做了 500 条保险领域的微调数据训练包含重疾险、医疗险、意外险的高频问答对和精心挑选的难负例。微调前后的对比如下评估指标通用 BGE-Reranker领域微调后Precision50.710.86Recall50.680.82NDCG50.740.88幻觉率9.1%4.8%仅仅 500 条领域数据Precision5 从 0.71 提升到 0.86提升幅度非常显著。这说明在垂直领域做 Reranker 的领域微调是性价比极高的优化手段。配图3面试怎么答 RAG Rerank 问题遇到这类问题建议按以下四层结构来回答总时长控制在 2-3 分钟。第一层先点 Bi-Encoder 的固有局限20 秒先说清楚为什么需要 RerankBi-Encoder 编码 query 和 document 时是完全独立的没有交互它只能衡量话题相关性无法判断文档是否真正包含答案。“语义相似度高不等于答案相关度高”这是召回精度差的根本原因。第二层说 Cross-Encoder 原理和级联架构1 分钟Cross-Encoder 把 query 和 document 拼在一起做深度交互输出精细的相关性分数。代价是无法预建索引不能用于全量检索。所以最优架构是级联Bi-Encoder 召回 Top-20快Cross-Encoder 精排取 Top-5准再送 LLM。可以直接给出数据引入 Reranker 后我们项目的 LLM 幻觉率从 18.3% 降到 6.4%。第三层说阈值过滤和具体项目数据1 分钟Reranker 打分后还要做阈值过滤宁缺毋滥——低于阈值的文档直接丢弃不追加凑数。阈值通过离线标注实验找 F1 最大点来标定。我们项目最优阈值 0.52F1 从 0.74 提升到 0.81。体现出对 RAG 系统工程细节的掌握。第四层说领域微调30 秒加分项通用 Reranker 在专业领域可能表现欠佳可以用 500-1000 条三元组数据query、正例、难负例做领域微调。难负例要选语义相近但答案不同的文档。我们项目微调后 Precision5 从 0.71 提升到 0.86。这一点很多候选人答不到说出来会让面试官眼前一亮。写在最后RAG 的核心链路是召回-精排-生成三段式很多工程师把精力都放在前端的向量检索调参上却忽视了中间的 Rerank 环节。而事实上Reranker 决定了最终送给 LLM 的内容质量是整个链路中对最终回答质量影响最直接的环节。噪声文档进去幻觉答案出来——这不是 LLM 的问题是 RAG 工程没做好。领域微调、阈值标定、级联架构这三板斧是工业级 RAG 系统在 Rerank 环节必须掌握的核心实践。下次再有面试官问起 Rerank希望你能从容地展开这套完整的思路而不是停留在语义相似度挺高的水平上。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

更多文章