Chapter 02

RAG 与知识库:给模型补上它不知道的事

上一章把 LLM 钉成了一个上下文有限、会幻觉的组件。正是这两个限制,逼出了这一章的检索增强(RAG)——在生成前先去外部知识里捞回相关片段塞进上下文,让模型"看着资料答题"而不是"凭记忆编"。

本章你要建立的心智模型

  • 什么时候该用 RAG、什么时候长上下文或微调更对——RAG 是规模决策,不是默认项。
  • 一条生产级 RAG 链路每一环的决策点与失败模式,以及怎么定位是"没检索到"还是"检索到了没用好"。
  • 为什么纯向量检索不够、混合检索 + rerank 各自解决什么问题。
  • 怎么用 RAGAS 三元组把"RAG 好不好"从主观感受变成可回归的数字。

2.1为什么要检索:RAG vs 微调 vs 长上下文

RAG = 在生成前先检索外部知识、把相关片段拼进 prompt 再让模型作答;它改的是模型"知道什么",不是模型"怎么说话"。

为什么需要它

LLM 的参数是训练那一刻冻结的:私域文档没见过、上周发的公告没见过、内部 SOP 更没见过。直接问,模型要么承认不知道,要么编一个像模像样的答案(幻觉)。把知识"写进参数"要重新训练,代价高且每次知识更新都得重训;把知识"塞进上下文"则便宜、即时、且天然带出处。RAG 选的是后者——用一次检索换掉一次重训。

面试里这道题的陷阱是把 RAG、微调、长上下文当成三个并列选项让你"选一个"。它们其实在回答不同的问题:RAG 改知识(模型该看哪些资料)、微调改行为(模型该用什么风格、格式、语气、能力)、长上下文改的是"这次对话能看多少"。中高级答法是先把问题归类,再谈取舍。

一条值得记住的规模启发式来自 Anthropic 的 Contextual Retrieval 一文:当整个语料能放进上下文窗口时(文中给的经验值是约 20 万 token / 约 500 页),直接把全部资料塞进 prompt + 开 prompt 缓存即可,无需 RAG——召回率 100%、没有分块和检索两段链路要维护,工程量和出错面都远小于 RAG。等到语料超过这个规模塞不下、或知识要实时更新 / 按权限过滤 / 可溯源时,才把 RAG 这条更复杂的链路顶上去。这是一道规模 + 工程的取舍,不是"做知识问答就默认上 RAG"。

表 2.1 · 三条路线在回答不同问题
方案解决什么代价 / 为什么不总用它
长上下文 + prompt 缓存一次性把全部资料喂进去,召回率 100%、无检索链路语料一大就塞不下;每次都全量进 prompt,首 token 延迟和成本随上下文线性涨
RAG(检索增强)补外部 / 私域 / 实时知识,降幻觉,答案可溯源,改知识不用重训多了分块 + 检索两段链路;召回不准则前功尽弃;选中行 = 大语料 / 常更新场景的默认
微调(SFT / LoRA)改风格、格式、语气、领域能力——让模型"换一种说法 / 学会一种任务"改不动"知识时效";要标注数据、要训练、知识一变还得重训;落地周期最长

三者不互斥,复杂系统常常叠加:用微调让模型学会"严格按引用作答、不编造"的行为,再用 RAG 喂它实时知识——行为靠微调锁、知识靠 RAG 供。下面这张决策树是面试白板上能直接画出来的判断顺序。

需要补充知识 / 改行为 语料 < ~200K tokens? 是 直接塞上下文 + prompt 缓存 否 知识常变 / 要溯源? 是 用 RAG 否 要改风格 / 格式 / 能力? 是 微调 否 长上下文 / 纯 prompt
图 2.3三选一不是品味问题,是顺着"规模 → 时效 → 行为"三道关往下落。注意:第一道关先问"语料塞得进上下文窗口吗"(Anthropic 给的经验值约 20 万 token)——塞得下就先用全塞 + 缓存,这是最常被跳过的判断。
想一想

