1. 什么是 RAG?

1.1 大模型的「阿克琉斯之踵」

在前面的章节中,大模型本质上是基于自身的训练语料和概率统计,结合用户的 prompt 来预测下一个 token(词或符号)的出现概率。这就意味着:模型输出的文本是"语言上最合理"的,但未必是"事实上的正确"。

大模型存在三个核心局限:

问题 表现
幻觉(Hallucination) 模型会自信地编造不存在的事实、数据、引用
知识滞后性 训练数据有截止日期,无法获取最新信息
知识局限性 对垂直领域、私有数据缺乏覆盖

1.2 RAG 是什么?

RAG(Retrieval-Augmented Generation,检索增强生成) 是一种让大模型在生成回答前,先从外部知识库中检索相关信息,然后结合这些检索到的内容来生成答案的技术。

核心理念:检索 + 生成——模型不再"凭自身经验瞎说",而是"查完资料再说"。

1.3 RAG vs 微调

维度 RAG 微调(Fine-tuning)
知识更新方式 实时检索,知识库更新即可 需要重新训练
适用场景 时效性、事实性问答 风格、格式、行为模式调整
可解释性 可溯源到具体文档 黑盒,难以解释
成本 低(无需 GPU 训练) 高(需要算力)
幻觉控制 较好(有外部知识约束) 仍然可能产生幻觉

2. RAG 的使用场景

RAG 非常适合用于那些需要事实性、专业性、时效性知识支撑的场景,它让大模型具备"知识更新"和"领域专精"的能力。

RAG 应用场景

企业知识问答

产品说明书

员工行为规范

业绩报告

智能客服

安全分析与情报检索

APT 攻击链分析

威胁情报分析

CVE 安全漏洞解读

专业领域问答

法律合规

医疗诊断辅助

金融风控

2.1 企业知识问答 / 智能客服

企业的知识往往比较私有化,不对外公布,大模型的训练数据集也没有相关的训练语料。通过 RAG,可以将企业的产品说明书、员工行为规范、业绩报告等资料构建为知识库,让客服智能体在回答问题时能够实时检索企业内部知识,生成基于事实的专业回答。

2.2 安全分析与情报检索

在网络安全领域,具有大量的威胁情报、漏洞数据、安全日志等知识源,RAG 可以构建这些数据的知识库,实现大模型对网络安全领域的 APT 攻击链分析、威胁情报分析、CVE 安全漏洞解读等。这类应用让安全分析师能够通过自然语言快速检索、分析和生成报告,大幅提升威胁处置效率。

2.3 法律、医疗、金融等专业领域问答

这些行业的专业知识更新快、术语多、错误代价高,传统大模型受限于训练数据的时间窗口和覆盖范围,往往无法准确反映最新法规、指南或政策变化。通过 RAG,我们可以把这些"新知识"持续地灌输给大模型,作为它的一个 “知识外挂”,实现持续知识更新与高质量领域问答。


3. RAG 的核心流程

RAG 的整体流程,以用户提问的时机为分界点,主要可以分为两步:

A2["🔧 文档预处理
加载 / 清洗 / 标准化"]
    A2 --> A3["✂️ 文档分片<br/>切分为 Chunk"]
    A3 --> A4["🧮 向量化<br/>Embedding"]
    A4 --> A5["🗄️ 生成索引<br/>存入向量数据库"]
end

subgraph online["🟢 Phase 2: 检索生成(在线)"]
    direction TB
    B1["❓ 用户提问"] --> B2["🧮 问题向量化"]
    B2 --> B3["🔍 相似度检索<br/>向量数据库"]
    B3 --> B4["📋 召回 Top-K Chunks"]
    B4 --> B5["📝 构建 Prompt<br/>将 Chunks 注入上下文"]
    B5 --> B6["🤖 LLM 生成回答"]
end

A5 -.->|"知识库就绪"| B3

style offline fill:#e3f2fd,stroke:#1976d2
style online fill:#e8f5e9,stroke:#388e3c -->

⚠️ 核心警示:「垃圾进,垃圾出」(Garbage In, Garbage Out, GIGO)

在构建索引这一环节,有一句非常经典且核心的警示性原则——GIGO。无论后续的检索环节做了什么高深莫测的优化和方案,数据源头的质量才是影响最终检索效果的最重要因素。源头数据脏、乱、差,后面的步骤做得再好也是徒劳。


4. 深入索引构建

索引构建的起点是文档,终点是索引。下面我们逐一拆解每个环节。

📄 原始文档

🔧 预处理

✂️ 分片

🧮 向量化

🗄️ 索引

• 格式统一
• 去除噪声
• 编码规范化

• 固定大小分片
• 递归分片
• 语义分片

• Embedding 模型
• 高维向量

• 向量数据库
• 相似度查询

4.1 文档预处理

文档预处理指的是将原始文档进行加载、解析,并转换为能够统一处理的标准化格式。

文档预处理的目标:让后续的「分片」和「向量化」能够在干净的数据上进行,从源头上保证知识库索引的质量。

4.2 文档分片(Chunking)

为什么需要分片?

一个文档可能有几十上百 MB,而 LLM 有 token 限制。如果将整篇文档全部丢给 LLM:

  • Token 浪费:大量无关内容占据上下文空间
  • 检索精度下降:无关信息会"淹没"真正相关的内容

所以我们需要把一个大的文档按照一定规则切分为较小的文本片段(Chunk)。检索时,匹配出来的是与用户问题最相关的那几个 Chunk,而不是整篇文档。

分片大小的权衡

主流分片方式对比
分片方式 原理 优点 缺点
固定大小分片 按固定字符数/token 数切分 简单高效 可能切断句子
递归分片 按优先级依次尝试多种分隔符 语义完整性好 实现稍复杂
文档结构分片 基于 Markdown/HTML 标题层级 保留结构信息 依赖文档格式
语义分片 基于 Embedding 相似度变化切分 语义边界精准 计算成本高
智能分片 综合多种策略,动态决策 效果最佳 复杂度最高
递归分片详解(Recursive Chunking)

递归分片是目前 LangChain / LlamaIndex 默认 的分片策略,也是实际生产中最常用的方式。其核心思想是:

按优先级依次尝试多种分隔符,只有当前分隔符无法将文本切到目标大小时,才降级使用下一个分隔符。

优先级从高到低:
  "\n\n"  →  "\n"  →  "。"  →  "."  →  " "  →  ""

  段落保持完整 → 句子保持完整 → 词保持完整 → 字符级强制切分

工作流程示意

# LangChain 中的典型用法
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,          # 每个块的目标最大长度
    chunk_overlap=50,        # 相邻块之间的重叠量
    separators=["\n\n", "\n", "。", ".", " ", ""]  # 分隔符优先级
)
chunks = splitter.split_text(document)

📌 chunk_overlap 的作用:让相邻 Chunk 之间有一定重叠,防止关键信息恰好被切在两个 Chunk 的边界上,导致信息断裂。

递归分片的本质,是在 「信息完整性」「向量检索精度」 之间做权衡——Chunk 太大会稀释语义导致检索不准,太小又会丢失上下文。递归分片通过逐级降级的方式,尽可能用大的语义单元(段落 → 句子 → 词)填满目标大小,从而在两者间取得较好的平衡。

4.3 向量化(Embedding)

数据清洗 + 分片完成后,每个文本块都需要被转换成一个高维数值向量,这个过程就叫向量化。目的是让文本的语义可以被机器理解相似度比较

什么是向量?

向量是具有大小和方向的多维数值数组。在大模型中,每个维度代表某种语义特征。因此,语义相似的句子,其向量在高维空间中的"距离"更近。

一维向量 [3]         —— 数轴上的一条有向线段
二维向量 [3, 4]      —— 平面上从原点指向 (3,4) 的有向线段
三维向量 [3, 4, 5]   —— 空间中从原点指向 (3,4,5) 的有向线段

高维向量 [0.32, -0.15, 0.78, ..., -0.42]
                    —— 384 / 768 / 1536 维
                    —— 每个维度不再代表物理坐标,而是某种"语义特征"
                    —— 维度越高,语义表达能力越强
文本如何变成向量(Embedding 过程)

📝 文本块
'小明爱吃西瓜'

🧮 Embedding 模型
(如 text-embedding-3-small)

📊 高维向量
[-0.32, 0.31, -0.23, ... , 0.12]

每个维度的含义举例:
第1维:是否包含'人物'
第2维:是否涉及'动作'
第3维:是否包含'时间'
...

