资讯动态

RAG开发学习路线:从原理到本地知识库问答系统搭建

发布时间:2026/10/20 15:24:05 来源:尧图企业网站定制
我给自己定了一个学习周期3月21日到4月7日前后18天主题就是RAG开发。在这段时间里我主攻的是检索增强生成Retrieval-Augmented Generation这条技术链路目标也从“能讲清楚RAG是什么”一步步推进到“能独立搭出一个带本地知识库的问答系统”。这篇内容算是对这段时间的完整整理包括学习路线怎么排的、每个环节背后真正值得琢磨的技术点、踩过的问题以及我现在回看会怎么优化这套方案。无论你刚接触RAG概念还是已经跑过一些demo但被效果问题卡住这篇应该都能找到对应参考。我收到不少朋友的问法是大模型不都已经很聪明了吗为什么还要专门学RAG这个疑问恰恰也是我最早入坑时的困惑。所以18天的安排里我并没有一上来就刷代码而是先把RAG到底在解决什么问题这件事理顺了。下面我就按我的学习路径把这段时间的内容完整展开。1. 从标题看这次学习计划的核心目标1.1 为什么是RAG而不是微调很多人在接触大模型开发时首先会想到微调Fine-tuning甚至默认“效果不好就该微调”。但RAG和微调解决的其实是两个维度的问题。微调是修改模型自身的参数让模型“记住”新的知识或说话风格而RAG是在生成过程中额外拉取相关资料让模型“查到”再“用到”。用一句话概括我后来的体会微调改变的是模型的记忆RAG改变的是模型的输入。大部分业务场景里真正需要的不是让模型记住某个知识点而是让它在回答时能够引用最新的、私有的、领域特殊的资料。比如企业内部规章制度、某款设备的维修手册、某门课程的全部讲义这些内容让模型用参数去背既不现实也没必要。RAG的优势在于知识即时更新资料变了换掉检索库就行模型参数完全不动。所以我在这次学习中给自己定的目标是围绕RAG把“资料准备 → 文档解析 → 分块切分 → 向量化 → 存储召回 → 重排 → 融合生成”这条链路亲手跑通并能在遇到效果问题时知道从哪个环节下手定位。1.2 这次学习要交付什么我给这次学习定了三个交付物第一一个跑在本地的最小RAG问答系统能对一份真实文档进行问答第二一套针对检索效果和问答质量的评估方法第三一篇可以讲给别人听的技术复盘也就是你正在看的这篇内容。这套交付物对应的是RAG开发中最核心的能力——不只是会调接口而是能自己构建、评估、优化一条检索生成链路。如果你也想复现机器要求并不高一张普通显卡甚至纯CPU都能跑基础版本只是速度差一些。我这次用的是已有的大模型推理接口和开源嵌入模型整体部署成本控制得很低。2. 学习路线与时间排期18天怎么拆2.1 四个阶段的整体脉络我把18天拆成了四个阶段不是平均分配而是权重明显偏向检索链路和工程实践。直接上表格阶段时间范围周期核心主题阶段产出第一阶段3.21 - 3.255天RAG原理与基础组件技术选型清单、概念思维导图第二阶段3.26 - 3.294天文档解析、分块与向量化本地向量库、可检索的文档集第三阶段3.30 - 4.35天检索链路与系统集成完整可用的最小RAG系统第四阶段4.4 - 4.74天评估、优化与复盘评估报告、优化清单、总结文章这个排期背后有两个考量。第一RAG的上手门槛其实不在“能否调用大模型”而在检索质量。很多人第一次跑通demo只用了半天但真正把召回结果调好花的时间远远更多。所以我给了“解析、分块、向量化”和“检索链路”两个阶段将近三分之二的时间。第二我刻意把评估放到系统集成之后而不是最后这样有充足的时间做多轮优化迭代而不是临近截止才仓促看效果。2.2 各阶段任务拆解第一阶段我主要做了三件事理解RAG的完整流程图、对比RAG与微调的适用边界、调研主流开源组件。学习资料我建议优先看原论文和成熟项目的架构文档而不是只刷短视频科普短视频能帮你建立直观印象但细节很快就会忘干净。第二阶段开始动手。先找了两份有代表性的资料一份是PDF格式的技术文档包含大量专业名词另一份是Markdown格式的文本资料。两类文档代表了解析难度的高和低。第二阶段的重点是体验“文档变成向量”的完整过程并初步建立评估意识——不是“能搜出来就行”而是“该搜出来的能不能搜出来”。第三阶段是真正的重头戏。我复用了开源的LangChain风格工具链和向量数据库快速搭建出第一版系统然后把精力花在调试检索逻辑上。这里有个重要的认识RAG系统的瓶颈多半在“检”不在“答”。你喂给大模型的上下文正确它很少会答错上下文本身就是错的或缺少关键片段它会用很流畅的语言编一个错误答案。因此第三阶段里我的调试重心完全放在检索链路。第四阶段是收尾也是最出经验的一个阶段。我针对测试记录里暴露出的问题逐一优化了分块大小、检索条数、重排策略和Prompt模板最后沉淀出这套优化方法。之所以把复盘写进计划是因为我发现如果学完不做整理很多细节会在三天内全部遗忘。2.3 每天的时间分配方案我每天大概投入2到3小时用的是“1小时概念理解 1小时编码实践 30分钟记录沉淀”的模式。记录沉淀是这次坚持下来最有用的动作哪怕只写两百字都行我会记三件事今天做了什么、遇到什么问题、明天要验证什么。这样做的好处是问题不会反复在同一个地方绊倒。如果你时间更紧我建议保住两个环节动手搭建最小系统、做效果评估。哪怕只跑通一个简单的文档问答你对RAG的理解深度也会远超只看文章的人。3. RAG链路核心环节每个“为什么”都值得抠一遍3.1 文档加载与解析格式适配是第一步文档加载这一步看起来简单实际上决定了后续分块和检索的质量。纯文本和Markdown直接读取就行PDF就要单独处理有的PDF是文本型可以直接提取有的是扫描件需要OCR识别。我选的那份PDF里有一些表格和代码片段直接解析会丢失结构信息这直接影响了retrieval阶段的效果。后来我改用按版面解析的工具保留标题层级和表格结构召回效果明显上升。这里的“为什么”值得多说一句RAG后面所有环节都是跟“文本块”打交道解析阶段丢掉的任何信息后面都找不回来。很多初学者把精力全花在Embedding模型选型上却忽略了解析环节结果就是“垃圾进垃圾出”。在RAG这条链路里前面每个环节的效果会累积放大所以前置环节的质量优先级高于后置环节。3.2 分块策略决定检索颗粒度分块Chunking是整个RAG链路里最容易被低估的环节。分块太大单个块包含的信息多但也很杂向量化之后的语义不够聚焦检索时容易召回一堆“沾边但不精确”的内容分块太小又可能导致上下文不完整模型看到的内容缺少关键背景。这两者我都实际感受过差别很明显。常用的分块方式有按固定字符数切、按段落切、按语义切。固定字符数实现最简单但容易把一个完整段落拦腰截断按段落切对结构良好的文档友好语义切分效果最好但有一定的计算开销。我这次的参数选择是这样的中文资料按token估算单块设置为300到500 token块之间加了50 token的重叠overlap。overlap的作用很实际——当检索目标恰好横跨两个块的边界时重叠区域能保证信息不至于被拦断。用数字举个实际例子一份约12000字的中文技术文档估算下来约6000个token。按每块400 token、重叠50 token切分大概会生成16到17个块。这个数量级对向量检索来说非常轻量查询响应基本无感知。3.3 向量化Embedding选对模型比堆参数重要向量化是把文本映射成稠密向量的过程也是RAG实现“语义检索”的基础。我直接用了一个开源中文Embedding模型维度是1024维。选型时对比过好几个模型各模型在中文语义匹配上的表现差异不算小建议优先看中文基准榜单上的表现而不是只看参数量大小或某个开源社区项目的star数。Embedding模型有个反直觉的特性不能像大模型那样指望它“理解”复杂指令它只需要判断两段文本语义相近。所以用于构建知识库的原文与查询时的问法如果差异很大向量检索很容易失配。比如文档里写“设备发生周期性抖动”用户问“机器总是一顿一顿的”字面差异很大糟糕的Embedding模型可能就匹配不到。针对这种情况的解法我会在后面的优化部分展开。还有一点必须注意查询文本和知识库文本应该使用同一个Embedding模型。如果索引时用A模型查询时不小心换成B模型两者的向量空间完全不同检索必然失败且难以排查。3.4 向量数据库与检索召回质量决定回答质量向量数据库负责存储向量并提供相似度检索常见的方案有Chroma、Milvus、Qdrant、pgvector等。我这次选择了Chroma原因很简单对单机小项目来说它足够轻量无需额外部署服务能直接嵌入Python进程使用。如果你的知识库规模到百万级向量或者需要高并发才需要考虑独立部署的向量数据库。相似度检索的距离度量也有讲究。我测试下来的经验是余弦相似度Cosine在处理文本向量时通常比欧氏距离L2更稳定因为余弦关注的是方向而非模长。但在实际应用中很多向量库默认用的就是L2只要模型本身做过归一化处理两者排名结果差别很有限。单靠向量检索远远不够我后续还加了混合检索。所谓混合检索就是把向量检索和基于关键词的稀疏检索BM25结果合并。BM25擅长精确匹配专有名词、编号、型号向量检索擅长语义相近的表达。RAG系统面对的查询往往混合了这两类需求只用一个都容易漏召回。3.5 重排Rerank对召回的粗筛结果精排序召回阶段的目标是“别漏”此时宁肯多召回一些相关或不相关的内容但真正送入大模型的上下文是有限的所以需要重排阶段把最相关的几个片段放到最前面。我用了一段语料实测过召回Top 5时前两名非常相关第三名开始明显跑偏。如果没有重排大模型会被后面这些不相关内容干扰回答中就可能出现信息混用。重排的常见做法是引入一个专门的交叉编码器Cross-Encoder模型它把查询和文档片段拼接后一起编码能更精细地判断相关性但计算成本也比双塔结构的Embedding模型高很多。所以标准的工程实践是召回阶段用轻量快速的向量检索拉出Top 20或Top 50再由重排模型精筛出Top 3或Top 4喂给大模型。这样兼顾了效率和精度。3.6 生成融合Prompt设计与上下文控制最后一步是把检索到的相关内容注入Prompt让模型基于这些资料进行回答。这里有个“度”的问题注入的资料太少模型缺少依据注入太多又可能超长或引入噪声。实践中还需要在Prompt里明确约束“只能依据提供的资料回答资料中没有的内容要说明不知道”能把幻觉比例降下去不少。更细一层的做法是给每条资料标注来源或序号要求回答时注明“根据第几段资料”。这样既方便核对答案有没有跑偏也方便用户追溯原始文档。这个细节我们实际使用时反馈很好因为通常一个错误答案如果没有来源根本没法判断它是模型编的还是资料里本来就有的。4. 从零搭建一个最小可用的RAG系统4.1 技术选型参考我这次用的是完全开源可本地部署的方案组合一个中文Embedding模型做向量化Chroma做向量存储和召回LangChain做流程编排大模型推理接口负责最终生成。如果你希望所有组件都跑在本地也可以选择本地部署的生成模型但会额外增加显存需求和推理延迟。选型时我给了自己三条约束不引入过于复杂的分布式组件、全程可本地运行、每个环节尽量可单独调试。这三点让后期的定位问题轻松很多——我可以先单独查Chroma里存了多少块再单独测试某一个块能否被检索到而不用每次都从整个链路里猜。4.2 核心实现框架下面这一段是经过精简的最小链路保留了核心逻辑你可以直接对照搭建自己的第一版系统# -*- coding: utf-8 -*- from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma from langchain.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser # 1. 文档加载 loader TextLoader(records.txt, encodingutf-8) documents loader.load() # 2. 分块每块400字符重叠50字符 text_splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap50, separators[\n\n, \n, 。, , , ], ) chunks text_splitter.split_documents(documents) print(f共生成 {len(chunks)} 个文本块) # 3. 向量化与入库 embeddings_model HuggingFaceEmbeddings(model_nameBAAI/bge-m3) vectorstore Chroma.from_documents( documentschunks, embeddingembeddings_model, persist_directory./chroma_db, ) # 4. 检索默认取Top 5 retriever vectorstore.as_retriever(search_kwargs{k: 5}) # 5. 构建Prompt prompt_text 基于以下资料回答问题。 资料 {context} 问题{question} 回答要求 1. 优先依据资料内容回答。 2. 如果资料中没有相关信息请直接说明“资料中未找到相关信息”不要编造。 3. 引用资料时标注来源序号。 prompt ChatPromptTemplate.from_template(prompt_text) # 6. 组装RAG链路 def format_docs(docs): return \n\n.join(f[{i1}] {doc.page_content} for i, doc in enumerate(docs)) rag_chain ( { context: retriever | format_docs, question: RunnablePassthrough(), } | prompt | chat_model # 你的大模型调用对象 | StrOutputParser() ) # 7. 发起一次问答 result rag_chain.invoke(设备出现抖动现象可能的原因有哪些) print(result)这段代码走完一遍RAG的最小闭环就成立了。实际使用中你还需要把文档加载替换成你自己的资料路径以及调整分块参数。这里我特别建议把文档内容打印出来看一眼因为分隔符顺序直接影响切分效果。上面代码里我把中英文标点都加入了分隔符这对中文文档非常关键——如果只用换行符分隔中文段落很容易被生硬截断。4.3 用一份样例数据验证链路是否健康搭好系统之后我先用一份自制的“单知识点”文档做了冒烟测试文档只有三小段分别写清A、B、C三个事实。我再分别用三种问法去提问直接问原文句子、改写问法问同一事实、问一个文档中不存在的事实。这三类问题其实在分别验证链路的不同能力改写问法测试语义检索是否真的“理解”了意图不存在事实的提问测试大模型是否会被诱导出幻觉。我的第一版系统在“改写问法”上的召回效果并不理想后来增加了混合检索把字面匹配和语义匹配两条路径的结果合并问题就基本解决了。5. 学习周期内实测的高频问题与排查套路5.1 问题速查表我把这段时间踩过的高频问题整理成了表格对应原因和解法都是实际验证过的判断不包含空洞的建议问题现象最可能的原因排查与解法回答的内容明显来自错误段落召回结果包含噪声或重排未生效打印检索返回的前5个片段人工判断相关性改写问法搜不到原文纯向量检索对同义改写不敏感增加BM25混合检索或换更强的Embedding模型分块后信息被截断chunk_size偏小且分隔符未配置中文标点调大块大小调整分隔符优先级加入重叠对不存在的事实给出答案Prompt约束不足或检索到强相关的误导片段在Prompt里明确“未找到则声明不知道”加强重排过滤准确率低但检索出的片段看起来相关关键细节在片段里只是顺带提及模型未重点关注提高相关片段权重把k增高后人工观察更多召回结果中文文档检索时英文标点导致切分混乱分隔符只有\n\n中文段落被整块保留在separators中加入中文句号、叹号、问号5.2 三个影响最大的排查经验第一个经验是“先看召回再看回答”。很多情况下回答错得离谱问题根本不出在模型而在于检索回来的内容里压根没有正确答案。直接在代码里打印retriever的返回结果一眼就能判断问题归属。这一步排查成本最低判断效率最高。第二个经验是“不要让k值一开始就太小”。第一版系统我把k设成了2检索结果里只要有一个噪声片段答案就会跑偏。后来把k调到5配合重排精筛后准确率提升非常明显。如果你的大模型上下文窗口足够k值稍大通常不会让效果变差尤其是你加了重排之后。第三个经验是“Prompt约束能显著降低幻觉”。我最早用的Prompt只有短短一句“基于以下资料回答问题”模型依然倾向凭空补充。后来加了“资料中未找到就直接说不知道”幻觉比例下降得极为明显。这个调整在工程上几乎不花成本却是我这次体验中性价比最高的一次优化。5.3 一个容易被忽略的坑重复段落由于分块重叠的存在同一句话可能出现在相邻的两个块里。检索Top 5时这两个块会同时被召回导致喂给模型的内容有大量重复既浪费上下文窗口又让模型更难抓重点。我的解法是在格式化上下文时做一次简单的去重判断保留一个块即可。这个细节几乎不会出现在官方文档里但实测能让回答质量提升脆响地上一档。6. 关于这段学习周期的几句复盘与后续扩展如果你也打算按18天的方式推进RAG开发我个人的体会是一定要给“检索质量”留足够时间因为调好检索的收益远超换一个更强的大模型。我后面测试过把生成模型换成推理能力更强的版本在检索质量不变的情况下效果提升有限但一旦把分块和重排调好哪怕生成模型没那么强回答也明显更稳。这个判断基本扭转了我的投入优先级。另外这套系统后续做扩展时可以顺着几个方向走一是覆盖更多文档类型比如PDF和扫描件这涉及OCR和版面解析二是把单轮问答升级成多轮对话这时需要维护对话历史并把历史问题改写为独立的查询语句三是加入更完整的评估体系用一组带标准答案的测试集定期回归效果。这三个方向任意推进一个都会推动整个系统向生产可用靠近。最后说一个我在实际学习中最有用的小动作每天学完顺手记录“今天有什么问题没解决”。到了第10天回头看第三天记录的问题会发现大部分已经无意间解决了。这种记录式的复盘比任何学习计划表都更能反映你的真实成长轨迹。RAG开发这条路不算难走但很容易被表象迷惑只要愿意把链路中的每一个环节都亲手验证一遍效果就会扎实地体现出来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