产品要求"客服机器人必须用公司规定的固定话术开头和结尾,并且能答出今天刚更新的资费表"。这两个要求该各用什么手段?

展开答案(先停 10 秒再点)

拆成两个问题:固定话术 = 行为,用微调(或更轻的:把话术写进 system prompt)锁定;今天的资费表 = 实时知识,用 RAG 检索最新文档喂入。把两者混为一谈、想"微调一个知道资费的模型",结果就是资费一改又要重训——这是面试里区分初级和中高级的典型判别点。

2.2RAG 链路总览(一张图看全)

一条生产级 RAG 由两段独立链路构成:离线把文档变成可检索的索引,在线把查询变成带引用的答案。

为什么需要它

面试官问"你的 RAG 怎么搭的",初级答"用 LangChain 调一下向量库",中高级答"分两段、每段哪几环、每环最容易出什么问题"。把链路拆清楚的真正价值在排障:线上"答得不对"时,能立刻判断是离线索引建错了、在线召回没捞到、还是召回到了但生成没用好——而不是从头瞎调。

下图把整条链路摊开。左半是离线索引(文档进来一次、建好索引);右半是在线查询(每个用户请求走一遍)。两段唯一的交汇点是中间那个索引存储——离线往里写,在线从里读。

离线索引 在线查询 文档 解析 分块 embedding 入库 索引存储 向量库 + BM25 读索引 查询 查询改写 混合召回 rerank 拼 prompt 生成 带引用回答 answer + sources 建一次
图 2.1RAG = 离线索引 + 在线查询两段链路,交汇于中间的索引存储。注意:检索和生成是两段独立环节——答得不好先分清是"没检索到"(混合召回 / rerank 出问题)还是"检索到了没用好"(拼 prompt / 生成出问题),这决定你去调哪一半。

每一环都有自己的决策点和失败模式,下表是排障时的对照清单。后面几节会把其中最关键的几环(embedding、chunking、混合检索、rerank、评估)单独展开。

表 2.2 · 每一环的决策点与典型失败模式
环节决策点失败模式
解析PDF / 表格 / 扫描件怎么抽?保不保留结构表格被拍扁成一行乱码,后续全错
分块切多大、overlap 多少、按结构还是按长度切碎丢上下文 / 切太大稀释语义
embedding选哪个模型、几维、领域适配中文用了英文模型,语义检索全面失准
入库向量库选型、索引类型(HNSW / IVF)索引类型选错,召回率或延迟不达标
查询改写要不要改、改成几条口语化 / 指代不清的 query 直接检索召回差
混合召回纯向量还是 BM25 + 向量、融合方式纯向量漏掉专名 / 编号 / 精确词
rerank要不要重排、用哪个 rerankertop-k 里相关的没排进前面,喂错片段
拼 prompt / 生成放几条、怎么约束引用检索对了,模型却忽略上下文照样编

2.3Embedding 与向量库(HNSW vs IVF)

Embedding 把一段文本压成一个稠密向量,语义相近的文本向量也相近;向量库则负责在亿级向量里快速找出与查询最近的若干个。

为什么需要它

计算机不能直接比较"这句话和那句话意思像不像"。embedding 把语义编码成几百到几千维的向量,于是"相似"变成了可计算的几何量——通常用余弦相似度。没有它,检索只能退回关键词匹配,"汽车"和"轿车"在字面上毫无关系,语义检索就无从谈起。

常用模型的关键参数要记牢,面试常问具体数字:OpenAI text-embedding-3-small 是 1536 维、3-large 是 3072 维;后者支持 Matryoshka 表示——可以把向量截断到更低维度而精度只小幅下降(3-large 截到 1024 维仅比全维差 1–2%),用更小的存储换几乎相同的效果。中文场景的开源首选是 BGE-M3(1024 维、覆盖 100+ 语言),它的特别之处是一个模型同时产出三种表示:dense(稠密,编码语义)、sparse(稀疏,等价 BM25 式的精确词权重)、multi-vector(多向量精排)。dense 管"意思像",sparse 管"词对得上"——这正好对应下一节的混合检索。