简单来说,就是用 Embedding 模型把文本"投影"到高维向量空间里。每个维度代表了文本在某个语义方向上的权重。最终,每段文字被转成一个高维坐标点,然后就可以进行数学上的比较了。

向量相似度计算方法

点积(Dot Product)

两个向量在同一方向上的重叠程度

值越大 → 越相似

最高效的计算方式

Transformer 注意力机制就使用点积

欧几里得距离(Euclidean Distance)

测量空间中两点之间的直线距离

距离越小 → 相似度越高

主要应用于图像相似度等几何场景

余弦相似度(Cosine Similarity)⭐ 最常用

比较两个向量的夹角

夹角越小 → 方向越一致 → 语义越相似

余弦值范围:[-1, 1]

1 = 语义高度相似,0 = 无关,-1 = 语义相反

✅ 只关心方向,不关心向量长度
✅ 特别适合文本语义匹配
✅ 大多数向量数据库默认支持

相似度直观示例

以下面这个二维坐标系为例(实际是多维度,此处简化为二维便于理解):

  • Query “小明爱吃什么” 与 “小明爱吃西瓜” 夹角极小 → 余弦相似度 ≈ 1,高度相关
  • Query “小明爱吃什么” 与 “小明喜欢打篮球” 夹角较大 → 余弦相似度较低
  • Query “小明爱吃什么” 与 “今天天气真好” 几乎相反 → 余弦相似度 ≈ -1,完全不相关

4.4 生成索引

完成向量化后,每一个 Chunk 都对应一个高维向量。但这些向量如果只是存在内存里,并不能快速查询和比较。所以需要将文本内容和高维语义向量持久化存储下来,利用向量数据库构建索引结构,为后续的检索生成提供相似度查询的功能。

向量数据库

向量数据库是一类专门用于存储、管理和检索高维向量数据的数据库。与传统数据库(如 MySQL)不同,它并不是基于精确匹配进行查询,而是通过计算向量之间的相似度来进行语义检索。

存储内容示例

以 Pgvector(PostgreSQL 的向量扩展)为例,直观地看下存储结构:

字段 类型 说明
embedding_id UUID 主键,唯一标识
embedding vector(1536) 高维向量,用于相似度匹配
text TEXT 原始文本块,LLM 引用的实际内容
metadata JSONB 元数据:文件名、页码、时间戳等

🗄️ 向量数据库存储结构

🏷️ metadata

{filename, page, timestamp, ...}

👆 精确过滤、分组、溯源

📝 text

'小明是一个喜欢吃西瓜的男孩...'

👆 LLM 引用的原始文本块

🧮 embedding

[-0.32, 0.31, -0.23, ... , 0.12]

👆 用于相似度查询的索引

📌 三者关系

  • embedding 是索引——用来做相似度匹配,把最相关的 Chunk 找出来
  • text 是内容——检索出来之后,真正注入给 LLM 做参考的是它
  • metadata 是辅助——让你能做精确过滤(如按文件名、时间戳过滤),提升检索精度

5. 总结

RAG 技术全景图

🟢 Phase 2: 检索生成

🔵 Phase 1: 构建索引

❓ 用户提问

📄 原始文档
(PDF/Word/MD...)

🔧 文档预处理
清洗 + 标准化

✂️ 文档分片
递归分片 / 语义分片

🧮 向量化
Embedding 模型

🗄️ 向量数据库
Pgvector / Milvus / FAISS

🧮 问题向量化

🔍 相似度检索
余弦相似度

📋 召回 Top-K

📝 构建 Prompt
注入上下文

🤖 LLM 生成回答

RAG 的本质:让 LLM 在回答问题前先"查资料",而不是凭记忆胡编。它的核心价值在于:

  1. 降低幻觉——回答有据可依、可溯源
  2. 知识可更新——不需要重新训练,更新知识库即可
  3. 领域可定制——企业私有数据、专业领域知识都能注入

整个系统的质量取决于两条线的协作:

  • 离线线(索引构建)决定了知识库的上限——Garbage In, Garbage Out
  • 在线线(检索生成)决定了实际用户体验——检索精度 × 生成质量

两者缺一不可,共同构成一个高质量的 RAG 系统。


📖 延伸阅读

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