先聊点实在的。RAG这个词我今年被问了几十遍,十个问的人里有八个其实是想解决同一个问题:模型不知道我这边的东西,我总不能每次把一万页资料都塞进提示词里吧?于是就有了RAG,它解决的不是“让模型更强”,而是“让模型能查到你的私有资料”。这篇文章没有任何炫技的意思,就讲清楚一件事:一个能用的知识库,到底是怎么从零搭起来的。
1. 什么才算真正的RAG
1.1 先搞清楚RAG在解决什么问题
大模型有两个天生短板,第一是知识有截止日期,第二是它对你私域里的内容一无所知。很多人第一个念头是微调,但实际上微调适合的是改变模型的“行为习惯”和“输出风格”,并不适合往模型里塞新知识。因为新知识是不断增长的,今天微调进去的内容,下周可能就过期了,你不能每次都重新训一遍。RAG的思路就不一样,它不改变模型的任何参数,而是把“查资料”这个动作前置:你先从自己的知识库里检索出相关内容,再把这部分内容连同用户的问题一起交给模型,让它基于这些材料作答。
这个思路在实际项目里非常吃香,原因有三:一来不需要昂贵的训练资源,二来知识更新成本极低,三来每次回答都能引到原始材料上,可信度高。对绝大多数需要做私有知识问答的团队来说,RAG是性价比最高的一条路。
1.2 RAG的完整流水线,一条线走完
如果要把RAG拆成三个阶段,那就是索引、检索、生成。索引阶段负责把原始文档清洗、切块、向量化,最后存进向量数据库;检索阶段接收用户问题,把问题也做同样的向量化,然后在库里搜索最相似的片段;生成阶段把检索结果拼成一个带上下文的提示词,交给大模型输出答案。
这三个阶段看起来简单,但每一环都有大量的细节决定最终效果。我在实际项目中见过不少团队把全部精力放在调prompt上,检索出的片段牛头不对马嘴,prompt写得再漂亮也没用。因此,做RAG的第一步不是急着写代码,而是先接受一个基本设定:效果的上限由检索质量决定,生成只是把检索结果“讲”出来罢了。
2. 知识库构建:文档加载与切块
2.1 不同格式的文档怎么统一处理
知识库的原材料五花八门,最常见的有PDF、Word、Markdown、HTML、纯文本,甚至还有Excel里的表格数据。这里有个新手最容易踩的坑:直接拿PDF转出来的文本去切块,不去管页眉页脚和杂乱换行。我之前的处理习惯是,先按文档类型分流,PDF用解析器抽取文本时,顺手把页眉页脚、水印、页码这些噪声剔掉;Word和Markdown相对干净,主要注意图片里的文字提取不进来,有需求时先跑OCR;HTML要先剥掉标签,只保留正文结构。
文本清洗这件事看着不起眼,实际对后续切块质量影响非常大。想象一下,原本一个连续的段落被PDF解析器截成了十几行碎片,直接切块后语义就被拦腰截断,检索时自然匹配不准。所以我会在清洗阶段就把断行拼回段落,必要时把换行符替换为空格再按段落重组,保证每个块内部是连贯的语义单元。
2.2 切块不是切豆腐,大小得看场景
切块是RAG里最考验经验的一步。块太大,每个块里塞了太多无关信息,向量化后语义被稀释,检索精确度下降;块太小,单个块承载的信息量不足,检索回来也回答不了复杂问题。我对不同场景的推荐值是:常规的FAQ或短文档,用256到512个token;技术文档、手册这类的长文本,用512到1024个token;如果单个文档本身就是一篇文章,按段落或者一级标题来切,效果往往比固定长度更好。
再讲一个容易忽略的细节:重叠。相邻块之间如果没有重叠,原文里一句话前半截在上一块末尾、后半截在下一块开头,切块时这句话就被拆散成两段残缺内容,检索时哪边都匹配不准。我一般会设置大约10%到15%的重叠量,这样即使切到边界处,两边的内容也都能保持相对完整。
2.3 切块策略怎么选,直接给结论
固定大小切块的优点是简单可控,缺点是容易切断语义;递归切块是目前很多框架的默认方案,它是按段落、句子、单词逐层回退切分,兼顾了语义边界和长度控制;还有一种方案是按文档结构切,比如把Markdown的标题层级当成天然边界,适合说明文档这种结构性强的文本。
我自己的选择是分两步走:先看文档有没有清晰的结构,有结构就按结构切,比如标题、章节、列表项;结构不明显就退回递归切块。切完之后做一个自检,随机挑几十个块看有没有语义断裂的情况,如果断裂明显,就把块长度调小或者把重叠调大。切块这块没有绝对最优解,多试几组参数再决定是常态。
3. 向量化与索引:让文本变成可计算的距离
3.1 Embedding模型怎么选
切完的块要交给Embedding模型转成向量。模型选型上,中文场景首选中文效果好的向量模型,英文场景可以用通用型模型,如果领域非常垂直,比如医疗、法律,那我会先拿领域语料做个小测试集,跑一遍看看TopK命中率再决定。这里必须提醒一句:同一个向量模型必须同时用在文档和用户查询上,两边模型不一致,向量空间都对不上,检索结果基本没法看。
维度方面,常见的有768维、1024维、1536维甚至更高。维度越高,表达力越强,但存储和计算开销也更大。对中小规模的私有知识库,几百个文档的量级,其实768维到1024维完全够用。高维向量在小库上没有收益,只会拖慢检索速度。
3.2 向量数据库选型参考
选向量数据库,我主要看三点:是否支持增量更新、检索速度如何、部署运维成本高不高。目前市面上常见的方案包括开源向量数据库、传统数据库加向量插件、以及全套托管的向量服务。如果只是本地做一个原型项目,开源向量数据库就够了,不用上重型的分布式方案。
数据量级小的时候,甚至可以用文件格式来存向量,几千条向量做暴力搜索的速度也完全可以接受。等到数据量到百万级别,再考虑上专门的向量索引,比如HNSW或者IVF系列。做知识库项目,最重要的不是一步到位,而是先跑通流程,后面数据量上来了再平滑迁移。
3.3 向量检索的工作原理
向量检索听起来高深,本质是算距离。最常用的余弦相似度衡量的是两个向量的方向是否一致,对文本的长度不敏感;内积距离更偏向数值大的向量,适合归一化后的向量;欧式距离则衡量绝对位置远近。大多数RAG项目用余弦相似度就够了,这也是很多向量数据库默认的相似度度量方式。
检索阶段的大致动作是:把用户问题交给同一个Embedding模型转成向量,然后在库里按相似度排序,取TopK个片段。这个K值的设定也很关键,我一般先用K等于4到6起步,太少了容易漏答案,太多了会塞进无关内容干扰模型判断。K值不是越大越好,这一点必须记住。
4. 检索策略与进阶优化
4.1 从“只会找相似”到“找得准”
基础版RAG直接用向量相似度检索,已经能应付不少简单问答,但遇到专业术语多、说法和文档里不完全一致的问题时,就会暴露出短板。比如用户问“这个产品的保修政策是什么”,文档里写的是“质保条款”,两者的向量相似度不一定排在最前面。这时候单纯的向量检索就有点吃力了。
我的做法是引入混合检索:向量检索负责语义匹配,同时并行跑一个关键词检索,比如用BM25算法,把两者结果合并后再做重排序。混合检索的实现成本不高,但对效果提升非常明显,尤其是人名、产品名、编号这类实体密集型查询,关键词检索能精准命中,向量检索则负责兜底同义改写的情况。
4.2 重排序,很多人忽略的一步
检索返回的TopK片段,有时候排序并不理想,可能最相关的片段排在第五位,前四个都是沾边的内容。解决这个问题就需要一个重排序模型,把候选片段和问题一起交给一个交叉编码器,重新计算相关性打分,再按新的分数排序。
我之前做的一个技术问答知识库,加入重排序之后,回答准确率直接提升了一大截。重排序阶段只需要处理检索出来的少量候选,计算成本完全可控,强烈建议在比较重要的项目里把它加上。重排序模型同样要注意和中英文场景匹配,尽量挑跟你的Embedding模型同源或者经过同语言语料训练的。
4.3 Prompt模板设计
检索质量过关之后,生成阶段的提示词模板也有讲究。一个基础模板包含三个部分:系统指令、检索到的参考资料、用户问题。系统指令里要明确告诉模型“请基于参考资料回答问题,不要编造资料中没有的内容”,还要说明如果资料里找不到答案,就老实说不知道。
如果不想让模型完全被参考资料绑死,可以在模板里加一句“可以结合自身知识进行补充”,但必须强化“事实性表述以资料为准”。实际使用中,我会把引用来源也拼接进结果,让每个回答都能回溯到知识库里的原始片段,这对企业内部场景尤其重要,出了问题能追责,而不是对着一个黑盒干瞪眼。
5. 实操过程中踩过的坑
5.1 垃圾进垃圾出,文本清洗决定下限
前面一直在强调清洗,因为这块的“坑”是隐性的。我第一次做RAG时,直接拿爬虫抓下来的网页文本切块入库,结果检索出的片段里全是导航菜单和版权声明。后来调了半天prompt和参数都没用,最后才发现是数据的问题。用了一个过滤规则把无意义的短行、重复的页眉页脚、超链接文本都滤掉之后,效果立竿见影。
5.2 索引和查询的Embedding不一致,是最隐蔽的Bug
有一次线上反馈检索结果突然变得很差,排查了半天,发现是因为某次升级后把查询侧的Embedding模型换了新版本,但索引侧还是旧版本生成的向量,两边向量空间不同,检索当然全乱套。后来我干脆在配置里锁死了模型版本,升级前先全量重建索引。这个坑很冷门,但一踩就是大半天。
5.3 K值不要贪大
刚开始做RAG,总觉得检索得越多越稳,K值直接设到20。结果模型每次要消化20段内容,上下文窗口被占掉一大半,回答冗长且前后矛盾。后来把K值压回6,加上重排序,回答既简洁又准确。K值的设定玄学一点讲,得看你的块大小和模型的上下文窗口,块大就少召回几块,块小可以适当多召回几块,但单次喂给模型的总token量要保持在一个舒适的范围内。
最后再分享一个体会:RAG项目的调试路径,永远先查数据、再查检索、最后才查生成。数据脏,一切白搭;检索差,再好的模型也答不出来。如果你正在搭自己的第一个知识库,先把清洗和切块做扎实,再慢慢调后面的环节,你会发现问题少一大半。