直觉:把"开卷考试"接到大模型上
大模型的知识冻结在训练时刻的权重里,更新一次代价高昂,而且它无法凭空知道你公司内网的文档。RAG(Retrieval-Augmented Generation)的核心思路非常朴素:别让模型靠记忆答题,给它一份相关材料,让它开卷考试。
但"开卷"这件事在工程上远比听起来复杂。它不是简单地把文档塞进 prompt,而是一条完整的数据流水线:切分、向量化、检索、重排、拼接、生成。每一环都有自己的失败模式。下面我们逐段拆开。
机制:一条完整的 RAG 数据流
整条链路分**离线(建库)和在线(查询)**两个阶段。
离线阶段:
1 | 原始文档 → 清洗 → 切分(chunking) → embedding 模型 → 向量 + 原文 → 写入向量库 |
在线阶段:
1 | 用户 query → embedding → 向量检索(召回 top-k) → 重排(rerank top-n) → 拼 prompt → LLM 生成 |
切分(chunking):被低估的关键一步
切分粒度直接决定召回质量。chunk 太大,单个块里混入大量无关内容,稀释了语义向量,也浪费 LLM 的 context;太小,又会切断上下文,导致一个完整论述被劈成两半,检索时只命中一半。
常见策略:
- 固定窗口 + overlap:每 chunk 约 256~512 token,相邻 chunk 重叠 10%~20%,避免边界处语义断裂。
- 按结构切分:沿 Markdown 标题、段落、代码块边界切,保持语义完整性。
- 父子分块:用小 chunk 做检索(语义精准),命中后返回它所属的大 chunk(上下文完整)。这是实践中性价比很高的技巧。
向量化与检索:相似度的数学
embedding 模型把一段文本映射成一个 维向量(典型 在数百到数千量级)。检索时计算 query 向量 与每个文档向量 的相似度,最常用余弦相似度:
若向量已做 L2 归一化(),余弦相似度退化为内积,计算更快。暴力检索是 ,当 达到百万、千万级时无法接受,因此生产中用 ANN(近似最近邻) 索引,例如 HNSW(分层可导航小世界图)。HNSW 把搜索复杂度从 降到约 ,代价是召回率略低于 100%("近似"二字的来源)和较高的内存占用——图结构本身要存边。
重排(rerank):召回与精排的分工
为什么召回之后还要重排?因为向量检索用的是双塔(bi-encoder):query 和 document 各自独立编码成向量,再算相似度。双塔快,可离线预算文档向量,但 query 和 document 之间没有交互,精度有上限。
rerank 用的是交叉编码器(cross-encoder):把 [query, document] 拼在一起送进模型,输出一个相关性打分。两者的 token 在每一层 attention 里充分交互,精度高得多,但无法预计算——每个 query-doc 对都要现算,所以慢。
工程上的标准做法是两级流水:
1 | bi-encoder 召回 top-50 (快,覆盖广) |
用便宜的召回换覆盖率,用昂贵的精排换准确率,把 cross-encoder 的计算量限制在小集合上。
代码:一个最小可用的 RAG 骨架
1 | def rag_answer(query, vector_db, reranker, llm, k=50, n=5): |
注意第 3 步那句 prompt 约束——它是抑制幻觉的关键防线,明确告诉模型"没有依据就别编"。
工程权衡与踩坑
1. 召回不到 ≠ 模型不行。 RAG 的失败大多在检索端而非生成端。如果答案根本没被召回,再强的 LLM 也无能为力。调试 RAG 时第一步永远是:把召回的 chunk 打印出来人工看,确认答案是否在里面。
2. 纯向量检索打不过关键词。 向量擅长语义相似,但对精确匹配(产品型号、错误码、人名)反而不如传统 BM25。生产系统普遍用混合检索:向量召回 + BM25 召回,再融合。常见融合方法 RRF(Reciprocal Rank Fusion)只看排名不看分数,避免两套打分量纲不一致的问题:
其中 是平滑常数(常取 60), 是文档 在第 个检索器里的排名。
3. context 不是越多越好。 把召回的 50 个 chunk 全塞进去,既烧 token 又触发"中间遗忘"(lost in the middle)——模型对长上下文里居中位置的信息利用率明显下降。所以才要重排后只取 top-n,把最相关的放在头尾。
4. embedding 模型和 query 必须同源。 建库用的 embedding 模型一旦更换,整个向量库必须重建,否则 query 向量和库里的文档向量不在同一语义空间,检索结果是噪声。
5. 切分时机决定一切。 表格、代码、公式被粗暴切断是常见事故。建库前务必针对你的文档类型定制 chunking,别迷信"一刀切 512 token"。
小结
RAG 不是"给 LLM 喂文档"这么一句话,而是一条召回-精排-生成的流水线。它的工程精髓在于用快而粗的双塔召回保证覆盖率,用慢而准的交叉编码器精排保证准确率,再用混合检索弥补语义向量在精确匹配上的短板。当你的 RAG 效果不佳时,按"切分 → 召回 → 重排 → prompt"的顺序逐段排查,比盲目换更大的生成模型有效得多。