
RAG也就是Retrieval-Augmented Generation检索增强生成。这两年只要做过知识库问答、私有大模型应用基本没人能绕开这个词。看标题里的8.3就知道这是某套系列内容里的一个专题位置不过我更建议你把它当成一份独立完整的实战笔记来读。它解决的是大模型最要命的问题不知道你的私有知识、不知道最近的动态、回答起来还动不动一本正经地胡说八道。把检索和生成拼在一起等于给模型外挂了一个可以随时查的资料库。这篇内容适合正在做RAG落地、或者打算从零搭一套知识库问答系统的开发者我会把设计思路、组件选型、核心代码、踩坑经验一次性讲透。1. RAG系统整体设计与思路拆解1.1 大模型的先天短板与RAG的定位先搞清楚我们到底在解决什么问题。大语言模型靠海量语料预训练出来的能力很强但它有一个非常尴尬的先天短板——知识是静态的。模型训练完成的那一刻它的知识就被冻结住了。你今天问它昨天的新闻、你们公司内部的制度文档、某个产品的私有技术手册它要么说不知道要么靠训练时的残存记忆开始编。这种“编”不是它故意骗你而是模型本质上是在做下一词预测它没有“查证”这个动作。你问一个它没见过的问题它也会按照概率分布强行生成一个看起来合理的回答。这就是幻觉的来源。RAG的定位特别直接既然模型记不住那就不让它硬记改成在回答之前先去外部知识库里检索把相关内容拿出来拼接进提示词里让模型基于这些真实资料去回答。整个过程从“靠记忆回答”变成“先查证再回答”。用生活里的话说这就像让一个经验丰富的专家去参加闭卷考试。你把他关在考场里他只能靠脑子里的存量知识答题遇到没复习过的就可能胡编。现在你允许他带着资料库进去先翻书再答题准确率自然完全不同。RAG就是给大模型开卷考试的权利。1.2 RAG的标准三段式流程一个完整的RAG系统无论前端界面多复杂、组件多花哨核心链路永远逃不开三步索引、检索、生成。索引阶段是“备课”。把乱七八糟的源文档PDF、Word、网页、数据库导出做解析、清洗、切成小段然后用向量模型把每一段变成一堆浮点数最后连同原始文本一起存进向量数据库。这个阶段的质量直接决定了后续能不能查得到。检索阶段是“查资料”。用户输入一个问题同样用向量模型把问题变成向量然后在向量数据库里找最相似的若干个文本片段。这里关键是“语义相似”不是简单的关键词匹配。你说“公司年假怎么算”即使资料里通篇没有“年假”两个字但只要有“员工休假管理办法”相关的段落向量相似度也能把它们捞出来。生成阶段是“写答案”。把用户问题、检索到的资料片段、自定义规则拼成一个提示词交给大模型。模型参照资料生成答案并且我们会在提示词里明确要求“如果资料中没有相关内容就老实说不知道不要瞎编”。这三个阶段环环相扣缺一个都会出问题。索引没做好后面检索就是垃圾进垃圾出检索没做好找了一堆不相关的内容给模型生成的答案比不检索还离谱生成阶段提示词没写好再好的资料也被浪费。1.3 为什么优先考虑RAG而不是微调我经常被问到同一个问题私有知识接入到底是做RAG还是做微调Fine-tuning这不是二选一的关系但在绝大多数场景下RAG应该排在前面。微调的实质是改变模型的权重让模型“记住”你的数据。这件事成本很高每次数据更新都得重新训练而且训练不好容易把模型原有的通用能力弄坏。更麻烦的是微调后的模型依然存在幻觉它只是把新知识“记”进了参数里回答问题的时候还是没有一个“引用了哪份文档”的明确依据。RAG的优势在于四个字动态、可控。知识库内容可以随时增删改索引重建十分钟搞定不用动模型一根参数。回答的时候模型明确看到你给它贴出来的资料片段你甚至可以要求它逐条标注答案来自哪一段。出了问题你可以顺着检索链路定位到底哪一步丢了。当然如果模型本身的指令理解能力和语言风格达不到你的要求那就是另一回事可以考虑用微调去调底层能力。但纯粹为了塞知识进去RAG是成本最低、见效最快、也最容易迭代的方案。我自己的原则是先上RAG看效果哪里不够再针对性补而不是一上来就动权重。2. RAG核心组件拆解与选型2.1 文档解析与清洗最容易被低估的一环很多人搭RAG一上来就选模型、建索引结果跑去问“我那个扫描版PDF上的表格怎么查不到”这才发现源头就烂了。文档解析是RAG系统的地基地基松了后面再精美的架构都白搭。第一步是格式转换。PDF、Word、HTML各有各的解析工具但普遍会遇到问题PDF里文字是图片的扫描件表格结构会丢页眉页脚会混进来双栏排版会被切成八块再拼错。实际项目中我一般优先考虑那些支持OCR和版面识别的解析工具把扫描件先过一遍OCR再针对表格做结构化抽取。如果文档本身是Word或者Markdown优先用原始格式解析别走PDF这道弯路损失更小。第二步是清洗。所有跟正文无关的东西都要删掉页眉页脚、页码、水印、脚注、重复的标题、文档内部的目录导航、超链接那串长尾巴。再不济也要在切分之前把“第一章”和页码这类噪声从文本里剥离。这里有一个经验清洗规则宁可写得保守也不要激进。你为了省事用一个正则把“第X条”全删了结果正文里原本想保留的条款引用也没了。第三步是元数据标记。给每个文本片段打上来源标签比如文档名、章节标题、页码、更新时间。这块很多人会偷懒但我强烈建议加上因为后续做溯源、过滤、权限控制全都要靠它。用户问“这个政策是不是最新的”你得能顺着元数据翻出原始出处给他看。2.2 文本切分chunk size与overlap怎么定清洗完的文档一般都很长不可能整篇塞给模型。向量模型有个输入长度上限检索的粒度也需要控制所以必须把文档切成小块也就是chunk。chunk size也就是每块多长直接影响两个东西语义完整性和检索精度。块太长一个向量里塞了一堆主题相近但不同的小点检索时往往只能捞到“跟问题沾点边”的块具体答案藏在块的后半段模型看着大片文字也容易抓不住重点。块太短比如一句话一块语义就不完整了一个逻辑完整的段落被劈成两半检索时反而匹配不上。我给一个实用区间中文场景下chunk size取300到500个字符比较合适英文可以放到800到1000字符。这不是拍脑袋而是考虑到中文一句话的信息密度比英文高同样的语义长度占的字符数更少。如果你用的是现成的向量模型一定要先看清楚它的最大输入长度别把chunk size设得比模型上限还大。overlap也就是相邻块之间的重叠长度很多人会忽略。它的作用是防止“语义断层”。假设一条规定被切在两块的正中间前一块结尾说了一半后一块开头继续说如果两块没有重叠检索时任何一块对这个问题来说都是“缺胳膊少腿”的。设置10%到20%的重叠能有效缓解这个问题。比如chunk size是400overlap给50到80个字符折中一下就行。切分策略上也不要迷信固定长度。如果你的文档本身就是有结构的比如Markdown标题、章节段落那就更推荐“按结构切分”先用标题把文档切成大块再在超长块内部按固定长度二次切分并且把父级标题拼进子块的文本里形成“章节名小节名正文”的完整上下文。这样检索出来的片段自带上下文信息答案质量会明显好一截。2.3 向量化模型怎么选向量化模型的任务是把文本变成一串固定长度的数字比如1024维。这个模型的质量决定了“语义相近”到底准不准。选型时主要看三件事是否支持中文、输入长度够不够、embedding维度跟你选的数据库是否协调。先说中文支持。现在很多通用模型英文表现得很好中文却经常词不达意。直接用开箱即用的中文向量模型是比较稳的选择我自己用得比较多的是bge系列中英文效果都比较均衡。如果你的知识库以英文为主那选择面更宽很多国外开源模型都很优秀但中文场景下我建议优先测bge或者同级别的国产模型实测下来中文相似度计算更靠谱。再说输入长度。向量模型接收的是文本不是词。你选的chunk size不能超过模型最大长度同时也要关注模型的手感有些模型对超过两三百字的文本区分度会急剧下降这时候即使chunk size设到800实际检索效果反而不如切成300。这个没有统一答案最土也最有效的办法是拿一批真实问题去小规模测试比较不同chunk size下的召回率。最后是维度。向量维度越高存储占用的空间越大但并不是维度越高效果越好。bge-large是1024维bge-base是768维如果知识库规模不大几百GB以下用base级别的已经够跑了。维度还跟向量数据库的索引类型强相关后面选库的时候一起考虑。2.4 向量数据库对比不只看性能向量数据库是用来存向量、做相似度检索的。现在市面上选项很多有专门的向量数据库也有普通数据库加了向量插件。我梳理一下主流选择帮你按场景对号入座。轻量级嵌入式方案适合个人项目和原型验证。安装简单代码里几行就能初始化一个本地文件库数据量在百万条向量以内、单机跑完全够用。这类方案的优势是把复杂度降到最低缺点是横向扩展能力弱不适合大规模并发和分布式部署。重量级分布式方案适合企业级线上系统。它支持数据分片、多副本、高并发还带了监控和运维工具适合真正要上生产的项目。缺点是需要单独部署服务运维成本高资源占用也更大。如果你的知识库只有几万条向量用这种方案就属于杀鸡用牛刀。还有一个容易被忽略的点检索模式。很多向量库支持多种相似度算法最常见的是余弦相似度cosine和点积dot product。关键前提是你得保证索引数据和查询数据经过了同样的向量模型和归一化处理否则算法名对上了距离计算却跟你的预期完全不同。建collection时建议把相似度度量显式设成cosine最常见的坑就是这里。选库的时候不要只看网上评测里那些百万级召回率的数字先想清楚你的数据量、并发量、运维能力。知识库一万条数据和一亿条数据面对的问题完全不是一个量级。3. 实操过程与核心环节实现3.1 环境准备与依赖安装实操之前先把环境搭好。我以最常见的Python技术栈为例核心依赖就四样文档解析库、向量模型库、向量数据库客户端、大模型推理SDK。如果只是快速验证我习惯先装好一个轻量级向量库和SentenceTransformer。下面这行命令装核心依赖pip install sentence-transformers chromadb另外根据你的文档类型补解析库。如果主要是纯文本和Markdown装个基础库就够了如果是PDF和Word批量处理建议额外加上专门的解析工具同时准备好OCR能力扫描版PDF没有OCR就是一堆图片怎么检索都白搭。大模型推理服务单独准备。你可以调用某个云端API也可以在本地用工具部署一个开源模型。这一步不影响RAG的核心链路但要注意最终生成质量跟模型基础能力强相关一个表达能力很弱的模型就算检索做得再准也写不出像样的答案。我的建议是系统原型阶段用什么模型无所谓先跑通链路后续再换更强的模型对比。3.2 索引构建全流程从原始文档到可检索向量这是RAG系统里“备课”的过程我直接写一个可以照着改的最小闭环。假设你已经把一份说明文档解析成了清洗后的文本保存在documents列表里每个元素是带元信息的文本。下面这段代码完成切分、向量化、入库from sentence_transformers import SentenceTransformer import chromadb # 1. 加载向量模型 model SentenceTransformer(BAAI/bge-large-zh-v1.5) # 2. 连接本地向量库 client chromadb.PersistentClient(path./kb_store) collection client.get_or_create_collection( namedoc_cn, metadata{hnsw:space: cosine} ) # 3. 简单的固定长度切分overlap80 chunk_sizes [] chunk_ids [] chunk_metas [] for idx, doc in enumerate(documents): text doc[text] start 0 step 400 overlap 80 part_id 0 while start len(text): end start step chunk text[start:end] chunk_sizes.append(chunk) chunk_ids.append(fdoc_{idx}_part_{part_id}) chunk_metas.append({ source: doc[name], page: doc.get(page, ), chunk_index: part_id }) part_id 1 start end - overlap # 4. 批量向量化并写入数据库 chunk_vectors model.encode(chunk_sizes).tolist() collection.add( idschunk_ids, documentschunk_sizes, embeddingschunk_vectors, metadataschunk_metas ) print(f入库完成共 {len(chunk_sizes)} 块)这段代码里有一个非常关键的点collection.add一次传入了id、原文、向量和元数据四份内容它们必须一一对应。很多人测试的时候只传向量后面检索出来只有向量数字看着一脸懵——因为没把原文存进去。实际项目里切分逻辑要比上面复杂得多。建议至少做到按文档结构先分段再对超长段做二次切分切分时把父级标题拼到子块开头最后在元数据里记录每个块对应的原文页码范围。这样检索出来的结果能直接追溯到PDF的某一页用户追问时你拿得出手。3.3 检索与生成把资料喂给大模型索引构建好了接下来是查询链路。用户输入一个问题先向量化再查库最后拼提示词交给大模型。query 公司项目延期了请假流程怎么走 # 1. 检索向量库 query_vector model.encode(query).tolist() results collection.query( query_embeddings[query_vector], n_results5, include[documents, metadatas, distances] ) # 2. 取出检索到的文本片段拼接成上下文 context_chunks results[documents][0] metadatas results[metadatas][0] context for i, chunk in enumerate(context_chunks): source metadatas[i][source] context f[{i1}] ({source})\n{chunk}\n\n # 3. 构造提示词让模型基于资料回答 prompt f你是一个知识库问答助手。请严格基于下面提供的资料回答问题。 要求 - 如果资料中没有相关信息直接回答“资料中未找到相关内容”不要编造。 - 回答时尽量引用资料中的关键信息。 - 如果问题与资料无关请明确说明。 资料 {context} 问题 {query} 回答 # 4. 调用大模型生成最终答案 answer llm_chat(prompt) print(answer)这里llm_chat是一个抽象函数实际使用时换成你选定的推理服务SDK即可。不管用哪个模型提示词的写法一定要守规矩先定角色再说要求然后给资料最后才丢问题。顺序乱了模型很容易抓不住重点。为什么要把资料放在问题前面因为大模型对提示词开头和结尾的内容关注度更高。资料作为主体放中间问题放在最末模型在生成时会把注意力放在“即将回答的那个问题”上同时又能参考前面的资料。这算是提示词工程里一个非常实用的细节。还有一点我必须强调n_results5这个数字不是越大越好。检索回来的片段越多上下文越长模型越容易被无关信息带偏而且输入token成本也越高。实际项目中我一般先取5条左右看效果再调整到3到7之间。核心原则是检索回来的每一段都得是“跟问题高度相关”的而不是凑数量。3.4 关键参数调优的实操心得RAG上手容易调好难。几个参数直接影响体验我说说自己的调试顺序。第一优先调的是chunk size和overlap。我见过太多项目一上来就调提示词、换模型效果没起色最后发现是切分粒度不对。建议准备一组覆盖不同场景的测试问题做A/B对比分别用200、400、800的chunk size跑一遍记录每条问题的回答质量。这一步投入两小时后面能省两天。第二调的是top-k也就是捞多少条上下文。判断标准很简单把检索结果直接打印出来看。如果前5条里只有一条相关top-k加不加都没意义问题出在召回精度上如果相关片段总排在第6、第7位那就把top-k适当加大让模型能看到候选答案。第三才轮到提示词。提示词能优化的是输出格式、约束语气、是否要求分点但它拯救不了“根本检索不到”。顺序一定不要颠倒先保证查得准再优化怎么答。最后一个参数是向量库的索引参数。以常见的HNSW索引为例M控制每个节点的最大连接数ef_search控制查询时扫描的范围。M调大召回通常更好但索引更占内存ef_search调大查询更准但延迟变高。真要调的时候别一上来就乱拧先用默认值跑通看日志里“召回不准确”还是“延迟高”再对症下药。4. 常见问题与排查技巧实录4.1 检索召回效果差总查不到相关内容这是RAG项目里出现频率最高的问题。“查不到”分两种一种是压根不相关另一种是明明有正确答案但没被捞回来。先做最基础的排查直接打印检索返回的原始结果对比用户问题看返回的文本片段跟问题到底有多相关。如果完全不相关优先怀疑三件事向量模型不行、文本切分太粗、查询措辞和文档用语差太远。向量模型不行是最常见的。中文专用和通用模型在语义相似度上的差距非常明显。我测试过一个真实场景问“离职补偿怎么算”用通用模型检索返回的文档全是关于“入职流程”的换了中文优化过的向量模型返回的全部是“经济补偿”相关章节。这不是玄学是模型训练语料和优化目标的差异。文本切分太粗的典型表现是检索出来的片段包含正确答案但埋在大段无关文字里top-k那几块都是半个章节答案被稀释了。这种情况把chunk size调小或者改成按段落切分效果立竿见影。还有一类情况是“问法和写法不一致”。文档里写“员工严重违纪公司可解除劳动合同”用户问“我犯了大错会不会被开除”。“违纪”“解除”跟“犯错”“开除”在字面上差异很大向量模型能不能识别这种同义关系就看模型本身的能力了。遇到这种一个实用的兜底方案是做查询扩展把用户问题改写几个同义版本分别去检索然后合并去重。成本不高但召回率能提升一截。4.2 检索到了资料回答还是胡说八道有些人会想资料都找到了怎么答案还是不对这个问题通常出在生成环节而不是检索环节。先检查你的提示词有没有“强制约束”。如果只是简单地把资料贴给模型没有明确说“只能根据资料回答”那模型就可能结合自己脑子的知识自由发挥。特别是当资料内容和模型旧知识矛盾时模型倾向于挑它更“熟悉”的答案。提示词里必须写明资料中没有的信息一律回答不知道。再检查资料拼接是否完整。有时候正确答案被切到了两块检索只回到其中一块模型看到的信息本身就是残缺的。这属于切分问题回到检索端可以把相关块的邻接块也一并取回来或者增加overlap。还有一种情况很隐蔽检索到的5块资料里前2块相关后3块不相关模型被后3块带偏了。你光看上下文觉得“反正模型会自己判断”但模型真的会跟不相关的内容较劲。解决方案是把top-k调小或者加一个相关性过滤设定相似度阈值低于阈值的片段直接丢弃别喂给模型。4.3 系统变慢并发一高就扛不住性能问题通常是工程化的门槛。几个常见瓶颈按优先级排列向量化耗时、向量检索耗时、大模型生成耗时。先看大模型推理这个通常是最慢的环节。一次回答要生成几百个token在无GPU加速或模型量级大的情况下RT动辄几秒到几十秒。优化手段无非是换更小的模型、开启流式输出、做缓存。用户看到的“慢”大部分时间都耗在这所以缓存命中很关键相同或相近的问题直接返回历史答案。向量检索本身很快但如果数据量涨到几千万条又没有合适的索引查询延迟会指数级上升。HNSW这类索引默认参数要调同时确认机器内存足够向量检索是典型的内存密集型任务内存不够会疯狂走磁盘速度直接崩掉。批量入库慢也常被忽略。如果你有几万份文档还是按文档逐条向量化、逐条入库跑一天都跑不完。正确做法是批量能一次塞1000条就不要一次塞1条。向量模型支持batch推理一次编码一个列表比循环编码快得多。数据库的写入也是批量比逐条高效。4.4 工程化落地避坑清单最后给一份踩坑汇总每个坑我都见过真实项目往里跳索引和查询必须用同一个向量模型。这句废话我还是要提因为换模型后忘记重建索引的情况太多了。模型A生成的向量和模型B生成的向量放在同一个向量库里检索结果完全随机而你从日志里根本看不出问题。向量相似度不等于语义正确性。余弦距离接近只说明两个文本在向量空间挨得近不说明事实正确。RAG系统永远需要事实核查机制比如强制模型引用原文、在答案下方附上来源片段别指望向量库自己保证准确性。chunk size不能超过向量模型的输入长度。超过之后有些模型会截断有些模型会报错还有些会继续算但效果极差。无论哪种都会在你发现之前就已经污染了索引。文档更新后要更新对应的元数据。知识库不是一次建完就完事的。现实世界里文档会改版、会过期如果你没有在元数据里记录更新时间检索时排在前面的永远是旧版本。建议至少在元数据里加last_modified字段查询结果按新旧程度做一次二次排序。不要把用户问题直接拼进索引文本。有人为了让“问题-答案”匹配更准把历史问答对也切块入库这没什么问题但如果把用户输入原封不动地存进数据库后续检索时可能会把别人问过的问题再次捞出来造成隐私泄露。5. 进阶方向与个人实操体会5.1 多路召回与重排序精准度再上一个台阶基础RAG跑通之后你想进一步提升效果最推荐的下一步就是加一个re-ranking重排序环节。前面的流程是向量检索后直接喂给模型中间缺一个“精筛”步骤。多路召回的意思是不只靠向量相似度一种方式。比如“向量检索关键词检索”组合向量检索管语义关键词检索管精确匹配。文档里出现一串专业编号“GB/T 12345-2020”向量检索可能觉得语义相近但不精确关键词检索却能一击命中。两种结果合并得到一个更大的候选集合。候选集合扩大之后用重排序模型再打分把最相关的几段精准地排在前面。重排序模型和向量模型不是一回事它专门做“给定一个查询和一个文档给出相关性分数”的任务精度更高但速度也慢所以它只适合拿来做第二步精筛而不是对全库跑一遍。我现在做的项目中加了重排序之后答案准确率提升非常明显这是投入产出比最高的一档升级。5.2 从向量到结构混合检索与知识图谱基础RAG的另一个局限是它把文档打碎成小块后块与块之间的逻辑关系就丢了。一个政策文件里“第一章总则”规定的基本原则可能影响“第三章罚则”里具体条款的解释。切块之后模型看到的只是孤立的几个片段没有全局关系。应对方案之一是GraphRAG方向在切块的同时把文档里的实体和关系抽取出来建一张知识图谱。比如从技术文档里抽出“模块A依赖于模块B”“接口C返回错误码D”这些关系。用户问“模块A改动会影响谁”时向量检索可能找不齐全但知识图谱可以通过关系遍历把所有受影响模块一次性捞全再做信息聚合。这条路线适合文档之间有强逻辑关联的场景代价是构建成本高不是所有知识库都值得上。混合检索也是一个轻量替代向量检索关键词检索元数据过滤一起用。用户问“2023版的考勤制度”你直接先用元数据把年份过滤成2023再在剩余文档里做向量检索命中率比纯向量高得多。这类结构化信息的约束向量模型本身很难处理但用元数据过滤就是一句话的事。5.3 一些个人体会我自己做过几个RAG项目之后最大的感受是RAG系统的复杂度不在模型而在工程。很多人以为选个大模型、装个向量库就能用实际跑起来才发现文档解析的边角料、切分的粒度、检索召回的质量、提示词的约束每一环都在消耗你的时间。印象最深的是第一次做真实项目时换了好几个向量模型调了许多参数效果总是不稳定。后来发现根因不是模型而是那份PDF文档本身有大量扫描页和复杂表格解析出来的文本顺序全是乱的。把解析环节重做一遍之后其他什么都没改效果立刻就好了。这让我养成了一个习惯遇到RAG效果不好先看原始文档变成纯文本之后长什么样再谈模型和参数。也强烈建议你在项目一开始就搭一个简单的评测集哪怕只是几十条“问题-标准答案”的配对。每次调整切分方式、换模型、改提示词都在同一套评测集上跑一遍对比回答质量。没有这个基准你会一直在“感觉变好了”和“好像又变差了”之间反复横跳。如果真要从头推荐一个落地路径我会说先用最小的技术栈把端到端链路跑通不管多粗糙能查能答就算成功然后再去补解析质量、精调chunk、加深检索策略。RAG是那种“一旦动起来问题就会告诉你下一步该做什么”的系统先跑起来比什么都重要。