
1. RAG 全链路到底在解决什么问题先把话说直白一点RAGRetrieval-Augmented Generation检索增强生成本质上就是给大模型外挂了一个“开卷考试”的能力。模型本身的知识是训练时冻结的你问它公司内部文档、昨天刚发的公告、某个垂直领域的冷门规范它要么答不上来要么一本正经地胡说。RAG 要干的事就是在用户提问的那一刻先去你的知识库里把相关片段捞出来塞进提示词再让模型基于这些真实材料组织答案。我做过好几个从零到一的 RAG 项目踩过的坑比读过的论文还多。很多人一上来就盯着“用哪个向量模型”“用哪个框架”结果链路跑通了效果却惨不忍睹。问题往往不在模型而在整条链路的工程细节文档怎么切、切完怎么向量化、检索怎么召回、召回后怎么重排、重排后怎么拼提示词、多轮对话怎么维护上下文。这六个环节任何一个掉链子最终答案都会崩。这篇内容适合三类人看一是刚接触 RAG、想搞明白全链路到底有哪些环节的开发者二是已经跑通了 demo、但效果不稳定想优化的人三是需要给团队做技术选型和方案设计的人。我会把每个环节的“为什么这么选”讲透而不是只丢一堆代码。因为 RAG 这东西抄代码容易理解取舍难而真正决定效果的恰恰是那些取舍。先给一个整体认知一条完整的 RAG 链路从用户提问到最终回答中间至少经过查询理解 → 向量化 → 向量检索 → 重排 → 上下文组装 → 生成六个阶段。每个阶段都有它的核心指标和常见陷阱。下面我按这个顺序把每个环节拆开讲中间穿插我实际项目里的参数选择和踩坑记录。2. 知识库构建文档处理与切块策略2.1 文档解析别小看这一步脏数据毁所有知识库构建的第一步不是向量化而是把原始文档变成干净的文本。这一步最容易被忽视但它的质量直接决定后面所有环节的天花板。我见过太多项目PDF 里的表格解析出来是一坨乱码扫描件 OCR 错字连篇结果检索出来的片段驴唇不对马嘴模型再强也救不回来。常见的文档类型和处理方式差异很大。纯文本和 Markdown 最好办直接读就行。PDF 分两种电子版 PDF 可以用 PyMuPDF 或 pdfplumber 抽取扫描版 PDF 必须走 OCR这时候识别准确率就是关键中文场景下 PaddleOCR 是比较稳的选择。Word 和 PPT 用 python-docx、python-pptx 处理。HTML 用 BeautifulSoup 或 trafilatura 提取正文记得去掉导航栏、广告这些噪声。提示文档解析阶段一定要保留元数据比如来源文件名、页码、章节标题、更新时间。这些元数据在后面做引用溯源和过滤时非常有用丢了就找不回来了。我个人的经验是解析完的文本要做一轮清洗去掉连续空行、统一全角半角、修正明显的 OCR 错误、把断行的句子重新拼接。特别是从 PDF 抽出来的文本经常一句话被硬生生拆成好几行如果不合并切块的时候会把语义切碎。2.2 切块RAG 效果的分水岭切块Chunking是 RAG 里最玄学也最关键的环节。切得太大检索出来的片段包含太多无关信息噪声干扰模型切得太小语义不完整检索到了也答不好。这里没有万能参数但有清晰的权衡逻辑。最基础的是固定长度切块比如每 512 个 token 一块块之间留 50 到 100 token 的重叠。重叠的目的是防止一句话正好被切在边界上导致语义丢失。这种方式实现简单适合结构松散的文本但缺点是经常把一段完整的论述拦腰截断。进阶一点的是按语义或结构切块。Markdown 按标题层级切代码按函数切法律合同按条款切论文按章节切。这种方式能保证每个块语义自洽检索质量明显更高。我现在的项目基本都用结构感知的切块只有在文本完全没有结构时才退回固定长度。还有一种递归字符切块LangChain 里的 RecursiveCharacterTextSplitter 就是典型代表。它按优先级依次尝试用段落、句子、逗号来切尽量在语义边界处断开。这个策略在通用场景下表现不错是我常用的默认方案。关于块大小我给几个实测参考值中文文本块大小 300 到 500 字重叠 50 到 80 字这个区间在大多数场景下召回和精度的平衡比较好。英文的话 token 数可以放到 512 到 800。如果是问答对形式的知识库一个 QA 对就是一块不用再切。切块策略适用场景优点缺点固定长度结构松散文本实现简单、块大小均匀易切断语义结构感知Markdown/代码/合同语义完整、检索准需针对格式适配递归字符通用场景兼顾语义与均匀参数需调优语义切块高质量要求语义边界最准计算成本高注意切块大小不是越小越好。块太小会导致检索时召回一堆碎片模型拼不出完整答案块太大则单块信息密度低检索精度下降。一定要用你自己的数据做 A/B 测试别照搬别人的参数。2.3 元数据与去重让知识库更“干净”切完块之后每个块都要挂上元数据。除了前面说的来源、页码还可以加分类标签、权限等级、生效时间。这些在检索时可以作为过滤条件比如只检索某个部门可见的文档或者只检索最新版本。去重也很重要。同一个内容在多个文档里重复出现会导致检索时返回一堆相似片段浪费上下文窗口。我一般用文本哈希或者向量相似度做去重相似度超过 0.95 的块只保留一个。这一步能显著提升检索结果的信息密度。3. 向量化把文本变成可检索的数学表示3.1 向量模型选型中文场景怎么挑向量化的核心是把文本映射成一个高维向量语义相近的文本在向量空间里距离也近。选向量模型是这一步的关键决策。英文场景下 OpenAI 的 text-embedding-3 系列是稳妥选择但中文场景要考虑中文语义理解能力。目前中文表现比较好的开源模型有 BGE 系列BAAI 出品、M3E、GTE 等。BGE-large-zh 在中文检索任务上长期霸榜维度 1024效果稳定。如果追求更强的多模态能力SigLIP2 这类模型可以同时处理图文适合有图片的知识库场景。选型时重点看三个指标检索准确率看 MTEB 榜单、向量维度影响存储和检索速度、推理速度影响构建效率。维度不是越高越好。1024 维和 768 维在实际检索效果上差距往往不大但存储和计算成本差不少。百万级文档的话维度每增加一倍向量库的存储和内存占用基本也翻倍。所以要在效果和成本之间找平衡。3.2 向量化实操批量、并发与归一化向量化本身不复杂但工程上有几个细节要注意。第一是批量处理别一条一条调模型那样效率极低。一般 batch size 设 32 到 128具体看显存和模型大小。第二是并发控制如果用 API 做向量化要注意限流加个重试和退避机制。第三是归一化。很多向量模型输出的向量需要做 L2 归一化这样余弦相似度就等价于内积检索时可以用更快的内积计算。大部分模型库会自动处理但自己实现时别忘了这一步。from sentence_transformers import SentenceTransformer import numpy as np model SentenceTransformer(BAAI/bge-large-zh-v1.5) def embed_texts(texts, batch_size64): embeddings model.encode( texts, batch_sizebatch_size, normalize_embeddingsTrue, # L2 归一化 show_progress_barTrue ) return embeddings # 批量向量化 chunks [文本块1, 文本块2, 文本块3] vectors embed_texts(chunks) print(vectors.shape) # (3, 1024)提示BGE 系列模型在检索时建议给查询加一个指令前缀比如“为这个句子生成表示以用于检索相关文章”这样能提升检索效果。这个细节很多人不知道但实测有提升。3.3 向量库选型从本地到分布式向量存哪儿小规模场景几万条以内用 FAISS 就够了它是 Facebook 开源的本地向量检索库轻量、快、无需部署服务。中等规模百万级可以考虑 Milvus、Qdrant、Weaviate 这些专业向量数据库支持分布式、过滤、持久化。如果已经在用 PostgreSQLpgvector 插件是个省事的选择不用额外维护一套系统。选型时重点考虑数据规模、是否需要元数据过滤、是否需要持久化、运维成本。我个人的建议是除非数据量真的很大否则别一上来就上分布式向量库FAISS 加个本地持久化能撑很久等真撑不住了再迁移。向量库适用规模特点运维成本FAISS万级以内轻量、快、本地低pgvector十万级复用 PG、支持 SQL 过滤低Qdrant百万级过滤强、API 友好中Milvus千万级分布式、功能全高4. 检索与重排决定召回质量的核心环节4.1 向量检索相似度计算与 Top-K 选择检索阶段就是把用户查询也向量化然后在向量库里找最相似的 Top-K 个块。相似度一般用余弦相似度或内积。Top-K 的选择是个权衡K 太小可能漏掉相关片段K 太大则引入噪声。我一般先用较大的 K比如 20 到 50召回再用重排模型精筛。纯向量检索有个天然短板它对关键词的精确匹配不敏感。比如用户搜一个特定的产品型号“X200-Pro”向量检索可能返回一堆语义相近但型号不对的文档。这时候就需要混合检索把向量检索和关键词检索BM25的结果融合。4.2 混合检索向量 关键词的互补BM25 是经典的关键词检索算法它基于词频和逆文档频率打分对精确匹配非常敏感。把 BM25 和向量检索的结果用 RRFReciprocal Rank Fusion倒数排名融合合并能同时兼顾语义匹配和关键词匹配效果通常比单一方式好不少。RRF 的思路很简单对每个文档把它在不同检索方式里的排名取倒数再求和得分高的排前面。这样既不需要归一化不同检索方式的分数又能让在多种方式里都排名靠前的文档脱颖而出。def rrf_fusion(vector_results, bm25_results, k60): scores {} for rank, doc_id in enumerate(vector_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) for rank, doc_id in enumerate(bm25_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) return sorted(scores.items(), keylambda x: x[1], reverseTrue)4.3 重排用 Cross-Encoder 精筛召回阶段追求的是“不漏”重排阶段追求的是“精准”。重排模型Reranker通常是 Cross-Encoder 结构它把查询和文档拼在一起输入模型直接输出相关性分数。这种方式比向量内积更准但计算量大所以只对召回的 Top-K 做重排。常用的重排模型有 BGE-reranker 系列、Cohere Rerank 等。BGE-reranker-large 在中文场景表现很好。实测下来加一层重排能把检索精度提升 10 到 20 个百分点是性价比很高的优化。注意重排模型和向量模型最好配套使用比如都用 BGE 系列这样语义空间一致效果更稳。混用不同厂商的模型有时会有意外需要实测验证。4.4 多轮对话下的检索设计多轮对话是 RAG 的一个难点。用户第二轮的提问往往是“那它的价格呢”这种指代性查询直接拿去检索向量里根本没有“它”指什么检索必然失败。解决办法是查询改写用 LLM 结合对话历史把当前问题改写成独立的、完整的查询再去检索。比如历史是“介绍一下 X200 这款产品”当前问题是“那它的价格呢”改写后变成“X200 产品的价格是多少”。这样检索就能命中正确文档。查询改写这一步在多轮场景里几乎是必需的不做的话体验会断崖式下跌。5. 上下文组装与生成最后一步别翻车5.1 上下文组装怎么拼提示词检索和重排之后你手上有一批相关片段接下来要把它们拼进提示词。这里有几个讲究。第一是排序把最相关的片段放在最前面或最后面因为模型对上下文首尾的信息更敏感中间容易忽略。第二是去重和截断相似片段合并总长度控制在模型上下文窗口的合理比例内一般不超过 70%给模型留出推理和回答的空间。第三是标注来源每个片段前面标上编号和来源让模型在回答时能引用。这样既方便用户溯源也能减少模型编造。提示词模板我一般这么写你是一个严谨的问答助手。请仅基于以下参考资料回答问题 如果资料中没有相关信息请明确说明“根据现有资料无法回答”。 参考资料 [1] 来源产品手册.pdf 第3页 内容X200 产品的售价为 2999 元…… [2] 来源公告.md 内容…… 用户问题X200 的价格是多少5.2 生成阶段温度、引用与幻觉控制生成阶段的参数也有讲究。RAG 场景下温度temperature建议调低0 到 0.3 之间因为我们要的是基于事实的准确回答不是创意写作。温度太高模型容易自由发挥引入幻觉。幻觉控制是 RAG 的核心价值之一。除了在提示词里明确要求“仅基于资料回答”还可以要求模型在回答里标注引用编号比如“根据 [1]X200 售价 2999 元”。这样用户能一眼看出答案有没有依据。如果模型答不出要允许它说“不知道”这比编一个错误答案强得多。提示如果发现模型经常忽略参考资料自己发挥可以在提示词里加一句“参考资料之外的信息一律不得使用”并给出反例。实测这样能明显降低幻觉率。5.3 引用溯源与答案可信度引用溯源是 RAG 相比纯 LLM 的一大优势。用户看到答案的同时能看到来源可信度大幅提升。实现上把检索到的片段和它们的元数据文件名、页码一起传给模型让模型在回答里带上引用标记前端再把标记渲染成可点击的链接。这一步在垂直领域尤其重要比如医疗、法律、金融答案必须可追溯。我做过一个中药处方审核的场景每个审核结论都要能定位到具体的药典条款引用溯源就是刚需。6. 常见问题与排查技巧实录6.1 检索不到相关内容怎么办这是最常见的问题。排查思路按顺序来先看查询本身有没有问题多轮场景下是不是没做查询改写再看切块是不是把相关内容切碎了检查一下相关文档的切块结果然后看向量模型是不是不适合你的领域可以拿几个典型查询手动算一下相似度最后看 Top-K 是不是设太小或者过滤条件是不是把相关文档排除了。我遇到过一次检索死活召回不到某份文档最后发现是那份文档在解析时编码错了向量化出来是乱码。所以文档解析质量一定要在构建阶段就验证别等检索出问题才回头查。6.2 检索到了但答案不对这种情况通常是上下文组装或生成阶段的问题。先看拼进提示词的片段是不是真的包含答案有时候重排把正确片段排到了 Top-K 之外。再看提示词有没有明确约束模型基于资料回答。如果资料里有答案但模型答错可能是片段太长噪声太多试试减小块大小或提高重排阈值。还有一种情况是资料本身有冲突比如新旧两版文档说法不一致。这时候要么在元数据里加时间过滤只检索最新版要么在提示词里让模型注意版本差异。6.3 响应太慢怎么优化RAG 链路的延迟主要来自三块向量化查询、向量检索、LLM 生成。查询向量化通常很快几十毫秒。向量检索取决于数据规模和索引类型用 HNSW 索引能控制在毫秒级。大头在 LLM 生成尤其是上下文很长的时候。优化手段一是减少拼进提示词的片段数量重排后只取 Top-3 到 Top-5二是用流式输出让用户先看到部分结果三是对高频查询做缓存相同或相似查询直接返回缓存结果四是向量检索和重排并行化能省一点时间。问题现象可能原因排查方向召回为空查询改写缺失/切块过碎检查多轮改写、切块粒度答案错误重排漏召/提示词约束弱调大召回 K、强化提示词响应慢上下文过长/无缓存减片段数、加缓存、流式输出答案重复块重叠过大/去重缺失调小重叠、加去重幻觉严重温度高/约束弱降温度、加引用要求6.4 几个容易被忽视的坑第一个坑是向量模型和查询不一致。构建时用了一个模型查询时用了另一个向量空间对不上检索必然乱。一定要保证构建和查询用同一个模型、同一套预处理。第二个坑是元数据过滤写错。比如时间过滤用了字符串比较结果“2024-1-1”比“2024-10-1”还大把该留的文档过滤掉了。时间字段一定要用标准格式或时间戳。第三个坑是上下文超长被截断。拼提示词时没算好 token 数超过模型窗口被静默截断答案自然不对。一定要在组装前算好 token 预算。第四个坑是密钥泄露。如果 RAG 服务调用了外部 LLM API密钥千万别硬编码在前端或日志里。用环境变量或密钥管理服务日志里对密钥做脱敏。这个在团队协作里尤其要注意。7. 我踩过的坑和几条实在建议先说一个我印象最深的教训。早期做 RAG 时我特别迷信“大模型 大向量模型”觉得模型越大效果越好结果一个项目里用了当时最大的向量模型构建知识库花了两天检索延迟高得没法用效果却没比小模型好多少。后来才明白RAG 的效果是整条链路的下限决定的不是某个环节的上限决定的。切块切得烂再强的模型也白搭。第二条建议是一定要建评估集。没有评估集你所有的优化都是盲调。准备 50 到 100 个典型问题标注好正确答案和应该召回的文档每次改动后跑一遍看召回率和准确率的变化。这个投入非常值能帮你省下大量瞎试的时间。第三条是别过度设计。很多框架提供了花哨的功能比如 Agentic RAG、多跳检索、图检索但你的场景可能根本用不上。先用最朴素的链路跑通效果不够再针对性加组件。我见过太多项目链路复杂得像迷宫效果还不如一个简单的向量检索加提示词。最后分享一个实用技巧把检索到的片段和最终答案一起存下来做日志。这样当用户反馈答案不对时你能快速定位是检索的问题还是生成的问题。这个日志在优化阶段是无价之宝能帮你精准找到瓶颈在哪一环。RAG 这东西入门容易精通难难的不是代码是对每个环节的取舍和调优。希望这些经验能帮你少走点弯路。