刚开始学 RAG,很容易把目标定成一句话:把文档切块、转成向量,再让大模型根据搜索结果回答问题。
这条链路跑通当然重要,但它只证明"系统能返回内容"。被找回来的片段是不是正确、有没有漏掉关键证据、答案是否忠于资料、资料更新后能不能稳定生效,都还没有答案。
所以我更愿意把 RAG 当成一条需要逐段验证的数据链路,而不是某个框架的功能清单。这篇不是一份"我已经全部实测完"的教程,而是我根据 RAG 原始论文、综述和当前官方开发文档整理出的学习路线。目标很具体:知道先学什么、每一步练什么,以及做到什么程度才适合进入下一阶段。
先把 RAG 理解成两个系统,而不是一个聊天框
2020 年的 RAG 原始论文把它描述为参数化记忆与非参数化记忆的结合:模型参数负责语言能力,外部索引负责提供可以更新、可以追溯的知识。查询到来时,检索器先找到相关文档,生成器再结合问题与文档生成答案。
到了工程实现里,这通常可以拆成两条链路:
- 离线链路:收集资料、清洗、切块、生成向量、写入索引;
- 在线链路:理解问题、检索候选、排序或过滤、组织上下文、生成答案、返回引用。
这也是学习 RAG 时最先要建立的边界。文档成功入库,不代表查询能命中;查询能命中,不代表命中的片段足够回答;答案读起来顺畅,也不代表它忠于片段。
如果把这些状态揉成一个"效果不错",后面几乎无法定位问题。
第一阶段:先做一个最小闭环,但不要急着换框架
第一个练习不需要复杂知识库。找一份自己熟悉、可以人工核对的短文档,例如产品说明、个人笔记或一个小型开源项目的文档,然后准备 10 到 20 个问题。
问题不要全是能直接复制原句的简单题,至少包括:
- 文档中明确写出答案的问题;
- 需要连接两个片段才能回答的问题;
- 容易被相近术语干扰的问题;
- 文档里根本没有答案的问题。
最小流程可以是:加载文档,切成片段,为片段生成向量,保存索引;收到问题后检索 Top K 片段,把问题和片段交给模型,并要求资料不足时明确拒答。
下面只是概念伪代码,用来标出学习对象,不代表复制后即可运行:
ini
chunks = split(load(document))
index = embed_and_store(chunks)
contexts = retrieve(index, question, top_k=5)
answer = generate(question, contexts, require_citations=true)
这一步的学习目标不是记住 API,而是能够打印出每个中间结果:原始文档、切块结果、检索分数、最终上下文和答案引用。看不见中间状态的 RAG,很快会变成只能反复调整提示词的黑盒。
LangChain 当前的 RAG 文档同样把索引过程拆成加载、切分、向量化和存储。LlamaIndex 则把完整流程概括为加载、索引、存储、查询和评估五个阶段。这两个划分很适合做第一张学习地图。
第二阶段:把大部分时间花在数据和切块上
不少入门示例会给出一个固定 chunk size,然后直接进入向量检索。但真实资料不是等长的:标题、正文、表格、代码、FAQ 和页眉页脚承担的语义完全不同。
这一阶段要练的不是"找到一个万能数字",而是保住文档结构。可以对同一份资料做三组对照:
- 固定字符切块;
- 按标题和段落切块;
- 按文档类型定制,例如 FAQ 保留问题与答案,代码保留函数边界。
每组都用同一批问题测试,并记录三个东西:正确答案需要的证据落在哪个片段、检索结果有没有召回它、片段是否带着足够的标题和来源信息。
这里很容易出现一个反直觉结果:切得更小不一定更准。小片段更容易匹配局部词义,却可能丢掉限定条件;切得太大又会混入无关内容,增加上下文成本。chunk size、overlap 和结构信息应该一起看,而不是单独调一个参数。
还要尽早加入 metadata,例如文档名、章节、更新时间、产品版本和权限范围。它们不只是为了展示引用,后面还会参与过滤、更新和访问控制。
第三阶段:先评估检索,再讨论模型回答
这是我认为最值得刻意练习的一步。
给每个测试问题标出"应该命中的证据片段",形成一份很小的答案集。然后暂时不让大模型回答,只检查检索结果:
- Top K 中有没有正确证据;
- 正确证据排在第几位;
- 返回了多少看似相关但不能回答问题的片段;
- 无答案问题是否仍被强行匹配。
这样能把"没找到"和"找到了但模型没用好"分开。
语义搜索擅长找到用词不同但含义接近的内容,关键词搜索则对专有名词、型号、错误码和精确短语更敏感。当前 OpenAI Retrieval 文档也提供了混合搜索权重与相关性阈值的调整入口。学习时可以用同一批问题比较关键词、向量和混合检索,但不要一上来就同时调整所有参数。
更稳妥的方法是一次只改变一个变量:先固定数据和切块,比较检索方式;再固定检索方式,调整 Top K;最后才考虑 query rewrite、rerank 或多路召回。
第四阶段:让生成器学会依据证据回答,也学会不回答
检索通过后,再把注意力放到生成阶段。
这里至少需要四条约束:
- 答案以提供的资料为依据;
- 关键结论能指向具体来源;
- 多个来源冲突时暴露冲突,不自行拼成一个确定答案;
- 资料不足时明确说不知道,并说明缺少什么。
评估时不要只看"像不像正确答案"。至少分开记录:
- 答案是否回答了用户问题;
- 答案中的事实是否得到检索片段支持;
- 引用是否真的包含对应证据;
- 没有答案时是否越过资料自行发挥。
RAG 综述把常见质量维度概括为上下文相关性、答案忠实度和答案相关性。它们正好对应三类故障:找错了、编多了、答偏了。
第五阶段:再学习高级 RAG,而不是反过来
当最小闭环已经有固定测试集,而且能定位问题发生在哪一层,才适合继续学习高级方法:
- query rewrite:原始问题不适合直接检索时,先改写或拆解;
- hybrid search:结合关键词与语义召回;
- rerank:对候选片段重新排序;
- parent-child retrieval:小片段负责命中,大片段负责提供完整上下文;
- context compression:减少重复或低价值上下文;
- iterative retrieval:回答复杂问题时分多轮检索;
- metadata filter:按版本、部门、时间或权限缩小范围。
综述通常把 RAG 分成 Naive、Advanced 和 Modular 三类。这个分类适合用来理解技术演进,却不适合变成待安装的功能清单。每增加一个模块,都应该对应测试集中已经观察到的一类失败。
如果基础检索没有评估,增加 rerank 可能只是让错误结果换了顺序;如果文档结构已经在切块时丢失,换更大的模型也补不回来。
一条更适合工程实践的学习顺序
把前面的内容压缩成可执行路线,大致是这样:
第 1 周:看见完整链路
用一份小文档和 10 到 20 个问题跑通加载、切块、索引、检索和生成。强制打印中间结果,不追求界面。
第 2 周:只研究数据与切块
为同一份资料建立三种切块方案,对比正确证据能否进入 Top K,并检查标题、来源和版本信息是否保留。
第 3 周:建立最小评估集
给问题标注标准证据,区分检索命中、答案忠实和最终可用性。每次调整只改一个变量,保留前后结果。
第 4 周:处理真实失败
从 query rewrite、混合检索、rerank 和 metadata filter 中只选一个,解决测试集中已经存在的问题。不要为了"高级"而增加模块。
四周不是掌握 RAG 的承诺,只是一个控制学习范围的方法。真正的进度不应该用"看了多少教程"衡量,而应该看:能否解释一次错误答案究竟错在数据、索引、检索、上下文还是生成。
最后,给每次实验留一张记录表
每次修改至少留下这些字段:
css
数据版本:
切块策略:
embedding 模型:
检索方式与 Top K:
是否 rerank:
命中的标准证据:
答案是否被证据支持:
无答案问题是否拒答:
本次只改变了什么:
下一步要验证什么:
这张表看起来没有框架示例那么炫,但它能防止学习过程变成"换一个模型再试试"。
RAG 的入口确实是向量检索,但真正的工程能力,是知道一条答案经过了哪些环节,在哪一层失真,以及下一次只该改哪里。