1. 为什么“检索增强”成了大模型落地的必选项
大模型刚火起来那阵子,我身边不少做应用的朋友都有个朴素的想法:模型参数够大、训练数据够多,问它什么它就该知道什么。结果真上手做业务系统才发现,模型对自家公司内部的文档、产品手册、客服话术、历史工单几乎一无所知,问它一个上周刚更新的价格政策,它能一本正经地编出一个完全不存在的数字。这个现象在圈子里有个很形象的说法叫“幻觉”,说白了就是模型在不知道答案的时候,倾向于用最像答案的语言把空填上,而不是老老实实说“我不知道”。
RAG,也就是检索增强生成,正是冲着这个痛点来的。它的核心思路其实一点都不玄乎:既然模型脑子里没装这些私有知识,那就在回答问题之前,先去外部知识库里把相关资料捞出来,塞进模型的上下文里,让它“看着材料答题”。这就像开卷考试和闭卷考试的区别——闭卷考的是模型预训练时记住的东西,开卷考的是它临场阅读和归纳的能力。而绝大多数企业场景,需要的恰恰是开卷考。
我之所以想系统聊这个话题,是因为RAG看起来简单,真做起来坑非常多。很多人第一次搭RAG,就是“文档切一切、向量存一存、问题搜一搜、答案拼一拼”,跑个demo效果惊艳,一上真实业务就原形毕露:检索回来的东西驴唇不对马嘴,或者明明检索对了模型却答偏了,再或者文档一更新整个库就得重建。这些问题背后,涉及文档切分策略、向量模型选型、检索召回与重排、上下文组装、生成约束等一整套工程细节。这篇文章我会把这些环节拆开讲透,既讲清楚每一步“为什么这么做”,也给出可以直接抄作业的参数和代码,适合正在做RAG落地、或者被RAG效果折磨过的开发者参考。
2. RAG的完整链路拆解:从一份PDF到一句靠谱的回答
2.1 离线索引阶段:文档是怎么变成“可检索知识”的
RAG的链路分成两段,一段是离线的索引构建,一段是在线的查询生成。很多人只盯着在线那一段调优,其实离线阶段的质量直接决定了天花板。索引阶段干的事,是把各种格式的原始文档(PDF、Word、网页、数据库记录)转成一段段文本,再转成向量,存进向量数据库。
这里面第一个关键动作是文档解析。PDF是最麻烦的,尤其是那种扫描件或者排版复杂的报告,直接抽文字经常抽出乱码或者顺序错乱。我的经验是,能用结构化格式(Markdown、HTML、JSON)就别用PDF,如果非要用PDF,优先选带文本层的,扫描件走OCR,并且一定要保留标题层级信息,因为标题对后续切分和检索权重影响很大。
第二个动作是文本切分,也就是chunking。这是最容易被低估的一步。切得太碎,一段话被拦腰截断,语义不完整,检索出来模型也读不懂;切得太大,一个chunk里混了好几个主题,向量表示被稀释,检索精度下降。业界常见的做法是固定长度加重叠,比如每段500个token,相邻段重叠50到100个token,防止关键信息正好卡在边界上被切断。
但固定长度切分有个明显缺陷:它不管语义边界。更好的做法是按语义或结构切分,比如按Markdown的标题层级切,一级标题下的内容作为一个父块,二级标题下的作为子块,检索时用子块匹配、返回时带上父块上下文。这种“父子块”策略在实际项目里效果提升很明显,因为它兼顾了检索精度和上下文完整性。
2.2 向量化:选对嵌入模型比调参更重要
文本切好之后,要转成向量。这一步用的是嵌入模型(embedding model)。嵌入模型的质量直接决定检索的召回率,我的观点是:在RAG里,嵌入模型的选择比大模型的选择还关键。因为大模型再强,你喂给它的资料是错的,它也答不对。
选嵌入模型要看几个维度。第一是语言支持,中文场景一定要选中文语料训练充分的模型,很多英文为主的模型在中文语义相似度上表现很差。第二是维度,维度高表达能力更强但存储和计算成本也高,常见的有768维、1024维、1536维,一般1024维是个不错的平衡点。第三是最大输入长度,如果你的chunk比较长,模型支持的长度要够,否则会被截断。
这里有个实操细节:查询和文档必须用同一个嵌入模型。我见过有人索引时用一个模型,查询时图省事换了另一个,结果向量空间都不一致,检索结果完全是随机的。另外,有些嵌入模型对查询和文档有专门的指令前缀(比如查询前加“query:”,文档前加“passage:”),用之前一定要看模型文档,加不加前缀效果差很多。
2.3 向量库与检索:相似度搜索只是起点
向量存进向量数据库之后,查询时把用户问题也转成向量,做相似度搜索,返回最相近的top-k个chunk。这一步听起来简单,但有几个参数需要认真对待。
相似度度量常见的有余弦相似度和内积。余弦相似度只看方向不看长度,适合大多数文本场景;内积对向量长度敏感,如果向量做过归一化,两者等价。选哪个取决于你的嵌入模型是怎么训练的,一般模型文档会说明推荐用哪种。
top-k的选择是个权衡。k太小,可能漏掉关键资料;k太大,噪声变多,还会挤占大模型的上下文窗口。我的经验是先用一个中等值比如5到10,然后结合重排来精筛。这里就要提到**重排(rerank)**了。向量检索是“粗筛”,它基于语义相似度快速从海量文档里捞出候选;重排是“精筛”,用一个更精细的交叉编码器模型,把问题和每个候选chunk拼在一起打分,精度更高但速度慢,所以只对粗筛出来的少量候选做。加了重排之后,检索质量通常有明显提升,这是很多RAG系统效果上不去的关键补丁。
2.4 生成阶段:怎么让模型“照着材料说话”
检索回来的资料要组装进提示词,连同用户问题一起发给大模型。这一步的提示词设计非常讲究。核心原则是:明确告诉模型只能用给定资料回答,资料里没有就说不知道,不要自己发挥。同时要给出引用要求,让模型在答案里标注信息来自哪段资料,方便溯源。
上下文组装还有个技巧叫上下文压缩。检索回来的chunk里往往有很多和问题无关的句子,全塞进去既浪费token又干扰模型。可以用一个小模型或者规则,把每个chunk里真正相关的句子抽出来再拼给大模型。另外,chunk的排序也有讲究,把最相关的放在最前面或最后面,因为大模型对上下文首尾的信息更敏感,这是所谓的“迷失在中间”现象。
3. 检索召回不准?先别急着换模型,查这五个地方
3.1 切分粒度是不是和问题粒度对不上
检索不准最常见的原因,是chunk的粒度和用户问题的粒度不匹配。用户问的是一个很具体的小问题,比如“某产品的退货期限是几天”,但你的chunk是整节产品政策,几百字混在一起,向量表示被稀释,相似度算出来就不高。反过来,用户问的是一个宏观问题,你的chunk却是一句话,检索出来的碎片拼不成完整答案。
解决办法是多粒度索引。同一份文档,既切成大块也切成小块,小块用于精确匹配,大块用于提供上下文。检索时先用小块的向量去匹配,命中后把对应的大块返回给模型。这样既保证了匹配精度,又保证了上下文完整。这个策略我在多个项目里验证过,对问答类场景提升尤其明显。
3.2 嵌入模型是不是没针对你的领域微调
通用嵌入模型是在海量通用语料上训练的,对日常语言理解很好,但遇到专业术语密集的领域,比如医疗、法律、工业设备手册,它的语义空间可能没覆盖好。举个例子,用户问“设备报E05错误怎么处理”,文档里写的是“故障代码E05对应过载保护触发”,通用模型可能觉得这两句相似度一般,因为它们用词差异大。
这种情况下,领域微调嵌入模型是值得投入的。用领域内的问答对或者相似句对做对比学习训练,让模型学会把“报错”和“故障代码”、“怎么处理”和“对应措施”拉到相近的向量位置。如果微调成本太高,退而求其次可以用混合检索,把向量检索和关键词检索(比如BM25)结合起来,关键词检索能兜住那些术语精确匹配的情况,两者结果融合后重排,效果通常比单用向量好。
3.3 查询本身是不是需要改写
用户的提问往往很口语、很模糊,甚至带错别字,直接拿去做向量检索效果不好。这时候需要查询改写。常见做法有几种:一是用大模型把口语化问题改写成更规范的检索式;二是做查询扩展,把一个问题拆成多个子问题分别检索再合并;三是做假设文档嵌入,让模型先“假装”生成一个答案,再用这个答案去检索,因为答案的措辞往往和文档更接近。
我实测下来,多查询扩展在复杂问题上效果最稳。比如用户问“新员工入职要办哪些手续”,可以扩展成“入职流程”“新员工报到材料”“入职需要提交的证件”几个查询,分别检索后去重合并,召回率比单查询高不少。代价是多几次检索调用,延迟会上升,需要根据业务对延迟的容忍度来权衡。
3.4 重排模型是不是被忽略了
前面提过重排,这里再强调一次,因为太多人跳过这一步。向量检索的相似度是“粗粒度”的,它把问题和文档分别编码成向量再算距离,问题和文档之间的交互信息丢失了。重排模型是把问题和文档拼在一起过一遍,能捕捉到细粒度的匹配关系,比如否定、条件、数量这些向量检索容易忽略的东西。
选重排模型时,同样优先选支持中文的。重排的top-k一般设成粗筛的2到3倍,比如粗筛召回20个,重排后取前5个给大模型。重排模型比嵌入模型大,推理慢,所以候选数量不能太多,否则延迟吃不消。
3.5 元数据过滤是不是没用上
很多文档自带丰富的元数据,比如时间、部门、文档类型、版本号。用户问“最新的报销政策”,如果你不按时间过滤,可能检索出一堆旧版本,模型看到互相矛盾的内容就懵了。元数据过滤是在向量检索之前或之后加一层结构化筛选,把不相关的文档先排除掉,能大幅提升精度。
实操上,可以在索引时把元数据一起存进向量库,检索时带上过滤条件。比如按时间倒序取最新的,或者按部门限定范围。这一层看似简单,但在企业知识库场景里往往是效果提升的捷径,因为很多错误答案不是语义匹配错了,而是匹配到了过期或不适用的文档。
4. 生成答案跑偏:提示词、上下文与模型的三方博弈
4.1 提示词里必须写死的几条约束
生成阶段最怕的就是模型“自由发挥”。我见过模型明明检索到了正确资料,却因为资料里有一句无关的旧信息,就把旧信息当答案输出。要压住这种行为,提示词里必须写死几条约束。
第一条,限定知识来源:明确告诉模型“只根据以下资料回答,不要使用你自己的知识”。第二条,允许说不知道:告诉模型“如果资料中没有相关信息,直接回答‘根据现有资料无法回答’,不要编造”。第三条,要求引用:让模型在每句话后面标注来源编号,这样既方便溯源,也能倒逼它基于资料说话。第四条,格式约束:如果需要结构化输出,把格式要求写清楚,比如用JSON或者固定的小标题。
这几条约束写进去之后,幻觉率会明显下降。但要注意,约束太多太严,模型可能变得过于保守,明明资料里有答案却不敢答。所以约束的措辞要平衡,既要限制发挥,又不能让它畏手畏脚。
4.2 上下文塞太多反而更差
很多人有个误区,觉得检索回来的资料越多越好,恨不得把top-20全塞进去。实际上,上下文过长会带来几个问题:一是噪声干扰,无关内容多了,模型注意力被分散;二是中间迷失,前面提过,模型对长上下文中间部分的信息利用效率低;三是成本上升,token多了推理费用和延迟都涨。
我的做法是精挑细选。粗筛召回一批,重排精筛出最相关的几个,再做上下文压缩,把每个chunk里真正相关的句子抽出来。最终给模型的上下文控制在合理长度内,比如2000到4000个token,具体看模型窗口和问题复杂度。宁可给模型少而精的资料,也不要给多而杂的。
4.3 模型选型:不是越大越好
生成模型的选择要结合场景。如果任务简单,比如从资料里抽取事实性答案,一个小一点、快一点的模型就够了,成本低延迟低。如果任务复杂,需要多步推理、综合多份资料,那就得上能力更强的模型。另外,指令遵循能力比单纯的参数规模更重要,因为RAG场景高度依赖模型严格遵守提示词约束。
还有一个实操点:温度参数。RAG场景一般把温度调低,比如0.1到0.3,让输出更确定、更贴近资料。温度高了模型容易发散,增加幻觉风险。当然,如果业务需要一些创造性的表达,可以适当调高,但要配合更强的约束。
4.4 引用溯源:让答案可验证
引用溯源不只是为了好看,它是RAG系统可信度的基石。用户看到答案旁边标着来源,可以点进去核对,这大大降低了误信错误答案的风险。实现上,可以在提示词里要求模型输出引用编号,然后在后处理阶段把编号映射回原始文档的链接或位置。
更严格的做法是答案级校验,用另一个模型或者规则,检查答案里的每句话是否真的能在引用的资料里找到依据。这一步会增加成本,但在高准确性要求的场景,比如客服、医疗咨询,是值得的。我一般会先做引用,如果业务反馈还有错误,再上校验。
5. 上线之后:RAG系统的持续运营与迭代
5.1 文档更新了,索引怎么同步
RAG系统不是搭完就一劳永逸的,知识库会更新,文档会增删改。如果每次更新都全量重建索引,成本高、耗时长。合理的做法是增量索引:文档新增时,只对新文档做解析、切分、向量化、入库;文档修改时,先删掉旧版本对应的向量,再插入新版本;文档删除时,同步删掉向量。
要实现增量,索引时得给每个chunk打上文档ID和版本号,更新时按ID操作。向量数据库一般支持按元数据删除,用这个能力就能做到精准更新。另外,更新最好做成异步任务,别阻塞在线查询,同时要注意更新期间的一致性,避免用户查到半新半旧的内容。
5.2 怎么知道系统答得好不好
上线之后得有评估机制,否则你不知道改动是变好了还是变坏了。评估分两层:检索层看召回率和准确率,也就是该召回的资料有没有召回,召回的资料有多少是相关的;生成层看答案的忠实度和相关性,忠实度指答案是否忠于资料、有没有编造,相关性指答案是否切题。
人工评估最准但成本高,可以先用自动评估做日常监控。检索层可以用标注好的问答对算指标;生成层可以用大模型当裁判,让它对比答案和资料打分。再配合用户反馈,比如点赞点踩、追问率,综合判断系统健康度。我习惯建一个小规模的黄金测试集,每次改动前后都跑一遍,确保没有回退。
5.3 成本和延迟怎么压
RAG的延迟主要花在检索和生成两段。检索段,向量搜索本身很快,慢的是重排和多次查询扩展;生成段,慢的是大模型推理,尤其是上下文长的时候。压延迟的思路有几个:一是缓存,高频问题的检索结果和答案缓存起来,命中直接返回;二是并行,多个查询扩展并行检索,重排和生成也可以流水线化;三是模型分级,简单问题走小模型,复杂问题才走大模型。
成本方面,嵌入和重排的调用量比生成大,但单价低;生成是大头。控制上下文长度、减少不必要的检索调用、用更小的生成模型,都能降成本。我的经验是,先保证效果,再在效果可接受的前提下逐步优化成本和延迟,别一上来就为了省钱牺牲质量,那样用户不买账。
5.4 几个我踩过的坑和对应经验
第一个坑是chunk重叠设太大。一开始怕切断信息,重叠设了200个token,结果索引膨胀,检索时经常召回一堆高度相似的重复内容,浪费上下文。后来把重叠降到50到100,配合语义切分,效果好很多。
第二个坑是忽略文档的时效性。有次用户问最新政策,系统返回了旧版本,因为旧版本在向量空间里和问题更接近。后来加了时间元数据过滤,默认只检索最近一年的文档,问题解决。
第三个坑是提示词里的资料编号和实际顺序对不上。检索结果经过重排后顺序变了,但提示词里的编号没同步更新,导致模型引用错乱。这个bug很隐蔽,排查了半天才发现,后来在组装上下文时统一重新编号,杜绝了这个问题。
第四个坑是用同一个嵌入模型处理所有语言。项目里有中英文混合文档,一开始用英文模型,中文检索效果很差。后来换成多语言模型,或者按语言分库用不同模型,才解决。
6. 写在最后的一点个人体会
RAG这个方向,技术迭代很快,新的嵌入模型、重排模型、向量库层出不穷,但底层逻辑是稳定的:把对的知识在对的时机喂给模型,并约束它别乱说。我见过太多团队在模型选型上纠结很久,却忽略了切分和检索这些更基础、更影响效果的环节。实际上,一个切分合理、检索精准的RAG系统,配一个中等能力的生成模型,效果往往好过一个切分粗糙、检索拉胯的系统配顶级模型。
如果你正准备做RAG,我的建议是先跑通最小闭环,然后拿真实业务问题去测,重点看检索回来的资料对不对。检索对了,生成基本不会太差;检索错了,再怎么调提示词都是治标不治本。把精力优先投在文档解析、切分策略、嵌入模型和重排上,这几块的投入产出比最高。至于那些花哨的Agent、多跳推理,等基础检索稳了再考虑也不迟。