类比 · 带边界声明

embedding 像给每段文本在语义空间里发一个 GPS 坐标,意思接近的落在同一片区域。但类比到此为止:GPS 是 3 维、人能想象;embedding 是上千维,高维空间里"距离"和"密度"的直觉会失效(维度灾难),所以才需要专门的近似最近邻索引,而不是简单地"算出所有距离再排序"。

向量库的核心是索引结构——它决定"在一亿个向量里找最近的 10 个"是亚毫秒还是几秒。两种主流路线:HNSW(基于图,多层跳表式导航)召回率高、查询亚毫秒、写入即插不需重建,代价是内存占用是原始向量的 2–5 倍;IVF(先聚类分桶、查询只搜邻近几桶)省内存、适合超大静态集,但需要周期性重建,且配合 PQ 量化(IVFPQ)才能把十亿级向量压进可控内存。

表 2.3 · HNSW vs IVF——向量索引选型
索引优势代价 / 适用
HNSW(图)recall@10 > 95%、查询亚毫秒、写入不需重建——增量更新友好内存 2–5×;中小规模 / 频繁更新的默认选择
IVF(Flat)(聚类)省内存、构建快需周期重建、召回随分桶数波动;适合 > 50M 的相对静态集
IVFPQ(聚类 + 量化)把向量压缩,十亿级也能进内存量化有损、精度下降;只在亿级以上、内存吃紧时才用
洞察 · 近似 = 用召回换速度(而且不报错)

HNSW、IVF 都是近似最近邻——为换亚毫秒延迟,它们不保证找到真正的 top-k。recall@10 ≈ 95% 的另一面是:约 5% 的查询会悄悄漏掉一个本该召回的相关文档,而系统不会报错,只是答得差一点、且你无从察觉。所以"召回不准"常常不是哪里写错了,而是近似索引的固有代价——这也是为什么要靠加大召回候选数、混合检索与 rerank(下一节)把这 5% 抢回来。

带来的代价:HNSW 的内存放大是真实成本——1 亿个 1024 维 float32 向量原始就约 400GB,HNSW 还要再叠 2–5 倍的图结构内存。这正是"亿级向量怎么扛"这道追问的落点:要么换 IVFPQ 用量化压、要么分片到多机(Milvus / Qdrant 这类分布式向量库),不能无脑全上 HNSW。库的选型按规模和运维成本排:单机 / 嵌入式选 FAISS(库,无服务进程);要持久化 + 分布式选 Milvus / Qdrant;已经在用 Postgres、想少加一个组件就选 pgvector。

想一想

线上已经用 text-embedding-3-small 入库了几百万条向量,现在想换成召回更好的 BGE-M3。存量向量还能直接用吗?

展开答案(先停 10 秒再点)

不能。不同 embedding 模型输出的是各自独立的向量空间,维度不同(1536 vs 1024)、坐标含义也完全不同——拿新模型编码查询去和旧模型编码的库里向量算距离,结果是噪声。换 embedding 模型意味着必须对全部存量文档重新编码、重建索引,这是一次性的批处理成本,也是为什么 embedding 选型要在上线前定好、不要轻易换。这个"换模型 = 全量重建"是高频追问。

2.4Chunking:切分策略与父子块

Chunking 把长文档切成适合检索的小片段;切多大、怎么切,直接决定召回的上限。

为什么需要它

embedding 模型有输入长度上限,向量库也只能按"段"检索。但切分是把双刃剑:切太小,一句话脱离上下文,"它指的是上一段那个产品"这种信息丢失,检索到了也看不懂;切太大,一个块里混了好几个主题,向量被多个语义"平均"稀释,既检索不准、又在拼 prompt 时挤占宝贵的上下文预算。chunking 就是在这两端之间找平衡。

