1. 项目概述:RAG到底是什么
前阵子折腾了几个基于大模型的问答系统,绕来绕去最终都落到同一个核心方向——RAG检索增强生成。说实话,RAG这个词在圈子里出现的频率已经高到有点“泛滥”,但真正把它讲清楚、能直接落地到自己的项目里的文章并不算多。很多人知道RAG是“检索+生成”,但问到为什么要拆成这两步、检索模块怎么设计、哪一步最影响最终效果,就有些含糊了。
RAG的全称是Retrieval-Augmented Generation,翻译过来就是检索增强生成。它的核心思路很简单:在大模型回答之前,先从外部知识库里检索出和用户问题相关的片段,把这些片段作为上下文一起喂给大模型,让它基于这些材料来组织答案。这就像让一个记忆力有限的助手在做汇报之前先查一遍资料,而不是光靠脑子硬编。
它能解决的问题非常明确:一是大模型训练数据截止时间带来的知识时效性问题,二是模型对私有数据、专业领域数据“一无所知”的尴尬局面,三是回答时的幻觉问题——模型一本正经地编造不存在的细节。这三个痛点,恰好是企业在落地大模型时最常遇到的问题,也是RAG能在工业界快速铺开的原因。
这篇文章面向的是两类人:一类是刚接触大模型应用、准备在项目里引入RAG的开发者,另一类是已经在用RAG但觉得效果不理想、想搞清楚“到底是哪一环出了问题”的从业者。我会从整体设计思路、核心细节、实操流程、常见问题到进阶方向,把RAG这条线完整地捋一遍。很多经验来自我实际项目里踩过的坑,不一定适用于所有场景,但大概率能让你少走几步弯路。
2. 方案拆解:RAG的架构设计与核心模块
2.1 为什么不是“直接问大模型”,而是要多此一举加检索
很多人第一次接触RAG时都会问同一个问题:现在的大模型知识量已经很大了,直接问不就行了,为什么还要套一层检索?这个问题问到点子上了。ChatGPT也好,其他开源模型也罢,它们的能力上限确实高,但有一个致命短板——它们只知道训练时见过的数据。
训练数据截止日期决定了模型对“新近发生的事”是盲区。我试过一个很直观的例子:问模型某个刚发布的开源库的API用法,模型给出的答案里混着旧版API和它“推测”出来的新接口,结果自然是没法直接用来开发的。更别说企业内部的项目文档、规章制度、产品手册这些根本不会出现在公开训练数据里的内容,模型编造答案的概率极高。
RAG的解法是把知识的来源从“模型参数”转移到“外部索引”。参数里存的是模型的语言能力和推理能力,外部索引里存的是你的私域知识。这样做的第一个好处是知识可以随时更新——你只需要重新索引文档,不需要重新训练模型。第二个好处是可追溯——模型回答的每一句话都能对应到具体文档片段,这在合规要求高的场景里是刚需。第三个好处是可控——你决定哪些文档进索引,哪些不进,模型就只能在这套知识范围内作答。
理解了这个设计逻辑,后面看RAG的每个模块就不会觉得是拼凑,而是有一条清晰的主线:让模型在“被约束的知识范围”内发挥它的语言能力。
2.2 完整流程里的五个关键环节
一个标准的RAG流程,我习惯拆成五个环节:文档加载与解析、分段切分、向量化与存储、检索、生成。整体链路如下:
- 文档加载:把PDF、Word、Markdown、网页等不同格式的资料读入系统,转成纯文本。
- 分段切分:根据语义或结构把长文档切成若干个小块,这个环节直接决定检索粒度。
- 向量化:用嵌入模型把每个文本块映射成高维向量,语义相近的文本在向量空间里距离更近。
- 存储索引:把向量写入向量数据库,同时保留原文元数据,方便后续过滤和回溯。
- 检索与生成:用户提问后,把问题也向量化,在库里召回最相关的Top K个片段,拼接成提示词交给大模型生成答案。
这个流程看起来简单,但每个环节都有坑。比如文档加载环节,PDF里的表格和图片如果直接忽略,信息就丢了;分段切分环节,切得太碎语义会断裂,切得太整检索会不精准。后面我会逐个环节展开讲。
2.3 关键技术选型:向量数据库与嵌入模型
先聊向量数据库。目前市面上的选择不少,开源的有Milvus、Qdrant、Chroma、Weaviate,托管的有各类云服务。我个人的使用体会是——不要因为某个向量库“热门”就无脑选,要看你的数据量和查询模式。
数据量在百万级以内、想快速验证的,Chroma和Qdrant完全够用,部署轻量、API友好。数据量上了千万级、对查询性能和分布式能力有要求的,Milvus这类重一些的方案更合适。数据库本身没有绝对优劣,只有匹配不匹配。
嵌入模型的选择更关键,它直接决定检索质量的上限。国内可用且效果不错的开源嵌入模型不少,通常用中文场景的话,推荐MTEB榜单上排名前列的中文嵌入模型,比如bge系列、m3e系列等。选模型时要关注几个维度:语义理解能力、向量维度、最大输入长度、推理速度。
有一个容易被忽视的点:嵌入模型的输入长度限制。很多常见嵌入模型单次只能处理512个token左右的文本,如果你切分出的文本块超过这个限制,索引时会直接截断,信息损失不说,检索出来的片段还可能是残缺的。所以切片长度的设计,需要和嵌入模型的编码上限联动考虑,这一点很多人一开始就走偏了。
3. 核心细节解析:从文档到向量的完整链路
3.1 文档解析远比你想象的复杂
文档加载环节,最常见的误区是“拿到文本就直接用”。我踩过最大的坑是PDF解析——用某个通用解析库处理扫描版PDF,产出的是乱码和错位的文字,索引进去以后检索到的内容完全不可读。后来换用OCR方案才解决问题,但OCR的耗时是普通解析的几十倍。
不同文档类型要区别对待。PDF要分“文本型”和“扫描型”处理,前者用文本提取即可,后者必须走OCR。Word文档要注意嵌入图片和表格,直接提取文本会把表格内容错位拼接。网页内容要先去噪——导航栏、广告、页脚这些噪声如果一起进索引,检索时会被大量无关内容干扰。
我现在的做法是:统一转成Markdown格式再做切分,Markdown保留的标题层级和列表结构对后续切分非常有帮助。比如一份产品文档,转成Markdown之后可以按标题结构来切分,每个章节一个块,这会比盲目的按固定长度切分准确得多。
3.2 分块策略:决定检索质量的第一道关卡
分块是整个RAG流程里最容易被低估的环节,但它几乎决定了检索阶段能召回什么内容。想象一下,你问“这个项目的部署方法是什么”,如果文档被切成了每个500字的小块,恰好部署方法分布在两块中间,那么两块都不是完整答案,模型拿到的上下文就是残缺的。
分块的三种常见策略:
- 固定长度分块:按字符数或token数硬切,简单粗暴,实现成本最低,但语义完整性没有保障。
- 结构感知分块:按Markdown标题、HTML的DOM结构、PDF的章节层级来切,保留文档本身的逻辑结构,质量明显更好。
- 语义分块:用模型判断文本边界,在语义转折处切分,质量最高但计算成本也最高。
我在实际项目里的做法是:优先用结构感知分块,文档没有明显结构时退回到固定长度分块,并配合一定的重叠。重叠的意思是在相邻块之间保留一部分相同的文本,比如每块500字,后一块从原文的400字节处开始,让前后两块各共享100字。这样做的好处是在切分边界处的语义信息不会突然断裂,检索时跨边界的内容也有机会被命中。
3.3 查询不只有向量:混合检索才是正解
纯向量检索有一个经典问题:它擅长语义相似,但有时候用户的问题和文档中答案并没有多少字面重叠,却包含明显的关键词线索。举个例子,用户搜索“怎么退钱”,而文档里写的是“退款流程”四个字。“退钱”和“退款”在向量空间里距离很近,向量检索能搞定;但如果是“B2B客户如何取消订阅”这种问题,文档里的“取消”和“订阅”对应的是完全不同的措辞,纯向量检索容易漏掉精确的术语匹配。
这个场景下就需要混合检索。混合检索是把向量检索和关键词检索(比如基于BM25的全文检索)两条路都跑一遍,再把结果合并去重。BM25擅长精确匹配和术语命中,向量检索擅长语义扩展,两者互补效果远超单用其一。合并策略上,最简单的做法是加权求和或者轮流取Top N,进阶做法是用重排序模型对合并后的候选集统一打分。
3.4 重排序:提升精度的关键一步
检索阶段召回Top 20甚至Top 50的候选片段,其中可能只有两三个是真正相关的。如果直接把Top 5喂给大模型,其中混入几个不相关内容,模型就会被带偏。重排序(Rerank)就是在召回之后再加一道精细的筛选。
重排序模型和嵌入模型不同,它接收的是一个“问题+文档片段”的组合,输出一个相关性分数。它比嵌入模型更重,也更准确,但处理速度更慢,所以只能对候选集中的几十条做重排,而不是对全库重排。典型的流程是:向量检索 + 关键词检索各召回一批 → 合并去重得到几十条候选 → 重排序模型打分数 → 取分数最高的Top K → 输入大模型。
我见过不少项目把资源都花在“优化提示词”上,结果检索结果里混着大量无关片段,提示词写得再好也没用。检索质量上不去,后续生成效果一定受限,这可以说是RAG系统的第一定律。
4. 实操过程:从零搭建一个可用的RAG系统
4.1 技术栈选择与基本环境
搭建RAG的技术栈,我常用的组合是:Python + LangChain或LlamaIndex做流程编排,向量数据库选Qdrant或Milvus,嵌入模型用开源模型本地部署,生成模型按需选云端API或本地模型。
还是先说明一下:我的项目背景是某公司的内部知识库问答系统,把产品文档、排查手册、历史工单等几千篇资料做成一个能让客服快速查询的问答工具。整个系统跑在公司内网环境,数据不能出内网,所以模型和向量库都走自部署。
环境搭建时有一个容易踩的坑:依赖版本冲突。LangChain的版本迭代很快,不同版本之间API差异很大,网上很多教程其实已经过时。我的建议是直接锁定一个版本分支,按照官方文档来写代码,不要盲目参考老博客。另外一个建议是先跑通最小链路再逐步加模块,不要一开始就把重排序、多路召回全部堆上,否则出了问题根本定位不到是哪个环节。
4.2 数据准备与索引构建
第一步先把原始文档统一处理成Markdown格式。我这边有一部分文档是PDF版,文本型PDF直接用解析库转,扫描型PDF先过OCR再转。转完之后做一次人工抽检,确保表格没乱、章节标题完整、代码块缩进正常。
第二步设计分块策略。我用的标准是:优先按Markdown的H2/H3标题切块,如果一个章节内容太长就再做固定长度切分,块长度设为600字左右,重叠设置为100字左右。这里要特别注意,嵌入模型如果有中文分词或token限制,需要先确认最大长度,确保切出来的块不会超限被截断。
第三步批量生成向量并写入向量库。这一步建议用批量处理方式而不是一条条插入,速度能快上不少。同时给每条记录打上元数据标签,比如文档来源、更新时间、所属产品线。元数据的作用很大,检索时可以按来源过滤,排除了某些过期文档后再做相似度匹配,精确度提升明显。
4.3 年检检索链路与提示词构建
检索链路我推荐做成“三步走”:向量召回 → 关键词召回 → 重排序。向量召回用嵌入模型把用户问题转成向量,在库里取Top 20;关键词召回用BM25在全文索引里取Top 10;两边结果合并去重后,用重排序模型逐条打分,取分数最高的Top 5。
这里有个经验:召回数量宁可多一点,也别太少。Top 20看起来很多,但真正相关的可能只有三四条。如果只召回Top 5就把候选池锁死了,后续重排序的发挥空间就没了。先召回足够多的“可能的候选”,再用重排序精准筛选,这才是合理的链路。
提示词构建方面,我惯用的模板是:
你是文档问答助手。请仅基于以下参考资料回答问题。 如果资料中没有相关信息,明确回答“资料中未找到相关内容”,不要编造。 参考资料: {context} 用户问题:{question}这个模板的核心是“明确表达限制”。直接告诉模型不要编造、资料里没有就明说,能显著降低幻觉出现的概率。
4.4 生成模型的接入与流式响应
生成模型我选用的是开源模型,通过推理服务对外提供接口。接入方式上有一个细节值得注意:流式响应。问答系统的用户体验非常依赖首字延迟,如果等模型完整生成完再返回,用户要等好几秒才看到第一个字;改用流式响应后,用户几乎在0.5秒内就能看到内容输出,体验提升非常明显。
流式响应实现起来也不复杂,调用模型服务时开启流式模式,后端把生成的内容逐块转发到前端。中间要注意的是连接断开时的处理——用户中途关闭页面,后端应该停止生成并释放资源,否则模型还在空转,白白消耗算力。
5. 常见问题与排查技巧实录
5.1 检索效果差的九成原因在这里
RAG项目上线后,最常收到的反馈是“答案不对”。排查这个问题有一套固定的路径,我建议按顺序从下游往上游倒查:
第一步看生成环节。如果模型拿到的上下文里根本没有正确答案,那问题一定出在检索或索引环节,别急着调提示词。把一次用户问题的完整链路日志拉出来,看看到底召回了哪些片段,人工判断这些片段和问题是否相关。这一步能快速定位责任方。
第二步看重排序。如果召回片段里已经有正确答案,但重排序后正确答案没进Top 5,那就是重排序模型的问题。可以试试调整候选集大小,或者换更强的重排序模型。
第三步看嵌入模型。如果连召回阶段都没有正确答案,问题大概率出在向量化上。换一个更强的嵌入模型,检查分块粒度是否合适——块太大,答案片段被淹没在大量无关文本中,向量相似度会被稀释;块太小,语义不完整,检索也难以命中完整答案。
根据我的统计,90%以上的检索问题最终都落到两个根源:分块策略不合适,或者召回数量太少。这两个环节看似基础,却决定了系统整体的效果上限。
5.2 幻觉问题:模型总在“一本正经地胡说八道”
RAG系统另一个高频问题是幻觉——上下文明明没这个信息,模型还是编出来了。我遇到过一个案例,某用户问产品支不支持某功能,文档里完全没有提到这个功能,结果模型给了一段“如何启用该功能”的详细步骤,看起来有模有样,实际上是它从其他产品输出中迁移拼接出来的幻觉内容。
解决幻觉,单靠提示词写“不要编造”是不够的。更有效的手段是让模型在无法回答时主动认怂,同时把知识边界约束得更死。我的做法是双管齐下:
- 提示词明确要求:“仅使用参考资料中的信息回答,参考资料中没有的信息一律回答‘资料中未找到相关内容’。”
- 如果文档中可以用关键词判定“确实没有这个功能”,那么在提示词中额外给一条“已知事实约束”,告诉模型该功能不支持,别被自由生成带跑偏。
还有一点:降低生成温度也能减少幻觉。温度调低会让模型的输出更聚焦于提供的上下文,但也不能太低,否则回答会变得机械,缺乏灵活性。
5.3 性能优化:从1分钟到5秒的调优过程
我最初搭建的RAG系统,用户提问到收到完整回复要将近1分钟,完全没法用。逐段排查性能瓶颈,最终做了几件事:
一是把索引构建改为异步批量写入。原先每插入一条记录就调一次embedding接口,再单条写入向量库——瓶颈在等待上。改成熟批量后,整体索引时间从小时级降到分钟级。
二是把检索环节的向量化查询加上缓存。很多问题是重复的,或者表述不同但语义相同。我在检索前加了一层查询缓存,命中缓存的直接返回之前的结果,减少了大量不必要的计算和接口调用。
三是对生成环节做了流式输出优化并分批处理后端逻辑。最终首字延迟控制在1秒左右,完整回答一般在5秒内输出完毕。
性能优化的原则其实很简单:先找瓶颈再优化,不要一上来就改架构。把耗时打点加上,一眼能看出时间花在哪一环,绝大多数情况都花在等待外部服务返回和生成模型推理上。
5.4 高频问题速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 答案完全无关 | 分块太小或太大 | 调整分块长度与重叠,验证召回片段 |
| 答案相关但不完整 | 召回数量太少 | 增加初次召回数量,加大重排序候选集 |
| 答案包含编造内容 | 提示词缺少约束 | 显式声明只使用参考资料,降低生成温度 |
| 检索速度慢 | 全库扫描或查询无索引 | 建立索引,启用过滤检索,考虑索引分区分片 |
| 索引构建很慢 | 逐条embedding和写入 | 改为批量处理,异步任务队列剖析耗时 |
| 长文档信息丢失 | 文档解析忽略了表格/图片 | 解析时保留表格内容,必要时转图片走OCR |
6. 进阶方向:RAG还能怎么玩
6.1 多查询RAG与自适应检索
基础RAG有个隐含假设:用户的问题能通过一次检索找到答案。但现实里很多问题需要拆解成多个子问题分别检索。比如“某新功能对性能有什么影响,部署时需要注意什么”,这个问题包含两个子主题,一次检索很难同时覆盖两部分信息。
多查询RAG的思路是:先用大模型把原始问题改写或拆解成多个子问题,然后对每个子问题分别执行检索,最后汇总所有结果再交给模型生成。这个方法实现不复杂,但对回答质量提升明显,尤其是面对复杂问题。
自适应检索的思路则是让系统判断“要不要检索”。有些问题本身就是常识性的,比如“什么是货币”,强行检索外部文档反而可能引入噪声。做法上是先让模型判断问题是否需要检索,或者先做一次快速检索看看相似度够不够,不够就直接让模型回答,不检索。这个策略能节省大量不必要的检索开销,也能避免外部噪声干扰。
6.2 图表RAG与结构化数据检索
很多领域知识存在于图表和关系型数据中。普通的RAG流程遇到这类数据会天然失效——纯文本切分无法表达图结构中的关联信息。图表RAG的做法是把实体和关系抽取出来,构建知识图谱,然后通过图查询来召回相关的子图,把子图作为上下文喂给大模型。思路很好,但工程复杂度也高,对非技术团队并不友好。
结构化数据方面的常见方案,是把数据库表结构、字段说明先给大模型理解,让它生成SQL去查询数据,再把查询结果作为上下文做问答。这种方案在处理“上个季度营收多少”这类问题时,比纯RAG的“查文档”方式可靠得多。
6.3 RAG与微调:不是二选一,而是分工
很长一段时间里,RAG和微调被当作对立方案讨论,但实际上它们解决的是不同问题。RAG擅长动态知识、私有数据、可追溯性要求高的场景;微调擅长改变模型的风格、能力边界以及消化稳定的规则性知识。理想的做法是先用微调让模型适应你的领域语言风格和输出格式,再叠加RAG引入具体的知识内容。微调让模型更“懂行”,RAG让模型更“有料”,两者配合的效果远好于单用任一方案。
我见过一个项目,模型微调之后仍然回答不准,原因是新增的业务知识需要持续更新,微调跟不上数据变化节奏。后来改成“轻量微调定风格 + RAG管知识更新”的组合,效果才稳定下来。如果你遇到的场景是知识频繁变动,优先考虑RAG而不是频繁微调。
6.4 评估体系:没有评测就没有优化
最后一个常被忽略的点:RAG系统的优化必须有评测作为依据。没有评测,再多的经验调整都是玄学。系统级的评测通常拆成两个维度:检索质量和生成质量。
检索质量可以用召回率、命中率来衡量——在测试集里标注好每道题对应的正确答案片段,跑一遍检索链路,看正确答案有没有出现在Top K里,以及排序是否靠前。生成质量则可以用回答的相关性、准确性、忠实度等指标来评,目前不少团队在实际项目中采用“离线指标 + 线上人工抽检”相结合的方式,离线指标用来快速迭代,人工抽检用来兜底。
我习惯在项目里建一个100题左右的评测集,覆盖不同难度和场景,每次改动之后跑一遍全量评测,看看关键指标是上升还是下降。有了这套体系,优化才有方向。否则今天调一下分块,明天换一下重排序,效果变化根本看不出来,就只能靠感觉做事。
7. 最后再说几句实在的
这套RAG系统我从最初只能回答“文档里在哪一页”的笨重原型,一点一点调成能勉强应付真实客服问题的可用系统,中间走了不少弯路。印象最深的教训是:不要在一开始就追求“完美架构”。先把一条最简单的链路跑通——加载、切分、向量化、检索、生成——哪怕效果糙一点,至少有了一个可以观察和测量的基底。之后再根据数据表现,逐环节优化,每一步的收益都能看得见。
另外想提醒的是,RAG不是一个“配置完就能躺着用”的系统。文档更新了要重新进索引、业务术语变了可能要重新挑嵌入模型、评测集也要随着业务扩展持续维护。它更像一个需要长期维护的工程产品,而不是一次性交付的工具。
如果你正准备在自己的项目里引入RAG,我建议从一个小型但真实的问题场景开始,先跑通链路,再逐步扩展。技术本身的入门门槛不算高,真正拉开差距的,是对细节的把握和持续的调优迭代。