「RAG 效果差」是个笼统病,直接开药都是瞎蒙。面试官爱听的不是你换过几个模型,而是会不会分层定位:答案质量差,可能死在入库、检索、融合、精排、生成任何一层。

我把自己 Nuxt3 知识库(my-nuxt-app)的调试工具链整个做成了一套排查方法,这篇按层过一遍。

总原则:控制变量 + 只看检索不生成

两条铁律贯穿所有排查:

  1. 召回策略是唯一变量。我把检索做成 hybrid / bm25 / vector 三档模式开关,同一问题各跑一遍,后面的精排、生成完全一致——谁拖后腿一眼看见。
  2. 检索和生成分开验。有 retrieveOnly 开关:只看召回了哪些片段、名次如何,不跑生成。答案不对,先确认是「没捞到」还是「捞到了模型没用好」,这两件事的修法完全不同。

分层排查

层 1:入库(文档→分块→向量)

七步建库链路:知识整理 → 清理格式化 → 内容切分 → 向量化 → 挂元数据 → 载入向量库 → 集成。效果差的病根很多在这:切太碎丢上下文,切太粗检索不准。

我项目的真实分块参数:递归切分器 chunker.ts,目标 800 字符(标题→空行段落→句号→硬切四层降级,短文档直接一片不硬切);BM25 侧启动全量预热——最新实测:172 个 chunk / 3015 个词项,建索引 2688ms(TiDB 拉取+分词+倒排全算上)。

排查动作:随机抽几个 chunk 看语义是否自包含;问一个已知有答案的问题,确认目标 chunk 的文本入库时没被解析器吃掉(表格/PDF 是重灾区)。

层 2:检索(两路单测)

关键词路和向量路各留了单路调试接口:/api/rag/keyword-search、/api/rag/vector-search——单路粗召、不精排,专治「不知道哪路烂」。

典型病症对照:

  • 专有名词/条款号/型号搜不到 → 关键词路问题:查分词(我的 BM25 用中文分词器切 query 和文档,分词烂则倒排全烂)、查索引预热是否成功(控制台看 docs/vocab 数)
  • 换个说法就搜不到 → 只有 BM25 在干活,向量路没接上或阈值过严
  • 召回一堆语义相近但没用的 → 向量路正常但缺精排,去层 4

层 3:融合(RRF 名次日志)

两路各召 top20,RRF 融合(score=Σ1/(60+rank))。FusedResult 里我专门存了每个 id 的 ranks: [向量路名次, BM25路名次](未命中记 null),服务端控制台按模式打印——融合后排名怪,直接看是「一路强推」还是「两路共认」。

坑:别把余弦分和 BM25 分直接加权,量纲不同(0~1 vs 无上限),必须转名次。

层 4:精排

粗召本质是盲选——入库时分块单独算的向量,从没和你的问题同框比过。精排用 cross-encoder(bge-reranker-v2-m3)把「问题+候选」成对打分,融合 top20 → 精排 top5。

排查信号:召回明明有正确答案却排在 15 名开外进不了上下文 → 看 rerank 分数;rerank 服务挂了会自动降级按 RRF 分排,日志里有标记,别把降级的锅算到检索头上。

层 5:生成

检索没问题、答案还是烂,才轮到生成层:prompt 里资料怎么拼、有没有约束「只用资料作答、引用标来源页码」、模型是不是被无关片段带偏(top5 塞太多噪声也是病,回头减 FINAL_TOP_K)。

常见三病因(课件口径 + 我的实战对应)

  1. 文档处理和切割颗粒度问题 → 层 1
  2. 召回不准,想命中的片段排序太靠后 → 层 2/3/4
  3. 检索到了但整合生成没用对 → 层 5

对应五个提准手段(我项目落了的标 ✓):切分技巧 ✓(递归四层+短文档保护)、取相邻片段(父页面类方案,评估后我没做)、多召回+rerank ✓(20→5)、混合检索 ✓(BM25+向量)、Query 重写 ✓(改写只进检索)。

面试话术一句话

「我调试 RAG 的路径是分层二分:先用 retrieveOnly 把检索和生成切开,再用三模式开关把两路召回切开,每层都有接口和名次日志。定位到层之后,改参数—回归—再定位,不靠玄学。」

这就是面试官要听的:不是你知道多少技巧,而是你有能把问题范围每次减半的方法。

本篇最早于 2026 年 10 月 10 日发布于小红书,此为博客完整版。