RAG 这个词从 2023 年火到现在我身边做后端、做算法、做产品的朋友几乎都动过手。但真正让我印象深刻的不是谁把 Demo 跑通了而是谁把它推到了生产环境还能稳住。我见过太多团队卡在同一个地方本地用几十页 PDF 试的时候效果惊艳一上真实知识库就开始胡言乱语检索回来的东西驴唇不对马嘴答非所问最后项目不了了之。这篇就把我从零搭一套 RAG 知识库的完整路径摊开讲包括每一步为什么这么做、参数怎么定、以及那些文档里不会写、只有踩过才知道的坑。不管你是刚听说 RAG 想上手还是已经跑通 Demo 正发愁怎么落地下面这些内容应该都能对上你的场景。1. 先把 RAG 到底在解决什么想清楚1.1 大模型的两个硬伤决定了 RAG 的存在很多人一上来就急着装环境、拉模型结果做到一半发现方向都不对。我觉得动手之前得先弄明白 RAG 到底补的是哪块短板。大模型有两个绕不过去的问题一是知识截止它的训练数据有明确的时间点之后发生的事它一概不知二是幻觉遇到不知道的东西它不会说我不知道而是编一个听起来特别合理的答案给你。这两个问题在通用闲聊里无所谓但一旦落到企业知识库、客服问答、内部文档检索这些场景就是致命的。RAG 的全称是 Retrieval-Augmented Generation检索增强生成。它的思路特别朴素既然模型不知道那我就在它回答之前先把相关资料从我的知识库里捞出来塞进它的上下文让它看着材料答题。这就像开卷考试模型是那个脑子好使但没背过这本书的考生检索系统负责在考试时把对的那几页翻给它看。理解了这一点后面所有的工程决策都有了判断标准——任何一步都是为了让翻到的那几页尽可能准、尽可能全、尽可能不干扰模型。1.2 一个完整的 RAG 链路拆成哪几段我把整条链路拆成两大阶段这个划分方式贯穿全文后面每一节都对应其中一段。离线索引阶段数据进来的时候做一次或定期做文档加载 → 文本切分 → 向量化 → 存入向量库。这一步的目标是把非结构化的文档变成可以被快速检索的结构化数据。在线检索阶段用户每次提问时做查询改写 → 向量检索 → 重排序 → 拼装上下文 → 交给大模型生成。这一步的目标是在用户提问的瞬间从海量片段里精准捞出最相关的那几条。提示新手最容易犯的错是把全部精力砸在换个更强的模型上而忽略了索引和检索。实测下来RAG 效果差八成问题出在检索环节而不是生成环节。模型再强你喂给它的材料是错的它也答不对。1.3 为什么能跑通和能落地是两回事Demo 阶段你用的是自己精心挑选的几篇文档问题也是你自己想的当然效果好。但真实场景里文档格式五花八门PDF、Word、Excel、网页、扫描件内容有大量重复和噪声用户的问题千奇百怪还有错别字和口语化表达。这时候你会发现检索命中率hit rate断崖式下跌。所谓 hit rate就是用户真正需要的那条知识有没有被检索结果覆盖到。这个指标是 RAG 落地的生命线后面我会专门讲怎么把它从 60% 拉到 90% 以上。2. 文档加载与切分决定上限的一步2.1 文档加载远没有想象中简单我一开始以为加载就是把文件读成文本结果第一个坑就栽在这。PDF 分两种一种是原生电子版文字可以直接提取另一种是扫描件或图片型 PDF本质是一堆图片直接提取出来是空的。后者必须走 OCR。我当时的做法是先判断 PDF 里有没有文本层没有就走 OCR 流程这个判断逻辑能省掉大量无效处理。Word 和 Excel 相对好办但 Excel 有个坑表格里的数据如果直接按行转文本会丢失表头信息检索时根本对不上。我的处理是把每一行都拼上表头变成字段名: 值的形式再入库。网页内容则要处理导航栏、广告、页脚这些噪声不然检索出来的全是版权所有这种废话。# 判断 PDF 是否需要 OCR 的简化逻辑 import fitz # PyMuPDF def needs_ocr(pdf_path, sample_pages3): doc fitz.open(pdf_path) text_len 0 for i in range(min(sample_pages, len(doc))): text_len len(doc[i].get_text().strip()) # 采样几页如果平均每页文本极少基本就是扫描件 return text_len / min(sample_pages, len(doc)) 502.2 文本切分的粒度直接决定检索质量切分chunking是 RAG 里最被低估、也最影响效果的一步。切太大一个片段里混了好几个主题检索时噪声大切太小一句话被拦腰截断语义不完整。我试过固定长度切、按段落切、按标题层级切最后发现没有万能方案得看文档结构。对于结构清晰的文档比如产品手册、规章制度我优先按标题层级切让每个 chunk 天然带一个语义边界。对于没有明显结构的连续文本用递归字符切分配合重叠overlap。重叠的作用是防止关键信息正好卡在切分点上被割裂一般设成 chunk 大小的 10% 到 20%。切分策略适用场景chunk 大小建议重叠建议按标题层级手册、规范、结构化文档按语义自然分段无需重叠递归字符切分通用连续文本300-500 字50-100 字按句子切分问答对、短文本1-3 句1 句语义切分高质量要求场景动态动态2.3 中文切分的一个隐藏坑英文按空格和标点切很自然中文不行。我早期用默认的字符切分器处理中文结果经常把人工智能切成人工和智能两半检索时两个片段都匹配不上完整语义。解决办法是换用对中文友好的切分器或者自己按中文标点。做句子边界识别。另外中文的 token 密度和英文不同同样 500 字中文的信息量往往更大所以中文 chunk 可以适当小一点我一般控制在 300 到 400 字。注意切分完一定要抽样人工看一眼。我见过有人切出来的 chunk 全是半句话检索效果差还找不到原因。花十分钟抽查能省掉后面几天的排查。3. 向量化与向量库选型别被参数吓住3.1 Embedding 模型怎么选向量化的本质是把一段文本映射成一个高维向量语义相近的文本在向量空间里距离也近。选 embedding 模型我主要看三点中文效果、维度、以及能不能本地跑。维度不是越高越好高维度检索慢、存储贵1024 维对大多数中文场景已经够用。如果数据敏感不能出内网就得选能本地部署的模型。我实测下来中文场景里专门针对中文优化的模型比通用多语言模型效果好一截尤其是在专业术语和长句上。选型时别只看榜单拿你自己的真实数据跑一批 query看 hit rate这才是最靠谱的评估方式。3.2 向量库从轻量到生产向量库的选择跨度很大从几行代码就能跑的本地库到需要集群运维的分布式方案都有。我的建议是按数据量级和并发需求选别一上来就上重型方案。数据量小几万条以内、单机、验证阶段用本地文件型向量库零运维装完就能用适合快速验证。数据量中等、要持久化、要并发用支持持久化和索引优化的单机数据库方案。数据量大、高并发、要分布式才考虑集群型向量数据库。我踩过的一个坑是早期为了显得专业直接上了分布式方案结果数据才几千条运维成本高得离谱查询延迟还不如本地库。技术选型要匹配当前阶段过度设计是另一种浪费。3.3 相似度度量方式的选择向量检索靠计算相似度常见的有余弦相似度、点积、欧氏距离。大多数文本 embedding 模型训练时用的是余弦相似度所以检索时也优先用余弦。这里有个细节如果你的向量做了归一化余弦相似度和点积是等价的可以省一次计算。我一般入库前统一归一化检索时用点积速度更快。4. 检索环节hit rate 从这里开始爬坡4.1 纯向量检索的天花板在哪纯向量检索也叫稠密检索擅长语义匹配用户问怎么退款文档里写申请退货流程它能匹配上这是关键词检索做不到的。但它也有软肋对精确的专有名词、编号、代码不敏感。用户问错误码 E1024 怎么解决向量检索可能给你返回一堆泛泛的错误处理文档就是找不到 E1024 那条。这就是为什么很多落地项目最后都上了混合检索向量检索负责语义关键词检索比如 BM25负责精确匹配两路结果融合。融合方式我常用的是加权求和或者倒数排名融合RRF后者不需要调权重更省心。4.2 重排序把最相关的顶上来检索回来一堆片段顺序对不对很关键因为大模型的上下文窗口有限排在前面的权重更高。重排序rerank就是用一个更精细的模型对初步检索的结果重新打分排序。它的原理是初步检索用的是双塔结构query 和文档分别编码快但精度有限重排序用的是交叉编码query 和文档一起送进模型精度高但慢。所以典型做法是先用向量检索快速召回几十条再用重排序精选出最相关的几条。我实测的一个数据加了重排序之后top-3 的命中率能提升 15 到 25 个百分点。这个投入产出比非常高强烈建议加上。4.3 查询改写用户问的和文档写的往往不是一回事用户提问很随意可能带口语、带错别字、或者问得很笼统。直接拿原始 query 去检索效果经常打折。查询改写有几个常用手段同义扩展把咋退钱改写成如何申请退款补上正式表达。多查询生成让模型基于原问题生成几个不同角度的子查询分别检索再合并覆盖更全。指代消解多轮对话里它多少钱这种问题得先结合上文把它还原成具体对象再检索。提示查询改写会引入额外的一次模型调用增加延迟。我的做法是只在检索结果置信度低的时候才触发改写平时走快速路径兼顾速度和效果。5. 上下文拼装与生成最后一步别翻车5.1 上下文不是塞得越多越好新手常犯的错是把检索到的所有片段一股脑塞给模型觉得给得越多答得越准。恰恰相反无关片段是噪声会干扰模型判断甚至诱发幻觉。我的原则是宁缺毋滥只放真正相关的 top-k 条一般 3 到 5 条。如果检索结果的相关性分数都很低那说明知识库里根本没有这个知识这时候应该让模型直接说资料中没有相关内容而不是硬编。5.2 提示词模板的设计要点拼装上下文时提示词模板很关键。我一般会明确告诉模型三件事一是你的回答必须基于下面提供的资料二是资料里没有的不要编直接说不知道三是如果资料之间有冲突指出来。这三条能大幅降低幻觉率。另外给每个片段标上来源方便模型引用也方便用户溯源。PROMPT_TEMPLATE 你是一个严谨的知识库助手。请严格根据下面提供的资料回答问题。 要求 1. 只使用资料中的信息不要依赖你自己的知识补充。 2. 如果资料中没有相关信息直接回答根据现有资料无法回答该问题。 3. 如果不同资料存在矛盾请指出矛盾之处。 资料 {context} 问题{question} 回答5.3 引用溯源让答案可信生产环境里光给答案不够用户会问你凭什么这么说。所以我会让模型在回答时标注引用了哪条资料前端再把对应原文展示出来。这不仅提升可信度也方便用户自己核对。实现上给每个 chunk 一个唯一 ID拼上下文时带上 ID让模型在回答里引用。6. 那些文档不会写的踩坑记录6.1 坑一知识库更新了检索还是老答案这是最隐蔽的坑。文档更新了但向量库里还是旧数据检索出来的自然是过时内容。根因是索引和源文档没有同步机制。我的解决方案是给每个 chunk 记录来源文档的 ID 和版本号文档更新时先删掉该文档对应的所有旧 chunk再重新索引。别偷懒做增量追加否则新旧数据混在一起检索结果会非常混乱。6.2 坑二相似文档太多检索结果高度重复企业文档里经常有大量相似内容比如同一份制度的不同版本、不同部门的类似流程。检索时这几条高度相似的片段会一起被召回占满了 top-k 名额反而把真正有用的那条挤掉了。解决办法是在入库时做去重或者检索后做多样性筛选比如 MMR 算法保证召回结果的多样性。6.3 坑三表格和图片信息丢失纯文本切分会把表格结构破坏掉图片里的信息更是直接丢失。我处理表格时会把表格转成 Markdown 或自然语言描述再入库。图片则用多模态模型生成文字描述把描述文本一起索引。这样检索时图片内容也能被匹配到。有朋友问 RAG 知识库能不能存图片答案是能但存的不是图片本身而是图片的语义描述。6.4 坑四评估缺失全靠感觉调参我早期调 RAG 全靠感觉好像好了一点非常不靠谱。后来我建了一个小评估集准备 50 到 100 个真实问题每个问题标注好标准答案和应该命中的文档。每次改动后跑一遍看 hit rate 和答案准确率的变化。有了这个基准调参才有方向。这个评估集不用很大但一定要有而且要来自真实用户问题。常见坑根因解决方案更新后检索旧答案索引未同步按文档 ID 删除重建结果高度重复相似文档多入库去重 MMR 筛选表格图片丢失纯文本切分转结构化描述再索引调参无方向缺评估集建真实问题评估基准7. 从 Demo 到生产还差哪些工程化动作7.1 缓存省下大量重复计算真实场景里很多问题是重复或高度相似的。我给检索结果和最终答案都加了缓存相同或相似的 query 直接返回缓存结果既降延迟又省钱。缓存 key 可以用 query 的向量做近似匹配这样怎么退款和如何退钱能命中同一个缓存。7.2 监控上线只是开始上线后要盯几个指标检索延迟、生成延迟、hit rate、用户反馈点赞点踩、以及无法回答的比例。其中无法回答比例突然升高往往意味着知识库有内容缺失或者检索出了问题是最灵敏的报警信号。7.3 渐进式优化路线我的建议是分阶段推进第一阶段先跑通基础链路用最简单的方案上线收集真实问题第二阶段根据真实问题优化切分和检索加混合检索和重排序第三阶段再做查询改写、缓存、监控这些工程化增强。别想着一步到位RAG 是个需要持续迭代的系统先上线拿到反馈比闭门造车强一百倍。8. 进阶方向本体 RAG 与智能体 RAG 值不值得上8.1 本体 RAG 解决的是知识割裂普通 RAG 把文档切成孤立片段片段之间的关联关系丢了。比如A 是 B 的上级和B 负责 C 项目这两条信息分开检索永远拼不出A 通过 B 关联到 C这个结论。本体 RAGOntology RAG的思路是引入知识图谱把实体和关系显式建模检索时能沿着关系做多跳推理。它适合知识之间关联性强、需要推理的场景比如医疗、法律、复杂设备故障排查。但它的构建成本高需要先定义本体、抽取实体关系不是所有场景都值得。8.2 智能体 RAG 让检索更主动传统 RAG 是一问一检索一答的固定流程。智能体 RAGAgentic RAG则把检索交给一个能自主决策的智能体它可以判断这个问题需不需要检索、需要检索几次、检索结果够不够、要不要换个查询再试。这种模式在处理复杂多步问题时优势明显但延迟和成本也更高。我的经验是简单问答用传统 RAG 就够复杂推理场景再考虑智能体 RAG别为了追新概念而过度设计。8.3 怎么判断该不该上进阶方案判断标准很简单如果你的评估集显示失败案例主要是知识缺失或检索不到那先优化基础检索如果失败案例主要是信息都在但拼不出答案那才考虑本体或智能体方案。先定位瓶颈再选方案别反过来。9. 一套可复制的本地知识库搭建流程9.1 环境准备与依赖我把整套流程整理成可复制的步骤零基础也能跟着走。核心依赖就几个文档解析库、embedding 模型、向量库、以及一个大模型本地或调用均可。本地跑的话模型量化版本对显存要求低消费级显卡也能带动。# 核心依赖示意按实际选型调整 pip install pymupdf # PDF 解析 pip install sentence-transformers # embedding pip install chromadb # 本地向量库 pip install langchain # 流程编排9.2 完整流程串起来加载遍历文档目录按格式分别解析扫描件走 OCR。清洗去掉页眉页脚、导航、重复段落。切分按文档结构选切分策略中文控制 chunk 在 300-400 字加 10%-20% 重叠。向量化用中文优化的 embedding 模型向量归一化。入库连同来源 ID、版本号一起存入向量库。检索混合检索召回重排序精选 top-3 到 top-5。生成套用提示词模板要求基于资料回答并标注来源。评估用真实问题集跑 hit rate持续迭代。9.3 上线前必须做的几件事上线前我必做三件事一是抽样验证检索质量随机抽 20 个问题看召回结果对不对二是压测延迟确认高峰期响应时间可接受三是准备兜底话术检索不到时给用户一个体面的回复而不是让模型硬编。这三件事做完心里才有底。10. 我个人的几条经验之谈做了这么多套 RAG我最大的体会是RAG 的功夫八成在数据两成在模型。你把文档清洗干净、切分合理、检索精准用个中等模型效果都不会差反过来数据一团糟用再强的模型也是白搭。所以别急着追新模型、新框架先把数据这条线理顺。第二个体会是评估先行。没有评估集所有的优化都是盲人摸象。我现在的习惯是项目一开始就同步建评估集边做边测改动有据可依。第三个是别过度设计。我见过太多项目数据才几千条就上分布式向量库、上智能体框架结果复杂度爆炸效果还不如简单方案。技术选型永远匹配当前阶段能跑通、能迭代比看起来高级重要得多。最后分享一个我常用的小技巧给检索结果加一个相关性阈值。当最高相关性分数低于某个阈值时直接判定为知识库无相关内容走兜底话术不交给模型生成。这一招能挡掉相当一部分幻觉实测非常有效。阈值怎么定拿你的评估集跑一遍看正确命中和错误命中的分数分布取一个能分开两者的值就行。
阅读完成 · 觉得有帮助?