
做了几个月RAG相关项目从最开始在公司内部搭知识库问答到后来自己折腾本地部署中间踩的坑多得能写一本书。这篇文章不聊太悬的理论就实打实分享RAG落地实践的完整步骤还有那些我踩过的、希望你别再踩的坑。不管你是想用RAG做企业知识库、个人文档问答还是想弄明白ontology RAG、agentic RAG这些新名词到底怎么回事这篇都值得往下看。1. 先把问题想清楚RAG到底来干什么1.1 从一个真实需求说起我接手第一个RAG项目时需求很简单公司有几百份产品文档分布在不同部门手里格式五花八门有PDF、Word、Markdown还有几个Excel表格。员工想查某个功能的用法得问对应部门的人或者翻半天共享盘。老板的想法是搞一个智能问答输入问题直接返回答案最好还带上出处。这个需求听起来就是典型的RAG场景。但真正动手之前你得先理解RAG到底解决了什么问题。LLM大语言模型的知识是训练时固定的它不知道你公司内部文档写了什么。RAG的思路很简单用户提问时先从你的知识库里检索相关片段把这些片段作为上下文塞给LLM让它基于这些内容来回答。说白了就是开卷考试——模型本身不需要记住你的文档只需要从小抄里找到答案。1.2 不是所有场景都需要RAG这里有个判断标准如果你的问题可以通过模型微调或者直接用现成模型能力解决没必要上RAG。RAG适合的场景有几个共同特征知识更新频繁比如产品手册每周都在变知识具有私有性只存在于企业内部文档里回答需要可追溯、有出处不能凭空编造文档量大不可能全部塞进prompt里反过来如果只是闲聊、通用问答直接调API就行搞RAG纯属自讨苦吃。另一个常见误区是想用RAG做推理型任务比如让系统算一笔复杂的账、做多步骤逻辑判断老实说RAG不太擅长那是agentic RAG或者更复杂的编排方案要解决的问题。1.3 RAG系统的四段流程心里要有数任何一个RAG系统拆到底都是四个环节文档加载与清洗、文本切分、向量化与索引、检索与生成。后面所有的优化、踩坑、调参都是围绕这四个环节展开。你不可能跳过任何一个步骤。哪怕是最简单的ollama 本地知识库方案也逃不出这个框架。我习惯把RAG看成一条流水线每一环节都会损失信息也会引入噪声。加载环节丢格式切分环节拆断语义向量化环节压缩信息检索环节召回不准生成环节模型发挥。你的任务是让每个环节的损失最小化而不是指望某个环节一键完美。2. 技术选型不是越新越好而是越适合越好2.1 从零搭建还是用现成框架第一版RAG项目我用了LangChain。当时它的生态比较成熟文档加载器一堆向量存储接口也算统一。但用下来我得说句实话LangChain的抽象层太多出了问题不好排查。尤其当你做的是企业级落地经常需要自己定制某些环节高度封装的框架反而碍手碍脚。后来我自己做了个判断如果你只是验证概念POC用LangChain、LlamaIndex这类框架没问题快。但如果是生产系统我会推荐用原生代码把链路写清楚每一步的输入输出都是你可以控制的。做原生实现也就百行级别的代码量配合LangChain的文档加载器和向量库客户端完全够用。另外说一下LangChain4j这是Java生态的LangChain移植版。如果你公司的主力开发语言是Java这个库值得关注。它在中文社区比较活跃文档更新也勤快。RAG这块数据库连接倒是其次关键是它的文本切分和检索接口写起来挺顺手。不过我的经验是先别急着上框架先把流程想明白框架只是工具。2.2 向量数据库到底怎么选市面上的向量数据库多到让人眼花缭乱Chroma、Milvus、Qdrant、Weaviate、pgvector、Elasticsearch的向量检索…我的选择思路很简单数据量小用Chroma或者纯内存的向量索引就够数据量大或者要上生产反而该重点考虑pgvector或者Elasticsearch。为什么这么说很多团队一上来就部署Milvus结果发现运维成本很高平时也就几万条向量杀鸡用牛刀。Chroma适合原型验证轻量跑起来快。但当数据量到百万级、需要高并发访问时Chroma就吃力了。我自己最常用的是pgvector因为公司原本就有PostgreSQL加个扩展就行不用额外维护一套存储。对中小团队来说复用已有基础设施比引入新组件更实在。如果你对性能要求极高再考虑专门的向量数据库。还有个小提醒不管怎么选先把你现有数据的规模摸清楚别凭感觉。2.3 embedding模型的选择决定检索地板这是RAG落地里最容易被低估的环节。向量化的质量直接决定检索的上限后面所有重排序、调参都是在矮子里拔高个。我的经验排序是这样的第一效果优先就选商业API的embedding接口比如OpenAI的text-embedding系列效果确实好。但要考虑数据安全问题企业内部文档不一定允许发到外部API。第二中文场景开源模型我用得最多的是BGE系列和M3E。BGE-large效果不错但模型体积大M3E中文效果好速度快。如果你做的是纯中文知识库M3E是性价比很高的选择。第三英文场景可以考虑E5、GTE这类模型。实际选择时有一个很关键的基础测试先拿几组你自己业务里的典型问答对分别用不同embedding模型跑一遍看召回结果别只看模型排行榜分数。排行榜分数是通用数据集的不一定代表你场景里的真实表现。2.4 从RAG到GraphRAG、ontology RAG、agentic RAG最近这些名词听上去唬人但本质都是对原始RAG缺点的修补。原始RAG最大的毛病是知识割裂——你的文档被切成很多块每块单独向量化块与块之间的关联信息丢失了。比如系统里有两份文档一份说登录超时导致会话失效另一份说登录超时时间默认为5分钟拆开看没问题但模型回答登录超时多久才失效时可能只能检索到其中一部分逻辑连不起来。GraphRAG的思路是用知识图谱把实体之间的关系显式建模检索时能沿着图结构找到关联信息。ontology RAG则是先把领域本体概念结构、关系约束定义好让检索更懂行。agentic RAG则是把RAG交给agent来编排它自主决定什么时候检索、搜什么、要不要多轮查询。我的建议是先把标准RAG做到80分再考虑这些进阶方向。很多团队连文本切分和重排序都没做好上来就想搞GraphRAG最后只会更加失控。这些进阶方案复杂度更高、调试更难但对特定场景多跳问答、复杂关联推理、需要精确逻辑确实值得尝试。后续我会单独写文章细说这里不展开了。3. 完整落地步骤从一段文档到一个能问答的知识库3.1 环境准备最基础也最啰嗦的环节以一套最经典的本地化方案为例文档、Python环境、向量库、LLM本地或API、embedding模型。我的建议是先用Python3.10以上的版本避免一些依赖兼容问题。装依赖是最容易踩坑的地方。比如LangChain的光环版本变化频繁旧教程里的写法经常跑不通。向量库Chroma配合langchain使用时版本不匹配会报AttributeError这类错误。我的做法是先把核心依赖版本固定再写代码。用requirements.txt锁死LangChain、Chroma、ollama的版本。能省太多无谓的报错排查时间。如果你完全本地部署需要准备Ollama。它主要是跑LLM还支持拉取embedding模型。下载安装不复杂但要注意模型文件比较大动辄几个GB先确认磁盘空间。Windows用户注意路径不能有中文否则加载模型时莫名其妙报错。3.2 文档加载与清洗源头决定了终点质量这里深坑极多我一个个说。首先是PDF。Python生态里解析PDF的库很多PyPDF2、pdfplumber、fitz等但解析效果差异极大。文本型PDF还好扫描件PDF必须走OCR否则出来的全是乱码。表格型PDF最容易翻车pdfplumber保留表格结构的能力强一些但会把表格变成文本块结构信息还是丢失了。第二个是图片和OCR。某次我处理一份产品宣传册里面有大量截图和图示。文字提取出来是碎片图片信息直接没了。如果你的文档里图片承载了核心信息就需要一个图像理解模型来把图片转成文字描述再喂给RAG。这个成本和复杂度都要提前评估。第三元数据。加载文档时一定要保留每段文本的来源信息包括文件名、页数、章节等。否则后续问答答对了用户问出自哪里你只能哑口无言。最糟的是当知识库出现冲突内容时没有元数据你根本无法排查哪条信息是旧的、哪条是错的。我的源文档清洗原则是加载完之后先用肉眼看一下分块前的文本长什么样。不需要看全部抽样看几页即可。如果原始文本就已经烂掉了那后面所有优化都是白费。3.3 文本切分RAG落地最核心的参数游戏切分是所有环节里最不严谨但又最影响结果的部分。我踩过最大的坑是以为用默认的chunk_size1000, chunk_overlap200就行结果很多查询命中率上不去。后来才明白切分策略必须结合你的文档类型和查询模式来设计。切分维度通常有几种按固定字符数切分最简单但容易切断语义按句/段落切分自然语义完整按Markdown标题切分适合结构化文档按HTML标签切分适合网页内容递归切分先按段落再按句子逐级降维实际项目里我推荐递归切分。LangChain的RecursiveCharacterTextSplitter就是这个思路。核心参数两个chunk_size每块长度和chunk_overlap同相邻块的交叠长度。chunk_overlap的作用是尽量避免切断关键信息比如一个关键句刚好落在两块的交界处overlap能让它至少完整出现在某一块里。参数没有黄金值完全取决于文档结构和检索场景。我一般这么测试准备三到五个典型问题分别用小切300、中切500-800、大切1000-1500跑一遍检索看哪组切片能同时覆盖所有问题的正确答案片段。一般查询偏短问题、参数类问题的切片小一点效果好查询偏综述类、需要完整段落的切片大一点更合适。这里有个实操技巧切分时保留标题路径。比如一份手册中第一章 第二节 配置步骤切分后的每个chunk都应该带上这个标题前缀。这样检索时即使问题中不含段落原文模型也能通过标题上下文理解这是哪个章节的内容。很多时候这个技巧比调整分块大小更有效。3.4 向量化与索引构建维度、距离和性能的权衡切分完成之后就是向量化。我习惯把切分后的每个chunk导入到向量库同时把元数据一并存进去。向量库里的存储结构大致是文本内容、向量数组、元数据有时还会加一个主键ID方便更新。首先要注意向量维度必须和embedding模型输出维度一致。BGE-large是1024维M3E有768维的版本也有1024维的OpenAI的text-embedding-3-small是1536维。维度不一致时检索运算直接报维度不匹配错误。这个错误比较低级但真有人会犯。其次距离计算方式。向量库默认用余弦相似度比较多也有用欧几里得距离或者内积的。如果你的embedding模型是归一化之后输出的这三种度量方式的结果几乎等价。但如果不确定就用余弦相似度。它有语义上的意义也最直观。索引构建时批量插入比逐条插入快很多。但向量库Immediate的索引更新策略会影响查询性能。我通常的做法是分批次写入每批几千条每批结束后可以不立即落盘等全部写完再统一持久化。对小型项目来说ast时数据量几千条跑起来很快这点差别可以忽略。3.5 检索链路实现只取最像的还不够标准RAG的检索深度远不止取TopK个最相似的chunk这么简单。这个环节你至少需要考虑三个层面第一个是混合检索。纯向量检索对语义匹配效果好但遇到关键词精准匹配就容易压不住——比如用户搜索产品型号SR-100向量检索可能找到一堆含义相近但型号对不上的内容。解决方式是把BM25经典的关键词检索引擎算法和向量检索结果做融合两者取交集或加权合并。第二个是重排序Rerank。第一轮向量检索取Top100然后交给重排序模型精排再取Top5作为最终上下文。重排序能显著提升答案的命中率。中文场景推荐bge-reranker系列效果和速度的平衡比较好。第三个是相似度阈值。这个坑我踩得太深了。阈值设太高比如0.8很多相关问题召回不到系统动不动就找不到相关内容阈值设太低比如0.3检索出来一堆不相关的内容LLM会被噪声误导。我的做法是先不设阈值查看一批真实问题的相似度分布找到明显的分界线再加阈值。别拍脑袋定。3.6 生成环节一个常被忽视的调优区检索到的内容最终要喂给LLM生成答案。这个环节的坑集中在prompt构造和上下文组织上。我首先建议prompt里明确三件事必须基于给定的上下文回答不要编造不知道就拒绝回答尽量引用原文的关键信息。很多RAG项目答得胡编乱造问题不在模型而在prompt没约束好。其次控制上下文长度。很多LLM窗口大你感觉多发点没事。但实践证明上下文越长模型注意力越分散越容易忽略真正相关的信息。我一般将最终送入生成器的上下文压缩在800~1500个token以内并且把相关性最高的chunk放前面。很多情况下还需要对检索结果做压缩只保留关键句子或关键段落而不是整块原文。这就是所谓的上下文压缩Context Compression技术。第三点给LLM演化提示词的空间。比如可以写instruction让模型先引用文档再给答案或者让它输出答案后附带基于XX文档第X页。在小范围测试时这些细节的威力比想象中更大。4. 踩坑实录那些坑我替你踩了4.1 检索命中率低先别急着换模型我碰到很多同行一着急就换embedding模型、向量数据库、甚至整个框架。但真实情况是大多数命中率不理想的问题根源在切分策略和数据清洗根本不在模型。有一个案例我记得特别清楚某次知识库里全是产品QA记录每条QA是一个段落加一个答案切分时我用了固定长度切分把问题和答案切得七零八落问答时检索到的内容往往只有一半。后来改成按原文的QA结构做切分每条问答作为一个chunk命中率一下子从40%升至85%以上。所以排查顺序雷打不动源文档清洗 → 切分策略 → 检索效果评估 → 重排序 → 生成效果。哪一环出问题就去优化哪一环不要跳级。模型们只是背锅侠。4.2 黄金参数陷阱同一套参数在不同的文档上差别巨大有段时间我迷信网上流传的chunk_size500, chunk_overlap50是最佳参数。换了文档类型之后效果一落千丈。后来我才意识到参数和文档结构深度绑定。技术文档适合大头因为每章结构化很好切大切小都行。但聊天记录、论坛帖这种碎片化的文本切大了容易串味切小了又没有上下文你得先清洗再按会话分组切分。还有法规条款这类正式文档一条条按编号切分效果最好。我的建议是做一个小的参数自动调优脚本准备一份带标准答案的测试集几十个问题就够多跑几组chunk_size、chunk_overlap、top_k的组合计算命中率或检索召回率从中选出当前语料的最优参数。这个过程不复杂但价值极大。4.3 本地化部署Ollama 简易RAG的典型坑本地部署最吸引人的地方是数据不出去而且看着自主可控。但坑也不少。最大的坑是机器配置。embedding模型加LLM同时吃显存一次加载两三个模型显存不够直接OOM。我这个项目里用的是Ollama加载两个模型一个embedding模型几百MB到1GB级别一个LLM7B级别大概要4~6GB显存。建议预留至少8GB的显存空间。没有GPU的朋友们就纯CPU推理速度会非常感人但小规模的个人知识库还是可以接受的。第二个坑是API版本兼容。Ollama的API风格和OpenAI不完全一样我用Python请求时踩过返回格式不一致的坑。可以用ollama-python这个库来解决也可以直接用requests手动请求但一定先打印一次返回结果确认字段再写后续代码。第三个是向量库和本地模型服务都是常驻进程。一旦知识库更新了需要重新向量化并写入向量库否则问答结果仍然是旧索引。这个更新机制要在设计阶段就想好别等上线了再补。4.4 中文编码永远是个逃不掉的宿命中文文档的坑特别多。首先是编码问题有些古老的Word或文本文件是用GBK而不是UTF-8编码直接读会乱码。Python读取时要指定编码或者用chardet去检测。其次是中文分词。英文按空格分就够了中文需要分词器。好在我们做RAG时切分过程和分词不是一回事向量化时会自动对句子做语义编码不太依赖显式分词。但如果你用了BM25关键词检索中文分词质量就非常关键了。这个场景下建议用jieba或者更专业的中文分词库。第三是标点问题。中文里的全角标点和英文半角标点会让切分规则不一致。有次一个正则表达式把中文句号。当成普通字符导致按句切分失败整个文本被当成了一个巨型chunk。这种问题不跑数据根本不发现。4.5 知识库能存图片吗这个问题其实不难答网上看到有人问RAG知识库能存图片吗。结论是直接存二进制图片没用但你可以让RAG看得懂图片描述。有两种主流做法一种是把图片OCR/图像标注成文字存成文本后再进RAG。另一种是使用多模态embedding模型直接把图片本身向量化然后检索时用文本、图片混合匹配。第一种做法简单效果好但会丢细节。比如一张设计图纸文字描述提取了标题和注释但图中的布局、比例关系就丢了。第二种做法能保留更多的图像语义但对模型要求高维护成本也大。绝大多数实际业务场景里第一种做法就够用了。我做的知识库里有图片时都会先走一遍OCR和图像理解把描述生成text block再进RAG链路。4.6 知识割裂问题比想象中更影响体验最开始提到知识割裂是RAG的原生短板。我实际遇到过的最典型场景是同时维护了一份产品FAQ和一份用户手册。FAQ里说可以使用恢复出厂设置来解决问题手册里说恢复出厂设置会清空所有数据。用户问恢复出厂设置会丢数据吗时系统通常只检索到FAQ而忽略手册导致回答不全面。知识割裂不是说理不清而是你的检索链路根本没有把这些关联文档放一起考虑。为此我尝试过一个折中方案切分文档时将常见FAQ中涉及到的外部相关章节的标题或摘要补充到FAQ段落作为上下文。相当于手工给知识库做了关联推荐。说实话这有点粗暴但成本低效果好。后面我再接触GraphRAG和ontology RAG时才意识到这才是解决知识割裂的正路但那是进阶玩法了。4.7 评测不会测就不知道好不好最后一个大坑是很多团队根本不知道怎么评测RAG系统。丢几个问题上去看答案顺不顺眼这不叫评测。RAG评测至少要分成两块检索质量和生成质量。检索质量可以用命中率hit rate、MRR、NDCG这些指标来衡量。生成质量可以用忠实度faithfulness、答案相关度answer relevance、正确性等维度抽查。我自己的实践做法是准备一批真实问题约30~50条每条标注标准答案文本所在的文档位置。每次调完检索链路之后先跑检索评测看能否把这个位置的内容召回。检索达标了再测生成。否则生成一乱你不知道是检索的锅还是模型理解的锅。评测集虽然费工夫但它能让你告别“靠感觉调参”的状态。5. 写在最后的个人体会做完几个RAG项目后我最深的感受是RAG落地不是一顿猛操作就完事的它是一个反复调优、用评测驱动的过程。那些零基础可复制教程能让你快速跑通demo但真实项目里你在做的是让一个demo变成可用的生产系统。这一步的距离往往就是踩坑记录的厚度。如果只让我总结一句话那就是别迷信任何框架、任何模型、任何最佳实践回到你的数据和问题本身一步步把链路做扎实。我今天分享的这些步骤和坑都是从这个出发点收获的。后面我会继续写GraphRAG、agentic RAG这些方向的实战内容到时候再和大家复盘新的经验。