这是「程序员的 RAG 修炼手册」系列第二篇。上一篇我们聊了 RAG 到底在解决什么问题,这一篇聊怎么把它做好。


搭完第一个 RAG demo 的那天晚上,我的心情经历了一个完整的过山车。

图片

前半段是兴奋。文档丢进去,问题问出来,大模型居然真的能基于我的文档回答问题了。虽然只是一个最简单的 demo,但那种“跑通了”的成就感,和当年第一次把 Spring Boot 项目启动起来的感觉一模一样。

后半段是崩溃。我问了几个稍微复杂一点的问题,回答开始跑偏。明明文档里写得清清楚楚的内容,它检索不到;有时候检索到了,回答却驴唇不对马嘴;更离谱的是,偶尔它会一本正经地编造一个文档里根本不存在的信息。

这种感觉太熟悉了——就像你写完代码,单元测试全过了,一上集成环境就到处冒 bug。能跑,和能用,中间隔着一整个工程化的距离。

后来我花了不少时间研究 RAG 的优化方法,发现这里面的门道比我想象的深得多。从数据怎么切、检索怎么做、到最后答案怎么生成,每个环节都有大量可以优化的空间。今天我把这些整理成一张完整的地图,按“数据准备→知识检索→答案生成”三个阶段来拆。

如果你也卡在“demo 能跑但效果不行”的阶段,这篇应该能帮你找到方向。


图片

第一阶段:数据准备——你喂进去的质量,决定了输出的上限

上一篇文章里我提过,RAG 的数据预处理包括知识库构建、文档分块、向量化三步。那是“跑通”层面的理解。到了“做好”这个层面,数据准备要做的事情远不止这些。

有一句话在数据工程领域流传很广:Garbage In, Garbage Out。垃圾进,垃圾出。在 RAG 里这句话同样成立,甚至更加致命——因为你喂给大模型的不是全部数据,而是经过检索筛选的“精华”。如果你的知识库本身质量就有问题,那检索出来的“精华”可能是精准的垃圾。

数据治理:先做减法

在把文档丢进知识库之前,有一步很多人会跳过——数据评估与分类。

你的文档里有没有过时的信息?有没有前后矛盾的内容?有没有敏感数据不应该被检索到?有没有格式混乱导致解析出错的文件?

这些问题不解决,后面做再多优化都是在烂地基上盖楼。

具体来说,数据准备阶段的完整流程应该包括:数据评估(哪些该进库、哪些该淘汰)、数据清洗(去除噪音、修正格式)、敏感信息处理(脱敏或隔离)、数据标注(打标签便于后续分类检索)。这套流程和我们做后端开发时的数据治理逻辑是一样的——你不会把一个脏数据源直接接入生产环境,对吧?

智能文档解析:不只是“读文件”

很多人以为文档处理就是把 PDF 或 Word 转成文本。但实际上,一份文档里的信息结构远比纯文本复杂——有标题层级、有表格、有图片里的文字、有页眉页脚、有脚注引用。如果你的解析只是粗暴地把所有文字提取出来拼成一个长字符串,那后续的分块和检索效果一定会打折扣。

智能文档技术要做的是:文档解析(识别结构)、文档理解(理解语义关系)、文档分析(提取关键信息)。比如一份技术文档,按标题层级来理解它的知识结构,远比按固定字数切割要合理得多。

分块策略:这是一个工程权衡问题

上一篇提到过,分块可以按固定大小(比如 chunk size=1000,overlap=10%),也可以按语义切分。但到了优化阶段,你会发现分块策略的选择直接影响后续所有环节的效果。

切太细,每个 chunk 缺乏完整语义,检索到了也不够用;切太粗,一个 chunk 里混了多个主题,检索精度下降。这和数据库分表是一个道理——分得太细查询要跨表 join,分得太粗单表数据量太大查询慢。

更高级的做法是多粒度知识提取:按照不同的标题级别对文档拆分,对各个粒度的 chunk 分别做知识提取和组合,通过去重降噪,保证知识不丢失也不冗余。这样在检索时,可以根据问题的粒度匹配到合适层级的知识块。


第二阶段:知识检索——召回率是生命线

数据准备好了,接下来是检索。这是 RAG 效果好不好的核心战场。

上一篇我们说过,基本的检索流程是:把用户问题向量化,在向量数据库里做相似度检索,找到最相关的几个 chunk。但在实际使用中,你会发现一个残酷的现实——用户问的方式和知识库里存的方式,经常对不上。