典型经验值:每块 256–512 token、相邻块留 10–20% overlap(重叠是为了让跨边界的句子不被切断)。但"切多大"只是表象,真正的策略差异在"按什么切":

表 2.4 · 四种 chunking 策略
策略怎么切代价 / 适用
fixed(定长)每 N token 一刀,无视文档结构实现最简,但会从句子 / 表格中间切断;只适合结构松散的纯文本
recursive(递归分层)优先按段落切,过长再按句子、再按字符逐层退务实默认——尊重结构又有兜底;绝大多数场景的起点
semantic(语义)按相邻句 embedding 相似度找语义边界下刀边界最自然,但要额外算 embedding、慢且贵;主题混杂的长文才值得
parent-document(父子块)用小块(128–256 token)去检索、命中后返回它所属的大块(512–1024 token)喂给模型多存一层映射;但同时拿到"检索准"和"上下文全",是高级 RAG 的常用解

父子块值得单独强调,因为它直接化解了"切大切小都不对"的两难:小块检索、大块喂入。检索时用语义聚焦的小块去匹配(精准命中),命中后不把小块给模型,而是返回它所在的父块(保留完整上下文)。面试里这是"chunk 怎么切"这道题的加分答法——它说明你理解了"检索粒度"和"生成粒度"可以解耦。

陷阱

用一套定长切分套全部文档类型,是最常见的失败模式。表格被按 token 拦腰切断后,行列对应关系彻底丢失;代码块被切断后语法不完整;合同条款被切散后"本条所称……"失去指代对象。表格、代码、合同各自需要专门的切分策略(如表格整块保留、代码按函数 / 类边界切),不能一刀切。

2.5混合检索 + RRF + Rerank

混合检索 = 同时用关键词(BM25)和语义(向量)两路召回再融合;rerank = 用更重的模型对融合结果精排,把真正相关的顶到最前。

为什么需要它

纯向量检索有一类系统性盲区:它擅长"意思像",却弱于"字要对"。专有名词、产品型号、错误码、合同编号、人名——这些精确匹配需求,向量会把"ERR-5012"和"ERR-5013"编码得很近,于是召回错号;向量还表示不了否定——"无糖"和"含糖"语义相近,按纯向量检索"无糖饮料"就会把含糖的也一并召回,语义相似度天生分不清"是"与"不是"。BM25 这类关键词检索恰好相反,对精确词命中极强。两者短板互补,所以生产 RAG 几乎都上混合检索,而不是赌单一通道。

问题随之而来:BM25 给的是词频打分、向量给的是余弦相似度,两套分数量纲完全不同,没法直接相加。解法是 RRF(Reciprocal Rank Fusion,倒数排名融合)——不看分数绝对值,只看排名:一个文档在某一路里排第 rank 位,就贡献 1/(k+rank) 分,两路相加即融合得分,k 通常取 60。因为只用排名,天然规避了跨尺度归一化的难题。

rrf_fusion.py(演示用,未在本机执行) Python
# RRF:按排名融合两路召回,无需跨尺度归一化
def rrf_fuse(bm25_ids: list[str], vec_ids: list[str], k: int = 60) -> list[str]:
    scores: dict[str, float] = {}
    for ranked in (bm25_ids, vec_ids):          # 两路召回结果,各自已按相关性排序
        for rank, doc_id in enumerate(ranked, start=1):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
    # 分数高者在前:在两路都靠前的文档自然胜出
    return sorted(scores, key=scores.get, reverse=True)

bm25 = ["d3", "d1", "d7"]   # 关键词路:d3 精确命中排第一
vec  = ["d1", "d5", "d3"]   # 语义路:d1 语义最近排第一
print(rrf_fuse(bm25, vec))  # ['d1', 'd3', 'd5', 'd7'] —— d1/d3 两路都靠前,融合后领先

