
RAG 这个词这两年出现的频率越来越高但凡聊到让大模型“说自己的话、答自己的料”基本绕不开它。我最早接触 RAG 是在一个内部知识库问答的需求里当时团队想让模型能回答产品手册、运维文档里的细节问题直接微调成本太高、更新又慢于是把目光转向了检索增强生成这条路。实测下来RAG 确实是目前把私有数据接进大模型最务实的一种做法不用动模型权重数据更新只需重建索引回答还能给出出处排查问题时心里有底。这篇内容我想把 RAG 的原理讲透再把本地化落地这一整套流程拆开揉碎讲清楚——从文档怎么切、向量怎么选、检索怎么调到生成环节怎么约束模型别乱编。适合刚接触 RAG 想搞明白底层逻辑的开发者也适合已经跑通 demo 但效果不稳定、想找优化方向的朋友。全文基于我在几个内部项目里的实际做法整理涉及参数的地方会给计算过程和取舍理由能直接抄作业的部分我会尽量写细。1. RAG 到底在解决什么问题1.1 大模型的三个硬伤与 RAG 的定位先把问题摆清楚不然容易把 RAG 当成万能药。大模型有三个绕不过去的硬伤一是知识有截止时间训练数据之后发生的事情它不知道二是私有数据进不去公司内部的文档、代码、工单它没见过三是容易一本正经地胡说也就是幻觉尤其在它不确定的时候反而答得更自信。解决这三类问题常见有三条路微调、长上下文直接塞、RAG。微调是把知识“焊”进权重里成本高、更新慢而且容易灾难性遗忘适合学风格和格式不适合频繁变动的知识。长上下文是把资料全塞进 prompt简单粗暴但受窗口限制、成本随长度线性上涨资料一多就撑不住。RAG 的思路不一样它是在生成之前先去外部知识库里检索相关内容把检索结果作为上下文喂给模型让模型“看着资料答题”。打个生活化的比方微调像是让学生把整本教材背下来考试RAG 像是开卷考试允许带资料进场答题前先翻到相关那几页。开卷的好处是资料可以随时换、答案有据可查坏处是翻书翻得不准就会答偏——这也正是 RAG 效果好坏的关键所在。所以 RAG 的定位很明确它解决的是“知识从哪来”的问题不解决“模型会不会推理”的问题。检索到的资料质量决定了回答的上限模型的能力决定了能不能把资料用好。理解这个边界后面调优时就不会把检索的锅甩给模型也不会指望换个更大的模型就能救回烂检索。1.2 RAG 的完整链路拆解一条完整的 RAG 链路我习惯拆成离线和在线两段。离线段是数据准备文档采集、清洗、切分、向量化、入库这一步是一次性或周期性的。在线段是每次问答实时跑的query 进来先做向量化去库里检索 top-k 相关片段拼进 prompt交给模型生成最后把答案和出处一起返回。这里面每一环都有坑。切分切得不好一个完整语义被拦腰截断检索出来就是半句话向量模型选得不对语义相似度算不准该召回的没召回top-k 设得太大噪声片段挤占上下文模型反而被带偏prompt 约束不到位模型看着资料还能自由发挥。我见过不少 demo 跑得挺顺、一上真实数据就拉胯的案例问题基本都出在这几个环节的细节上而不是框架本身。下面这张表是我对链路各环节影响权重的粗略判断基于几个项目的调优经验不是绝对数值但能帮你判断优化优先级环节对最终效果的影响调优难度常见问题文档切分高中语义截断、块过大过小向量模型高低换模型即可领域不匹配、中文支持差检索策略高中高单一向量召回漏检重排序中中排序不准、引入延迟Prompt 约束中高低模型自由发挥、不引用生成模型中低换模型即可指令遵循差从表里能看出来切分、向量、检索这三块是重头戏也是本地化实践里最花时间的地方。生成模型反而是最容易换的效果不行换一个就行但前面三块烂了换再强的模型也白搭。2. 本地化落地的整体架构设计2.1 为什么选择本地化而不是调 API先说清楚为什么要本地化。调云端 API 跑 RAG 当然省事但有几类场景绕不开本地部署数据敏感不能出内网、调用量大成本扛不住、需要离线可用、对延迟有硬要求。我参与的几个项目基本都是前两个原因内部文档涉及业务细节走外网不合规量大了按 token 计费一个月账单能吓死人。本地化的核心是把向量模型、向量库、生成模型三样都放在自己机器上。这里有个关键取舍生成模型本地跑对硬件要求最高7B 级别的模型量化后大概需要 6-8G 显存13B 要 12-16G再大就得靠多卡或者更激进的量化。如果硬件有限一个折中方案是检索和向量化本地做生成走内网自建的推理服务既保证数据不出内网又不至于被单机显存卡死。我的建议是先把链路跑通再谈性能。一开始别追求大模型7B 量化版足够验证流程等检索调好了、prompt 定型了再根据效果决定要不要上更大的模型。很多人一上来就纠结模型选型结果检索一塌糊涂换了三个模型都没救回来白白浪费时间。2.2 组件选型与硬件估算组件选型我按“够用、好维护、社区活跃”三个标准来。向量模型优先看中文语义能力本地部署常用的是 BGE 系列或者 M3E 这类中文优化过的模型维度一般是 768 或 1024。向量库方面数据量在百万级以内轻量方案完全够用没必要上重型分布式库数据量再大或者要支持复杂过滤再考虑更专业的向量数据库。硬件估算给个参考算法。向量库内存占用大致等于向量条数 × 维度 × 4 字节float32比如 50 万条 768 维向量就是 500000 × 768 × 4 ≈ 1.5GB加上索引开销按 1.5 倍算2GB 出头很轻。生成模型这边7B 模型 FP16 需要约 14GB 显存4bit 量化后约 4-5GB加上 KV cache 和上下文开销8G 显存能跑但上下文别开太大。这些数字是我实测的粗略值具体会因框架和量化方式有浮动。注意向量维度不是越高越好。1024 维比 768 维召回略好但存储和计算都涨三成数据量不大时收益不明显。先用 768 维跑通效果不够再升维。3. 文档处理与切分RAG 效果的地基3.1 文档清洗与格式统一这一步最枯燥但最不能省。真实文档五花八门PDF 有页眉页脚、扫描件是图片、Word 里有大量样式标记、网页抓下来一堆导航栏。直接把这些丢进去切分切出来的全是噪声。我的做法是先统一转成纯文本或 Markdown再做一轮清洗。PDF 提取优先用能保留结构的解析方式扫描件得先走 OCR。清洗规则我一般会做这几件事去掉连续空行和多余空格、剔除页眉页脚这类重复出现的行、把表格转成结构化文本表格直接切分很容易散架、统一标点和全半角。这里有个容易忽略的点保留标题层级信息。标题是天然的语义边界切分时如果能带上所属章节标题检索出来的片段可读性和相关性都会好很多。清洗完建议抽样人工看几十条确认没有大面积乱码或结构丢失。我踩过一次坑某批 PDF 提取出来中文全是乱码没检查就入库了结果检索永远召回一堆乱码片段排查了半天才发现是提取环节的问题。3.2 切分策略与参数计算切分是 RAG 里最需要动脑子的环节。切太大一个块里混了好几个主题检索精度下降切太小语义不完整模型拿到半句话没法答。业界常用的起点是块大小 500-800 字符、重叠 50-100 字符但这个值必须按你的文档类型调。重叠的作用是防止语义在边界被切断。比如一句话正好跨在两个块之间有重叠就能保证至少有一个块包含完整句子。重叠比例一般取块大小的 10%-20%太小起不到保护作用太大又造成冗余存储和重复召回。我一般用递归切分先按段落切段落超长再按句子切句子还超长才硬切。这样能最大限度保住语义边界。代码上大致是这样from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size600, chunk_overlap80, separators[\n\n, \n, 。, , , , , ], ) chunks splitter.split_text(cleaned_text)注意 separators 里中文标点要放在英文空格之前因为中文文档主要靠句号问号断句。这个顺序错了切分质量会明显下降。另外技术文档和 FAQ 类文档的切分逻辑不一样FAQ 最好一问一答作为一个块别把问题和答案切开技术文档按章节切更合理。没有万能参数只能拿真实数据试切完抽几条看看是不是完整语义。3.3 元数据设计被低估的检索增强手段很多人只存文本和向量其实元数据能大幅提升检索质量。我通常会给每个块打上来源文件名、章节标题、页码、更新时间这些标签。好处有两个一是检索时可以按元数据过滤比如只搜某个产品线的文档二是生成时可以把出处带给用户答案可信度立刻不一样。更进一步可以在块文本前面拼上章节标题再向量化相当于给每个块加了“上下文前缀”。实测这个做法对召回率有肉眼可见的提升因为标题里往往包含关键主题词能帮向量模型更好地理解这个块在讲什么。成本几乎为零强烈建议加上。4. 向量化与检索调优实战4.1 向量模型的选择与本地部署向量模型决定了语义相似度算得准不准是检索质量的天花板。选型时重点看两点中文语义能力和领域匹配度。通用模型在通用语料上表现不错但遇到专业术语密集的领域比如医疗、法律、工控召回会明显下降。这种情况要么找领域微调过的模型要么自己拿领域语料做对比学习微调后者成本高但效果最实在。本地部署向量模型很轻一个几百 M 的模型 CPU 都能跑批量向量化时用 GPU 加速会快很多。部署时注意归一化算余弦相似度前要把向量归一化成单位向量这样内积就等于余弦相似度检索时直接用内积索引更快。很多框架默认帮你做了但自己写代码时容易漏。批量向量化有个细节batch size 别设太大显存不够会 OOM也别太小吞吐上不去。我一般从 32 开始试看显存占用往上加。另外入库前一定要校验向量维度一致混入不同模型产生的向量会导致检索结果完全错乱这种问题排查起来很痛苦。4.2 检索策略从单一向量到混合召回单一向量检索有个天然短板它对精确匹配不敏感。用户问一个具体的型号、编号、专有名词向量检索可能召回一堆语义相近但没命中关键词的片段。解决办法是混合检索向量召回负责语义相似关键词召回比如 BM25负责精确匹配两路结果融合排序。融合常用 RRF倒数排名融合逻辑很简单每个文档在每路召回里的排名取倒数相加排名越靠前得分越高两路都靠前的文档最终得分最高。这个算法不需要调权重鲁棒性好我基本默认用它。伪代码大概是这样def rrf_fusion(vector_results, keyword_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(keyword_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) return sorted(scores.items(), keylambda x: x[1], reverseTrue)k 取 60 是论文里的经验值实际用下来 40-80 之间差别不大。混合检索的代价是多一路召回和融合计算延迟会涨一些但对召回率的提升很值尤其是文档里专有名词多的时候。4.3 重排序用精度换召回检索阶段为了不漏top-k 一般设得比较大比如 20-50但真正喂给模型的只能留几条3-5 条不然上下文塞满噪声。这中间的筛选就靠重排序。重排序模型reranker比向量模型更重、更准它对 query 和每个候选片段做交叉编码算出精细的相关性分数再取 top-n。这个思路叫“召回求全、排序求精”。向量检索快但粗reranker 慢但准两级配合能在延迟可控的前提下大幅提升精度。实测下来加了 reranker 之后喂给模型的片段相关性明显提升模型答非所问的情况少了很多。代价是延迟。reranker 对每个候选都要跑一次模型20 个候选就是 20 次推理用 GPU 加速后一般能控制在几百毫秒。如果对延迟极敏感可以只对 top-10 做重排或者用更小的 reranker 模型。我的经验是这一步的性价比很高除非延迟卡死否则别省。4.4 检索参数调优的实操方法参数调优别拍脑袋要有评估集。做法是人工构造几十到上百条“问题-标准答案-应召回的文档块”三元组然后跑检索看命中率。核心指标看两个Recallk前 k 条里有没有包含正确块和MRR正确块排得靠不靠前。Recall 低说明召回不够加大 k 或换模型MRR 低说明排序不准上 reranker。我一般按这个顺序调先固定切分和向量模型调 k 看 Recall 曲线找到 Recall 不再明显上升的拐点再上混合检索看提升最后加 reranker 看 MRR。每一步都记录指标别一次改多个变量不然不知道是哪个改动起的作用。这套流程听起来笨但比凭感觉调靠谱得多。5. Prompt 设计与生成约束5.1 上下文拼装与引用规范检索到的片段怎么拼进 prompt直接影响模型能不能用好。我的模板一般包含三部分系统指令你是基于资料回答的助手资料里没有就说不知道、检索到的片段带编号和出处、用户问题。片段之间用明确的分隔符隔开并标注来源方便模型引用。关键约束有两条只依据资料回答、资料不足时明确说不知道。这两条能大幅压低幻觉率。但光写指令不够模型有时还是会自由发挥所以我会在 prompt 里要求它回答时标注引用的片段编号这样即使它编了也能通过引用是否对得上快速发现。提示片段数量别贪多。3-5 条高质量片段比 10 条混杂的效果好因为上下文越长模型越容易抓错重点而且长上下文本身有“中间遗忘”现象关键信息放中间容易被忽略。把最相关的放最前面。5.2 生成模型本地部署与量化取舍本地跑生成模型量化是绕不开的话题。常见的有 4bit、8bit 量化量化越狠显存占用越小但精度损失越大。我的经验是8bit 量化基本无损4bit 在多数问答任务上可接受但涉及数字计算、代码生成这类任务时 4bit 容易出错。如果硬件允许优先 8bit实在紧张再上 4bit并且重点验证量化后在你任务上的表现。推理框架方面本地部署常用的是支持量化和高效推理的那几套选型时看它对量化格式的支持和吞吐表现。部署时注意上下文长度设置RAG 场景下上下文要能装下系统指令加几条片段一般设 4K-8K 够用设太大反而吃显存。温度参数建议调低0.1-0.3RAG 要的是忠实于资料不需要创造性。5.3 答案后处理与出处回填生成完别直接返回做一层后处理能提升体验。常见的有把模型标注的片段编号映射回真实出处文件名、页码拼在答案末尾检查答案里有没有“根据资料”这类空话有的话精简掉对超长答案做截断或分段。如果模型输出了引用编号但内容对不上可以标记为“低置信”提示用户核实。这一层还能做兜底如果检索阶段最高分低于某个阈值说明库里可能根本没有相关内容这时直接返回“未找到相关资料”比让模型硬答要诚实得多。阈值怎么定拿评估集跑一遍看正样本和负样本的分数分布取一个能分开两者的值我一般取正样本最低分的 80% 左右作为保守阈值。6. 常见问题排查与避坑实录6.1 效果问题的定位思路RAG 效果不好先别急着换模型按链路逐段排查。我总结了一个排查顺序先看检索再看拼装最后看生成。具体做法是拿一个答错的问题手动看检索出来的片段里有没有正确答案。如果没有问题在检索切分、向量、召回策略如果有但模型没答对问题在拼装或生成prompt、上下文顺序、模型能力。这个二分法能快速定位问题域。我见过太多人一上来就怀疑模型不行结果一查检索正确片段压根没召回换十个模型也没用。反过来检索没问题但答案还是错那才轮到调 prompt 或换模型。6.2 高频问题速查表下面这张表是我在实际项目里整理的高频问题和对应处理办法按出现频率排序现象可能原因排查与处理答非所问检索召回不相关检查切分是否截断语义换向量模型加混合检索说不知道但库里有召回没命中加大 top-k检查 query 改写确认向量维度一致答案含资料外内容prompt 约束不足强化“仅依据资料”指令降低温度要求引用专有名词答错精确匹配弱加 BM25 关键词召回混合融合长文档答不全上下文截断检查块大小和 top-k确认上下文长度够响应慢reranker 或模型推理慢减候选数换小模型开 GPU 加速重复召回同一段重叠过大降低 chunk_overlap检索后去重6.3 几个踩过的坑第一个坑是向量库更新不同步。文档更新了但索引没重建用户问新内容永远答旧版本。解决办法是把文档更新和索引重建做成流水线文档一变就触发重建或者至少定时全量重建。增量更新要小心删除旧向量不然新旧混在一起更乱。第二个坑是query 和文档表述差异大。用户口语化提问文档是书面语向量相似度算出来偏低。一个低成本办法是做 query 改写让模型先把用户问题改写成更接近文档表述的形式再检索实测召回率有提升。这个改写可以用小模型做延迟增加不多。第三个坑是评估集偏差。自己构造的评估问题往往和文档表述接近测出来指标虚高上线后真实用户问法千奇百怪效果打折扣。建议评估集里刻意混入口语化、模糊、带错别字的问法尽量贴近真实分布。7. 性能与成本的实际权衡本地化 RAG 跑起来之后性能和成本的账要算清楚。延迟主要花在三块向量化 query几十毫秒、检索加 reranker几百毫秒、生成取决于输出长度和模型大小几秒到十几秒。生成是大头想压延迟优先从生成端下手减输出长度、换更小的模型、开推理加速。成本主要是硬件折旧和电费。一台带单卡的机器能撑起中小规模的内部问答量再大要考虑多实例和负载均衡。我的经验是先单机跑通、压测找瓶颈再决定扩不扩别一上来就堆硬件很多瓶颈其实是检索参数没调好导致的无效计算。还有一个容易被忽略的成本是维护成本。文档在变、模型在迭代、用户问法在演化RAG 系统不是一劳永逸的得有定期评估和调优的机制。我一般会留一个反馈入口让用户对答案点“有用/没用”攒一批 badcase 定期分析这比拍脑袋调参有效得多。我个人在实际操作中的体会是RAG 的难点从来不在“把链路搭起来”demo 半天就能跑通难的是让它在真实数据、真实问法下稳定好用。切分、向量、检索这三块的地基打不牢后面怎么调都是事倍功半。所以如果你正准备上手我的建议是前期多花时间在数据清洗和切分上多构造评估集把检索指标盯住生成端反而是最后才需要操心的部分。这套流程走下来一个能用的本地 RAG 系统基本就成型了。