举个例子。知识库里存的是“员工离职需提前30天提交书面申请”,用户问的是“我想辞职要多久才能走”。语义完全一样,但用词完全不同。如果只靠向量相似度,有可能检索不到这条信息,或者排名靠后被截掉了。

这就是为什么说“召回率是生命线”——如果相关的知识根本没被检索到,后面生成环节再强也没用,巧妇难为无米之炊。

混合检索:向量+关键词,取长补短

单一的向量检索有盲区,单一的关键词检索也有盲区。向量检索擅长捕捉语义相似性,但对精确匹配(比如专有名词、编号、日期)不够敏感;关键词检索(比如 BM25)擅长精确匹配,但对同义词和语义改写无能为力。

工程化的解法是:两种都用,多路召回,然后合并结果。这就是混合检索的思路。向量召回一批,关键词召回一批,两批结果合在一起,再做统一的排序和去重。

这和我们做搜索引擎的逻辑一样——你见过哪个生产级搜索系统只用一种召回方式的?

图片

重排序:初筛之后的精排

多路召回解决了“找得全”的问题,但找回来的结果里,相关性参差不齐。这时候需要一个重排序模型(Reranker)做精排——对每个候选 chunk 和用户问题做深度的相关性打分,把真正相关的排到前面。

目前常用的重排序模型有两类:

BGE-Rerank,开源方案,基于 Transformer 的 Cross-Encoder 结构,可以本地部署,适合数据敏感的场景和垂直领域。它直接计算 query 和 document 的交互相关性得分,精度不错,而且你可以完全掌控数据不出域。

Cohere Rerank,商业 API 方案,通过云端调用,集成简单,在多语言场景下表现优异。适合快速验证和对部署成本敏感的团队。

选哪个?看你的场景。如果数据敏感、需要私有化部署,选 BGE-Rerank;如果追求快速集成、多语言支持好,Cohere Rerank 更省事。这又是一个典型的技术选型问题——没有绝对的好坏,只有适不适合。

查询扩展与双向改写:让“问”和“存”对得上

前面说的“用户问法和知识库存法对不上”的问题,除了混合检索,还有一个思路——改写。

改写分两个方向:

第一个方向,把用户的查询改写成多个语义相近的表述(相似语义改写)。比如用户问“怎么辞职”,系统自动扩展为“离职流程”“辞职申请”“员工离职手续”等多个查询,分别去检索,合并结果。这样即使某一种表述匹配不上,其他表述可能匹配得上。

第二个方向,双向改写。要么把查询改写成文档风格(Query2Doc),要么在建库时就为每个文档生成可能的查询(Doc2Query)。这种方法特别适合缓解短文本向量化效果差的问题——一句简短的用户提问,向量化后的信息量有限,改写成更丰富的表述后,检索效果会明显提升。

Small-to-Big 索引策略:先定位,再展开

这是一个我觉得特别巧妙的策略。

核心思想是:用小粒度的内容(摘要、关键句)建索引,但链接到大粒度的原文。检索时先通过摘要快速定位,定位到之后再把对应的完整段落或章节拉出来作为上下文。

为什么这样做?因为摘要短小精悍,向量化后的语义密度高,检索匹配更精准;但回答问题时又需要完整的上下文信息。Small-to-Big 兼顾了检索精度和上下文完整性。

用产品思维来理解:这就像电商的搜索结果页——你先看到的是商品标题和缩略图(small),点进去才看到详情页(big)。先用精简信息帮你快速筛选,再用完整信息帮你做决策。


第三阶段:答案生成——最后一公里的质量把控

检索到了相关的 chunk,接下来要把它们和用户问题一起发给大模型生成答案。这一步看似简单——拼个 prompt 不就行了?但实际上,怎么拼、拼多少、怎么约束大模型的输出,都有讲究。

上下文组装策略:不止一种拼法

上一篇文章里我只提了最基本的方式——把检索到的 chunk 直接拼在 prompt 里。但实际上,根据场景不同,有几种不同的组装策略:

第一种,直接拼接。把所有检索到的 chunk 按相关性排序后直接拼进 prompt。适合 chunk 数量少、单个 chunk 信息量适中的场景。简单直接,调用 LLM 次数少。

