面试官让你手撕代码?混合检索与Rerank精排底层实现全解(非常详细),从原理到实战精通,收藏这一篇就够了!
上周有个学员面蚂蚁就被追到了代码层面。前面聊混合检索和 Rerank 的策略聊得很顺畅面试官也频频点头。然后画风突变“策略我了解了现在我想看看你对实现细节的理解。你们的 retrieval 函数从接收到用户 query 到返回最终结果中间经过了哪些步骤每一步的输入输出是什么”他开始讲先做 Embedding然后去向量库搜……面试官打断“向量检索和 BM25 是串行还是并行结果怎么合并合并用的什么算法RRF 还是加权求和参数怎么设的”他卡住了——知道用了 RRF但说不清 RRF 的公式和为什么 k60。面试官继续“Rerank 对所有页的结果都做精排吗Top 100 全部过 Cross-Encoder 不会太慢你们怎么优化的”又答不上来。面试的上半场考的是你知不知道下半场考的是你做没做过。策略层面的东西看几篇文章就能讲但代码层面的细节只有真正写过、调过、踩过坑的人才能讲清楚。今天把检索函数从入口到返回的每一行逻辑拆开讲。一、retrieval 函数的全景从 query 到结果在我们的实战项目中检索模块的核心是一个 retrieval 函数。它做的事情用一句话概括接收用户 query返回最相关的 Top K 个文档片段附带相似度分数和来源信息。整个函数的执行流程分五步第一步构建检索请求。把用户的 query、知识库 ID、相似度阈值、分页参数等打包成一个请求对象。第二步执行混合检索。同时发起向量检索和 BM25 关键词检索用融合算法合并结果。第三步重排精排。对合并后的候选结果用 Cross-Encoder 模型重新打分排序。第四步阈值过滤。过滤掉相似度低于阈值的结果避免返回不相关内容。第五步组装返回。把最终结果文本内容、相似度分数、文档来源、页码等打包返回。下面逐步拆解。二、第一步构建检索请求def retrieval(self, question, embd_mdl, tenant_ids, kb_ids, page, page_size, similarity_threshold0.2, vector_similarity_weight0.3, top1024, rerank_mdlNone, highlightFalse): ranks {total: 0, chunks: [], doc_aggs: {}} RERANK_PAGE_LIMIT 3 req { kb_ids: kb_ids, size: max(page_size * RERANK_PAGE_LIMIT, 128), question: question, vector: True, topk: top, similarity: similarity_threshold, }几个关键设计决策size 为什么设成 page_size × 3因为前 3 页要做 Rerank 精排。Rerank 会重新排序原来排第 15 的可能被提到第 1——所以必须先拉回足够多的候选至少 3 页的量精排后再截取当前页。RERANK_PAGE_LIMIT 3 是什么意思只对前 3 页做精排。第 4 页及以后直接用初步检索的排序不再调 Cross-Encoder——因为精排计算成本高用户翻到第 4 页的概率很低不值得花这个算力。top 1024 是初步检索的最大候选数。向量库最多返回 1024 条保证候选池足够大。三、第二步混合检索的实现这是整个函数的核心。search 方法内部同时执行了向量检索和 BM25 检索然后融合。向量检索部分def get_vector(self, txt, emb_mdl, topk10, similarity0.1): # 对query文本生成Embedding向量 qv generate_embedding(txt) embedding_data [float(v) for v in qv] # 构造向量匹配表达式 vector_column_name fq_{len(embedding_data)}_vec return MatchDenseExpr(vector_column_name, embedding_data, float, cosine, topk, {similarity: similarity})BM25 检索部分对 query 做分词和关键词提取后构造一个 MatchTextExpr用 Elasticsearch 的 query_string 查询。关键配置是 minimum_should_match——控制查询词中至少有多少比例必须匹配。两路结果的权重分配# 向量检索的权重vector_weight vector_similarity_weight # 默认0.3# BM25的权重 1 - 向量权重text_weight 1.0 - vector_similarity_weight # 默认0.7为什么默认给 BM25 更高的权重0.7 vs 0.3因为在我们的金融保险场景中大量查询是精确术语型的“免赔额”“等待期”“犹豫期”BM25 的关键词匹配对这类查询更有效。如果你的场景以语义查询为主应该反过来给向量更高权重。融合算法RRFReciprocal Rank Fusion两路检索返回的分数量纲不同向量是 0-1 的余弦相似度BM25 是无上界的 TF-IDF 分数不能直接相加。RRF 的巧妙之处在于它只看排名不看分数def rrf_fusion(vector_results, bm25_results, k60): doc_scores {} # 向量检索的排名贡献 for rank, (doc_id, _) in enumerate(vector_results, start1): doc_scores[doc_id] 1 / (k rank) # BM25的排名贡献 for rank, (doc_id, _) in enumerate(bm25_results, start1): if doc_id in doc_scores: doc_scores[doc_id] 1 / (k rank) else: doc_scores[doc_id] 1 / (k rank) # 按融合分数排序 return sorted(doc_scores.items(), keylambda x: x[1], reverseTrue)为什么 k60k 是一个平滑参数值越大排名靠后的结果贡献越小。k60 是信息检索领域的经验值在大多数 benchmark 上表现最优。我们也实测过 k20 和 k100差异在 1% 以内所以直接用默认值。RRF vs 加权求和的对比我们在 200 条测试 query 上做过对比——RRF 的 MRR 比加权求和高 3 个百分点0.89 vs 0.86。RRF 的优势在于不需要归一化、不需要调权重参数、对异常分数不敏感。实际项目中强烈推荐 RRF。四、第三步Rerank 精排的分页策略精排是整个检索流程中计算最昂贵的环节——每个候选都要跟 query 拼接后过一次 Cross-Encoder。所以必须控制精排的候选数量。if page RERANK_PAGE_LIMIT: # 前3页做精排 if sres.total 0: sim, tsim, vsim self.rerank_by_model( rerank_mdl, sres, question, 1 - vector_similarity_weight, # token权重 vector_similarity_weight, # 向量权重 ) # 精排后按分数重新排序截取当前页 idx np.argsort(sim * -1)[(page-1)*page_size : page*page_size]else: # 第4页及以后跳过精排直接用初步排序 sim tsim vsim [1] * len(sres.ids) idx list(range(len(sres.ids)))rerank_by_model 内部做了什么它把 query 和每个候选 Chunk 拼接成 (query, document) 对送入 Cross-Encoder比如 bge-reranker-large做相关性打分。然后把 Cross-Encoder 的分数跟原始的 token 相似度、向量相似度做加权融合得到最终的综合分数。三个分数的融合sim最终综合相似度用于排序和阈值过滤tsim纯 token 相似度BM25 维度的贡献vsim纯向量相似度Embedding 维度的贡献返回结果中三个分数都会带上方便后续分析哪个维度贡献最大。五、第四步和第五步过滤与组装for i in idx: # 阈值过滤分数太低的直接跳过 if sim[i] similarity_threshold: break # 达到当前页数量就停 if len(ranks[chunks]) page_size: break chunk sres.field[id] d { chunk_id: id, content_ltks: chunk[content_ltks], doc_id: chunk.get(doc_id, ), docnm_kwd: chunk.get(docnm_kwd, ), kb_id: chunk[kb_id], similarity: sim[i], # 综合分 vector_similarity: vsim[i], # 向量分 term_similarity: tsim[i], # 关键词分 positions: position_int, # 原文位置 } ranks[chunks].append(d) # 文档聚合统计 if dnm notin ranks[doc_aggs]: ranks[doc_aggs][dnm] {doc_id: did, count: 0} ranks[doc_aggs][dnm][count] 1doc_aggs 是什么统计每个文档被召回了多少个 Chunk。如果某个文档的 Chunk 被大量召回说明这个文档跟 query 高度相关——这个信息可以用于前端展示最相关的文档。六、Elasticsearch 的索引配置BM25 检索的效果很大程度上取决于 ES 的索引配置。在我们的项目中做了三个关键优化自定义分词器 保险领域词典jieba 默认会把意外伤害切成意外“伤害”把犹豫期切成犹豫“期”。加入自定义词典后这些术语会被当作完整的词保留。分词准确率从 73% 提升到 96%直接带动 BM25 召回率从 0.79 提升到 0.84。同义词过滤器在 ES 分析器中配置同义词——孩子,儿童,小孩,未成年人会被当成等价词处理。用户搜孩子摔伤也能匹配到包含未成年人意外伤害的文档。字段权重章节标题字段section_title的权重设为正文的 2 倍。如果查询词出现在标题中说明这个 Chunk 的主题就是用户要找的内容应该排在前面。七、面试怎么答检索实现先画流程30 秒。“retrieval 函数分五步构建请求 → 混合检索向量BM25 并行→ RRF 融合 → Rerank 精排 → 阈值过滤返回。”讲混合检索40 秒。“向量检索用 BGE-M3 生成 Embedding在 Milvus 的 HNSW 索引上做 cosine 搜索BM25 用 Elasticsearch 的 query_string配了自定义分词器和保险领域同义词。两路结果用 RRF 融合——只看排名不看分数避免了分数量纲不一致的问题k60 是经验值。”讲 Rerank 策略30 秒。“不是所有结果都做精排——只对前 3 页做 Cross-Encoder 重排第 4 页及以后跳过。因为精排计算成本高用户翻到后面页的概率很低。精排后的综合分数是 token 相似度、向量相似度和 Cross-Encoder 分数的加权融合。”讲量化效果20 秒。“RRF 比加权求和 MRR 高 3 个百分点自定义分词器让 BM25 召回率从 0.79 提到 0.84整套组合的最终 MRR 达到 0.92。”写在最后策略和代码是两个层面的东西。策略告诉你应该做混合检索Rerank代码告诉你具体怎么做、每一步的输入输出是什么、为什么这样设计。面试的前半场靠策略通关后半场靠代码拉开差距。如果你能在面试中画出 retrieval 函数的五步流程讲清楚 RRF 的公式和 k60 的来源解释为什么只对前 3 页做精排——面试官会知道你是真正做过这个系统的人而不是看了几篇博客的理论家。学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%免费】