RAG到底怎么干活:原理链路、最小搭建与避坑指南 先聊一个很多人问过我的问题RAG到底是什么市面上讲向量检索的文章一堆讲Embedding的文章一堆讲大模型 Prompt 的文章也一堆但把这些概念串起来讲清楚“RAG 到底怎么干活”的反而很少。我的理解特别简单RAG 就是给大模型安排一场开卷考试。模型脑子里的知识有截止日期还经常记岔了你直接让它答它就一本正经地编。RAG 的思路是答题之前先递给它一份参考资料让它照着资料答别凭记忆瞎说。这篇东西的定位不是论文复述而是给真正要上手做 RAG 的人看的。我会用通俗的话拆解 RAG 的原理链路然后给出一套最小可运行的搭建流程最后把我踩过的坑和一些优化思路一并倒出来。无论你是刚接触这个概念的产品经理还是要落地一个知识库问答系统的开发者照着这个思路走至少不会跑偏。1. 为什么大模型需要 RAG模型的两个“先天毛病”1.1 模型不是数据库它是在“预测下一个词”很多人对大型语言模型有一个误解觉得模型就是个超级百科全书问什么它就能从“记忆”里把正确答案翻出来。这个类比一开始就是错的。模型的底层逻辑是给定前面一串文字预测下一个最可能出现的词是什么。它没有一张真实的“知识表格”它的“知识”是训练时从海量文本里学到的统计规律。这带来一个直接后果当模型不确定答案时它不是告诉你“我不确定”而是会按概率生成一个看起来像模像样的回答。这就是所谓的幻觉。打个比方你问一个记忆力不太好的人一个历史事件的年份他记不清了大概率会给你编一个合理但不正确的年份而不是坦诚地说不知道。模型也是一样而且它编得比你更流畅、更自信。1.2 知识有保质期第二个毛病更实际训练数据是有截止日期的。一个今年训练出来的模型你问它去年年底才发生的新政策、新产品参数它基本答不上来。不是它笨是它的“课本”里根本没有这部分内容。更麻烦的是企业内部场景。公司内部的知识库、技术文档、项目复盘、客户对话记录这些东西互联网上搜不到训练数据里更不可能有。你想让模型基于这些私有资料回答问题光靠模型本身的参数是做不到的因为那些知识从来就没进过它的“脑袋”。1.3 为什么偏偏是 RAG 来解决有两条路可以让模型“学会”新知识一条是微调把知识通过训练塞进模型参数里另一条就是 RAG把知识放在模型外面需要的时候检索出来喂给它。微调这件事成本和风险都不低。每次知识更新都要重新训练一轮训练完你也不知道模型到底记住没记住记歪了没有。而且微调本质上是“改变模型的性格”如果只是为了补充新知识很多情况下属于杀鸡用牛刀。RAG 的思路本质上更像人类的工作方式不要求你什么都记得只要求你查资料查得准然后照着资料说话。知识放在外部文档里文档更新了系统下次检索到的内容自然就更新了。模型本身的参数不用动成本低行为可控而且回答的时候能告诉你“我是根据哪份文档说的”可解释性也好。这就是 RAG 这几年成为大模型落地主流方案的根本原因。2. RAG 的工作链路逐段拆解2.1 一句话讲清整体链路把 RAG 拆开看其实就是三个阶段先把文档切成小块并转成向量存起来用户提问时把问题也转成向量去库里找最相似的内容片段最后把找到的内容片段和用户问题一起塞给模型让它基于这些片段生成答案。这个链路说起来很短但每一环都有讲究。很多人做 RAG 效果不好不是模型的问题而是前面两步没做好。文档切得不对、向量算得不准、检索回来的内容不对后面生成环节再强也白搭。所以别急着调 Prompt先检查检索。2.2 文档如何被“翻译”成向量要让计算机知道两段文字“像不像”第一步是把文字变成一串数字也就是向量。这个过程叫 Embedding。一个嵌入模型会把一句话映射成一个几百维甚至上千维的向量向量里每个维度的数值代表这句话在某个语义方向上的特征。语义相近的句子它们对应的向量在多维空间里的距离也近。这就好比两个人填一份性格测试问卷每道题的打分构成了他们的性格向量。如果两个人在绝大多数维度上打分接近我们就可以说“这两个人性格相似”。Embedding 做的事情就是给文字打分只不过这个“问卷”是模型在海量文本上训练出来的维度更复杂能捕捉到语义层面的相似。中文场景下选择嵌入模型的时候要注意语种覆盖度。有些英文模型效果很好但放在中文长文档上表现就一般。实际测试时不能只看文档里的示例分数要拿你自己业务里的真实问题去测看检索回来的内容是不是你想要的。2.3 检索是怎么找“相关资料”的向量入库之后检索阶段做的事情是把用户问题也转成向量然后和库里所有文档向量算相似度按相似度从高到低排序取前 K 个片段返回。常用的相似度指标是余弦相似度计算两个向量之间的夹角余弦值值越接近 1 表示方向越一致也就是语义越接近。这里有个新手容易忽略的点检索的目标不是“找到最相似的一句话”而是“找到足够回答问题的那一小段上下文”。有时候最相似的片段只是和问题有重复关键词并不包含答案所需的信息。这也是 RAG 系统最常见的失败原因之一。检索质量决定了整个系统的天花板生成模型只是在这个天花板之下发挥。2.4 生成阶段参考资料怎么喂给模型检索到了相关内容之后生成环节原则上已经没什么玄学成分了。你要做的就是把参考内容和用户问题组织成一段清晰的 Prompt告诉模型以下是一些参考资料请基于这些资料回答问题不要使用资料之外的知识。实际经验里这段 Prompt 的措辞会影响最终效果。比如“请严格基于参考资料回答”就比“请回答以下问题”效果更稳。我一般还会在 Prompt 里补一句如果资料中没有相关内容请直接回答“资料中没有找到相关信息”不要编造。这一句话能显著减少幻觉残留。这里要注意一个度参考资料里可能同时包含相关和不相关的句子。不要把整个检索结果原封不动全塞进去上下文窗口是有限的塞得太多反而会稀释有用信息。生成阶段的 Prompt 设计既要是非分明又要给模型留出合理组织语言的自由度。3. 动手前必须想清楚的三个决策3.1 RAG 还是微调别搞反我见过不少团队明明做的是知识库问答却先花两周微调模型效果还不理想。其实大多数“让模型知道某件事”的需求用 RAG 就能解决微调更适用于“让模型学会某种行为模式”的场景。对比维度RAG微调知识更新改文档即可实时性强需要重新训练周期长实现成本相对低无训练环节需要训练资源和数据准备可解释性可指出参考来源黑盒难以归因幻觉问题大幅降低但仍有残留可能缓解但无法根除适用场景知识问答、文档检索、私有数据接入风格模仿、输出格式控制、特定任务能力强化我的建议是先上 RAG跑通了发现模型“行为”不对再考虑微调。RAG 相当于给模型换资料微调相当于换脑回路前者便宜得多也容易回退。3.2 向量库到底怎么选向量库是 RAG 的存储底座市面上的选择大概分三类开源自托管方案、商业托管服务、以及传统关系型数据库的向量插件。开源自托管的好处是可控、数据不出内网适合对数据安全要求高的场景但你需要自己运维处理容量和并发问题时也要自己扛。商业托管版几乎零运维按量付费适合快速验证但长期跑大量数据时成本要仔细算。关系型数据库加向量插件则适合本身已经在用这套数据库、不想再引入新组件的团队缺点是向量检索性能一般弱于专业向量库。选型的时候不要只盯着排行榜。你先估算一下自己的数据量级和查询频率几万条文档开源方案完全足够百万级向量且对毫秒级响应有要求就要认真考虑索引类型和硬件资源了。3.3 Embedding 模型和切块策略Embedding 模型的选择直接影响检索质量。中文场景优先考虑在中文语料上训练过的开源嵌入模型它们对中文长文本、专有名词的表示通常更好。另外要注意向量维度和输入长度限制——维度越高存储和计算成本越大输入长度限制决定了你单次能编码多长的文本。切块策略是另一个容易被忽视但极其重要的环节。文档不是整篇塞进向量库的太长的话检索片段会包含大量无关内容而且超过嵌入模型输入上限。切得太碎又会导致语义不完整。我一般用两个经验值常规技术文档按 300 到 500 字切一块块与块之间留一点重叠比如 50 字保证语义不至于在切口处断裂。如果文档有明显的层级结构比如 Markdown 标题优先按结构切而不是硬按字数切。切片切得好不好直接决定检索命中率这个钱不能省。4. 一个最小可跑的 RAG 系统实录4.1 整体技术栈与目录结构下面这套流程是我在模拟项目 X 里实际跑过的方案不依赖任何商业服务用自己的环境和开源组件就能搭起来。整体结构分四块文档读取、文本切块、向量化存储、查询生成。rag_demo/ ├── data/ # 原始文档目录 ├── ingest.py # 文档加载、切块、入库 ├── query.py # 查询、检索、生成 ├── requirements.txt # 依赖清单这里用到的核心组件只有三个一个文本加载器、一个嵌入模型、一个向量存储。大模型可以接本地部署的开源模型也可以接云端 API不同选择的差异主要体现在响应速度和成本上。4.2 文档加载与切块加载文档这一步常规做法是遍历目录下的文本文件、PDF 或 Markdown提取正文。PDF 解析最容易出幺蛾子排版复杂的两栏论文、扫描件解析出来经常是乱序的。建议优先准备 Markdown 或纯文本格式的文档作为测试源跑通流程后再处理复杂格式。def load_documents(data_dir): docs [] for file_path in glob.glob(f{data_dir}/**/*.md, recursiveTrue): with open(file_path, r, encodingutf-8) as f: text f.read() docs.append({source: file_path, text: text}) return docs def split_text(text, chunk_size400, overlap50): 按字符数切块保留重叠区间 chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) if end len(text): break start end - overlap return chunks切块参数看起来简单实际要反复调。我一开始用 200 字切块结果很多概念被拦腰切断检索回来的片段总缺后半句。后来改成 400 字、重叠 50 字情况好了很多。如果你的文档句子特别长比如法律合同可以适当加大 chunk 尺寸。4.3 向量化与索引构建切块之后每一块文本都要过一遍嵌入模型生成向量然后连同原文和来源信息一起写入向量库。向量库里存的不只是向量本身还有对应的原文因为最后要把原文喂给大模型。def build_index(docs, embed_model, vector_store): for doc in docs: chunks split_text(doc[text]) for chunk in chunks: # 生成向量 vec embed_model.encode(chunk) # 写入向量库保存原文和来源 vector_store.insert({ vector: vec, text: chunk, source: doc[source] })入库之后建议做一个简单的抽查随机取几个 chunk用相似度检索看返回结果是不是合理的。这一步能提前暴露嵌入模型选型不当或者切块策略有问题的情况省得后面排查半天。4.4 查询链路代码查询链路相对简洁先把用户问题转成向量然后从向量库中查出最相似的 TopK 个片段最后把这些片段组装进 Prompt 交给模型。def retrieve_and_answer(question, embed_model, vector_store, llm, top_k5): # 1. 问题转向量 q_vec embed_model.encode(question) # 2. 检索 TopK hits vector_store.search(q_vec, top_ktop_k) # 3. 拼接上下文 context \n\n.join([h[text] for h in hits]) # 4. 组装 Prompt prompt f请基于以下参考资料回答问题。 如果资料中没有相关信息请直接回答“资料中未找到相关内容”不要编造。 参考资料 {context} 用户问题{question} 请给出回答 # 5. 调用大模型生成 answer llm.generate(prompt) return answer, hits这里 TopK 的值我一般设 5 左右。太小可能漏掉关键信息太大无关内容混进来会干扰模型判断。具体数值要根据测试效果调整但起步值用 5 不会太离谱。4.5 用三个问题验证系统系统跑通之后不要急着上线先拿三类问题做冒烟测试。第一类是文档里直接能搜到答案的问题比如“某模块的配置项有哪些”考察系统的检索命中能力。第二类是推断类问题答案不在某一句话里而需要模型把多个片段的信息整合起来考察上下文的连贯性。第三类是文档里根本没有答案的问题看系统会不会老老实实说没有而不是强行编一个。三类问题都通过系统才算基本可用。5. 运行中常见的坑与排查方法5.1 检索结果看着相关但答案不对这是最让人头疼的情况你把检索回来的片段打印出来看明明每一段都在说相关的东西但模型答出来的内容就是不对。问题往往出在“相关但不完整”上。比如用户问的是某个功能的限制条件检索回来的片段提到了这个功能但讲限制条件的那句话在下一个 chunk 里没被召回。排查方法很简单把命中的前几个片段完整打印出来人工读一遍看答案信息到底有没有在片段里完整出现。如果没出现说明切块切坏了出现了但模型答错才考虑调 Prompt 或换模型。不要跳过人工读片段这一步这是定位问题的关键。5.2 答案还是模型胡编的加了“没有找到就直说”的约束之后仍然有幻觉残留多数情况是参考材料里确实存在相似但不完全对口的表述模型把它过度泛化了。比如资料里写“A 方案适用于低频场景”用户问“高频场景用 A 方案行不行”模型可能就顺着话头说行。这种问题的解法有几个方向一是把 TopK 调小让模型只盯着最相关的几段二是在 Prompt 里强调“只能基于参考资料逐条对应回答不要推广到未提及的场景”三是检查切块时是不是把不相关的内容一起切进来了。多数时候是检索精度不够而不是模型太笨。5.3 多轮对话丢上下文用户问完“第一题的答案是什么”接着问“那第二题呢”系统如果直接把“第二题”当独立问题去检索基本查不到。这是多轮对话场景里最常见的问题。解决思路是在进入检索之前先把多轮对话压缩成一个独立的查询。也就是用模型把“历史对话 当前问题”改写成一句完整的、自包含的查询语句再去检索。这个过程叫查询改写成本很低但对多轮场景的提升非常明显。我建议所有做对话式 RAG 的团队都先补上这一步。5.4 更新索引后效果反而变差文档更新是一种高风险操作。重新嵌入并覆盖原向量时如果文档被重新切块切法变了同一段内容的向量就变了可能导致原本能命中的片段现在查不到。这种情况常见于人工改了几行字就重新全量入库的场景。稳妥做法是给每个文档固定一个内容哈希值作为标识。只有内容真正变化了才删除该文档旧的向量块并重新嵌入。不要无脑全量删除重建尤其是数据量上来之后全量重建的成本和风险都不可接受。5.5 排查顺序与速度优化建议我整理了一个排查顺序遇到问题按这个顺序查效率最高先检查检索结果是否包含答案信息再检查 Prompt 是否把信息用好最后才怀疑大模型本身。绝大多数 RAG 问题都出在前两步。速度优化方面主要瓶颈集中在 Embedding 生成和检索两个环节。Embedding 计算耗 CPU 和内存批量处理比逐条处理快很多可以先把所有 chunk 批量跑完入库。检索层面数据量大了之后要建合适的索引同时可以考虑给向量库加缓存把高频查询的结果直接缓存起来。6. 进阶优化方向与个人体会6.1 重排 Rerank给检索结果二次打分基础 RAG 用向量相似度直接取 TopK但向量相似度不等于“对回答问题的贡献度”。更稳的方案是先用向量检索召回 20 条候选再用一个重排序模型对候选逐条打分选出最相关的 5 条。这个过程叫 Rerank。重排模型比嵌入模型重得多但它只处理少量候选耗时可控。实测下来加一层重排之后答案准确率提升非常明显尤其当你的文档里存在大量表面相似但实际不同的内容时重排能帮你把噪声过滤掉。这是性价比最高的一项优化强烈建议优先做。6.2 查询改写、多路召回与 HyDE查询改写前面提过本质是让问题更适配合检索。多路召回是再进一步同一个问题一路走关键词匹配一路走向量检索一路走知识图谱各路结果合并去重后再统一重排。多路召回能提高召回率但实现复杂度也高数据量不大时不必急着上。HyDE 的做法比较取巧先让大模型针对问题生成一个虚构答案再用这个虚构答案去检索。因为虚构答案比问题本身包含更多与目标文档相关的表述检索命中率往往更高。代价是每次查询多一次大模型调用延迟和成本都会增加。这个方案适合答案表述比较“文档化”的场景不适合实时性要求很高的系统。6.3 评估体系没有度量就没有优化很多团队做 RAG 全凭感觉改了切块参数也不知道有没有变好换了个嵌入模型也说不上来值不值。我建议从一开始就搭建一个最小评估集准备 50 到 100 组“问题-标准答案-来源文档”每次改动之后跑一遍评估集对比回答准确率和检索命中率。评估集的构建不需要很复杂从你们业务真实用户提问里挑一批典型问题就行。关键是固定下来不要每次临时找问题。有了这个基准你调参的时候才有据可依。我现在改任何 RAG 组件第一件事就是跑评估集数字不过关再好的直觉也不好使。6.4 Agent 化 RAG 是趋势但要务实最后说一句 RAG 的演进方向。现在大家都在做 Agent 化 RAG也就是让模型自己决定查什么、查几次、要不要换个方式再查。这种方案在复杂问答场景下上限更高但引入编排逻辑之后系统复杂度和排查难度也同步上升。我的建议是先把基础 RAG 做好把数据治理和评估体系建起来再考虑 Agent 化。不要一上来就搞全自动编排否则系统出了 Bug 你根本不知道是检索的问题、模型的问题还是 Agent 决策的问题。好的 RAG 系统从来不是某个环节有多强而是每个环节都稳定可靠。做 RAG 这段时间我最大的体会是这个系统的上限由检索决定下限由模型兜底而中间所有的坑都出在数据上。切块切得合理、文档治理得干净、检索结果靠谱后面一切都顺了。如果你刚起步别急着追新概念先把一条最朴素的链路跑通把数据琢磨透再一步步往上加东西。这个路径我走了很多弯路才摸清楚希望你不用再走一遍。