RAG(检索增强生成)概述

1 阅读14分钟

一、什么是 RAG

RAG(Retrieval Augmented Generation,检索增强生成)是一种将外部知识检索与大语言模型生成相结合的技术架构。它解决了 LLM 的两个核心痛点:知识过时(训练数据有截止日期)和幻觉(对不确定的问题编造答案)。核心思路是在 LLM 生成答案之前,先从知识库中检索出与用户问题相关的文档片段,将其注入到 Prompt 中,让模型"有据可查"地回答问题。

完整 RAG Pipeline 的数据流分为两个阶段:

离线阶段(Indexing)负责建库:文档加载 → 文本分块 → 向量嵌入 → 存入向量数据库。这个过程只需要执行一次(或在知识库更新时重新执行),产出的是一个可以被高效检索的向量索引。

在线阶段(Query)负责问答:用户提问 → 问题向量化 → 向量相似度检索 → 构建带上下文的 Prompt → LLM 生成答案。每次用户提问都会实时走这个流程,延迟主要来自向量检索和 LLM 推理两步。


二、Embedding:语义空间的基石

Embedding 是 RAG 的数学基础。它把自然语言文本"翻译"成一组高维浮点数向量(如 384 维、1024 维、1536 维),使得语义相似的文本在向量空间中距离更近,语义无关的文本则相互远离。

2.1 核心概念

一段文本经过 Embedding 模型后变成一个固定长度的浮点数组。比如 "今天天气真好" 可能被编码为 [0.12, -0.45, 0.78, ..., 0.33] 这样一个 384 维的向量。向量的每一维代表文本在某个"语义方向"上的投影,所有维度共同编码了文本的完整语义信息。这不是简单的关键词匹配,而是真正的语义理解——"我喜欢吃苹果"和"苹果是我最爱的水果"即使用词不同,向量也会很接近。

2.2 相似度度量

衡量两个向量"有多像"的方式有三种。余弦相似度只看向量方向(忽略长度),范围 [-1, 1],1 表示完全相同方向,0 表示无关,-1 表示完全相反,是最通用的选择。L2 距离是欧氏空间中两点的直线距离,数值越小越相似,ChromaDB 的默认度量就是它。点积在向量已归一化时等价于余弦相似度,某些模型(如 OpenAI text-embedding-3)输出的就是归一化向量,此时用点积最高效。

2.3 模型选择

项目中实验对比了两个模型。all-MiniLM-L6-v2 输出 384 维,轻量且完全本地运行,无需额外服务,但中文短文本语义区分度较差。Qwen3-Embedding-0.6B 输出 1024 维,中文优化,短文本区分度好,支持 32K tokens 长上下文,但需要 Ollama 服务运行,模型体积 639MB。实际项目中需要注意:一旦选定 Embedding 模型入库,查询时必须使用同一个模型,不能混用。


三、向量数据库:ChromaDB 实战

向量数据库是 RAG 系统的"存储引擎",负责高效存储向量并支持百万级数据的相似度检索。相比手写暴力 KNN(O(n) 逐个比较),向量数据库使用 HNSW 等 ANN(近似最近邻)算法,将查询复杂度降低到 O(log n)。

3.1 核心概念

ChromaDB 的数据模型围绕 Collection(集合)展开,类比关系数据库中的"表"。每条记录包含四个部分:Document 是原始文本(方便调试和展示),Embedding 是文本的向量表示(可自动生成也可手动传入),Metadata 是附加属性(用于精确过滤,如来源文件、话题标签、难度等级),ID 是唯一标识符。

3.2 查询能力

ChromaDB 的查询分为两类。语义查询通过 queryTexts 参数传入自然语言问题,系统自动计算 embedding 后做相似度搜索。精确过滤通过 where 参数对 metadata 做条件筛选(支持 eqeq、ne、gtgt、in、and等操作符),或者通过whereDocument参数对文档内容做关键词过滤(and 等操作符),或者通过 whereDocument 参数对文档内容做关键词过滤(contains)。两者可以组合使用:先用 metadata 缩小范围,再用向量搜索排序。这就是"混合搜索"的雏形——语义理解 + 精确条件的结合。

