召回与排序优化
Q:为什么在检索阶段引入BM25?它和向量检索怎样组合?
来源:快手 AI Agent 开发一面 【字节二面同题:多路检索 + 向量/关键词各解决什么】【淘天Agent开发同题:为什么加 BM25 + 具体解决了什么 bad case】【钉学科技 FDE 实习一面追问:双路短板、融合与分类 bad case 验证】
新手答:“BM25 是传统检索方法,加上它可以互补。”
高手答:
为什么引入 BM25:向量检索擅长语义匹配,但在三类场景下表现很差:
- 精确术语:错误码
ERR_OOM_KILL、API 名称getUserProfile——向量化后精确信息被稀释 - 低频专业词:Embedding 模型对训练数据中低频的词 embedding 质量差
- 新词/缩写:近期出现的产品名、术语,Embedding 模型没见过
BM25 基于词频和逆文档频率做精确匹配,恰好弥补这三个短板;但它对同义词、改写和没有词面重合的语义查询较弱。两路是互补关系,不应预设固定增益,是否有效必须由本业务评测集证明。
组合方式:主流有两种融合策略:
方案一:分数融合(Score Fusion)
final_score = α × normalize(vector_score) + (1-α) × normalize(bm25_score)
需要先对两路分数做归一化(min-max 或 z-score),否则量纲不同无法直接加权
方案二:倒数排名融合(Reciprocal Rank Fusion, RRF)
RRF_score(d) = Σ 1/(k + rank_i(d))
只看排名不看分数,对分数分布差异免疫,k 通常取 60比例怎么设:不是拍脑袋定的。初始值用 0.7 向量 + 0.3 BM25,然后在评估集上做 grid search:
遍历 α = [0.5, 0.6, 0.7, 0.8, 0.9]
对每个 α,计算 Recall@10 和最终回答质量
选最优 α。不同业务场景最优值不同:
技术文档 → BM25 权重可以更高(精确术语多)
日常对话 → 向量权重更高(同义表达多)完整检索流程(从 query 到最终上下文):
- Query 预处理:意图识别、查询改写、关键词提取
- 双路并行召回:向量路取 Top-20~50,BM25 路取 Top-10~20,并行执行
- 合并去重:按文档 ID 去重,同一文档被多路命中的加分
- Rerank 精排:用 Cross-Encoder(如 bge-reranker-v2、Cohere rerank)对 query-doc pair 做精细打分
- Top-K 截断:取精排后 Top-3~5 作为最终上下文
- Prompt 组装:把检索结果按相关度排序拼入 Prompt,交给 LLM 生成回答
评估不能只看一个聚合 Recall。要把 bad case 按查询类型分桶,例如:产品编码、版本号、错误码等精确词面查询;同义词、口语改写和近义表达等语义查询;同时包含精确约束与自然语言描述的混合查询。分别检查 BM25 单路、向量单路、合并后的候选集和 rerank 后结果,才能发现“总体指标上涨,但产品编码类反而退化”这类问题,并把失败样本持续沉淀为分类回归集。
更广义的多路检索策略:
BM25 + 向量检索只是多路检索的起点。生产级 RAG 系统的多路检索可以更丰富:
| 检索路径 | 原理 | 擅长 | 不擅长 |
|---|---|---|---|
| 向量检索 | 语义相似度 | 同义改写、模糊表述 | 精确关键词、专有名词 |
| BM25 | 词频-逆文档频率 | 精确关键词、专有名词 | 同义词、语义理解 |
| 元数据过滤 | 结构化字段匹配 | 时间范围、类别筛选 | 语义理解 |
| 知识图谱 | 实体关系遍历 | 多跳推理、关联查询 | 模糊意图 |
融合策略:
多路召回后需要融合排序,主流方案:
- RRF(Reciprocal Rank Fusion):
score = Σ 1/(k + rank_i),不需要归一化,简单有效 - 加权分数融合:
score = α × vector_score + β × bm25_score,需要分数归一化到同一量纲 - 级联融合:第一路做粗召回(高召回率),第二路在结果集上做精排(高精确率)
实际生产中,RRF 是默认首选——不需要调权重,对不同分布的分数天然鲁棒。
差距在哪:新手只说了”互补”两个字。高手说清了 BM25 解决的三类具体问题、两种融合方案的差异、比例调优方法论,以及完整的端到端检索流程。面试官考的是你对混合检索的工程化认知——不只是”加了 BM25”,而是知道怎么组合、怎么调参、怎么评估。
追问:RRF 融合中 K 参数一般取值多少?大一点、小一点对结果有什么影响?
RRF 公式:score(d) = Σ 1/(K + rank_i(d)),K 是一个平滑常数,决定了排名差异的敏感度。
| K 值 | 效果 | 适用场景 |
|---|---|---|
| K 很小(如 1~10) | Top-1 和 Top-2 的分数差异极大,”赢家通吃” | 某一路检索质量明显优于其他路,希望它主导结果 |
| K=60(默认值) | 排名差异被适度平滑,各路贡献相对均衡 | 多路检索质量相当,需要综合各路信号 |
| K 很大(如 200+) | 排名差异几乎被抹平,趋近于”被多少路命中”的计数 | 更看重文档被多路召回的次数,而非在单路的排名 |
实际调参方法:
1. 默认 K=60,大多数场景不需要调
2. 如果发现某一路检索远强于其他路 → 减小 K,让强路主导
3. 如果各路检索互补性强(向量找语义、BM25 找关键词) → 保持或增大 K
4. 在评测集上做 grid search:K 属于 {10, 30, 60, 100},看 Recall@K 和 MRR 变化RRF 相比加权分数融合的核心优势:不需要归一化。不同检索路径的分数量纲和分布可能差异巨大(向量余弦 0~1,BM25 可能 0~50),RRF 只看排名不看分数,天然免疫分布差异。
追问:长尾或语义模糊的查询,怎么提升召回准确率?
长尾查询的核心问题是信息量不足——query 太短、太模糊、或用了非标准表述,导致检索系统无法精准匹配。
分层应对策略:
| 策略 | 原理 | 适用场景 |
|---|---|---|
| HyDE(假设性文档嵌入) | 让 LLM 先”编”一篇假想答案,用假想答案的 embedding 去检索 | query 极短但意图明确 |
| Query 扩展 | 一条 query 变 3~5 条子 query,多路召回合并 | query 模糊、可能有多种理解 |
| 领域词典 + BM25 | 对专业术语做标准化映射,用 BM25 精确匹配 | 医疗、法律、金融等术语密集领域 |
| 伪相关反馈(PRF) | 第一轮检索的 Top-K 结果作为反馈,扩展 query 做第二轮检索 | 初始召回有部分相关结果但不够 |
| 渐进式放宽 | 初始用严格条件检索,无结果则逐步放宽约束 | 用户需求精确但知识库覆盖不足 |
实测中,HyDE 对短 query 提升最大(Recall +20~30%),但会增加一次 LLM 调用的延迟。Query 扩展是性价比最高的通用方案。
追问:基于 Milvus,BM25 与向量检索的分数归一化具体怎么做?
来源:阿里淘天 AI应用开发一面
两路检索的分数量纲完全不同——BM25 分数是无界的(取决于文档长度和词频),向量余弦相似度在 [-1, 1] 区间。不归一化直接加权会导致一路完全压过另一路。
三种归一化方案:
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| Min-Max 归一化 | 每路取当前 batch 的 min/max 映射到 [0,1] | 简单直观 | 不同 query 的 min/max 波动大,不稳定 |
| Z-Score 归一化 | 减均值除标准差 | 统计稳定 | 需要维护全局统计量 |
| RRF(Reciprocal Rank Fusion) | 不看分数只看排名:score = Σ 1/(K+rank_i) | 无需归一化,对异常值鲁棒 | K 参数需调优 |
Milvus 实战:Milvus 2.4+ 原生支持 hybrid search,内置 RRF 和 WeightedRanker 两种融合策略。推荐先用 RRF(K=60 是常用起点),如果对精排效果不满意再切 WeightedRanker 手动调权重。
Q:Rerank 后一般返回几个块?TopK 截断策略怎么设计?
来源:快手 AI Agent 开发一面 【字节二面追问:Re-rank 的作用 + 为什么有了向量相似度还需要它】【淘天Agent开发追问:低分阈值提前过滤策略】
新手答:“返回 5 个左右。”
高手答:
通常返回 3~5 个块,这个范围是多个因素权衡的结果:
太少(1-2 个):信息可能不完整,复杂问题需要多个证据源佐证
太多(8-10 个):
① 噪声增加——低相关度的块干扰模型判断
② Lost in the Middle——模型对中间位置的信息关注度下降
③ Token 成本线性增长
④ 生成延迟增加验证方法:在评估集上做消融实验:
固定其他参数,遍历 K = [1, 2, 3, 5, 8, 10]
对每个 K 计算:
- 回答准确率(人工评判或 LLM-as-Judge)
- Faithfulness(回答是否忠于检索结果)
- Token 消耗和延迟
通常 K=3~5 是准确率和成本的甜点区间TopK 截断策略:
不建议用固定 K 一刀切。更好的做法是分数阈值 + 上限 K + 断崖检测三条件组合:
def adaptive_topk(reranked, max_k=5, min_score=0.6, score_drop=0.3):
selected = []
for i, result in enumerate(reranked[:max_k]):
if result.score < min_score:
break
if i > 0 and (selected[-1].score - result.score) > score_drop:
break
selected.append(result)
return selected if selected else [reranked[0]]三个截断条件:
- 硬上限:最多取 max_k 个,防止上下文过长
- 绝对阈值:低于 min_score 的块直接丢弃——质量太差的证据不如没有
- 分数断崖检测:相邻块分数骤降超过阈值时截断——说明后面的块相关度显著下降
上下文过长/过短的处理:
| 情况 | 处理策略 |
|---|---|
| 上下文过长 | ① 对每个块提取关键句而非全文 ② 截断低分块 ③ 只保留和 query 最相关的段落 |
| 上下文过短 | ① 增大召回 K 值,降低召回阈值 ② 启用父 chunk 扩展上下文 ③ 查询改写后重新检索 ④ 兜底:告知用户“知识库中未找到足够信息” |
差距在哪:新手随便说了个数。高手用消融实验确定 K 值,用三条件自适应截断代替固定 K,且对上下文过长/过短都有处理方案。面试官考的是你调参时有没有数据支撑的方法论,以及面对边界情况的工程化处理。
Q:如何系统性提升 RAG 的检索相关度与生成效果?
来源:快手 AI Agent 开发一面
新手答:“换更好的 Embedding 模型。”
高手答:
优化要分检索阶段和生成阶段,两者瓶颈不同、手段不同:
检索阶段优化(提升相关度):
| 优化方向 | 具体手段 | 预期效果 |
|---|---|---|
| Query 侧 | 查询改写、意图识别、HyDE | 提升 query 和文档的匹配度 |
| 索引侧 | 父子索引、语义分块、元信息增强 | 提升文档的可检索性 |
| 模型侧 | Embedding 微调、ReRank 微调 | 提升语义匹配精度 |
| 召回策略 | 混合检索(BM25 + 向量)、多路召回 | 提升覆盖率 |
生成阶段优化(提升回答效果):
| 优化方向 | 具体手段 | 预期效果 |
|---|---|---|
| 上下文质量 | 精排后截断、去除低分块、冲突检测 | 减少噪声干扰 |
| Prompt 工程 | 引用约束、格式要求、拒答指令 | 提升回答的忠实度和格式规范 |
| 模型选择 | 复杂问题用强模型、简单问题用轻量模型 | 平衡质量和成本 |
| 后处理 | 答案事实性校验、引用来源标注 | 降低幻觉风险 |
优先级排序(投入产出比从高到低):
① 混合检索(加 BM25 路,成本几乎为零,Recall 提升 10-15%)
② Chunk 策略优化(父子索引、语义分块,一次性投入,长期受益)
③ 查询改写(显著提升长尾 query 的召回率)
④ ReRank 引入/微调(精排对最终质量影响大)
⑤ Embedding 微调(效果好但需要标注数据,成本较高)
⑥ Prompt 优化(持续迭代,每次小幅提升)如何验证优化是否有效:
离线评估:
检索指标:Recall@K、MRR、NDCG
生成指标:Faithfulness、Answer Relevancy、Completeness
工具:RAGAS 框架自动化评估
在线验证:
A/B 测试:新旧方案同时上线,对比用户满意度和任务完成率
灰度发布:先 10% 流量验证,确认无回退再全量
关键原则:每次只改一个变量,否则无法归因差距在哪:新手只想到换模型——这是最贵且不一定有效的做法。高手把优化拆成检索和生成两阶段,每个阶段有明确的手段和优先级排序,且有离线+在线的验证体系。面试官考的是你优化 RAG 系统时有没有系统性的方法论和效果验证闭环。
追问:实际项目中召回不准,你做过哪些改进?效果如何?
这种问题考的是实战排查方法论,而不是罗列技术。回答结构应该是“定位 → 归因 → 方案 → 效果”:
典型排查流程:
1. 定位:抽 100 条 bad case,标注失败原因
→ 发现 60% 是"召回了但排名靠后",30% 是"根本没召回",10% 是"召回正确但生成忽略"
2. 归因:
"召回了但排名靠后" → Rerank 模型对当前领域的区分度不够
"根本没召回" → query 用词和文档差异大(词汇鸿沟)
"召回正确但生成忽略" → 上下文中噪声 chunk 干扰了模型
3. 方案(按投入产出比排序):
① 加 BM25 路 → 解决词汇鸿沟问题,0.5 天
② Query 改写 → 用 LLM 做同义扩展,1 天
③ Rerank 模型换成 bge-reranker-v2 → 精排能力提升,0.5 天
④ 上下文截断策略从 Top-5 改为"分数阈值 + Top-3 兜底" → 减少噪声,0.5 天
4. 效果:Recall@10 从 72% → 85%,端到端回答准确率从 68% → 81%关键认知:优化不是“把所有方法都堆上去”,而是先做归因分析,找到占比最大的失败模式,针对性解决。每次改一个变量,用评测集验证,确认有效后再上线。
Q:RAG 中为什么引入父子索引?
来源:快手 AI Agent 开发一面
新手答:“为了更精确地检索。”
高手答:
父子索引解决的是 chunk 粒度的两难问题——检索精度和生成质量对 chunk 大小的需求是矛盾的。
小 chunk(100-200 字):
✅ Embedding 语义集中,检索精度高
❌ 上下文断裂,模型拿到的信息不完整
大 chunk(500-1000 字):
✅ 上下文完整,模型能理解前因后果
❌ Embedding 被多个主题稀释,检索精度下降父子索引的设计思路是解耦检索粒度和上下文粒度:
检索时用子 chunk(小粒度,精度高),返回时取父 chunk(大粒度,上下文完整)。这样同时拿到了检索精度和生成上下文质量。
实现方式:
- 离线阶段把文档切成大块(父 chunk),再把每个父 chunk 切成若干小块(子 chunk),子 chunk 记录
parent_id - 对子 chunk 做 embedding 入向量库
- 检索时用 query 匹配子 chunk,拿到结果后通过
parent_id取出对应的父 chunk - 父 chunk 去重后送入 rerank 和 LLM
进阶:还可以做多级索引——文档摘要作为最顶层,段落作为中间层,句子作为最底层。先粗筛文档,再定位段落,最后精确到句子,逐层缩小范围。
差距在哪:新手只说了“更精确”但不知道为什么需要两层。高手点出了核心矛盾——检索需要小 chunk 精度、生成需要大 chunk 上下文——父子索引用“小粒度检索、大粒度返回”同时满足两个需求。面试官考的是你对 chunk 策略的深入理解。
Q:RAG 系统的端到端性能如何优化?
来源:快手 AI Agent 开发一面
新手答:“用更快的向量数据库。”
高手答:
RAG 的端到端延迟 = 检索延迟 + 排序延迟 + LLM 生成延迟。优化从三个层面切入:
检索层优化:
① ANN 索引调优:HNSW 的 ef_construction 和 M 参数影响召回率和速度的平衡
② 预过滤:先用元数据(时间范围、文档类别)缩小候选集,再做向量检索
③ 并行召回:BM25 和向量检索并行执行,总延迟 = max(两路) 而非 sum(两路)
④ Embedding 缓存:高频 query 的 embedding 结果缓存,避免重复计算模型层优化:
① 流式输出(Streaming):用户不用等完整回答生成完才看到内容
② 模型路由:简单问题用小模型快速回答,复杂问题用大模型保证质量
③ 上下文压缩:减少送入 LLM 的 token 数——token 越少生成越快、成本越低
④ KV Cache 复用:相同 system prompt 部分的 KV cache 可以跨请求复用架构层优化:
① 语义缓存:用 embedding 相似度判断——"RAG 是什么"和"什么是 RAG"命中同一缓存
命中率高的场景能减少 40-60% 的 LLM 调用
② 异步预处理:文档入库时预计算 embedding、预生成摘要,查询时直接用
③ 请求合并:短时间内相同/相似 query 合并为一次检索
④ 分级 SLA:核心业务走低延迟链路,非核心走高质量链路各优化手段的延迟收益估算:
| 优化手段 | 典型延迟收益 | 实现成本 |
|---|---|---|
| 并行召回 | -30~50ms | 低 |
| 语义缓存 | 命中时 -500ms+ | 中 |
| 流式输出 | 首 token 提前 200-500ms | 低 |
| Embedding 缓存 | -20~50ms | 低 |
| 模型路由 | 简单问题 -300ms+ | 中 |
| 上下文压缩 | -100~300ms | 中 |
差距在哪:新手只想到”换更快的数据库”——这只是检索层的一个点。高手从检索、模型、架构三层拆解了完整的性能优化方案,且给出了每个手段的延迟收益估算。面试官考的是你对 RAG 系统全链路延迟的分析和优化能力。