融合只解决了"两路结果怎么合并排名",没解决"排名够不够准"。召回阶段用的是 bi-encoder:query 和 doc 各自独立编码成向量再比距离——快(向量可预先算好、查询时只比距离),但 query 和 doc 之间没有交互,粗。rerank 换上 cross-encoder:把 query 和每个候选 doc 拼在一起送进模型,让两者在注意力层充分交互后直接输出一个相关性分——准,但慢(每个候选都要跑一次模型,无法预计算)。

表 2.5 · bi-encoder vs cross-encoder——为什么两步不能合并
维度bi-encoder(召回)cross-encoder(精排)
编码方式query / doc 分开编码query + doc 拼一起编码,全交互
速度快,向量可预计算(~5ms 召回 top-100)慢,每对都要现算(~50ms 重排 top-10)
精度高召回、低精度(粗筛)低召回、高精度(精排)
定位从百万级里廉价捞出 top-100把 top-100 精排成 top-10——准度由它兜底

所以标准流水线是两步分工:bi-encoder 在全量里廉价召回 top-100(求广、高召回),cross-encoder 只在这 100 个里精排出 top-10(求准、高精度)。合并不可行——让 cross-encoder 直接对百万级文档逐一打分,延迟会爆炸。常用 reranker 有 bge-reranker-v2-m3(开源、中文友好)和 Cohere Rerank(API)。

带来的代价:rerank 是一笔实打实的延迟和算力账。cross-encoder 每个候选都要现跑一次模型、无法预计算,重排候选数(top-100 还是 top-50)直接换算成延迟。权衡的旋钮就是这个候选数:调大召回更全但精排更慢,调小延迟低但容易把相关文档挡在重排门外。自建 reranker 还要额外占一块 GPU,用 Cohere 这类 API 则是按调用计费——这条延迟-成本-召回的取舍正是"rerank 怎么权衡"这道追问的落点。

query BM25(关键词) bi-encoder · 求广 向量(语义) bi-encoder · 求广 top-100 top-100 RRF 按排名融合 cross-encoder 精排 · 求准 → top-k
图 2.2一个 query 同时进 BM25 和向量两路,RRF 按排名融合,cross-encoder 精排出 top-k。注意:召回靠 bi-encoder 求广(高召回),精排靠 cross-encoder 求准(高精度),两步分工不可合并——把它们合成一步,要么慢到不可用、要么准度塌方。

2.6高级 RAG:查询改写 / HyDE / 上下文检索

高级 RAG 的共同思路:在"查询"或"文档"进入检索前,先用 LLM 加工一道,让两边在语义空间里更容易对上。

为什么需要它

基础 RAG 默认"用户问得清楚、文档块自带足够上下文",现实常常两头都不成立:用户问"它多少钱"(指代不清、口语化),文档块是从长文里切出来的孤立片段(缺背景)。query 和 doc 在语义空间里离得远,再好的检索器也对不上。高级技巧就是在检索前修补这道鸿沟。

三类主流手段:

  • 查询改写 / multi-query:让 LLM 把口语化、有指代的查询改写清楚,或扩展成多条不同措辞的查询分别检索再合并,提高召回覆盖面。
  • HyDE(Hypothetical Document Embeddings):让 LLM 先假想一段"理想答案",再拿这段假设答案去检索。原理是"答案和答案"在语义空间里比"问题和答案"更接近。代价/风险很明确——假设答案本身会漂移(模型编的方向偏了),反而把检索带偏,要谨慎用在模型有一定先验知识的领域。
  • Anthropic Contextual Retrieval:给每个 chunk 前置一段 50–100 token 的、由 LLM 生成的上下文说明("本段出自 X 文档关于 Y 的章节,讲的是……"),再对这个"加了上下文的 chunk"做 embedding 和 BM25。这直接补上了"切分后块缺背景"的问题。
洞察 · Contextual Retrieval 的量化收益

