第五章 RAG 基础¶
导读¶
RAG(Retrieval-Augmented Generation,检索增强生成)是 Agent 的前置技能——Agent 调用"检索工具"的本质就是 RAG。本章建立 RAG 心智模型:为什么需要 RAG、RAG 流水线、Chunking 策略、检索与重排、评测、RAG → Agent 演进。第 9 章 Agent 记忆系统会复用向量库 RAG 作为长期记忆主实现,第 11 章多 Agent 协作里检索子 Agent 是 Supervisor 模式中的常见角色——本章建立的概念会贯穿全书。读完本章你应该能在工程上搭起一个最小可用的 RAG 系统,并理解它的边界(什么能做什么做不了)。
5.1 为什么需要 RAG¶
LLM 有三个根本局限,RAG 是其中两个的工程解法。第一,知识截止:模型权重在训练时就固定了,无法回答训练后发生的事——你问"GPT-5 发布了吗",模型基于训练数据答不上来。第二,私有数据:模型见过的是公开互联网数据,没见过你公司的内部文档(退款政策、产品手册、客户数据),它对私有问题只能瞎编。第三,不可重训:每次更新知识都重训模型不现实——成本几十万到几百万美元一次,且训练周期几个月。
RAG 的核心思路是:模型答不上来,那就先去外部知识库找一段相关文档,再把文档塞进 prompt 里让模型基于文档回答。这相当于给模型一个"开卷考试"的机会——闭卷考试答不上来的问题,开卷就能答对。
Fine-tune vs RAG 决策表¶
工程师常问"新数据来了,是该 RAG 还是 fine-tune"?判断标准如下:
| 场景 | 选 RAG | 选 Fine-tune |
|---|---|---|
| 知识更新(产品文档变化) | ✅ | ❌ |
| 风格定制(让模型用公司语气说话) | ❌ | ✅ |
| 领域术语(医疗、法律专业词) | 部分 | ✅ |
| 推理能力提升(让模型更会推理) | ❌ | ✅ |
| 实时数据(每天变化的股价) | ✅ | ❌ |
| 私有数据(公司内部知识) | ✅ | 部分 |
| 输出格式定制(特殊 JSON schema) | 部分 | ✅ |
直觉是:知识用 RAG,能力用 fine-tune。RAG 解决"模型不知道什么",fine-tune 解决"模型不会做什么"。两者不互斥——可以同时用:模型先 fine-tune 学领域语言风格,再 RAG 注入实时知识。
何时该同时用 RAG 与 Fine-tune¶
工程师常问"既然两者互补,是不是都该用"?不是——同时上意味着两套基础设施都要维护,工程复杂度翻倍。判断是否同时用要看几个场景。第一,领域语言风格 + 领域知识:法律/医疗场景既要术语准(fine-tune 学风格)又要查具体案例(RAG 查判例),这种场景两者都要。第二,多语言 + 多语言知识:模型中文能力强但小语种弱,可以 fine-tune 学小语种 + RAG 注入小语种文档。第三,输出格式 + 实时数据:fine-tune 学特殊 JSON schema + RAG 注入实时数据,常见于企业内部 Agent。
第四,冷启动时先 RAG 后 fine-tune:项目早期先上 RAG 跑通流程积累用户数据;数据多了再 fine-tune 让风格稳定。第五,fine-tune 让模型更会调 RAG:可以 fine-tune 模型"何时该调 RAG 工具"——这是 Agent 训练的进阶方向。
RAG 的边界¶
RAG 不是万能解药。第一,RAG 不能让模型变聪明——RAG 给的是"参考资料",模型本身能力不够时,给参考资料也读不懂(如给小学生一本量子力学书)。第二,RAG 不能修复模型幻觉——模型仍可能基于检索到的内容瞎编,特别是当检索结果不相关或冲突时。第三,RAG 受限于检索质量——检索召回不准,模型再强也答不对。RAG 工程上 80% 的 bug 在检索阶段,不在生成阶段。第四,RAG 不适合"创作"任务——写诗、写小说、写代码这种创意任务 RAG 帮不上,反而会被检索到的"相似但不相关"内容带偏。
RAG 失败模式清单¶
工程上 RAG 失败模式有几个典型场景。第一,检索不到:用户问题与文档表述差异大(如问"产品保修多久",文档写"质保期 24 个月"),embedding 相似度低召回不到——解法是查询改写或同义词扩展。第二,检索到但不相关:召回 top-5 里有 3 个是噪声(如查"产品 A"召回"产品 A 的竞品")——解法是加 cross-encoder 重排。第三,检索到相关但模型不用:模型基于自己的预训练知识答,忽略 prompt 里的检索结果——解法是 prompt 加"必须基于以下文档回答,不要凭记忆答"。
第四,文档冲突:多个 chunk 给出矛盾答案(如不同时间版本的退款政策)——解法是元数据带时间戳,prompt 里优先取最新。第五,chunk 信息不全:单个 chunk 没有完整答案(如"24 个月"chunk 没说从何时算起)——解法是父子分块返回更大上下文。第六,模型过度引用:模型回答时把每个 chunk 都引用一遍,答案冗长——解法是 prompt 加"简洁回答,仅引用必要来源"。这些失败模式都要靠评测 + bad case 持续优化。
5.2 RAG 流水线¶
RAG 系统由"建库"和"查询"两个阶段组成,建库是离线的,查询是实时的。
建库阶段(离线)¶
建库分四步:
- 文档加载:从 PDF / HTML / Markdown / 数据库 / API 拉原始文本。要处理各种格式——PDF 表格、HTML 噪声、Markdown 标题层级。
- Chunking(切分):把长文档切成小块(chunk),每块几百 token。切分策略直接影响召回质量——下一节展开。
- Embedding:每个 chunk 用 embedding 模型编码成向量(768 / 1536 维)。这是建库最耗时一步(要编码几万到几百万 chunk)。
- 入库:把 (chunk_id, embedding, metadata, raw_text) 存到向量库。向量库支持 ANN(近似最近邻)搜索,查询时毫秒级返回 top-K 相似 chunk。
建库的几个工程细节¶
工程师搭建库 pipeline 时几个细节。第一,文档去重:同一份文档可能从多渠道拿到(CMS / API / 数据库),要按 hash 去重避免重复入库。第二,版本管理:文档更新时要支持版本——旧版本要么删除要么标记为"过期",避免检索到过期信息。第三,增量入库:新文档加进来要增量入库(不要全量重建),降低运维成本——大部分向量库支持 upsert 操作。第四,embedding 模型一致性:建库与查询必须用同一 embedding 模型,否则向量空间不一致——embedding 模型换版本要全量重建。
第五,embedding 批处理:建库 embedding 几百万 chunk 要批处理,单条调 embedding API 太慢——批量调用(一次 100-1000 chunk)能省 10-100 倍时间。第六,embedding 缓存:重复文档的 embedding 可以缓存(chunk 文本 hash → embedding),避免重复计算。第七,多语言混合建库:中英文混合文档可以用同一 embedding(如 bge-large-zh 也支持英文),但分语言建索引召回更准——权衡存储成本与召回质量。
查询阶段(实时)¶
查询分四步:
- 查询 embedding:用户问题用同一个 embedding 模型编码成向量。
- 向量检索:在向量库里搜与查询向量最相似的 top-K chunk(K 通常 10-50)。这一步用 ANN 算法(HNSW / IVF),毫秒级。
- 重排(rerank):用 cross-encoder 对 top-K 重新打分排序,留下 top-3-5。重排让精度大幅提升(recall@5 提升 10-20 个百分点)。
- 上下文组装 + LLM 生成:把 top-3-5 chunk 拼进 prompt,加一句"基于以下文档回答用户问题",让 LLM 生成答案。
各环节的工程陷阱¶
工程师搭 RAG 时每个环节都有陷阱。文档加载:PDF 表格容易丢结构(变成乱码);HTML 要去掉导航与广告噪声;扫描版 PDF 要 OCR。Chunking:太大召回精度下降(chunk 里只有部分相关),太小上下文丢失(chunk 信息不全)。Embedding:选型决定上限——通用 embedding(bge-large / OpenAI)适合英文,中文要考虑 bge-large-zh 或专用 embedding。向量库:HNSW 召回快但内存占用大,IVF 内存省但召回略差;选型看数据规模与硬件预算。
检索:纯向量检索对"关键词精确匹配"任务(如产品编号、人名)召回差——要混合 BM25 + 向量检索。重排:cross-encoder 慢(每对 (query, chunk) 要单独 forward),不能在向量库召回阶段用——只能在小集合(top-50 内)用。上下文组装:top-3-5 chunk 拼起来要控制总长度(不要超 4-8K token),且要标注来源便于溯源。LLM 生成:要让模型"基于文档答"而不是"凭记忆答"——加一句"如果文档没说就回答不知道"避免幻觉。
RAG 流水线的几个进阶要点¶
工程师搭生产级 RAG 时几个进阶要点。第一,查询改写(query rewriting):用户原始问题可能表述不清或缺乏关键词——让 LLM 先把问题改写得更适合检索(如同义词扩展、关键词提取),能显著提升召回率。第二,HyDE(Hypothetical Document Embeddings):让 LLM 先基于问题生成一个"假设答案",用假设答案的 embedding 检索——假设答案与文档同分布,召回率比直接用问题检索高。第三,多查询融合:让 LLM 给一个问题生成多个变体,并行检索,结果用 RRF 融合——能进一步提升召回率。
第四,multi-vector indexing:同一个文档建多个向量索引(如 chunk + 摘要 + 关键词),不同查询用不同索引——长文档召回精度大幅提升。第五,time-aware RAG:文档带时间戳,查询时按时间窗口过滤——适合"最近 30 天数据"这种时效性查询。第六,permission-aware RAG:每个 chunk 带 ACL(访问控制列表),查询时按用户权限过滤——企业级 RAG 必备。第七,incremental indexing:文档新增/更新时增量入向量库,而不是全量重建——降低运维成本。
5.3 Chunking 策略¶
Chunking 是 RAG 上限的决定因素——再好的 embedding 与重排也救不回烂 chunking。下面四种策略从简单到复杂递增。
固定窗口¶
最简单:按固定 token 数切(如每 256 token 一段)。实现简单,但可能截断语义——一句话被切两半,前后两个 chunk 都不通顺。优点是 chunk 大小一致,便于批处理;缺点是边界处语义丢失。
工程上很少单独用固定窗口,都要加 overlap——前后 chunk 重叠 10-20% token,让边界信息在两个 chunk 都出现。
递归切分¶
按"段落 → 句子 → 字符"逐级降,先用段落分隔符切,超长段落再用句子切,超长句子再用字符切。LangChain 的 RecursiveCharacterTextSplitter 就是这种实现。
递归切分保留了语义边界(段落、句子是语义单元),同时控制 chunk 大小。是工程上最常用的默认选择。
语义切分¶
用 embedding 比较相邻句子的相似度,在相似度"跳变"处切——相邻两句相似度突然下降说明话题转换,应该切。这种切分能保证 chunk 内语义连贯,但慢(要为每对相邻句子算 embedding)。
语义切分适合长文档、多话题文档(一本书、一份长财报)。短文档用递归切分即可,语义切分投入产出比低。
父子分块(small-to-big)¶
最进阶策略:建库时切小 chunk(如 128 token),但每个小 chunk 关联到一个大"父 chunk"(如 512 token)。检索时用小 chunk(精度高,召回准),返回时给 LLM 看父 chunk(上下文足)。
父子分块是 RAG 进阶标配——它解决了"小 chunk 召回准但上下文缺,大 chunk 召回差但上下文足"的矛盾。生产系统大多用这种策略。
Chunking 的元数据管理¶
每个 chunk 除了文本与 embedding,还要带 metadata——这是 RAG 进阶的必备。常用 metadata 字段:1. source:来源文档 ID 或路径,便于溯源。2. page:页码或位置,便于定位原文。3. timestamp:文档时间戳,用于时效性查询。4. language:语言标识,多语言 RAG 时按语言过滤。5. acl:访问控制列表(如 ["engineering", "manager"]),按用户权限过滤。6. doc_type:文档类型(产品手册 vs FAQ vs 公告),便于混合检索时按类型过滤。7. chunk_id:唯一 ID,便于增量更新与删除。
metadata 工程上的几个要点。第一,入向量库时一起存:大部分向量库支持 metadata 字段,可以在查询时 filter(如 WHERE acl CONTAINS user_role)。第二,更新时全量替换:chunk 改了 metadata 要整体重新入库——metadata 是不可变记录。第三,schema 设计要早定:后期加 metadata 要重建索引,要早想清楚业务可能要的过滤维度。
Chunking 的工程权衡¶
工程师选 chunking 策略时几个考量。第一,chunk 大小:256-512 token 是大多数场景的甜点——太小召回精度高但上下文缺,太大召回噪声多。第二,overlap:10-20% overlap 让边界信息不丢,但增加存储成本。第三,多模态文档:含图表的文档切分要特殊处理——不能把图表与说明文字切到不同 chunk。第四,元数据保留:每个 chunk 要带 metadata(来源、章节、页码),便于后续过滤与溯源。
第五,chunk 与 token 计费:建库时 embedding 调用按 token 计费——百万 chunk × 平均 300 token = 3 亿 token,OpenAI embedding $0.13/1M 约 40 美元,不算贵但要预算。第六,chunking 与重排的协同:重排比 chunking 更直接影响最终质量——chunking 决定上限,重排决定逼近上限的程度。优先把重排做好,chunking 策略用递归 + 父子分块就够。
5.4 检索与重排¶
检索是把查询向量与库内 chunk 向量比相似度,重排是细粒度匹配提升精度。两者结合是现代 RAG 的标配。
向量检索(HNSW)¶
HNSW(Hierarchical Navigable Small World,arXiv:1603.09320)是当前主流 ANN 算法。核心思想是分层图索引——上层稀疏图快速定位粗略区域,下层密集图精确找到最近邻。HNSW 查询复杂度接近 O(log N),且召回率高(top-10 召回 95%+)。
工程上 HNSW 的几个参数。第一,M:每个节点的连接数,越大召回越准但内存占用大。M=16 是常用值。第二,ef_construction:建图时候选集大小,越大建图越慢但图质量越好。第三,ef_search:查询时候选集大小,越大召回越准但查询慢。这三个参数要在数据集上 sweep 调优。
主流向量库 FAISS / Milvus / Qdrant / Weaviate / Pinecone 都支持 HNSW,工程上选型主要看:开源 vs SaaS、规模上限、过滤能力(按 metadata 过滤)、延迟 SLA。
BM25 关键词检索¶
向量检索擅长"语义相似",但对"精确关键词"召回差——你查"产品编号 SKU-12345",向量检索可能召回"SKU-1234"或"产品编号 12345",但 BM25(Lucene 等搜索引擎的算法)能精确匹配。
工程上的最佳实践是**混合检索**:BM25 + 向量检索并行跑,结果用 RRF(Reciprocal Rank Fusion)或加权融合。混合检索能同时处理"关键词精确"和"语义模糊"两种查询,召回率比纯向量高 5-15 个百分点。
Cross-Encoder 重排¶
向量检索是"双塔"结构——query 与 doc 独立编码后比相似度,速度快但精度有限。Cross-encoder 把 (query, doc) 拼一起送 transformer,能建模细粒度交互,精度高但慢(每对要单独 forward)。
工程上 cross-encoder 用法:向量库召回 top-20-50 → cross-encoder 对每个 (query, chunk) 打分 → 按分数排序取 top-3-5。常用模型:bge-reranker-large(开源,中文友好)、cohere-rerank(SaaS,效果强)、jina-rerank(开源,多语言)。
重排的工程价值¶
重排 +1 个 cross-encoder 比换 embedding 模型有效——这是 RAG 工程上的重要经验。原因:embedding 模型一旦换,整个向量库要重新建(百万 chunk 重新编码,几小时到几天),成本高;而重排模型换只需换查询时的重排器,向量库不动。所以 RAG 优化的优先级是:先把重排做好,再考虑换 embedding。
重排的几个工程细节。第一,重排模型与 embedding 模型不同:embedding 是"编码",重排是"打分"——重排模型一般比 embedding 模型小(300M-1B 参数),但效果好。第二,重排计算成本:top-50 重排要 50 次 forward,单查询耗时 100-300ms——对延迟敏感场景要权衡。第三,重排阈值:可以加阈值过滤,分数低于 X 的 chunk 不要——避免低质 chunk 进 LLM prompt。
混合检索的工程实现¶
混合检索(BM25 + 向量)的工程实现有几个要点。第一,两路并行检索:BM25 用 Elasticsearch / OpenSearch / Lucene,向量用 FAISS / Milvus / Qdrant——两路并行各召回 top-20-50,延迟与单路相近(并行而非串行)。第二,结果融合算法:RRF(Reciprocal Rank Fusion)是事实标准——每个 chunk 的融合分数 = sum(1 / (k + rank_in_each_retriever)),k=60 是常用值。RRF 简单稳定,但权重不可调;加权融合更灵活但需要调权重。
第三,BM25 中文要分词:中文不能像英文用空格分词,要用 jieba / HanLP 分词,否则 BM25 召回质量差。第四,BM25 模型要更新:文档更新时 BM25 索引要同步更新(不像向量库可以增量),用 ES 这类系统自动处理。第五,混合检索的调参:BM25 与向量两路的权重比、各自的 top-K、RRF 的 k 参数——要在评测集上 sweep 调优。第六,预计算 vs 实时:高频查询可以预计算 BM25 + 向量结果缓存,避免每次都两路检索——但缓存失效策略要设计好。
5.5 评测¶
RAG 评测要从"召回"和"答案"两条线分别评。
召回率(Recall@K)¶
评测检索阶段召回的好坏。定义评测集:每个问题标注"应该召回哪些 chunk"(golden context),跑检索后看 top-K 里命中几个。常用指标 Recall@5 / Recall@10——前 5 / 前 10 召回里命中 golden 的比例。
工程上 Recall@5 比 Recall@10 更重要——因为重排后只有 top-3-5 进 LLM prompt,超出 top-5 的 chunk 实际进不了生成阶段。Recall@5 低于 70% 说明检索要优化(chunking、embedding、混合检索)。
答案准确率¶
评测最终生成答案的好坏。指标包括:第一,faithfulness(忠实度):答案是否基于检索到的文档,没有幻觉。第二,answer relevancy(答案相关性):答案是否回答了用户问题。第三,context recall:检索到的文档是否覆盖了正确答案所需信息。
RAGAS framework¶
RAGAS(Retrieval-Augmented Generation Assessment)是 RAG 评测的标准 framework,覆盖四个维度:
- Faithfulness:答案是否忠实于检索到的上下文,无幻觉。
- Answer Relevancy:答案是否完整回答了用户问题。
- Context Recall:检索到的上下文是否覆盖了答案所需信息。
- Context Precision:检索到的上下文是否相关(避免噪声)。
RAGAS 用 LLM-as-Judge 自动评分,跑一次评测几分钟出报告。工程上建议每次 prompt / chunking / embedding 改动都跑 RAGAS 对比,是 RAG 系统迭代的反馈信号。
RAG 评测集的搭建¶
RAG 评测集搭建是工程师最易忽略但最关键的工程纪律。评测集要包含三类样本。第一,问答对:每条有 (query, golden_answer, golden_context) 三元组——golden_context 是人工标注的"应该召回的 chunk",golden_answer 是人工写的标准答案。第二,bad case 收集:线上失败案例要进评测集——用户反馈"答错了"的样本,标注正确答案后入库。第三,多样性覆盖:评测集要覆盖不同任务类型(事实问答 / 推理 / 摘要)、不同难度(简单 / 复杂)、不同语言、不同文档类型。
工程上评测集规模 200-500 条是甜点——少了不显著,多了标注成本高。每次系统改动前后都在评测集上跑 RAGAS,对比四个维度分数。分数提升显著才上线,分数下降必回滚——这是 RAG 迭代的反馈闭环。评测集要持续更新(每月加新 bad case、删过期样本),保持与生产分布一致。
为什么 LLM-as-Judge 适合 RAG 评测¶
LLM-as-Judge 在 RAG 评测里效果比一般任务好,原因:RAG 答案有"参考标准"(检索到的上下文),LLM 评"答案是否基于上下文"比评"答案是否正确"容易——前者只看一致性,后者要靠知识。所以 faithfulness 评分 LLM-as-Judge 与人工评分相关性高(0.85+),是事实标准。
但 LLM-as-Judge 仍不完美:answer relevancy 评分有偏好(倾向给长答案高分),要在评测时控制答案长度。Context Recall 与 Precision 要有 golden context 标注——这一步要人工做,不能 LLM 自评。
5.6 RAG → Agent 演进¶
RAG 本质是"单轮检索 + 单轮生成"——用户问一句,系统检索一次,LLM 生成一次答。但很多场景需要多步检索:第一句问"公司有什么产品",第二句基于答案问"产品 A 的价格"。单轮 RAG 处理不了这种多步推理——它每轮独立检索,没有"基于上轮结果调整下轮查询"的能力。
From single-turn retrieval to retrieval sub-agent¶
Agent 时代的 RAG 演进方向是"retrieval sub-agent"——把检索做成一个子 Agent,让它能反思、调整查询、多轮检索。例如用户问"我们公司 2024 年 Q3 财报相比 Q2 增长多少":
- 单轮 RAG:检索 "2024 Q3 财报",可能召回 Q3 但没 Q2 数据,LLM 答不全。
- Retrieval sub-agent:先检索 Q3 财报 → 发现需要 Q2 数据 → 主动再检索 Q2 → 比较两次结果 → 生成最终答案。这是 ReAct 模式的应用(第六章展开)。
关键的章节前向引用¶
第 9 章记忆系统会把向量库 RAG 作为长期记忆主实现;第 11 章多 Agent 协作里检索子 Agent 是 Supervisor 模式中的常见角色。本章建立的 RAG 概念在后续章节会反复出现:
- 第 9 章记忆系统:长期记忆 = 向量库 RAG。Agent 把"过去的对话 / 任务经验"存到向量库,下次执行任务时检索相关历史作为上下文。
- 第 11 章多 Agent:检索子 Agent 是 Supervisor 模式里的常见角色。Supervisor Agent 收到任务后分发给子 Agent,其中"检索子 Agent"专门负责查文档、查历史、查知识库。
- 第 13 章评测与安全:RAG 系统的评测要靠 RAGAS + 任务成功率双层——检索召回率与最终任务完成度。
RAG → Agent 的工程路径¶
工程师从 RAG 升级到 Agent 的路径是渐进的。第一,单轮 RAG:检索一次 + 生成一次,覆盖 80% 知识问答场景。第二,多轮 RAG:用户多轮对话,每轮独立检索,对话历史拼进 prompt——但仍是无状态检索,不能基于上轮调整下轮。第三,条件 RAG:让 LLM 先判断"是否需要检索",需要才检索——避免简单问题浪费检索调用。第四,retrieval sub-agent:让 LLM 能多次检索 + 调整查询 + 反思结果——本质是把检索做成一个 ReAct Agent。
第五,multi-agent retrieval:把检索做成一个独立 Agent,让 Supervisor Agent 协调多个检索子 Agent(按数据源 / 时间 / 权限分)——这是大规模企业 RAG 的标准架构。第六,agentic RAG:让 Agent 不只检索,还能基于检索结果执行后续动作(如"查到这个产品已下架,自动建议替代品并发出通知")。这是 RAG → Agent 的最终形态,本书后续章节会展开。
Agent 化 RAG 的几个工程难点¶
把 RAG 升级到 Agent 化会引入新的工程难点。第一,多步检索的成本爆炸:retrieval sub-agent 跑 5 步 = 5 次 embedding + 5 次向量库查询 + 5 次 LLM 调用——单次任务成本是单轮 RAG 的 5-10 倍。要靠"提前结束"(找到足够信息就停)和"模型路由"(简单步骤用便宜模型)控制成本。第二,多步检索的延迟叠加:5 步串行 5 秒以上,对实时场景不友好——要靠"并行检索"(同时调多个数据源)和"预取"(基于上一步预测下一步可能要的 chunk)降低延迟。
第三,反思机制的可靠性:让 LLM 自己评"检索结果够不够"不可靠——LLM 经常自信地说"够了"但其实不够。要靠"规则校验"(如"必须至少召回 N 条相关 chunk")兜底。第四,多源冲突处理:多个数据源召回的 chunk 可能矛盾(如"产品 A 价格 99 元" vs "产品 A 价格 89 元")——LLM 要能识别冲突并做决策(如优先取最新时间戳)。第五,评测复杂度:单轮 RAG 评 RAGAS 即可,Agent 化 RAG 要评"任务成功率"——这是第 13 章的主题。
代码片段:LangChain RAG chain + cross-encoder 重排¶
下面这段代码用 LangChain + bge-reranker 实现一个最小 RAG chain,约 25 行:
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain.retrievers import ContextualCompressionRetriever
# 建库
docs = TextLoader("knowledge.md").load()
chunks = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=64).split_documents(docs)
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh-v1.5")
db = FAISS.from_documents(chunks, embeddings)
# 检索 + 重排
retriever = db.as_retriever(search_kwargs={"k": 20})
reranker_model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-large")
compressor = CrossEncoderReranker(model=reranker_model, top_n=3)
rerank_retriever = ContextualCompressionRetriever(
base_compressor=compressor, base_retriever=retriever
)
# 查询
query = "公司退款政策是什么?"
top3 = rerank_retriever.invoke(query)
context = "\n\n".join([d.page_content for d in top3])
prompt = f"基于以下文档回答:{query}\n\n文档:\n{context}\n\n回答:"
# 把 prompt 送给 LLM 生成答案
这段代码示范了 RAG 的完整流程:加载文档 → 递归切分(512 token + 64 overlap)→ bge-large-zh embedding → FAISS 向量库 → top-20 召回 → bge-reranker 重排取 top-3 → 拼 prompt 给 LLM。生产系统还要加混合检索(BM25)、查询改写、来源标注、评测 pipeline。
工程实战要点¶
- Chunking 决定 RAG 上限:chunk 切不好,再好的 embedding 与重排也救不回;优先把 chunking 策略定好(递归 + 父子分块是甜点),再优化检索与重排。
- 重排 +1 个 cross-encoder 比换 embedding 模型有效:换 embedding 要重建向量库(几小时到几天),换重排模型只需换查询时重排器,向量库不动——优化顺序很重要。
- 评测要从召回与答案双线:Recall@K 看检索质量,RAGAS(faithfulness / relevancy / context recall)看答案质量;缺一不可,单独看一条线会误判。
- RAG ≠ 问答系统,是 Agent 的检索工具:把 RAG 当"问答产品"会低估它的价值——RAG 是 Agent 调用检索工具的实现基础,第 9 章记忆、第 11 章多 Agent 都建立在 RAG 之上。
- 私有数据 RAG 必须做权限隔离:向量库里的 chunk 要带 metadata(tenant_id / role),检索时按用户权限过滤——否则一个用户能查到另一个用户的私有数据,这是合规事故。
小结¶
- RAG 解决 LLM 的知识截止 + 私有数据问题——给模型"开卷考试"机会;Fine-tune 解决能力问题,两者互补不互斥。
- RAG 流水线 = 文档 → Chunking → Embedding → 向量库 → 检索 + 重排 → 上下文 + LLM;建库离线、查询实时。
- Chunking 决定上限:固定 / 递归 / 语义 / 父子分块;256-512 token + 10-20% overlap 是甜点。
- 检索 = 向量 ANN(HNSW)+ BM25 关键词混合;重排 = cross-encoder 在 top-20 内细排到 top-3。
- 评测双线:Recall@K 看召回,RAGAS(faithfulness / relevancy / context recall / precision)看答案。
- RAG → Agent 演进:单轮 RAG → 多轮 RAG → 条件 RAG → retrieval sub-agent → multi-agent retrieval → agentic RAG;第 9 章记忆系统、第 11 章多 Agent 协作里 RAG 是底层基础。
练习题¶
- RAG 解决了 LLM 的什么根本问题?
- Fine-tune vs RAG 何时各选?
- Chunking 策略对召回的影响?
- 重排在 RAG 中起什么作用?
- RAGAS 评测维度有哪些?
- RAG 与 Agent 的关系是什么?
参考答案¶
第 1 题答案
RAG 解决 LLM 的三个根本问题中的两个:知识截止(模型权重训练时固定,无法回答训练后发生的事)与私有数据(模型见过的是公开互联网,没见过你公司内部文档)。RAG 的核心思路是"开卷考试"——模型答不上来的问题,先去外部知识库找一段相关文档,把文档塞进 prompt 里让模型基于文档回答。第三个问题是不可重训(更新知识要重训几十万到几百万美元),RAG 通过外部知识库让"更新知识"变成"更新文档 + 重建向量库",成本与时间都降到天级别。第 2 题答案
直觉是"知识用 RAG,能力用 fine-tune"。RAG 适合:知识更新(产品文档变化)、实时数据(每天变化的股价)、私有数据注入(公司内部知识)。Fine-tune 适合:风格定制(让模型用公司语气说话)、领域术语(医疗法律专业词)、推理能力提升、输出格式定制。两者不互斥——可以同时用:先 fine-tune 学领域语言风格,再 RAG 注入实时知识。决策表的核心是看"模型是不知道什么"(用 RAG)还是"模型不会做什么"(用 fine-tune)。第 3 题答案
Chunking 决定 RAG 上限。chunk 太大(> 1024 token)召回精度下降——chunk 里只有部分相关,召回时整块进 prompt 噪声多;chunk 太小(< 64 token)上下文丢失——chunk 信息不全,LLM 基于不完整 chunk 答不全。256-512 token + 10-20% overlap 是大多数场景的甜点。策略上递归切分(按段落→句子→字符降级)保语义边界,父子分块(小 chunk 检索 + 父 chunk 返回)解决"小召回准但上下文缺"的矛盾,是 RAG 进阶标配。Chunking 没做好的 RAG,重排再强也救不回。第 4 题答案
重排在 RAG 中起"精度提升"作用。向量检索是双塔结构(query / doc 独立编码),召回快但精度有限;cross-encoder 把 (query, doc) 拼一起送 transformer,能建模细粒度交互,精度高但慢。工程上重排用法:向量库召回 top-20-50 → cross-encoder 对每个 (query, chunk) 打分 → 按分数排序取 top-3-5。重排能让 recall@5 提升 10-20 个百分点。重排的关键价值是:换重排模型比换 embedding 模型有效(向量库不动,成本极低),是 RAG 优化的高 ROI 杠杆。第 5 题答案
RAGAS 评测四个维度:1. **Faithfulness(忠实度)**——答案是否基于检索到的上下文,无幻觉;用 LLM-as-Judge 评,与人工相关性 0.85+。2. **Answer Relevancy(答案相关性)**——答案是否完整回答了用户问题;要控制答案长度避免 LLM-as-Judge 偏好长答案。3. **Context Recall(上下文召回)**——检索到的上下文是否覆盖了答案所需信息;要 golden context 人工标注。4. **Context Precision(上下文精度)**——检索到的上下文是否相关(避免噪声);同样要 golden 标注。RAGAS 跑一次几分钟出报告,是 RAG 迭代的事实标准反馈信号。第 6 题答案
RAG 是 Agent 调用"检索工具"的实现基础——Agent 的"检索子 Agent"本质就是 RAG 系统包装成工具暴露给上层 Agent。演进路径:单轮 RAG(一次检索 + 一次生成)→ 多轮 RAG(多轮独立检索)→ 条件 RAG(LLM 判断是否需要检索)→ retrieval sub-agent(多次检索 + 反思 + 调整查询)→ multi-agent retrieval(多源子 Agent 协作)→ agentic RAG(检索 + 后续动作)。第 9 章记忆系统把向量库 RAG 作为长期记忆主实现,第 11 章多 Agent 协作里检索子 Agent 是 Supervisor 模式的常见角色。RAG 不只是"问答系统",是 Agent 的检索能力底座。下一章:第六章 ReAct:从对话到行动