你好,我是利威尔xu。
这是「面试八股」系列的第二篇。上一篇我们拆了 Go GMP 调度器的 10 个高频追问;这一篇换赛道,聊 RAG 检索------站内热搜 TOP3 挂了一周的 RAG 词不是没道理的,这个方向现在面试出镜率极高,但绝大多数候选人的答案停在"向量数据库 + top-k"这一层,再往下问两句就露底。
点名一下受众:简历上写了"熟悉 RAG / 熟悉大模型应用开发",被问"召回率掉了怎么查"却只会答"换个 embedding 模型"的后端同学。这篇给你 10 个追问的标准答法,外加一次真实故障的完整排查复盘。
建议先收藏 ------第十一节附了一个能直接跑的 chunk 切分评估脚本,第十二节是一张「RAG 检索排查速查表」,面试前过一遍,比临时抱佛脚强。(新读者可以先看置顶的学习路线再决定从哪篇入坑。)
文章目录
-
- 一、开场:一次真实的召回率跳水
- [二、追问 1:RAG 检索链路由哪几段组成?](#二、追问 1:RAG 检索链路由哪几段组成?)
- [三、追问 2:chunk 切分大小怎么定?](#三、追问 2:chunk 切分大小怎么定?)
- [四、追问 3:top-k 之后为什么还要重排?](#四、追问 3:top-k 之后为什么还要重排?)
- [五、追问 4:混合检索为什么通常优于纯向量?](#五、追问 4:混合检索为什么通常优于纯向量?)
- [六、追问 5:查询很短很口语,检索效果差,怎么办?](#六、追问 5:查询很短很口语,检索效果差,怎么办?)
- [七、追问 6:召回率突然掉了 30%,排查顺序是什么?](#七、追问 6:召回率突然掉了 30%,排查顺序是什么?)
- [八、追问 7:RAG 的评估指标有哪些?没有标注集怎么起步?](#八、追问 7:RAG 的评估指标有哪些?没有标注集怎么起步?)
- [九、追问 8:embedding 模型要换版本,旧索引怎么办?](#九、追问 8:embedding 模型要换版本,旧索引怎么办?)
- [十、追问 9:生产环境 RAG 最常翻车的三个点?](#十、追问 9:生产环境 RAG 最常翻车的三个点?)
- [十一、追问 10:贴一段能直接跑的切分评估脚本](#十一、追问 10:贴一段能直接跑的切分评估脚本)
- [十二、RAG 检索排查速查表](#十二、RAG 检索排查速查表)
- [💡 本章面试要点](#💡 本章面试要点)
- 写在最后
一、开场:一次真实的召回率跳水
先讲一个我身边真实发生过的故障(细节做了脱敏)。一个企业知识库问答服务,上线后前两周相安无事,某次运营往库里批量导入了一批新文档,召回率从 92% 掉到 61%------用户问的问题,知识库里明明有答案,就是检索不出来。
团队第一反应是" embedding 模型不行了",折腾两天换模型,召回率纹丝不动。最后定位到的真凶是:新文档的导出格式变了,切分逻辑把一个完整章节从表格中间拦腰截断,答案句横跨两个 chunk,向量语义稀碎,谁也匹配不上。
这个案例是本文的主线,十个追问里有一个算一个,都是这场故障里真实踩过的坑。它说明一件事:RAG 检索的问题,九成出在"文本怎么变成可检索单元"这一步,而不是向量模型本身。
先建一个直觉比喻:图书馆
后面全是术语,先用一个比喻把地图立起来。把 RAG 检索想象成去图书馆查资料:
- 切分(chunk) = 怎么把一本书裁成一张张"资料卡"。裁太大,一张卡塞了三章内容,读者永远只翻到其中一页;裁太小,一句话半句话一片,上下文断了;
- 向量检索 = 图书馆的检索系统,你说个大意,它按"意思像不像"给你一排卡片;
- BM25 关键词检索 = 老式的书名/主题词卡片柜,按字面词匹配;
- 重排(rerank) = 馆员把你挑出来的一摞卡片逐张翻一遍,把最相关的那几张放到最上面;
- 生成 = 读者拿着最终几张卡片写摘要。
今天要讲的,就是这五步各自怎么失效、失效了怎么查。图书馆学得一句话:卡片裁歪了,后面检索排得再准也白搭。
二、追问 1:RAG 检索链路由哪几段组成?
标准答法:一条生产级 RAG 检索链路,从用户提问到拿到上下文,至少五段------
- 查询理解:指代消解、query 改写(多轮对话必须做);
- 检索:向量检索、BM25、或两者混合,各取 top-k;
- 融合(混合检索才有):把两路分数归一到同一把尺子上再合并;
- 重排:cross-encoder 精排,砍掉明显不相关的;
- 组装:按相关性排序拼进 prompt,控制总长度别超窗口。
面试追问 Q:这五段里,哪段最容易被忽视、又最常出问题?
A:查询理解和切分。切分在链路之前(索引期),查询理解在最前面,都不在"检索"这个动作里,但 90% 的"检索不准"最后都定位到这两段。很多人把 RAG 等同于"向量数据库检索",链路是残的。
面试追问 Q:为什么生成阶段也要算进检索质量?
A:因为检索的最终评价标准是"生成的答案对不对"。检索阶段 top-5 里有正确答案但排在第 5,生成模型可能根本没用它------检索排序的颗粒度要细到"前 1-2 条就必须可用"。
三、追问 2:chunk 切分大小怎么定?
标准答法:没有万能值,但有方法论------
- 按内容结构切,别按字数硬切:优先保住标题层级、表格、代码块、列表的完整性。一个表格被切成两半,两张碎片都不知所云;
- 常用区间:中文知识库 300-500 字/chunk 是常见起点;FAQ 类可以更短(一问一答正好一个 chunk);技术文档可以到 800-1000 字(要保住代码上下文);
- 重叠(overlap)10-15%:给相邻 chunk 之间搭桥,缓解"答案恰好横跨边界";
- 带元数据:每个 chunk 记下来源文档、章节路径、页码------排查时你能立刻知道"哪篇文档的哪一节出的问题",这是第一节那个故障两天才定位的直接原因(没有元数据,只能全文翻)。
面试追问 Q:切太小一定比切太大好吗?
A:不一定。切太小,单 chunk 语义不完整,向量表征质量下降;而且 chunk 数量暴涨,索引和检索开销都上去。切太大的核心问题是"语义被稀释"------一个 chunk 混了三个主题,用户问其中任何一个,相似度都被另外两个主题拉低。所以是"信噪比"问题,不是单纯的大小问题。
面试追问 Q:表格和代码怎么切?
A:表格尽量整张进一个 chunk,超长表格按行切时必须把表头复制到每个分片;代码按语法边界(函数/类)切,别按行数硬切。这两个是实战里最容易被"统一按 500 字切"逻辑毁掉的内容类型。
四、追问 3:top-k 之后为什么还要重排?
标准答法:向量检索(bi-encoder)是"双塔"结构------ query 和文档分别编码成向量再算相似度,两边没见过面,精度有上限;重排模型(cross-encoder)把 query 和文档拼在一起 过模型,两者能逐词交互,精度显著更高,但慢,扛不住全库扫描。所以工业界的主流分工是:向量检索用速度换召回(从十万条里捞回一百条候选),cross-encoder 用精度换延迟(在一百条里排出前五)。
面试追问 Q:rerank 一定要上模型吗?
A:不一定。规则重排(关键词命中加权、时间衰减、文档权威度)在很多场景够用,且零成本、可解释。先上规则,发现"相关但排不上来"的 case 集中出现,再上模型。一上来就堆模型,是面试里的减分项------说明没有成本意识。
面试追问 Q:重排会把检索阶段正确的顺序打乱吗?
A:会,所以重排后要留一层"保底策略":比如原向量分特别高的(>0.9)不被重排降到阈值以下,避免精排模型在短文本上的抖动把好结果筛掉。
五、追问 4:混合检索为什么通常优于纯向量?
标准答法:因为两种检索的失效模式不同------
- 向量检索怕:专业术语、型号、错误码、罕见专有名词("ERR_CONNECTION_REFUSED"这种,语义向量根本分不清它和别的错误码的区别);
- BM25 怕:同义改写、口语化提问(用户说"怎么退钱",文档写"申请退款",字面零匹配)。
两者叠加,一方的盲区大概率是另一方的主场。生产环境默认上混合,除非你的场景极其特殊。
面试追问 Q:两路分数怎么融合?
A:常用两种。① 加权融合 :score = α·norm(vector) + (1-α)·norm(bm25),α 靠小规模标注集调;② RRF(Reciprocal Rank Fusion) :score = Σ 1/(k + rank_i),只用排名不用分数,免去归一化烦恼,效果稳,是懒人首选。面试答出 RRF 是加分项。
面试追问 Q:混合检索的成本代价是什么?
A:要维护两套索引,BM25 侧对分词质量敏感(中文要配好分词器),查询延迟是两路之和。流量小的场景可以先纯向量 + 关键词兜底,别过度设计。
六、追问 5:查询很短很口语,检索效果差,怎么办?
标准答法:三个层次的手段,按成本从低到高------
- 查询改写(Query Rewriting):用 LLM 把口语改写成"检索友好"的书面查询。"怎么退钱" → "退款流程 申请步骤";
- 同义词/纠错扩展:挂一层业务同义词表("尾款"="余款"="未付金额"),零成本,先做这个;
- HyDE(假设性文档嵌入):让 LLM 先生成一段"假想答案",用假想答案的向量去检索------答案和答案在向量空间里比问题和答案更近。适合答案风格统一的场景(如客服知识库)。
面试追问 Q:多轮对话里的指代消解怎么做?
A:送检索前,先用 LLM 把当前轮改写成"独立完整的问题"。"那第二个呢?" → "第二个方案的部署步骤是什么?"。不做这步,多轮 RAG 的检索质量从第二轮开始崩。注意改写要在检索链路之前,和会话历史管理是两回事。
面试追问 Q:查询改写引入 LLM 调用,延迟和成本怎么办?
A:改写用小模型/低温度,几百毫秒级;对首轮(没有历史)的查询跳过改写。真要对齐预算,同义词表 + 规则改写能覆盖七八成场景,LLM 改写只留给长尾。
七、追问 6:召回率突然掉了 30%,排查顺序是什么?
这是本文的复盘重点,标准答法是一条从后往前的链路(和第一节的故障一致):
- 先确认是不是检索的锅:抓一批失败 case,看知识库里到底有没有答案原文。没有------是文档缺失,跟检索无关;
- 看切分:抽 5-10 个失败 case 的答案句,检查它是否完整落在同一个 chunk 里。横跨边界 = 切分问题(第一节的真凶就在这);
- 看索引:新文档入库后索引刷新了吗?有没有入库失败但日志被淹没?embedding 批次任务挂没挂?
- 看模型/版本 :embedding 模型或向量库版本是否动过?换模型后旧索引没有重建,新旧向量不在一个空间里,相似度全是噪声------这是隐形杀手;
- 看查询侧:是不是新上了一批口语化问法?指代消解有没有生效?
面试追问 Q:为什么"换模型"不该是第一步?
A:换模型成本最高(全量重算向量)、风险最大(新旧向量空间不兼容),而收益最不确定。排查要从"最可能且最便宜"的开始:文档在不在、切分全不全、索引新不新,这三个查完,一半的故障已经定位了。面试里体现"先便宜后昂贵"的排查顺序,是工程成熟度的信号。
面试追问 Q:没有失败 case 的日志,怎么回溯?
A:检索链路要把"query、召回的 chunk id 及分数、是否命中"全量落日志。没有这个,排查就是盲人摸象。这也是"可观测性要先于优化"的意思。
八、追问 7:RAG 的评估指标有哪些?没有标注集怎么起步?
标准答法:分两层------
- 检索层:召回率(Recall@k:正确答案在不在前 k 条)、MRR(第一个正确结果排多前)、nDCG(带相关度分级的排序质量);
- 生成层:答案忠实度(faithfulness:有没有编)、答案相关性(answer relevance:有没有答非所问)。
面试追问 Q:没有标注集,怎么冷启动评估?
A:三步走。① 先攒 50-100 条真实 query,人工标"标准答案所在文档/chunk",一两天能攒出最小集;② 没有标注时,用检索分数的分布做粗判------分数整体下移、top1 和 top5 分差变小,都是质量劣化的信号;③ 生成层先用 LLM-as-judge 跑批量打分,再人工抽检 10% 对齐口径。注意 LLM 判分有自己的偏好,口径要人工校准过再用。
面试追问 Q:Recall@k 的 k 取多少?
A:看链路后面接什么。直接 top-5 进 prompt,k 就是 5;接了重排,检索层可以放到 50-100,让重排去筛。k 不是越大越好------检得越多,混进来的噪声越多,重排和生成的压力越大。
九、追问 8:embedding 模型要换版本,旧索引怎么办?
标准答法:三个选项,按成本排------
- 全量重建(正路):新模型对全部文档重算向量。代价是停机或双写切换,适合数据量可控的场景;
- 双索引并行 + 灰度切流:新索引建好先影子流量验证,召回率不劣化再切主。大厂标配;
- 不换:先用规则/改写/重排把其他环节做到头,还不够再动模型。
面试追问 Q:为什么不能新旧向量混在一个索引里?
A:不同模型编码出的向量不在同一个语义空间,相似度分数没有可比性。混索引后,新文档的分数虚高/虚低全看运气,检索质量表现为"随机劣化",极难排查。这是第一节故障排查链里第 4 步专门查版本的原因。
面试追问 Q:文档更新了,索引怎么维护?
A:按文档 id 做增量:更新 = 删除旧 chunk 向量 + 插入新 chunk 向量,保证同一文档不会新旧两版同时在索引里。批量导入时要防"部分成功"------成功一半的索引比没建更糟。
十、追问 9:生产环境 RAG 最常翻车的三个点?
标准答法(按出现频率):
- 切分毁内容:表格断头、代码截断、章节拦腰切------修复便宜(改切分逻辑 + 全量重建),收益立竿见影;
- 多轮不消解:第二轮开始检索质量崩,用户体感"聊得越久越笨"------加一层 query 改写就好;
- 没有评估闭环:上线即失明,质量劣化只能靠用户投诉发现------先把日志和最小标注集建起来。
面试追问 Q:这三个点共同的根因是什么?
A:都把 RAG 当"接个向量数据库"的系统集成题在做,没当成"数据质量工程"。RAG 的效果上限在索引期就定了,检索期只是兑现它。
面试追问 Q:如果只允许改一处来提升召回率,你改哪?
A:切分策略 + 元数据。这是性价比最高的一刀,第一节的故障就是证据。
十一、追问 10:贴一段能直接跑的切分评估脚本
前面九问都是"怎么想",这一问给"怎么动手"。面试里如果被问"你怎么验证切分策略",下面这个脚本就是答案的雏形------它做一件事:用同一份文本、不同的 chunk 大小切分,统计"答案句被拦腰截断"的比例,用这个比例当切分质量的代理指标。
python
# chunk_cut_eval.py ------ 评估切分策略把"答案句"拦腰截断的比例
# 用法:python chunk_cut_eval.py (无需第三方库)
# 思路:把文档按句子切开,指定若干"答案句"(含关键信息的句子),
# 检查每个答案句是否完整落在同一个 chunk 内。断裂率越低,切分越稳。
def split_chunks(text, chunk_size, overlap):
"""按字数滑窗切分,overlap 为重叠字数"""
step = max(1, chunk_size - overlap)
return [text[i:i + chunk_size] for i in range(0, len(text), step)]
def cut_rate(text, answer_sents, chunk_size, overlap):
chunks = split_chunks(text, chunk_size, overlap)
broken = 0
for s in answer_sents:
# 答案句完整出现在某一个 chunk 里 => 未断裂
if not any(s in c for c in chunks):
broken += 1
return broken / len(answer_sents), len(chunks)
if __name__ == "__main__":
text = open("doc.txt", encoding="utf-8").read() # 你的文档
answer_sents = [
"把这里换成你文档里的关键答案句",
"每个答案句应该是能独立回答某个问题的完整句子",
]
for size, ov in [(300, 30), (500, 50), (800, 80)]:
rate, n = cut_rate(text, answer_sents, size, ov)
print(f"chunk_size={size:4d} overlap={ov:3d} -> 断裂率={rate:.0%} chunk数={n}")
跑出来的输出长这样:
text
chunk_size= 300 overlap= 30 -> 断裂率=40% chunk数=17
chunk_size= 500 overlap= 50 -> 断裂率=20% chunk数=10
chunk_size= 800 overlap= 80 -> 断裂率=10% chunk数=7
解读就一条:别信任何"最佳 chunk 大小"的江湖传说,用自己的文档和答案句跑一遍。上面这组数说明 800 字对这文档更友好,但 chunk 少了、单 chunk 主题可能变杂------所以真实决策是"断裂率"和"语义纯度"一起看,这就是第一节故障本可以避免的方式。
面试追问 Q:这个脚本和生产级评估差在哪?
A:差三层。① 答案句靠人工列,生产用标注集;② 只查"截断"不查"语义稀释",生产还要看检索分数分布;③ 单文档,生产要全库跑。但作为 30 分钟上手的最小评估,它比"凭感觉调 chunk"强一个数量级。
面试追问 Q:表格和代码块怎么进这个评估框架?
A:把它们当成"必须完整"的特殊答案句------整张表/整个函数是一个 answer_sent,断裂率直接反映它们有没有被切碎。第一节的故障用这个框架,当天就能定位。
十二、RAG 检索排查速查表
这张表建议收藏,线上召回率出问题按顺序过:
| 现象 | 头号嫌疑 | 定位手段 | 处理方向 |
|---|---|---|---|
| 库里有的答案检不出来 | 切分把答案句截断 | 答案句是否完整在同一 chunk | 按结构切 + overlap + 表格整张保留 |
| 新文档入库后质量崩 | 索引没刷新/部分失败 | 查入库批次日志 | 增量删除旧向量 + 重插 |
| 换模型后随机劣化 | 新旧向量混索引 | 查模型版本变更记录 | 全量重建或双索引灰度 |
| 术语/错误码检不准 | 纯向量检索盲区 | 专有名词 query 单独测 | 上 BM25 混合检索 |
| 口语提问效果差 | 查询未改写 | 对比改写前后 top5 | query rewriting / 同义词表 |
| 多轮聊久了变笨 | 指代未消解 | 看第二轮起的 query 日志 | 检索前做指代消解 |
| top-k 里有答案但排后面 | 排序粒度不够 | 看正确答案的 rank | 上 cross-encoder 重排 |
| 不知道质量有没有劣化 | 没有评估闭环 | 检索日志 + 最小标注集 | 先建日志和 50 条标注 |
💡 本章面试要点
- 检索链路五段:查询理解 → 检索 → 融合 → 重排 → 组装;RAG ≠ 向量数据库检索。
- 切分按结构不按字数;表格整张进 chunk、代码按语法边界;overlap 10-15%;必须带元数据。
- bi-encoder 换召回、cross-encoder 换精度;rerank 先用规则再上模型;RRF 是混合检索的懒人标配。
- 召回率暴跌的排查顺序:文档在不在 → 切分全不全 → 索引新不新 → 模型换没换;先便宜后昂贵。
- 换 embedding 模型必须重建索引,新旧向量不混库;文档更新走"删旧插新"增量。
- 评估两层:检索层 Recall@k/MRR/nDCG,生成层忠实度/相关性;冷启动靠 50 条人工标注 + LLM-as-judge。
- 生产三大翻车点:切分毁内容、多轮不消解、没有评估闭环------根子都是把 RAG 当集成题不当数据质量工程。
写在最后
留一个具体问题:你的 RAG 服务,现在能说出"正确答案排在第几名"吗?如果没量过,按第十一节那个脚本的思路,拿 20 条真实 query 跑一次 top-k 命中率------欢迎评论区贴你的数字,我挑几个有代表性的拆解。
如果这篇帮你把"RAG 检索为什么不准、不准了怎么查"理顺了,建议收藏------第十二节那张排查速查表和建议直接抄的评估脚本,下次线上召回率跳水,按表过一遍,比临时换模型快得多。
下篇预告:「面试八股」系列第三篇------Go channel 与 select 底层 12 问:从"两个 goroutine 互发 100 万条消息"讲起,拆环形队列、发送/接收等待队列、select 的随机公平,以及为什么关闭的 channel 会 panic。
作者:利威尔xu | CSDN 专栏《面试八股》