Anthropic 公布的数据很能说明问题:相比基础 RAG,contextual embedding 把检索失败率降低 35%,叠加 contextual BM25 降 49%,再叠加 rerank 降 67%。注意这条收益链和本章前几节是同一套组合拳——上下文增强 + 混合检索 + rerank 协同。而"给每个 chunk 都调一次 LLM 生成上下文"听起来很贵,但配合 prompt 缓存(文档主体只算一次),成本约 \$1.02 / 百万 token,工程上可接受。

想一想

HyDE 让模型先编一个假设答案去检索——这不正是第一章说的"幻觉"吗?为什么这里反而有用?

展开答案(先停 10 秒再点)

关键在于假设答案不会进入最终回答,它只用来生成一个"更像答案"的检索向量。即便假设答案有事实错误,只要它在语义空间里把查询推向了正确的文档区域,检索就受益;真正的答案仍由检索回来的真实文档生成。所以 HyDE 是"用一次受控的幻觉换更好的检索方向"。但风险也在此:若假设答案整体跑偏(模型对该领域毫无先验),就会把检索带到错误区域——这就是为什么它适合模型有基础知识、不适合完全陌生的私域。

2.7RAG 评估(RAGAS 三元组)

RAG 评估分两侧:检索侧看"有没有捞对、捞全",生成侧用三元组看"答得对不对、有没有支撑、切不切题"。

为什么需要它

"答得不对"是一句没法回归的主观判断。要让 RAG 能像代码一样持续迭代而不退化,必须把质量拆成可计算的指标,放进 CI 里跑——改了 chunk 大小、换了 reranker,指标涨了还是跌了,一目了然。没有评估,每次调参都是凭感觉,且无法防止"修好一类问题又引入另一类"的回归。

评估顺着图 2.1 的"两段链路"分两侧。检索侧衡量召回质量:命中率、MRR(平均倒数排名,相关文档排得越靠前越高)、Recall@k(前 k 个里捞回了多少相关文档)。生成侧是面试高频的 RAGAS 三元组,它把"答得好不好"拆成三个正交的维度:

表 2.6 · RAGAS 评估指标——两侧四个维度
侧 / 指标问的是什么低分说明哪一环出了问题
检索 · context precision召回的相关 chunk 有没有排在前面融合 / rerank 排序差
检索 · context recall该召回的相关信息是不是都召回了分块 / 召回漏了,或 k 太小
生成 · 忠实度 faithfulness回答里的每个论断是否都有上下文支撑检索对了但模型在编——生成的锅
生成 · 答案相关性 answer relevancy回答是否切题、没跑偏没啰嗦模型答非所问 / 答了无关内容

这套三元组(上下文相关性 / 忠实度 / 答案相关性)的实际算法多用 LLM-as-judge——让一个评判模型按维度打分,RAGAS 框架把这套流程封装好了。它的杀手锏是诊断定位:忠实度低和答案相关性低指向完全不同的修复方向。

洞察 · 用三元组定位故障

三个维度组合起来就是一张排障表:忠实度低 + 上下文相关性高 = 资料捞对了、模型却没用它(生成的锅,去收紧 prompt 约束、强制引用);上下文相关性低 = 资料就没捞对(检索的锅,去查分块 / 混合检索 / rerank);答案相关性低但忠实度高 = 答得有依据但答偏了题(query 理解或拼 prompt 的问题)。这正好把图 2.1 那句"先分清是没检索到还是没用好"量化成了可读数的信号。

§面试题检索