第二种,逐 chunk 摘要再合并。对每个 chunk 分别让大模型做摘要或提取关键信息,然后把摘要合并后再生成最终答案。好处是可以并发处理,适合 chunk 数量多的场景;缺点是各 chunk 之间缺少上下文关联。

第三种,链式推理。在第一个 chunk 上生成中间结果,然后把中间结果和下一个 chunk 合并,再生成新的中间结果,依次类推。这种方式能部分保留上下文的连贯性,同时控制单次输入的 token 量。

第四种,打分筛选。对每个 chunk 分别生成候选答案,然后对候选答案打分排序,返回得分最高的。适合对答案质量要求极高、愿意多花计算资源的场景。

选哪种?回到产品思维的“场景”二字——你的文档有多长、chunk 有多少、对响应速度的要求是什么、对准确性的容忍度是多少。没有万能方案,只有场景匹配。

提示词模板优化:给大模型的指令越清晰,输出越可控

这一点很多人会忽略。检索做得再好,如果你给大模型的 prompt 写得模糊,它的输出照样不可控。

一个好的 RAG 提示词模板应该包含几个关键要素:明确的角色设定(你是一个基于文档回答问题的助手)、明确的约束条件(只基于以下参考资料回答,如果资料中没有相关信息请明确说明)、明确的输出格式要求(如果需要的话)。

这和我们写接口文档是一个道理——你给调用方的文档越清晰,对方的实现越不容易出错。prompt 就是你和大模型之间的“接口文档”。

动态防护栏:像 code review 一样 review 大模型的输出

即使前面所有环节都做好了,大模型的输出仍然可能出问题。幻觉(编造不存在的信息)、遗漏(漏掉关键信息)、不完整(回答了一半)——这些问题在生产环境中是不可接受的。

解决方案是设置动态防护栏——通过规则、约束和反馈机制,对生成的内容做检查。比如:检查答案中的关键信息是否能在检索到的 chunk 中找到出处(防幻觉);检查用户问题中的关键要素是否都被回答了(防遗漏);检查答案的完整性和逻辑连贯性(防不完整)。

这就像代码上线前的 code review——不是不信任开发者(大模型)的能力,而是在流程上加一道质量关卡。

更进阶的做法是 FoRAG 这类两阶段生成策略:先让大模型生成一个回答大纲,然后基于大纲逐步扩展生成最终答案。先有骨架再填肉,结构化程度更高,输出质量更稳定。


我们拉回来看全局

写到这里,你可能会觉得:优化的点也太多了吧,从哪开始?

我的建议是:先诊断瓶颈,再对症下药。

图片

如果你的 RAG 回答经常“答非所问”——大概率是检索阶段的问题,优先看召回率,考虑混合检索和重排序。

如果检索到的内容是对的,但大模型的回答有偏差——看生成阶段,优化 prompt 模板,加防护栏。

如果整体效果时好时坏、不稳定——回头看数据准备阶段,可能是知识库本身的质量参差不齐。

这和我们做后端性能优化的思路一模一样:先用监控定位瓶颈在哪个环节,再针对性地优化那个环节。不要上来就全面铺开,那样既浪费精力又看不到效果。

RAG 优化不是某个单点技巧,是一整条工程链路的系统性提升。每个环节提升10%,串起来就是质的飞跃。这和做后端架构是一个道理——性能优化从来不是改一行代码的事,是从数据库索引到缓存策略到接口设计到部署架构的全链路思考。


最后说一句

掌握这套优化能力意味着什么?

意味着你不只是“会调 API 搭 demo 的人”,而是“能交付生产级 AI 应用的人”。

这两者之间的差距,就是市场愿意为之付费的差距。会调 API 的人越来越多,能把 RAG 做到生产可用的人依然稀缺。前者是入门门槛,后者是真正的技术壁垒。

所以我的行动建议是:选一个你最熟悉的业务场景——可能是你公司的内部文档、你负责的产品帮助中心、或者你感兴趣的某个垂直领域——搭一个 RAG,然后用今天这张地图一步步优化它。

不用追求一步到位。先把基线跑通,再逐个环节诊断和提升。每一次优化都是在积累你的 AI 工程化能力,这些能力会成为你未来做任何 AI 应用的底座。

下一篇,我们聊聊 RAG 的进阶方案选型——GraphRAG、RAFT、Qwen-Agent,这些名词到底在解决什么问题,应该先学哪个。


这是「程序员的 RAG 修炼手册」系列第二篇。

第一篇:我学RAG踩的第一个坑,可能你也在踩