RAG 从入门到实践:系统学习与复习指南
本文整理自 RAG 学习笔记,覆盖从 LLM 基础原理到 RAG 完整实战的全链路知识体系,适合作为系统复习参考。
一、前置基础:LLM 是怎么工作的
在理解 RAG 之前,需要先搞清楚大模型到底是怎么"说话"的。
1.1 核心逻辑:看上文,猜下文
LLM 的本质是一个自回归语言模型 ------它不会一次性输出整段话,而是一个字一个字往外蹦。
arduino
输入:"我今天很"
↓ 模型处理
输出概率:"开" 62%、"难" 18%、"累" 8%、其他...
↓ 选最高的
输出:"开" → 拼成 "我今天很开心"
↓ 再喂进去,猜下一个字...
每次只多输出一个 token(≈一个字或一个词),你觉得它在流畅对话,其实它把"猜下一个字"这个游戏玩了成千上万次。
1.2 猜字的核心机制:QKV 自注意力
Transformer 架构里,每个 token 怎么知道应该关注前文哪些字?靠的是 QKV 自注意力机制。
| 概念 | 全称 | 通俗含义 | 一句话 |
|---|---|---|---|
| Q | Query(查询) | "我想找谁" | 当前 token 发出的"寻人启事" |
| K | Key(键) | "我是谁" | 每个 token 给自己的身份标签 |
| V | Value(值) | "我有什么" | 每个 token 携带的实际信息 |
1.3 打比方------图书馆查资料
你写论文要查"Transformer 注意力机制":
- Q:你脑子里的研究问题------"注意力到底怎么算的?"
- K:每本书的书名、标签、分类号------帮你定位哪本书
- V:书里的实际内容------你真正要读的东西
1.4 完整计算过程(举例)
输入 "我爱吃苹",模型要猜下一个字。以最后一个 token "苹" 为例(它负责猜下一个字):
css
第 1 步:每个字通过训练好的 W_Q、W_K、W_V 分别生成自己的 Q、K、V
(矩阵乘法,这里直接展示结果)
token Q K V
─────────────────────────────────────────────────
"我" [0.2, 0.5, 0.1] [0.3, 0.1, 0.8] [1.0, 0.2, 0.5]
"爱" [0.4, 0.1, 0.6] [0.5, 0.7, 0.2] [0.1, 0.9, 0.3]
"吃" [0.7, 0.3, 0.4] [0.6, 0.2, 0.5] [0.4, 0.1, 1.0]
"苹" [0.6, 0.2, 0.7] [0.4, 0.5, 0.3] [0.3, 0.4, 0.8]
less
第 2 步:
Q("苹") · K("吃") = 0.92 ← 最高!动词"吃"是猜食物的最强信号
Q("苹") · K("我") = 0.76
Q("苹") · K("苹") = 0.55
Q("苹") · K("爱") = 0.58
为什么"吃"得分最高? 因为训练数据里"吃苹果"、"吃香蕉"大量出现,梯度下降让 W_K("吃") 和 W_Q(食物相关 token) 的向量方向越来越对齐。Q·K 的分数就是训练出来的统计规律。
arduino
第 3 步:softmax 把分数变成权重(总和为 1)
原始分数:[0.92, 0.76, 0.58, 0.55]
softmax → [0.31, 0.26, 0.22, 0.21]
"吃" 31% | "我" 26% | "爱" 22% | "苹" 21%
scss
第 4 步:用权重加权平均所有字的 V,融合整句话的信息
输出 = 0.31 × V("吃") + 0.26 × V("我") + 0.22 × V("爱") + 0.21 × V("苹")
= 0.31×[0.4, 0.1, 1.0]
+ 0.26×[1.0, 0.2, 0.5]
+ 0.22×[0.1, 0.9, 0.3]
+ 0.21×[0.3, 0.4, 0.8]
─────────────────────────
= [0.47, 0.37, 0.67] ← 融合了全文信息的向量
erlang
第 5 步:这个向量过全连接层 + softmax → 词表概率
"苹果" → 62% ← ✅ 最高!
"果" → 18%
"面包" → 12%
"香蕉" → 5%
其他 → 3%
模型输出:"果",和前面的"苹"拼成完整的 "苹果"
二、LLM 的死穴:幻觉从哪来
2.1 什么是幻觉(Hallucination)
大模型知道的知识,完全取决于训练时喂给它的数据集。有两个盲区:
- 知识有截止日期:训练数据之后发生的事,它不知道
- 没见过私有数据:企业内部文档、产品手册、规范,它也没见过
关键问题是:LLM 不会说"我不知道" 。它是概率生成模型,面对不知道的问题不会拒绝回答,而是基于学到的语言模式"编造"一个听起来合理但实际错误的答案------这就是幻觉。
2.2 幻觉的场景危害
| 场景 | 幻觉表现 | 后果 |
|---|---|---|
| 医疗问答 | 编造不存在的治疗方案 | 危及生命 |
| 法律咨询 | 引用虚构的法条和判例 | 法律纠纷 |
| 企业客服 | 给出错误的退换货政策 | 客诉、退款损失 |
| 金融分析 | 捏造财报数据 | 投资决策失误 |
三、解决幻觉的两条路:微调 vs RAG
3.1 微调(Fine-tuning)
用新数据继续训练大模型,让模型"学会"新知识。
缺点:
| 问题 | 说明 |
|---|---|
| 成本高 | 需要大量标注数据 + GPU 算力 |
| 周期长 | 训练一次几天到几周 |
| 不可溯源 | 记住了知识,但说不清从哪学的 |
| 更新困难 | 知识过期 → 重新训练 |
适用场景:大公司、专业垂直领域(医疗影像诊断、法律文书生成)------需要模型"学会一种能力",而不只是"记住一堆事实"。
3.2 RAG(检索增强生成)
不训练模型,而是给它配一个外部知识库。 每次提问先查资料,把资料塞进 Prompt,再让模型对着资料回答。
传统 LLM:
用户问 → LLM 凭训练记忆回答 → 可能幻觉 ❌
RAG:
用户问 → 知识库检索 → 找到相关文档 → 拼入 Prompt → LLM 对着资料回答 ✅
3.3 对比总结
| 维度 | 微调 | RAG |
|---|---|---|
| 成本 | 高(GPU + 标注 + 时间) | 低(只需 Embedding + 存储) |
| 知识更新 | 重新训练 | 增删文档即可 |
| 可解释性 | 黑盒 | 可溯源到具体文档 |
| 适用方向 | 学"能力"(推理风格、格式) | 学"知识"(事实、规范、政策) |
| 幻觉控制 | 不一定,可能学到错误信息 | 显著降低(有据可查) |
四、RAG 核心机制深度拆解
4.1 三个字母的含义
| 字母 | 全称 | 含义 | 通俗理解 |
|---|---|---|---|
| R | Retrieval | 检索 | 去知识库搜相关文档 |
| A | Augmented | 增强 | 把搜到的文档塞进 Prompt |
| G | Generation | 生成 | LLM 根据增强后的 Prompt 生成答案 |
4.2 检索阶段:怎么找到相关文档?
方案一:关键词搜索(传统方案,效果差)
直接在文档里做字符串匹配。
arduino
用户问:"那种红色脆脆的水果是什么?"
文档里有:"红富士苹果是一种..."
关键词匹配:"红色"✗、"脆脆"✗、"水果"✗ → 搜不到 ❌
根本缺陷:只做字面匹配,不理解语义。换个说法、同义替换就搜不到了。
方案二:向量语义搜索(现代 RAG 标配)
把文本转成向量(一串数字),用向量间的夹角衡量语义相似度。
css
用两个维度直观理解(真实模型 768~4096 维):
可食用性 硬度
水果 → [0.9, 0.3]
苹果 → [0.9, 0.5]
香蕉 → [0.8, 0.3]
石头 → [0.1, 0.9]
水果 vs 苹果:夹角 ≈ 10° → 强相关 ✅
水果 vs 香蕉:夹角 ≈ 12° → 强相关 ✅
水果 vs 石头:夹角 ≈ 75° → 基本无关 ❌
即使用户问"红色脆脆的水果",它的向量也和"红富士苹果"的向量夹角很小------搜的是意思 ,不是字面。
五、嵌入模型与向量化
5.1 什么是嵌入模型(Embedding Model)
专门把文本(图片/语音/PDF)转成向量的模型。 和生成式 LLM 是两回事:
| 对比维度 | 嵌入模型 | 生成式大模型 |
|---|---|---|
| 任务 | 文本 → 向量 | 文本 → 文本 |
| 模型大小 | 小(几百 MB) | 大(几百 GB) |
| 推理成本 | 低(便宜) | 高(贵) |
| 推理速度 | 快 | 慢 |
| 代表模型 | text-embedding-3-small | GPT-4o、Claude |
5.2 向量化的意义
把非结构化文本变成计算机能计算的数字:
arduino
"你好啊" → [0.21, -0.53, 0.78, ..., 0.09] (768 维)
"今日は" → [0.02, -0.61, 0.81, ..., 0.12] (日语"你好",意思相近,向量也接近)
两个向量的语义相似度用余弦相似度(Cosine Similarity)计算,范围 -1 到 1,越接近 1 越相似。
5.3 支持的数据类型
不只是文本------只要嵌入模型支持,都可以向量化做语义搜索:文本、PDF、图片(多模态嵌入模型)、语音(转文字后向量化)、代码文件。
六、向量数据库
6.1 为什么需要专门的向量数据库
传统数据库(MySQL、PostgreSQL)存的是结构化数据(数字、字符串),索引基于 B-Tree / Hash,擅长精确匹配。但高维向量的相似度计算需要 ANN(近似最近邻)索引,传统数据库做不到。
向量数据库专门为此设计,核心三板斧:
| 能力 | 说明 |
|---|---|
| 存 | 海量高维向量持久化存储 |
| 查 | 毫秒级 ANN(近似最近邻)检索 |
| 筛 | 结合 metadata 做标量过滤("只查第 3 章的文档") |
6.2 常见向量数据库
| 产品 | 定位 | 特点 |
|---|---|---|
| Chroma | 轻量开源 | 适合学习和小项目,Python/JS 双 SDK |
| Milvus | 企业级开源 | 分布式、高性能、多索引类型 |
| Pinecone | 云服务 | 全托管、开箱即用 |
| Weaviate | 开源 | 内置向量化模块,支持混合搜索 |
| Qdrant | 开源 | Rust 编写,性能优秀 |
| PGVector | PostgreSQL 插件 | 在 PG 里直接存向量,降低架构复杂度 |
6.3 相似度检索流程
ini
1. 用户问:"光光和东东是怎么成为朋友的?"
↓
2. 嵌入模型把问题向量化 → [0.34, -0.12, 0.78, ...]
↓
3. 向量库用 ANN 算法找最相似的 Top-K 个文档向量
↓
4. 返回相似度最高的 K 篇文档(如 K=3)
七、文档切分策略
7.1 为什么必须切分
两个原因:
- 上下文窗口有限:一整本书塞不进 Prompt
- 检索精度:一段 5000 字的文档,用户问其中一个小细节,无关内容会稀释向量相似度
7.2 切分策略对比
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 按章节 | 结构化强的长文 | 语义完整 | 粒度可能太粗 |
| 按段落 | 一般文章 | 自然语义边界 | 段落长度不均 |
| 按固定字数 | 通用 | 简单可控 | 可能切断语义 |
| 递归字符分割 | LangChain 默认 | 按分隔符优先级逐级切 | 需调 chunk_size |
| 语义分块 | 高质量要求 | 用模型判断自然断点 | 成本高 |
7.3 核心原则
每个切片保持完整的自然语义,不要拦腰砍断一句话。
7.4 hello-rag 中的实际切分
故事按章节切成了 7 个 Document,每段是一个完整情节:
第 1 章:角色介绍(光光)
第 2 章:角色介绍(东东)
第 3 章:友情情节(光光邀请东东踢球)
第 4 章:友情情节(互相帮助练习)
第 5 章:高潮转折(赢得比赛)
第 6 章:结局(友谊升华)
第 7 章:尾声(多年以后)
8.1 环境配置
arduino
import 'dotenv/config'
自动读取 .env,把敏感信息加载到 process.env:
ini
OPENAI_API_KEY=sk-xxxxxxxx
OPENAI_API_BASE_URL=https://your-proxy.com/v1
MODEL_NAME=gpt-4o
EMBEDDING_MODEL_NAME=text-embedding-3-small
8.2 初始化 LLM(生成式模型)
arduino
import { ChatOpenAI } from 'langchain/openai'
const model = new ChatOpenAI({
temperature: 0, // RAG 场景必须为 0
model: process.env.MODEL_NAME, // 如 gpt-4o
apiKey: process.env.OPENAI_API_KEY,
configuration: {
baseURL: process.env.OPENAI_API_BASE_URL, // 代理/中转地址
}
})
temperature 参数(OpenAI 范围 0~2):
| 值 | 效果 | RAG 适用性 |
|---|---|---|
| 0 | 每次回答几乎一致,严格按输入 | ✅ RAG 必选 |
| 0.7~1 | 有一定创造性 | ❌ 可能偏离文档 |
| >1.5 | 创造性极强 | ❌ 容易编造 |
8.3 初始化嵌入模型
arduino
import { OpenAIEmbeddings } from 'langchain/openai'
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
model: process.env.EMBEDDING_MODEL_NAME, // 如 text-embedding-3-small
configuration: {
baseURL: process.env.OPENAI_API_BASE_URL,
}
})
关键区分 :ChatOpenAI(LLM,负责生成回答)和 OpenAIEmbeddings(嵌入模型,负责文本→向量)是两个独立实例,各司其职。
8.4 构建文档数组
javascript
import { Document } from 'langchain/core/documents'
const document = [
new Document({
pageContent: `光光是一个活泼开朗的小男孩...`, // 正文,参与向量化
metadata: { // 标签,不参与向量化
chapter: 1,
character: "光光",
type: "角色介绍",
mood: "活泼"
},
}),
// ... 共 7 个 Document
]
Document 的两个字段:
| 字段 | 作用 | 参与向量化? | 用途 |
|---|---|---|---|
pageContent |
文档正文 | ✅ 是 | 检索匹配的主体 |
metadata |
附加标签 | ❌ 否 | 过滤、溯源、权限控制 |
metadata 的三种实际用途:
- 过滤 :搜完后只留
type === "角色介绍"的结果 - 溯源:回答附上"参考自第 3 章"
- 权限:按文档密级过滤
8.5 定义查询
ini
const question = [
"光光和东东的友谊是怎样的?"
]
后续流程:向量化问题 → 相似度检索 → 返回最相关 Document → pageContent 拼 Prompt → LLM 生成答案。
当前代码到这里为止还是"准备阶段",真正的检索 + 增强 + 生成逻辑需要后续补全。
九、RAG 完整流程总结
markdown
═══════════════════════════════════════════════════════════════
RAG 完整数据流
═══════════════════════════════════════════════════════════════
【离线阶段:知识入库 --- 只在文档更新时做一次】
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 1.加载文档 │ → │ 2.切分 │ → │ 3.向量化 │ → │ 4.存入向量库│
│ Document │ │ Chunking │ │ Embedding│ │ VectorDB │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
【在线阶段:问答 --- 每次用户提问都执行】
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 5.用户提问 │ → │ 6.向量化 │ → │ 7.相似度 │ → │ 8.拼入 │
│ Question │ │ Embedding│ │ 检索Top-K │ │ Prompt │
└──────────┘ └──────────┘ └──────────┘ └─────┬────┘
↓
┌──────────┐
│9.LLM生成 │
│ 最终答案 │
└──────────┘
═══════════════════════════════════════════════════════════════
关键三步总结:
| 步骤 | 做什么 | 核心工具 |
|---|---|---|
| R 检索 | 问题向量化 → 向量库相似度计算 → 返回 Top-K 文档 | Embedding Model + VectorDB |
| A 增强 | 将检索到的文档拼接进 Prompt 作为背景知识 | Prompt 模板 |
| G 生成 | LLM 阅读参考资料,生成有据可查的回答 | ChatOpenAI / LLM |
十、如何系统学习和复习 RAG
10.1 七层递进学习路线
css
第 1 层:LLM 基础
├── 自回归生成(token 级别,一个字一个字蹦)
├── Transformer 注意力(QKV 计算过程,手推一遍)
└── LLM 局限性(幻觉、知识截止、上下文窗口有限)
第 2 层:为什么要 RAG
├── 幻觉产生的根本原因(概率生成 + 知识盲区)
├── 微调 vs RAG 对比(成本、可解释性、更新方式)
└── RAG 的核心思想:检索 → 增强 → 生成
第 3 层:嵌入与向量
├── 嵌入模型 vs 生成式 LLM(任务不同、成本不同)
├── 向量的语义含义(多维数字 = 语义坐标)
├── 余弦相似度计算原理
└── 多模态向量化(文本、图片、语音)
第 4 层:向量数据库
├── 为什么 MySQL 干不了(B-Tree 无法索引高维向量)
├── ANN 近似最近邻(牺牲微小精度换百倍速度)
├── 主流选型(Chroma 入门 → Milvus/Pinecone 生产)
└── 混合搜索(向量相似度 + metadata 标量过滤)
第 5 层:文档处理
├── 切分策略(按章节/段落/固定字数/语义/递归)
├── Chunk Size 与 Overlap 调参
├── metadata 设计(为过滤和溯源服务)
└── 多格式加载(PDF、网页、数据库、API)
10.2 核心概念速查表
| 概念 | 一句话解释 | 关键词 |
|---|---|---|
| LLM 幻觉 | 模型不知道但不会拒绝,基于概率编造答案 | 知识截止、概率生成 |
| RAG | 先查资料再回答,给 LLM 加上"外脑" | Retrieval + Augmented + Generation |
| 微调 | 继续训练模型来学新知识 | 成本高、GPU、标注数据 |
| 嵌入模型 | 文本→向量的专用模型 | 便宜、快、小、和 LLM 不同 |
| 向量 | 一段文本的"语义坐标" | 多维数组、余弦相似度 |
| 余弦相似度 | 两个向量夹角的余弦,衡量语义相关度 | -1~1,越近 1 越相似 |
| 向量数据库 | 存向量 + 快速 ANN 检索 | Chroma、Milvus、Pinecone |
| Chunk | 切分后的文本片段 | 保持完整语义 |
| Document | pageContent(正文)+ metadata(标签) |
正文向量化,标签不向量化 |
| metadata | 文档的标签属性 | 过滤、溯源、权限 |
| temperature | LLM 输出的随机度 | RAG 设为 0 |
| Top-K | 检索返回最相似的 K 篇 | 通常 3~10 |
| QKV | Transformer 注意力核心机制 | Q 找、K 标、V 给 |
| LangChain | AI 应用开发框架 | 封装 LLM、向量库、文档操作 |
10.4 自检清单
- LLM 的幻觉是什么?为什么会幻觉?
- 微调和 RAG 的优缺点?各自适用什么场景?
- RAG 三个字母代表什么?每一步具体做了什么?
- 关键词搜索比向量语义搜索差在哪?
- 嵌入模型和 LLM 有什么区别?
- 向量数据库解决了什么问题是 MySQL 解决不了的?
- Document 的
pageContent和metadata分别干嘛用? - 文档切分的核心原则是什么?
- 从用户提问到 LLM 回答,RAG 完整链路是怎样的?
- Q、K、V 分别代表什么?在 Attention 中如何配合?
- 余弦相似度怎么算?为什么用余弦而不是欧氏距离?
- temperature 在 RAG 场景应该设多少?为什么?
本文基于 LangChain + OpenAI Embeddings 实战代码编写,从 LLM 底层 QKV 机制讲到 RAG 完整工程链路,建议收藏作为系统复习手册。