上周有个学员面蚂蚁就被追到了代码层面。
前面聊混合检索和 Rerank 的策略聊得很顺畅,面试官也频频点头。
然后画风突变:“策略我了解了,现在我想看看你对实现细节的理解。你们的 retrieval 函数,从接收到用户 query 到返回最终结果,中间经过了哪些步骤?每一步的输入输出是什么?”
他开始讲"先做 Embedding,然后去向量库搜……"
面试官打断:“向量检索和 BM25 是串行还是并行?结果怎么合并?合并用的什么算法?RRF 还是加权求和?参数怎么设的?”
他卡住了——知道用了 RRF,但说不清 RRF 的公式和为什么 k=60。
面试官继续:“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_threshold=0.2, vector_similarity_weight=0.3, top=1024, rerank_mdl=None, highlight=False): 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, topk=10, similarity=0.1): # 对query文本生成Embedding向量 qv = generate_embedding(txt) embedding_data = [float(v) for v in qv] # 构造向量匹配表达式 vector_column_name = f"q_{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 的关键词匹配对这类查询更有效。如果你的场景以语义查询为主,应该反过来给向量更高权重。
融合算法:RRF(Reciprocal Rank Fusion)
两路检索返回的分数量纲不同(向量是 0-1 的余弦相似度,BM25 是无上界的 TF-IDF 分数),不能直接相加。RRF 的巧妙之处在于它只看排名不看分数:
def rrf_fusion(vector_results, bm25_results, k=60): doc_scores = {} # 向量检索的排名贡献 for rank, (doc_id, _) in enumerate(vector_results, start=1): doc_scores[doc_id] = 1 / (k + rank) # BM25的排名贡献 for rank, (doc_id, _) in enumerate(bm25_results, start=1): 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(), key=lambda x: x[1], reverse=True)
为什么 k=60? k 是一个平滑参数,值越大排名靠后的结果贡献越小。k=60 是信息检索领域的经验值,在大多数 benchmark 上表现最优。我们也实测过 k=20 和 k=100,差异在 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"] += 1
doc_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 融合——只看排名不看分数,避免了分数量纲不一致的问题,k=60 是经验值。”
讲 Rerank 策略(30 秒)。 “不是所有结果都做精排——只对前 3 页做 Cross-Encoder 重排,第 4 页及以后跳过。因为精排计算成本高,用户翻到后面页的概率很低。精排后的综合分数是 token 相似度、向量相似度和 Cross-Encoder 分数的加权融合。”
讲量化效果(20 秒)。 “RRF 比加权求和 MRR 高 3 个百分点;自定义分词器让 BM25 召回率从 0.79 提到 0.84;整套组合的最终 MRR 达到 0.92。”

写在最后
策略和代码是两个层面的东西。策略告诉你"应该做混合检索+Rerank",代码告诉你"具体怎么做、每一步的输入输出是什么、为什么这样设计"。面试的前半场靠策略通关,后半场靠代码拉开差距。
如果你能在面试中画出 retrieval 函数的五步流程,讲清楚 RRF 的公式和 k=60 的来源,解释为什么只对前 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 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

461



被折叠的 条评论
为什么被折叠?