3.3 距离配置

创建 Collection 时通过 metadata: { "hnsw:space": "cosine" } 指定距离度量。cosine 是余弦距离(推荐),l2 是欧氏距离,ip 是内积。注意 ChromaDB 返回的 distance 是余弦距离(= 1 - 余弦相似度),所以 distance 越小代表越相关。


四、文档加载与分块策略

RAG 的第一步是把原始文件变成可处理的结构化文本。这分为两个环节:加载器(Loader)负责读取文件并做初步拆分,分块器(Chunker)负责将文本进一步切分为适合 embedding 的小片段。

4.1 文档加载

加载器的核心输出是统一的 Document 数据结构:{ content: 文本内容, metadata: 元信息 }。不同格式有不同的加载策略。纯文本按空行(双换行)分段。Markdown 按标题层级拆分章节——这是技术文档 RAG 的最佳实践,因为标题天然就是语义边界,作者写作时已经按话题组织了内容。代码文件可以按函数/类切分(通过 AST 解析)。CSV 则每行一个 Document。

metadata 的设计非常重要,它记录了来源文件路径、章节标题、行号范围等信息,使得检索结果可以追溯到原始位置,方便用户验证和进一步阅读。

4.2 为什么需要分块

三个原因决定了必须分块。Embedding 模型有 token 限制(通常 512 tokens),超长文本无法编码。长文本的 embedding 会"稀释"语义——什么都沾点但不精确。检索粒度太大时,返回的大段文本中大部分内容与问题无关,浪费 LLM 的 token 窗口。

分块的目标是找到平衡点:每块足够短让 embedding 能精确编码语义,又足够完整不要把一句话切断,块间可以有适当重叠避免边界处信息丢失。

4.3 三种分块策略

固定长度分块是最简单的方案:按字符数切分,可配置 overlap(重叠区)。优点是块大小完全可控,缺点是完全不顾语义——可能在句子中间截断。

递归字符分块(模拟 LangChain 的 RecursiveCharacterTextSplitter)是工程中最常用的方案。核心思想是按优先级尝试不同分隔符:先按段落(\n\n)分,段落太长则按句号分,句子太长则按逗号分,还是太长则按字符强制切。这保证了尽可能在自然断句处切分,语义完整性好于固定长度。

语义分块(Semantic Chunking)是最智能的方案。它真正调用 Embedding 模型来决定在哪里切分:先将文本按句子切分,对每个句子计算向量,然后计算相邻句子的余弦相似度,在相似度出现"断崖式下降"的位置切分——这就是话题转换点。与前两种策略的本质区别是:切分位置由模型的语义理解驱动,而非字符数或分隔符驱动。breakpoint 策略有三种:百分位法(低于 P-th 百分位时切)、标准差法(低于均值减 k 倍标准差时切)、固定阈值法。代价是需要额外的 embedding 计算开销。

4.4 参数选择指南

chunk_size 太小(<100 字符)时每块缺乏上下文,embedding 信息量不足,检索结果碎片化。太大(>1000 字符)时块内包含多个话题,embedding 语义稀释,匹配精度下降。推荐范围是 200-500 字符(中文),overlap 取 chunk_size 的 10%-20%。不同文档类型适合不同策略:技术文档/代码用较小的 chunk(200-300)追求精确匹配,叙事文本/文章用较大的 chunk(400-600)保持上下文,FAQ/对话则按 Q&A 对切分。


五、完整 RAG Pipeline

将上述组件串联起来,就是端到端的 RAG 系统。

5.1 离线索引

加载文档 → 分块 → 批量调用 Embedding 模型 → 将向量和原始文本一起存入 ChromaDB。关键实现细节:批量 embedding 比逐条调用快得多;metadata 中保存来源文件和章节信息以供后续溯源;ChromaDB 的 add 方法会自动调用 embeddingFunction 生成向量。