先合上教程,把你能想到的答案在脑子里过一遍或写下来。写完再点开对照——直接点开等于把这一章当再读一遍。每题标注是否高频。

  1. [高频] RAG 是什么?相比微调解决了什么问题?
    参考答案 + 追问

    RAG = 生成前先检索外部知识、把相关片段拼进 prompt 再作答。相比微调,它补的是外部 / 私域 / 实时知识、降低幻觉、答案可溯源、且改知识不用重训。分工:微调改风格 / 格式 / 能力(行为),RAG 改知识(内容)。

    追问:何时微调、何时 RAG、何时都要? 知识常变 / 要溯源 → RAG;改风格 / 学新任务能力 → 微调;复杂系统两者叠加——微调锁"严格按引用作答"的行为,RAG 供实时知识。补一刀规模启发式(Anthropic Contextual Retrieval):语料能放进上下文窗口时(经验值约 20 万 token / 约 500 页),直接全塞 + prompt 缓存即可,无需 RAG;超过这个规模、或要实时更新 / 按权限过滤 / 可溯源,才上 RAG。

  2. [高频] 一条完整的 RAG 链路有哪些环节?每步的决策点是什么?
    参考答案 + 追问

    离线:解析 → 分块 → embedding → 入库(向量 + BM25 双写)。在线:查询改写 → 混合召回 → rerank → 拼 prompt → 生成 → 带引用回答。决策点见表 2.2(切多大、选哪个 embedding、索引用 HNSW 还是 IVF、要不要改写 / 重排、怎么约束引用)。

    追问:哪步最易出问题?怎么定位是召回还是生成的锅? 检索和生成是两段独立环节。用 RAGAS:上下文相关性低 = 检索的锅(查分块 / 混合检索 / rerank);上下文相关性高但忠实度低 = 生成的锅(资料对了模型没用,收紧 prompt 约束、强制引用)。

  3. [高频] 纯向量检索有什么问题?为什么要混合检索?两路怎么融合?
    参考答案 + 追问

    纯向量擅长语义相似,弱于精确匹配——专有名词、产品型号、错误码、合同编号会被编码得很近导致召回错号。BM25 这类关键词检索补这块短板,故用混合检索。融合用 RRF:文档在某路排第 rank 位贡献 1/(k+rank) 分(k≈60),按排名相加,无需跨尺度归一化,再交给 rerank。

    追问:RRF 的 k 起什么作用? k 控制高排名文档的权重衰减:k 越小,靠前名次的优势越被放大(头部主导);k 越大,各名次贡献越平均(长尾更有机会)。k=60 是经验上较稳的折中。

  4. [高频] Rerank 解决什么?为什么检索后还要重排?
    参考答案 + 追问

    召回用 bi-encoder(query / doc 分开编码)快但粗——高召回、低精度;rerank 用 cross-encoder 把 query + doc 拼在一起编码,让两者充分交互后直接打相关性分——准但慢。流水线:bi-encoder 召回 top-100(~5ms)→ cross-encoder 精排 top-10(~50ms),准度由精排兜底。

    追问:常用 reranker?延迟成本怎么权衡? bge-reranker-v2-m3(开源中文友好)、Cohere Rerank(API)。cross-encoder 每个候选都要现算无法预计算,所以只重排召回的少量候选(top-100→top-10),通过控制重排候选数把延迟压在可接受范围。

  5. [高频] Chunk 怎么切?切大切小各有什么问题?
    参考答案 + 追问

    太小丢上下文(脱离背景、指代失效),太大稀释语义 + 挤占上下文预算。典型 256–512 token、10–20% overlap。策略上 recursive(递归分层)是务实默认,semantic 按语义边界切更自然但慢,最优解常是父子块:小块检索、大块喂入——检索粒度和生成粒度解耦。

    追问:表格 / 代码 / 合同策略不同? 是。表格要整块保留(拦腰切断毁掉行列对应),代码按函数 / 类边界切(保语法完整),合同按条款切并保留指代上下文。用一套定长切分套所有类型是典型失败模式。

  6. [高频] Embedding 模型怎么选?中文选什么?
    参考答案 + 追问

    看 MTEB 榜单(任务匹配度)、权衡维度 vs 精度 / 存储、考虑领域适配。中文开源首选 bge-m3,备选 m3e / gte。BGE-M3 一个模型同出 dense + sparse + multi-vector,天然适配混合检索。

    追问:维度越高越好吗?换模型后存量向量怎么办? 不是越高越好——高维带来存储和检索成本,3-large 用 Matryoshka 截到 1024 维仅差 1–2%。换 embedding 模型后存量向量必须全量重新编码、重建索引,因为不同模型是各自独立的向量空间,不能混用。

  7. [高频] 怎么评估一个 RAG 系统?
    参考答案 + 追问

    分两侧。检索侧:命中率 / MRR / Recall@k。生成侧:RAGAS 三元组——上下文相关性、忠实度(faithfulness)、答案相关性。算法多用 LLM-as-judge,RAGAS 框架封装好了。

    追问:忠实度低 vs 相关性低分别怎么排查?怎么搭 CI eval 防回归? 忠实度低 = 模型没用好已召回的资料(收紧 prompt、强制引用);上下文相关性低 = 检索没捞对(查分块 / 混合检索 / rerank)。CI eval:维护一个带标准答案的评测集,每次改动跑 RAGAS,指标低于阈值就拦住合并——把"答得好不好"变成可回归的数字。

  8. 向量库怎么选?Milvus / FAISS / Qdrant / pgvector 各适合什么?
    参考答案 + 追问

    FAISS:库 / 单机、无独立服务进程,适合实验和嵌入式;Milvus / Qdrant:分布式 + 持久化,适合生产大规模;pgvector:复用已有 Postgres,少加一个组件。按规模 / 运维成本 / 团队栈选。

    追问:亿级向量怎么扛?HNSW vs IVF? HNSW(图)召回高、亚毫秒、写入不需重建,但内存 2–5×,是中小规模 / 频繁更新的默认;IVF(聚类)省内存适合超大静态集,IVFPQ(加量化)撑十亿级但精度有损。亿级以上要么用 IVFPQ 压、要么分片到多机,不能无脑全上 HNSW。

  9. [系统设计] 给 10 万用户设计一个企业知识库 RAG,怎么架构?
    参考答案 + 追问

    分层答:数据层——文档解析(表格 / 代码专门策略)+ 向量库 + BM25 双写;检索层——混合检索 + rerank;生成层——幻觉约束 + 强制引用;工程层——结果缓存、异步增量更新、可观测监控;安全层——文档级权限隔离、Prompt 注入防御。

    追问:文档实时更新怎么增量索引?多租户权限隔离?热点缓存? 增量索引:HNSW 支持写入不重建,文档变更触发对应 chunk 的重新 embedding + upsert;权限隔离:检索时按用户可见范围过滤(metadata filter / 分库 / 行级权限),不能让 A 检索到 B 的文档;热点缓存:高频 query 缓存检索结果甚至生成结果,配 prompt 缓存降首 token 成本。

亲手画一张图

合上教程,在纸上把图 2.1 的 RAG 链路默画出来——只画"离线索引"和"在线查询"两行、中间一个共享存储就行。画完回到 §2.2 对照:你画的图里,"检索"和"生成"有没有被清楚地分成两段?排障时你能不能在自己画的图上指出"该去调哪一半"?

进阶挑战 · 刚好够不着

你的 RAG 上线后用户反馈"答得不对"。给出一个能区分"召回没找到" vs "找到了但生成没用好"的诊断流程。

不要直接调参。设计一个分步流程,每一步能把故障域缩小一半,最终把锅落到"检索侧"还是"生成侧"。

提示(卡住再展开)

关键动作:把"检索回来的上下文"和"最终回答"分别拎出来看。第一步——拿出这次 query 召回的 top-k chunk,人工或用 context precision / recall 判断:正确答案所需的信息在不在里面? 不在 → 检索侧(去查分块 / 混合检索 / rerank / k 值)。在里面 → 第二步:看模型的回答有没有用上这些 chunk(忠实度 faithfulness)——没用上、自己编了 → 生成侧(收紧 prompt、强制按引用作答)。一次"召回内容 vs 回答"的对照,就把故障域切成了两半。这正是 RAGAS 三元组的实战用法。