1. 大模型“一本正经胡说八道”的背后:RAG到底治什么病
我最早接触到 RAG(检索增强生成,Retrieval-Augmented Generation)这个概念,是在给一个内部客服问答项目做大模型落地评估的时候。当时我们拿通用大模型直接对接用户问题,Demo 阶段效果看着还行,一问到产品退换货政策这种需要查最新规则的问题,模型就开始自由发挥了。它不会说“我不知道”,而是非常自信地告诉你“根据相关规定,7天无理由退换货适用于所有商品”。问题是,我们压根没有那条政策。这就是大模型最典型也最要命的痛点:幻觉。
很多刚入门的朋友会把幻觉理解为“模型变傻了”,其实不是。大模型的本质是一个概率语言引擎,它生成下一个词的依据是训练时学到的统计规律。只要某个知识点在训练语料里出现过、或者和语料中的某些片段长得像,它就倾向于把这个知识“润色”后输出,哪怕这个知识已经过时、被撤销、或者压根不存在。这在开放域闲聊里没什么问题,但在企业知识库、产品文档、法律条款、医疗建议这些对准确性要求极高的场景里,几乎是一票否决的问题。
RAG 解决的就是这个矛盾。它的思路非常朴素:既然你训练语料里没有我这份最新的内部文档,那我就先把这份文档检索出来,把相关内容拼进你的输入里,逼着模型“基于我给的资料回答”,而不是基于它自己那套过时的记忆回答。用大白话说,RAG 就是给大模型配了一个“开卷考试”的外挂。模型不再是闭卷默写,而是先翻书,再答题,翻到哪一页由检索系统决定,答得好不好由大模型生成能力兜底。
对于程序员来说,RAG 的另一层价值在于它的门槛。它不需要你重新训练模型,不需要几千张 GPU 卡,也不需要你懂复杂的强化学习和 LoRA 微调流程。你只要有一套可用的向量检索环境,加上对 prompt 的基本理解,就能在几天内搭出一个效果还不错的问答系统。这也是为什么我强烈建议还没接触过 RAG 的朋友先把它搞明白——它是大模型从“玩具”变成“生产力工具”的关键一步。
当然,RAG 也不是万能的。它解决的是“知识缺失”和“知识陈旧”的问题,如果你想让模型学会一种全新的说话风格、学会做某种特定格式的报表,那 RAG 帮不了你,那是微调或者领域训练的事。这个边界要先划清楚,不然项目后面很容易跑偏。
2. 一套可复现的最小RAG流程:从文档切分到答案生成
2.1 先把 RAG 的完整链路在脑子里画出来
很多人一上来就急着调库、跑代码,结果跑通了也不知道每一步在干什么,出了问题更不知道从哪里排查。我建议先花十分钟把整个流程走一遍,如果你能用自己的话把下面这个链路讲清楚,代码层面的东西其实都是细枝末节。
RAG 的完整流程可以拆成三个阶段:索引构建(Indexing)、检索(Retrieval)、生成(Generation)。
索引构建阶段:把文档切分成小块,每一块用 Embedding 模型转成一个向量,然后把向量连同原文一起存进向量数据库。这一步是离线做的,相当于给所有文档做了一套“坐标定位”。
检索阶段:用户发起一个问题,把这个问题也用同一个 Embedding 模型转成向量,然后去向量库里找最相似的那几个向量片段。找到的内容叫“上下文”,是被拼接给大模型的参考资料。
生成阶段:把用户原始问题加上检索到的上下文片段,一起组装成一个 prompt,发送给大模型,大模型根据上下文内容组织答案。这一步的关键是 prompt 设计——你要明确告诉模型“只根据下列资料回答,不要使用你自己的知识”。
这三个阶段任何一个出问题,最终效果都会打折。文档切得不好,检索就召回不对;检索召回的片段不对,大模型再聪明也答不对;prompt 里没有限定规则,模型就可能无视上下文自己发挥。所以不要只看某一个环节,RAG 是个全链路问题。
2.2 文档切分:决定上层建筑的地基工程
切分(Chunking)是 RAG 里最容易被低估的环节。很多初学者会用最简单的“按固定长度切”,比如每 500 个字符切一块。这种方案跑通 Demo 没问题,但遇到真实业务文档就很容易翻车。
我踩过最典型的一个坑:一份产品操作手册里有一个“退货政策说明”的章节,里面有表格、有备注、有引用,用固定长度切完后,一张完整的表格被腰斩成三块,检索时只召回其中的某一行,模型根据那一行生成答案,结果把“仅限未拆封商品可退”理解成了“所有商品可退”。
这里我补充一个判断切分好坏的实用标准:切出来的每一块,是否在语义上自洽?一个块应该是一个完整的信息单元,可以是一段话、一个小节、一个表格、一个列表。固定的字符数只是一个兜底手段,优先级最高的一定是“按语义边界切”。
实际操作中,我建议按下述优先级来选择切分策略:
- 如果原文档有标题结构(Markdown、HTML、PDF 书签),先按章节切,再把每章内部的段落做二次切分。
- 如果没有标题结构,优先按段落切,段与段之间用空行识别。
- 段落过长(超过模型上下文窗口或者 Embedding 模型最大长度)时,再按句子边界切。
- 一定不要把一个句子拦腰切断,因为那样会直接破坏语义完整性。
切分的长度也要考虑 Embedding 模型的输入上限。现在常用的开源 Embedding 模型输入长度一般是 512 或 2048 个 token,中文文本大约对应 500 到 2000 字左右。超过上限的部分会被截断,等于白切。我常用的做法是设置一个合理范围,比如“最少 200 字,最多 800 字”,同时允许和上一个块之间保留 50 字左右的 overlap(重叠),避免关键信息恰好落在边界处被丢掉。
2.3 Embedding 模型选型与向量库选择的经验
Embedding 模型是 RAG 里负责“语义理解”的组件。它的任务是把一段文字变成一个固定维度的向量,让“意思相近的文本在向量空间里距离更近”。选 Embedding 模型时,我见过不少朋友直接照搬某个项目的推荐,这容易出问题——不同领域、不同语言的文本分布差异很大,同一个模型在通用语料上表现好,不代表在你的垂直领域里也好。
给你一套可执行的选型思路:先在公开评测集上筛选两三个候选模型,比如针对中文、英文分别做检索召回率对比;再用你自己的业务数据建一个小测试集,约 50 条问答对就够了,分别跑一遍 RAG 全流程,看最终答案质量。这里特别提醒:不能只看检索准确率,还要看真实问题的答案正确率。我之前遇到过检索命中率 90% 但答案质量一塌糊涂的情况,原因是召回的片段排序位置不对,模型虽然看到了正确信息,但被其他噪声干扰了。
向量数据库的选型相对简单。小规模项目(几万条文本片段以内)直接用一个轻量方案就够了,比如在 Python 里用带向量索引的轻量库;数据量到了百万级,再考虑上分布式的向量服务。很多云厂商都有托管的向量数据库产品,不过如果是内部研发环境,我更倾向于用开源组件自建,方便调试、也方便迁移。
从成本角度考虑,初期不建议在向量数据库上花太多精力,先用最小的方案把全链路跑通,等数据量增长自然会有迁移的动力。
2.4 检索阶段的两步关键操作
检索阶段默认的动作是把用户问题转成向量后做相似度搜索。但这里有个隐藏问题:用户的提问方式往往和文档的表述方式差异很大。比如文档里写的是“商品自签收之日起7日内可申请无理由退货”,用户的问法可能是“我昨天刚收到货,现在能退吗”。这个问法就算 Embedding 模型能力很强,也不一定能和文档里的那句话完美对齐。
解决这个问题的常见手段是:先把用户问题改写成一个更适合检索的查询语句,再去做向量搜索。这个改写动作可以交给大模型来做。比如用户问“刚收的货能退吗”,改写系统把这句话转成“商品在签收后是否可以申请无理由退货以及退货时限要求”,这样再去和文档片段做向量匹配,命中率会明显上升。这个技术叫HyDE(Hypothetical Document Embeddings),原理是先用大模型生成一个假设性的答案文档,再拿这个假设答案去检索,因为假设答案和真实文档的语料风格更接近。
TopK(返回多少条片段)的取值也值得单独说。我见过有人上来就设 TopK=10,结果模型生成答案时被夹在十段(可能互有冲突的)信息里,产生了更多错觉。对于绝大多数问答场景,3 到 5 个片段其实就足够了。检索质量高的情况下,不需要堆砌大量上下文;检索质量不高的情况下,堆再多也只会增加干扰。我常用的策略是 TopK=5,然后在 prompt 里要求模型“当且仅当给定资料包含足够信息时才回答,否则明确说明”。这样即便某个片段是噪声,模型也能够主动跳过。
3. 构建中最容易翻车的三个环节与实际排查思路
3.1 检索几乎召不回有效内容:先别怪 Embedding 模型
我在帮某开发者的内部知识库项目调优时,遇到过一个非常典型的案例:索引构建时把 PDF 文档直接交给了某个解析库,没有做任何预处理,检索阶段不管问什么都召不回和问题相关的片段,召回来的全是乱码。他的第一反应是 Embedding 模型选得不行,想要换一个更大的模型。我让他把向量库里某几条内容打印出来看一眼,发现里面全是 PDF 页眉页脚、章节编号的碎片和重叠的排版文字,根本没法看。这时候换再强的 Embedding 模型也是白费。
这个坑的根源在于:向量检索只负责“语义相似度匹配”,它不负责理解“这段文字有没有意义”。一个全是乱码的文本片段和你的问题在向量空间里可能恰好离得很近,但它没有任何可用的信息。所以排查检索质量的时候,第一步不是换模型、调参数,而是检查索引里存的原文长什么样。我这次排查的链路如下:
- 随机抽 10 条索引里的文本片段,肉眼过一遍,确认文本质量正常。
- 发现大量片段源自 PDF 页眉页脚的重复内容。
- 回到文档解析环节,增加版式过滤,剔除页眉页脚、目录、空白页等噪声。
- 重新生成索引,检索准确率明显回升。
很多文档处理库都有“解析后重排”的能力,不只是把 PDF 当图片识别,而是保留标题层级、列表、表格结构。RAG 构建中,文档解析的重要性至少不亚于 Embedding 模型的选型。如果这一步做得糙,后面所有环节都跟着吃亏。
3.2 召回了正确片段,但生成答案还是错的
另一种情况更让人困惑:检索结果明明是对的——相关片段里已经把答案写得很清楚了——但大模型生成出来的答案还是错的。我排查过的一个项目就出现这种情况:用户问“月度报表的提交截止时间是几号”,文档片段里清清楚楚写着“每月第 3 个工作日之前提交”,大模型给出的答案却是“每月最后一个工作日之前提交”。
这个问题出在哪?我一次一次地检查了 prompt,发现其中并没有明确要求模型只能依赖给定资料回答,提示语写的是“以下是一些可能相关的背景资料”。对大模型来说,这句话的约束力约等于零。它依然会优先调用自己的内部记忆——“提交报表的截止日期通常在月末”——而不是基于给定的上下文回答。
这种问题的排查思路:
- 把完整的 prompt 打印出来,先看检索到的上下文是否真的拼进了 prompt。
- 检查 prompt 里的角色设定和约束指令是不是足够明确。
- 检查给定的上下文是否被其他重复的、无关的内容挤占。
修复方式是在 prompt 里加入强约束,比如:
你是一名企业知识库问答助手。请严格根据下面提供的资料回答问题。 如果你无法从资料中找到答案,请直接回复“根据现有资料无法回答”。 不要使用你自己的知识,不要对资料内容做引申推测。加了这句之后,同样的检索结果,答案正确率马上就不一样了。顺带一提,prompt 的这个约束在中文环境下尤其重要,因为中文大模型在开放回答时的“自由发挥”倾向表现得比英文更明显。
3.3 多轮对话之后,检索上下文被“污染”
RAG 项目做到后面基本都要支持多轮对话。用户先问“上个月的销售数据是多少”,再问“那这个月呢?”如果系统直接把“那这个月呢?”拿来当检索 query,检索出来的片段大概率是错的。这不是模型的问题,而是检索 query 的设计有缺陷。
正确做法是在进入检索阶段之前,先把多轮对话的省略指代问题做一次“指代消解”。可以先用大模型把对话历史压缩成一个当前问题,比如“从上个月的数据来看,趋势怎么样”改写为“本月销售数据与上月相比的变化趋势是什么”,再去执行检索。
我踩过的坑是在做指代消解时把整段历史全部塞给改写模型,导致模型迷失在上下文里,改写结果还不如直接用原始问题。后来我限制只保留最近两轮用户问句,效果反而更好。原因是:对话改写任务的目标是“提取当前意图”,而不是“总结整段对话”。输入太多反而引入噪声。另外,改写后的 query 要拿来做检索,所以它必须是一个信息完整的独立句子,不能有“那这个呢”“上面提到的那种呢”之类的指代词残留在里面。
4. 从“能用”到“好用”:RAG工程化的几个优化方向
4.1 Rerank 二次排序:把最相关的内容顶到最前面
向量检索的问题是:它的相似度度量是“整体的语义相似”,不是“针对当前问题的答案匹配”。举一个容易理解的例子:用户问“什么样的鞋子适合雨天穿”,文档里有一大段都在讲鞋子的保养方法,只在最后顺带提了一句“防水面料适合雨天”。整段的向量和用户问题可能已经很接近了,但真正有用的信息只有一句话。
初次检索(向量相似度搜索)会把这段文字整体排进候选集,但它的排序位置可能不够靠前,导致生成时被更靠前但相关性较弱的内容抢占了上下文位置。要解决这个问题,就引入Rerank(重排序)这一层。
Rerank 的思路是:向量检索阶段先放宽条件,召回 20 到 50 个候选片段,然后用一个专用的交叉编码器模型(cross-encoder)把“用户问题只有这一个片段”逐对计算匹配得分,再按得分重新排序,只保留 Top 3 到 Top 5 进 prompt。交叉编码器的计算量比向量相似度大得多,但精度也高得多。好处是它不再依赖 Embedding 模型的单向语义压缩,而是真正看到了问题和片段之间的交互。
工程上的惯用做法是:向量检索负责“广撒网”,Rerank 负责“精挑细选”。两者配合之后,RAG 的答案质量会有一个明显档次的提升,尤其是用户的提问方式和文档表述风格差异大的场景,效果显著。代价是要多一次模型推理,响应时长会增加几十毫秒到几百毫秒,但对于大多数知识库问答场景来说完全可以接受。
4.2 上下文压缩:不要一股脑把 TopK 全塞给大模型
很多人会进一个误区:为了让模型“看得更多”,把 TopK 调到 8、10,甚至把整个章节都拼进 prompt。这对于长上下文模型来说,表面上看没毛病,但实际效果不但没有变好,反而会出现“细节淹没”问题——正确答案确实在上下文中,但模型的注意力被大量无关细节分散掉了。
更好的方向是在进入 prompt 之前先做一次上下文压缩(Context Compression)。这一步的目的不是检索——因为检索已经做完了——而是筛掉和当前问题无关的句子,只保留候选片段中和答案直接相关的部分。这种做法在文档很长、内容有较多铺垫时的效果特别好。有一类工具专门做这件事,内部实现是把候选片段再喂给一个轻量模型,让它提取“和问题相关的句子”,然后只把提取出的结果拼进 prompt。
我实践中更推荐一种轻量做法:先让 Rerank 把 Top 5 的结果按得分排序,然后在 prompt 里给每个片段标注来源编号,并指示模型优先依据编号靠前的片段回答。这个方案不需要引入额外的模型,也能达到清晰有效的效果。大模型通常会对上下文开头的部分更关注,把最优片段放在 prompt 开始的地方,是一种低成本且有效的策略。
4.3 混合检索与评估闭环
向量检索不是唯一的检索方式。在生产环境里,我通常建议保留一个后备方案:关键词检索(BM25)。原因是向量 Embedding 对专有名词、缩写、型号名称的匹配能力有限。用户搜索“XK-200 型传感器故障代码 E-32”时,关键词匹配往往能直接把包含这段型号的记录捞出来,而向量检索可能因为语义空间里没有这个型号的“概念位置”而漏掉。
混合检索的典型做法是:同时跑向量检索和关键词检索,然后对两路结果做融合排序,最后再用 Rerank 统一精排。这个工程实现不难,但对效果稳定性的提升却非常明显。比如我维护的一个工业设备故障排查助手,处理型号+故障码这类精确匹配问题时,混合检索的准确率比纯向量检索高出将近 30 个百分点。如果你的业务文档里包含大量名称、编码、编号类信息,混合检索基本是必选项。
最后说说评估。RAG 系统不能只靠“感觉效果不错”来迭代,我个人建议无论项目规模大小,都建一个小的评估集,包含以下内容:5 到 10 条标准问答、几条需要引用多文档的复杂问题、几条故意超出知识库范围的无关问题(用于测试模型是否会被迫编造答案)。每次改动任何一个环节之后,用这个评估集跑一遍,对比答案的准确率和不编造率。有条件的还可以加上自动评估指标,但我是建议先人工核对一轮,性价比最高。没有评估闭环的 RAG 优化,基本等于闭眼开车。
5. 用了一个月之后的大实话与小建议
5.1 什么时候不要硬上 RAG
RAG 确实能解决很多问题,但它不是所有场景的最优解。在这个话题上聊点相对冷静的个人体会。
如果你的需求和知识文档几乎不相关,你需要的其实是模型自身的能力或者微调,RAG 帮不上忙。举例说:你想要一个模型把所有技术方案的输出格式统一成固定模板、固定语气、固定汇报逻辑,那更适合做指令微调。RAG 适合的是“知识在变、规则在更新、并且有现成全量文档”的场景——政策问答、产品手册、操作指南、故障排查、新人培训辅助,诸如此类。
另外,如果直接让用户从一份几千行的文档里搜索内容就能高效解决问题,那么做一个检索系统就足够了,不必硬套 RAG。我记得有一次在某个讨论组里看到有朋友问“我只需要让用户能在文档里搜到对应章节,为什么还要接大模型?”这个问题的答案是:如果目标仅仅是“找到内容”,那传统检索就够了;RAG 的增量价值在于“找到内容之后,用自然语言把答案组织好、解释清楚”,在跨章节信息整合的场景下尤其明显。想清楚这一点,你才能决定要不要接生成这个环节。
5.2 几个每次都会用到的实用小技巧
最后分享几个我每次搭 RAG 都会顺手加上、且验证过确实有用的细节:
第一,切分参数不要一次定死。我一般会让切分结果“先能改写再定死”。什么意思?新接一批文档时,先不直接建生产索引,而是随机抽几个段落,用代码生成 Embedding 后和几个测试 query 跑一下相似度,看看召回的内容是不是真的和测试 query 语义相关。预期不对就调切分策略,直到召回肉眼可见合理了,再全量建索引。
第二,Prompt 里的分隔符要稳定。我习惯用反引号包住每一条检索到的片段,并在片段开头加上来源编号,比如“资料1:反引号内容”。这样模型能清晰分辨哪部分是说理、哪部分是引用。调度得当的话,模型还会在答案里复述编号对应的来源,让问答案的人可以对原文核对,这是企业场景里很看重的一项能力。
第三,不要一次性把历史对话全部塞进上下文。每轮对话都要考虑问题改写和上下文长度控制。我习惯的做法是:用一个“对话清理”步骤把最近一轮独立问题提取出来,检索完生成答案后,只把当前这一轮的问答拼回历史中,其他历史内容不再进入检索。
第四,日志要记录全链路。线上运行 RAG 的时候,我强烈建议把用户问题、改写后的 query、检索出的 TopK 片段编号、Rerank 分数、最终的 prompt、生成结果这一整条链路记下来(至少保留一段时间)。任何一次线上答案翻车,只要翻一下日志,马上就能知道是检索召回不对、是 Rerank 排序不对、还是生成阶段出现了幻觉。没有这套日志做 RAG 线上运维,出了事故就像大海捞针。
5.3 后续还可以往哪里扩展
RAG 的项目边界一般会往两个方向扩展:一个是“多路知识源”,比如把企业内部文档、权限控制、在线标注知识库同时接入检索,让系统能在一个统一入口处理不同来源的知识查询,配合相应的版本管理和定时增量更新。另一个方向是“Agent 化”,把 RAG 升级成带工具调用的问答系统,比如用户问“这个型号最近有没有售后升级公告”时,不只是检索静态文档,还能主动触发查询接口去拿最新状态,再结合检索知识组织回答。
这两个方向都是在 RAG 主线上的自然演进——先保证检索回得准、生成答得对,再逐步把动态数据接入、权限过滤、多步推理这些能力加进去。我亲眼见过不少项目在第一步就急着想做完所有能力,结果检索效果一塌糊涂就盲目堆功能,后面返工成本很高。凡事先把基础链路的质量打磨稳定,再考虑扩展方向,维护起来的难度会完全不一样。
RAG 构建是个入门容易、做深很难的领域。好在它的每一层优化都有明确的思路去排查和验证,对技术背景不同的人也比较友好。希望这篇整理能帮你少走一些弯路,把精力花在真正影响效果的地方。