5.2 在线问答

用户提问 → 将问题转为向量(使用同一个 embedding 模型)→ 在 ChromaDB 中做 Top-K 相似度搜索 → 将检索到的文档片段拼接为上下文 → 构建 System Prompt 明确告知 LLM "基于以下参考资料回答" → 调用 LLM 生成答案。

Prompt 设计的关键原则:明确告知模型参考资料的边界(用分隔符包裹),要求模型只基于资料回答,对不确定的问题承认不知道,并标注信息来源。temperature 建议设为 0.1-0.3 以减少幻觉。

5.3 带来源引用

生产级 RAG 系统需要让答案可追溯。实现方式是在 Prompt 中给每段参考资料标编号([1]、[2]),要求 LLM 在回答中使用方括号标注引用来源。同时输出结构化结果:{ answer, sources[], confidence, needsMoreInfo },前端可以展示答案 + 可折叠的来源列表 + 置信度标签。置信度通过检索结果的相似度分数来评估——如果最相关的文档相似度都很低,说明知识库中可能没有相关信息。


六、混合检索:向量 + BM25

纯语义搜索有一个致命弱点:对精确关键词(如 API 名称、错误代码、版本号)的匹配较弱。如果用户问 "TypeScript 4.9 satisfies",语义搜索可能返回"类型检查"相关但不包含 satisfies 关键词的文档。反过来,纯关键词搜索不理解语义——用户问"怎么让函数参数类型灵活"时,搜不到"泛型"相关文档。混合搜索就是两者互补。

6.1 BM25 关键词搜索

BM25(Best Match 25)是信息检索领域的经典算法,也是 Elasticsearch 的默认排序算法。名字中的 25 代表它是 Okapi 信息检索系统的第 25 次迭代。核心思想是根据查询中每个词在文档中的出现频率(TF)和稀有程度(IDF)打分。两个关键参数:k1(1.2-2.0)控制词频饱和度,b(0.75)控制文档长度归一化。中文场景需要先分词(生产环境用 jieba),项目中用 bigram 作为简化方案。

6.2 RRF 融合策略

Reciprocal Rank Fusion(RRF)是合并多路检索结果的标准方法。公式:RRF(d) = Σ 1/(k + rank_i(d))。它只使用排名信息,不使用原始分数——这是它最大的优势,因为 BM25 分数和余弦相似度的量纲完全不同,直接加权混合需要复杂的归一化。RRF 的思想本质是一个"投票系统":每路检索为每个文档投一票,排名越靠前票数越高,最后统计总票数排序。k=60 是经验值,起到平滑作用,防止第 1 名和第 2 名的分数差距过大。

相比直接的加权混合(如 0.7×向量分数 + 0.3×BM25 分数),RRF 在生产环境中更受推荐,因为它不需要调归一化参数,结果稳定且鲁棒。


七、Reranker:两阶段检索精排

初始检索(无论是向量搜索还是 BM25)的排序都不够精确。原因在于 Embedding 检索是 Bi-Encoder 架构——query 和 document 被独立编码后再做相似度比较,无法捕捉两者之间细微的交互关系。Reranker 使用 Cross-Encoder 架构,将 query 和 document 一起输入模型做联合编码,精度更高但速度慢(需要逐对比较)。

7.1 两阶段架构

Stage 1(Recall):从大量文档中快速筛出候选集(top 20-50),使用向量搜索或混合搜索,追求召回率。Stage 2(Rerank):对候选集精确排序,选出最终的 top 3-5,使用 Cross-Encoder 或 LLM 评分,追求精确率。

7.2 三种 Rerank 方法

LLM Rerank 让大模型对每个 query-document 对逐个打分(0-10 分),优点是直观且能给出理由,缺点是贵和慢。Cross-Encoder 模型(如 Cohere Rerank、bge-reranker-v2、jina-reranker)是专门训练的小模型,兼顾精度和速度,生产环境首选。规则 Rerank 基于启发式规则(关键词命中加分、开头命中加分、长度适中加分),无 LLM 开销,适合简单场景。

