RAG 这个词现在有多热不用我多说。但热归热真正被面试官追着问你把完整链路跑一遍给我听听的时候能从头到尾讲清楚的人其实不多。我前段时间面字节一面就被摁着问了整整四十分钟的 RAG从文档怎么切、向量怎么存、检索怎么召回、重排怎么排一路问到线上延迟怎么压、命中率怎么评估。面完之后我干脆自己动手把离线建库到线上召回整条链路完整实现了一遍踩了不少坑也理清了很多之前模模糊糊的地方。这篇就把我这一整套实践过程摊开来讲。不管你是准备面试想系统梳理 RAG 流程还是正在做自己的知识库项目或者只是听说过 RAG 想知道它到底怎么回事应该都能从里面找到有用的东西。我会尽量把每一步为什么这么做讲透而不是甩一堆代码让你自己猜。整条链路涉及的核心环节包括文档解析与切分、向量化、向量库选型与建库、检索召回、重排、以及最终的生成与评估。下面按我实际跑通的顺序来。1. 先把 RAG 的完整链路在脑子里画成一张图很多人对 RAG 的理解停留在把文档塞进向量库然后搜出来喂给大模型。这个理解不算错但太粗了粗到面试官一问细节就露馅。我一开始也是这么想的直到自己动手才发现光是把文档塞进向量库这一句话背后就藏着文档解析、清洗、切分策略、元数据设计、向量化模型选型、批量写入、索引构建等一堆决策点。1.1 离线建库和线上召回是两条独立的流水线我建议你先把 RAG 拆成两条流水线来看这样思路会清晰很多。离线建库Offline Indexing是一条批处理流水线它的任务是把原始文档变成可以被快速检索的向量索引。这条线通常跑在后台对延迟不敏感但对质量极其敏感——因为这里出的问题线上再怎么优化都救不回来。它包含的环节大致是文档加载 → 解析 → 清洗 → 切分Chunking→ 向量化Embedding→ 写入向量库 → 构建索引。线上召回Online Retrieval是一条实时流水线用户提问后触发对延迟极其敏感。它的环节是Query 理解与改写 → Query 向量化 → 向量检索粗排→ 重排精排→ 上下文组装 → 交给 LLM 生成。这两条线共享的是同一个向量空间和同一套 Embedding 模型。这一点非常关键离线建库和线上召回必须用同一个 Embedding 模型否则向量空间对不上检索结果就是乱的。我见过有人建库用了一个模型线上换了个更好的模型结果召回率直接崩掉排查了半天才想起来是模型不一致。1.2 为什么要把粗排和精排分开这是面试里被问得最多的问题之一。核心原因是效率和精度的权衡。向量检索比如基于 HNSW 或 IVF 的近似最近邻搜索非常快百万级向量毫秒级就能返回 Top-K但它的精度有限——它是用向量距离近似语义相似度会漏掉一些真正相关但向量距离不够近的结果。而重排模型Reranker通常是 Cross-Encoder 结构它把 Query 和每个候选文档拼在一起送进模型打分精度高得多但计算量大没法对全库做只能对粗排返回的几十个候选做。所以标准做法是粗排用向量检索从百万级里捞出 Top 50~100精排用 Reranker 从这几十个里选出 Top 3~5 喂给 LLM。这个先粗后精的两阶段结构是整个 RAG 检索质量的核心保障。我实测下来加了重排之后最终喂给 LLM 的上下文相关性提升非常明显尤其是那种 Query 里有多个约束条件的场景。1.3 一张表看清每个环节的核心决策环节核心决策常见坑文档解析用什么解析器如何处理表格和图片PDF 解析丢格式表格变乱码切分切多大是否重叠按什么切切太碎丢上下文切太大引入噪声向量化选哪个 Embedding 模型维度多少中英文混用模型选错维度不匹配向量库用哪个库索引类型怎么选小数据用重型库索引参数没调召回Top-K 取多少是否混合检索K 太小漏召回K 太大引入噪声重排用哪个 Reranker取 Top 几重排后没截断上下文超长生成Prompt 怎么设计如何引用来源没约束模型出现幻觉编造这张表是我自己踩坑之后总结的每一个环节我都会在下面展开讲。你先有个全局印象知道每个环节大概在干什么、容易在哪出问题。2. 离线建库文档解析和切分决定了上限离线建库这条线我最大的体会是垃圾进垃圾出。如果你的文档解析和切分做得烂后面向量化再牛、重排再强都是在垃圾上做优化。这一步是地基地基歪了楼必倒。2.1 文档解析PDF 是永远的痛我这次处理的文档类型比较杂有 Markdown、Word、PDF还有一部分网页。Markdown 和 Word 都好说解析出来结构基本完整。真正让我头疼的是 PDF。PDF 的本质是打印格式它记录的是每个字符画在页面上的坐标而不是逻辑结构。所以一个看起来排版精美的 PDF解析出来可能是标题和正文混在一起、表格变成一堆错位的文字、多栏排版被读成一行。我试过好几个解析方案最后总结下来纯文本型 PDF用常规的 PDF 文本提取库基本够用但要注意处理换行和分栏。扫描型 PDF必须先做 OCR这一步质量直接决定后续一切。含复杂表格的 PDF普通文本提取会彻底崩掉需要专门的表格识别方案或者干脆人工校对。我的做法是解析完之后加一道清洗和结构化的工序把连续的空行合并、把明显的页眉页脚去掉、把识别出来的标题用 Markdown 的#标记出来。这一步看起来不起眼但对后续切分的质量影响巨大因为切分器往往依赖这些结构标记来判断在哪里断开。提示解析阶段一定要保留文档的元数据比如来源文件名、页码、章节标题。这些元数据在线上召回时可以用来做过滤和引用溯源非常有用。我一开始没存后来想加只在这份文档里搜的功能时只能重新建库。2.2 切分策略没有银弹只有权衡切分Chunking是 RAG 里最容易被低估、又最影响效果的环节。切分的本质矛盾是切得小检索精准但上下文不完整切得大上下文完整但引入噪声、稀释语义。我试过几种策略给你对比一下固定长度切分按字符数或 token 数硬切比如每 500 个 token 一块。实现最简单但经常把一句话、一个段落从中间切断语义破碎。我一开始就用的这个效果一般。按语义边界切分按段落、句子、标题层级来切。这个明显好于固定长度因为它尊重了文档的自然结构。我的做法是优先按标题切大块如果某一块还是太长再按段落切段落还长就按句子切。这样能保证每一块在语义上是相对完整的。重叠切分在相邻块之间保留一定的重叠比如 10%~20%防止关键信息正好落在切分边界上被割裂。这个技巧很实用代价是存储和计算量增加。我一般设 50~100 个 token 的重叠。递归切分这是我现在主要用的策略也是很多框架的默认推荐。它的思路是准备一组分隔符从粗到细依次尝试——先按段落分段落还太大就按句子分句子还大就按字符分。这样能在保证块大小可控的同时尽量在语义边界处断开。具体参数上我实测下来比较舒服的配置是块大小 300~500 token重叠 50~80 token。这个范围对大多数中文技术文档都适用。当然这不是铁律你得根据自己的文档特点调。比如法律合同这种一句话都不能断的块就得大一些比如 FAQ 这种一问一答的按问答对切最合适。2.3 元数据设计为线上召回埋好伏笔切分的时候我强烈建议你给每个 chunk 附上丰富的元数据。我这次设计的元数据包括source来源文件名page页码section所属章节标题chunk_id块唯一标识parent_id如果做了父子块结构记录父块 ID这些元数据在线上召回时能派上大用场。比如用户问第三章讲了什么你可以用section做过滤比如要做引用溯源你可以用source和page告诉用户答案来自哪一页。我甚至用parent_id做了一个小块检索、大块返回的策略——检索时用小块保证精准返回时把父块一起带上保证上下文完整。这个技巧后面讲召回时再展开。3. 向量化模型选型和批量处理的细节文档切好了下一步就是把它变成向量。这一步的核心是选对 Embedding 模型以及处理好批量写入的性能问题。3.1 Embedding 模型怎么选选 Embedding 模型我主要看四个维度语言支持、维度、最大输入长度、以及在你领域数据上的实际效果。语言支持不用多说中文场景就得选中文能力强的模型中英混合的场景要选双语模型。维度方面常见的有 768、1024、1536 等维度越高表达能力通常越强但存储和检索成本也越高。最大输入长度很关键——如果你的块是 500 token但模型最大只支持 256 token超出的部分会被截断信息就丢了。我这次用的是 SigLIP2 系列的向量化思路做多模态场景的探索因为我的文档里有一部分是图文混排的纯文本部分则用了主流的中文 Embedding 模型。这里要提醒一句不要盲目追求最新最强的模型。新模型不一定在你的数据上就好而且换模型意味着整个库要重建。我的建议是先选一个成熟稳定的模型把流程跑通等有了评估体系再对比不同模型的效果。注意Embedding 模型一旦确定离线建库和线上召回必须严格一致。模型版本、归一化方式、甚至 tokenizer 都要对齐。我踩过一次坑线上用了不同版本的 tokenizer导致同一个 Query 向量化出来的结果和建库时对不上召回率掉了将近一半。3.2 批量向量化和写入的性能优化向量化是个计算密集的操作如果你有几十万上百万个 chunk一个个调 API 会慢到怀疑人生。我的做法是批量请求把多个 chunk 打包成一个 batch 发给 Embedding 服务一次处理几十上百个。这能大幅减少网络往返开销。并发控制用线程池或异步并发但要注意别把服务打爆我一般控制在 5~10 个并发。失败重试网络请求难免失败一定要有重试机制并且记录失败的 chunk最后统一补跑。断点续传建库可能跑几个小时中途挂了不能从头再来。我会把已处理的 chunk_id 记录下来重启时跳过已完成的。写入向量库时同样要批量写入别一条条插。大多数向量库都支持批量 upsert一次写几百上千条效率高得多。我实测下来批量写入比单条写入快了一个数量级不止。3.3 向量库选型别一上来就上重型武器向量库的选择我的原则是够用就好别过度设计。给你一个简单的决策参考场景推荐方案理由数据量小 10万想快速验证内存向量库或本地文件索引零部署成本跑通流程最快数据量中等10万~百万单机单机版向量库部署简单性能足够数据量大百万以上要扩展分布式向量库支持水平扩展和高可用已有关系型数据库数据库的向量扩展不用引入新组件运维成本低我这次因为要完整跑通流程先用本地方案快速验证确认链路没问题后再换成了支持持久化和增量更新的方案。这里有个经验索引参数一定要调。比如 HNSW 索引的M和efConstruction参数直接影响检索速度和召回率。默认参数往往偏保守你可以根据数据量和精度要求调一调。我一般会把efSearch调大一些来提升召回代价是检索稍慢但在可接受范围内。4. 线上召回从 Query 到上下文的完整链路离线库建好了重头戏来了——线上召回。这是用户能直接感知到的部分也是面试官最爱深挖的地方。我把这条链路拆成 Query 处理、粗排、精排、上下文组装四步来讲。4.1 Query 理解与改写别拿用户的原话直接搜用户的问题往往口语化、有歧义、甚至包含多轮对话的指代。直接拿原话去检索效果通常不好。我做了两件事Query 改写用 LLM 把用户的口语化问题改写成更适合检索的形式。比如用户问那个东西怎么弄结合对话历史改写成XX 功能的配置步骤是什么。这一步能显著提升召回质量。Query 扩展针对短 Query 或者专业术语做同义词扩展或生成多个相关 Query然后分别检索再合并结果。比如用户问RAG 瓶颈可以扩展出RAG 性能问题RAG 检索准确率低等。这个技巧叫多路召回能有效缓解单一 Query 表达不全的问题。不过要注意Query 改写本身也会引入延迟和成本。我的做法是简单 Query 直接检索复杂 Query 才走改写做一个轻量的判断。4.2 粗排向量检索的 Top-K 怎么定粗排就是拿 Query 向量去向量库里做近似最近邻搜索返回 Top-K 个候选。这里的 K 怎么定是个经验活。K 太小可能漏掉真正相关的文档K 太大会给后面的重排增加负担也可能引入噪声。我一般把粗排的 K 设在50~100之间。因为后面还有重排兜底粗排宁可多召回一些保证召回率Recall把精度的活交给重排。除了纯向量检索我还试了混合检索Hybrid Search把向量检索和关键词检索比如 BM25的结果融合。向量检索擅长语义匹配但对精确的关键词、专有名词、代码符号不敏感关键词检索正好相反。两者结合效果比单用向量好不少。融合的方式可以用加权求和也可以用 RRFReciprocal Rank Fusion这种基于排名的融合方法后者不需要调权重更省心。4.3 精排Reranker 是质量的分水岭重排是我认为整条链路里性价比最高的一个环节。加一个 Reranker最终效果往往有肉眼可见的提升。Reranker 的原理前面说过是 Cross-Encoder把 Query 和候选文档拼起来打分。它比向量检索精度高因为它能建模 Query 和文档之间的细粒度交互而不是各自编码成向量再算距离。用 Reranker 有几个注意点候选数量一般对粗排的 Top 50~100 做重排取重排后的 Top 3~5。候选太多重排会很慢太少又浪费了重排的价值。延迟Cross-Encoder 计算量大是线上延迟的主要来源之一。如果对延迟敏感可以选轻量级的 Reranker或者减少候选数量。截断重排后一定要截断别把几十个文档全塞给 LLM那样既超长又引入噪声。我实测下来加了重排之后Top 3 的相关性明显比不加时高。尤其是那种 Query 里有隐含约束的场景向量检索容易把看起来像但实际不相关的捞上来重排能把这些过滤掉。4.4 上下文组装怎么把检索结果喂给 LLM检索出 Top 3~5 个 chunk 之后不能直接拼起来就完事。我做了几件事去重如果多个 chunk 来自同一段落去重避免重复。排序把最相关的放在最前面或最后面。有研究表明 LLM 对上下文首尾的信息更敏感中间容易忽略所以关键信息别放中间。加引用标记给每个 chunk 标上来源编号让 LLM 在回答时引用方便用户溯源。控制长度总长度不能超过 LLM 的上下文窗口要留出空间给系统 Prompt 和回答。我一般控制在窗口的 60%~70%。Prompt 的设计也很关键。我会明确告诉 LLM只根据以下资料回答资料里没有的信息不要编造如果资料不足以回答就直说。 这句话能大幅减少幻觉。同时要求它标注引用来源比如根据 [1]……。5. 评估与调优怎么知道你的 RAG 到底行不行跑通链路只是第一步真正难的是知道它好不好、哪里不好、怎么改。没有评估的 RAG 优化就是盲人摸象。5.1 命中率Hit Rate和几个核心指标评估 RAG 检索质量最常用的指标是命中率Hit Rate在测试集里正确文档出现在 Top-K 结果中的比例。比如你有 100 个测试问题每个问题都有一个标准答案文档如果 80 个问题的标准文档出现在 Top 5 里命中率就是 80%。除了命中率我还会看MRRMean Reciprocal Rank正确文档排名的倒数的平均值衡量正确结果排得靠不靠前。RecallKTop-K 里包含正确文档的比例。PrecisionKTop-K 里有多少是真正相关的。这些指标各有侧重命中率和 MRR 是我最常看的。要算这些指标你得先有一批带标注的测试数据。我一开始是自己手动标了几十个问题虽然少但足以发现明显的问题。5.2 分环节定位问题评估的价值在于定位问题出在哪个环节。我的排查思路是如果命中率低说明检索环节有问题。可能是切分不合理关键信息被切碎、Embedding 模型不合适、或者粗排 K 太小。我会先看那些没命中的问题它们的标准文档长什么样是不是被切碎了或者是不是语义上和 Query 差太远。如果命中率高但最终回答质量差说明问题在生成环节。可能是上下文组装有问题噪声太多、关键信息被淹没或者 Prompt 没设计好或者 LLM 本身能力不够。如果召回的相关文档排序靠后说明重排没起作用或者没配好可以考虑换 Reranker 或调整候选数量。我踩过的一个典型坑是命中率一直上不去排查半天发现是切分时把表格切碎了导致表格里的关键数据检索不到。后来针对表格做了特殊处理命中率立刻上来了。这就是分环节定位的价值——不定位你永远不知道问题在哪。5.3 一些实用的调优经验最后分享几个我实测有效的调优技巧小块检索、大块返回检索时用小块保证精准命中后返回它所属的父块保证上下文完整。这个技巧对长文档特别有效。给 chunk 加标题上下文在向量化之前把 chunk 所属的章节标题拼到 chunk 前面一起向量化。这样即使 chunk 本身没提到关键词标题里的信息也能帮上忙。定期重建索引文档会更新索引会过时。我设了一个定期重建的机制保证库里的内容是最新的。监控线上 Query把用户实际问的问题记录下来定期分析哪些问题召回效果差用这些真实数据来优化比拍脑袋强得多。RAG 这条链路说复杂也复杂说简单也简单。复杂在于每个环节都有坑简单在于只要你把每个环节的为什么想清楚了剩下的就是调参和迭代。我这次从离线建库到线上召回完整跑了一遍最大的收获不是学会了某个工具而是理解了整条链路的因果关系——知道每个决策会影响什么出了问题该往哪里查。这种全局视角比记住任何单个技巧都值钱。