7.3 完整检索链路

在一个生产级 RAG 系统中,检索链路是:用户问题 → 混合检索(向量 + BM25)获得 Top 20 候选 → Reranker 重排获得 Top 5 精选 → 拼入 Prompt → LLM 生成带引用的答案。


八、工程实践要点

8.1 数据质量是根基

RAG 做得好不好,数据质量的影响远大于算法优化。脏数据、格式混乱的文档、错误的 metadata 会直接导致检索结果垃圾。投入 80% 的精力在数据清洗和结构化上比调参有价值得多。

8.2 分块策略需要针对性

不同类型的文档应该用不同的分块策略。Markdown 文档用标题层级切分最自然,代码用函数/类切分,表格和列表应该保持完整不要拆开,FAQ 按 Q&A 对作为一个整体。给每个 chunk 附加丰富的元信息(来源文件、章节标题、页码、时间戳)是后续做精确过滤和溯源的基础。

8.3 检索质量的优化路径

从简单到复杂的优化路径是:纯向量搜索 → 加 metadata filter → 加 BM25 混合搜索 → 加 Reranker 精排 → 加查询改写(Query Rewriting)→ 加多轮追问(Agentic RAG)。不要一上来就搞最复杂的方案,先用最简单的看效果,有针对性地优化瓶颈环节。

8.4 评估指标

RAG 系统的评估需要关注三个维度:Faithfulness(答案是否忠实于检索到的内容,没有编造),Relevancy(检索到的内容是否与问题相关),Answer Correctness(最终答案是否正确解决了用户的问题)。没有评估体系的 RAG 优化是盲人摸象。

8.5 生产环境技术选型

向量数据库方面,小规模用 ChromaDB 够用,大规模可选 Milvus(支持 Sparse-BM25)、Qdrant(支持 sparse vector)、Weaviate(内置 hybrid search)。BM25 方面,Elasticsearch 8.x 原生支持 kNN + BM25 混合,Milvus 2.5 内置 Sparse-BM25,都是生产就绪的方案。Embedding 模型方面,中文场景推荐 Qwen3-Embedding 或 BGE 系列,需要注意入库和查询必须使用同一个模型。


九、技术栈总览

本项目 RAG 模块使用的技术栈如下:

组件选型说明
Embedding 模型Qwen3-Embedding-0.6B (Ollama)1024 维,中文优化
对比模型all-MiniLM-L6-v2 (ChromaDB 内置)384 维,轻量
向量数据库ChromaDB本地部署,HNSW 索引
LLMDeepSeek Chat (API)生成答案
开发语言TypeScript (tsx)类型安全
运行时Node.js + Ollama本地全栈

十、学习路径回顾

整个 RAG 模块按 Day 8-11 分四天完成:

Day 8 打下了 Embedding 基础:理解了文本到向量的映射原理,手写余弦相似度验证"语义相近向量更近",实现了手写 KNN 搜索模拟向量数据库原理,对比了 MiniLM 和 Qwen3-Embedding 两个模型在中文场景的差异,最后用 ChromaDB 替代手写搜索,体验了 metadata filter 和多集合管理。

Day 9 聚焦文档处理:实现了 Markdown 感知型加载器(按标题层级拆分),实现了三种分块策略并直观对比效果差异,特别是语义分块的真正实现——调用 Embedding 计算相邻句子相似度并在断崖处切分。

Day 10 串联完整 Pipeline:构建了 DeepSeek 知识库作为测试场景,实现了离线索引 + 在线问答的完整流程,做了有 RAG vs 无 RAG 的对比实验,以及带来源引用的结构化问答。

Day 11 进入高级优化:实现了 BM25 关键词搜索,通过实验直观展示了语义搜索和关键词搜索各自的擅长场景,用 RRF 融合策略将两者结合为混合搜索,最后实现了 LLM Reranker 和规则 Reranker 的两阶段精排。

至此,一个功能完整的 RAG 系统从底层原理到工程实现全部覆盖。