学习 RAG 必须掌握的前置知识:从向量到检索增强生成
本文定位:这是一篇面向即将学习 RAG(Retrieval-Augmented Generation,检索增强生成)的开发者的前置知识指南。RAG 是当前 AI 应用中最实用的技术之一,但它涉及多个跨领域概念——向量、Embedding、向量数据库、文本分块、相似度检索等。本文将这些概念逐一拆解,用通俗语言 + 图表 + 代码示例帮你建立完整的知识框架,为后续的 RAG 实战打下坚实基础。
适合谁读:
- 已掌握 LLM 基础调用(如 ChatModel),准备深入 RAG 的 Java/Spring 开发者
- 对"向量"、"Embedding"等概念一知半解,想要系统梳理的同学
目录
- 1. 为什么需要 RAG?——LLM 的三大痛点
- 2. RAG 全局架构概览
- 3. 什么是向量(Vector)?
- 4. 什么是 Embedding(文本向量化)?
- 5. 相似度计算——向量之间怎么比"像不像"?
- 6. 文本分块(Chunking)——RAG 质量的第一个关键
- 7. 向量数据库(Vector Database)——专门为向量设计的存储引擎
- 8. Token 与上下文窗口——理解 LLM 的"胃口"
- 9. Prompt Engineering 与 RAG 的关系
- 10. Spring AI 中的 RAG 组件映射
- 11. RAG 检索质量优化(进阶预览)
- 12. 总结
- 13. 参考资料
1. 为什么需要 RAG?——LLM 的三大痛点
在学习 RAG 之前,首先要理解它要解决什么问题。大语言模型(LLM)虽然强大,但存在三个致命短板:
| 痛点 | 具体表现 | 举例 |
|---|---|---|
| 知识过时 | 训练数据有截止日期,不知道最新信息 | 问"2025年Spring AI最新版本是什么?"→ 回答错误或不知道 |
| 幻觉(Hallucination) | 不知道的内容会一本正经地编造 | 问"你们公司的退款政策是什么?"→ 编一个看起来合理但完全虚构的政策 |
| 知识边界 | 不了解私有数据、企业内部文档 | 问"我们项目的技术架构是什么?"→ 无从得知 |
RAG 的核心思路:
┌─────────────────────────────────────────────────────────┐
│ │
│ 传统 LLM:用户问题 ───────────────→ LLM ──→ 回答 │
│ (只靠训练时的知识) │
│ │
│ RAG:用户问题 → 【先检索相关知识】→ 知识+问题 → LLM → 回答 │
│ (从知识库中找到最相关的内容) │
│ │
└─────────────────────────────────────────────────────────┘
一句话理解 RAG:开卷考试。LLM 不再只靠"记忆"(训练数据)答题,而是允许它翻阅"参考资料"(检索到的文档),然后基于参考资料回答问题。
2. RAG 全局架构概览
在深入每个概念之前,先看 RAG 的完整架构,建立全局认知:
┌─────────────────────── 离线阶段(Indexing)──────────────────────┐
│ │
│ 原始文档 │
│ (PDF/Word/Markdown/数据库...) │
│ │ │
│ ▼ │
│ ① 文档加载(Document Loading) │
│ │ │
│ ▼ │
│ ② 文本分块(Chunking / Splitting) │
│ │ │
│ ▼ │
│ ③ 向量化(Embedding) │
│ "每个文本块" → Embedding 模型 → "一个向量 float[1536]" │
│ │ │
│ ▼ │
│ ④ 存入向量数据库(Vector Store) │
│ 向量 + 原文 + 元数据 → 持久化存储 │
│ │
└──────────────────────────────────────────────────────────────────┘
┌─────────────────────── 在线阶段(Querying)──────────────────────┐
│ │
│ 用户提问:"Agent 是怎么调用工具的?" │
│ │ │
│ ▼ │
│ ⑤ 问题向量化(Query Embedding) │
│ 用户问题 → Embedding 模型 → 查询向量 │
│ │ │
│ ▼ │
│ ⑥ 向量相似度检索(Similarity Search) │
│ 在向量数据库中找到 Top-K 最相似的文档块 │
│ │ │
│ ▼ │
│ ⑦ 构建增强 Prompt(Augmented Prompt) │
│ 系统提示词 + 检索到的文档内容 + 用户问题 │
│ │ │
│ ▼ │
│ ⑧ LLM 生成回答(Generation) │
│ 基于检索到的知识生成准确、有据可依的回答 │
│ │
└──────────────────────────────────────────────────────────────────┘
上面的每一步都涉及一个或多个前置知识,下面逐一展开。
3. 什么是向量(Vector)?
向量是 RAG 的数学基石,理解向量才能理解后续的一切。
3.1 向量的直觉理解
数学定义:向量是一个有序的数字列表(数组),每个数字称为一个"维度"。
二维向量:[3, 4] → 平面上的一个点
三维向量:[1, 2, 3] → 三维空间中的一个点
高维向量:[0.23, -0.45, 0.89, 0.12, ..., 0.67] → 768维空间中的一个点
通俗理解:你可以把向量想象成一个人的"特征描述卡"。
张三的特征卡(用数字描述):
├─ 身高维度:175(cm)
├─ 体重维度:70(kg)
├─ 年龄维度:25(岁)
└─ 收入维度:15000(元/月)
→ 向量表示:[175, 70, 25, 15000]
两个"特征卡"越接近,说明两个人越相似。文本向量也是同样的道理——只不过维度从4个变成了768个或1536个,每个维度编码了文本的某个语义特征。
3.2 为什么用向量表示文本?
计算机天然擅长处理数字,不擅长直接理解文字。把文本转成向量后:
| 能力 | 文本形式 | 向量形式 |
|---|---|---|
| 比较相似度 | ❌ "猫"和"喵星人"字面完全不同 | ✅ 向量距离很近(语义相同) |
| 数学运算 | ❌ 无法对文字做加减乘除 | ✅ 可以计算距离、做聚类 |
| 快速检索 | ❌ 全文搜索依赖关键词匹配 | ✅ 向量索引毫秒级返回 |
| 跨语言 | ❌ "猫"和"cat"字面无关 | ✅ 多语言模型中向量接近 |
核心公式:
语义相近的文本 → 向量在空间中距离近
语义不同的文本 → 向量在空间中距离远
用一个直观的例子:
"猫" → [0.23, -0.45, 0.89, 0.12, ...]
"小猫咪" → [0.25, -0.42, 0.85, 0.15, ...] ← 距离很近!
"狗" → [0.18, -0.38, 0.72, 0.20, ...] ← 距离较近(都是动物)
"汽车" → [-0.71, 0.33, 0.02, -0.56, ...] ← 距离很远(不同领域)
4. 什么是 Embedding(文本向量化)?
Embedding 就是把文本转换成向量的过程和技术。
4.1 Embedding 模型的工作原理
┌──────────────────────────────────────────────────┐
│ Embedding 模型 │
│ │
│ 输入:一段文本(字符串) │
│ "Spring AI 的 Tool Calling 机制" │
│ │ │
│ ▼ │
│ 内部处理: │
│ 1. 分词(Tokenization) │
│ 2. 通过 Transformer 神经网络编码 │
│ 3. 提取语义特征 │
│ 4. 压缩为固定长度的浮点数组 │
│ │ │
│ ▼ │
│ 输出:一个固定维度的向量 │
│ [0.012, -0.034, 0.078, ..., 0.056] │
│ \_______________ _______________/ │
│ V │
│ 1536 个浮点数 │
│ (维度取决于模型) │
└──────────────────────────────────────────────────┘
关键点:无论输入文本是 10 个字还是 500 个字,Embedding 模型的输出永远是固定长度的向量。这就是"压缩编码"——把变长文本压缩成定长数字。
4.2 Embedding 模型 vs Chat 模型
这是初学者最容易混淆的两个概念:
| 对比维度 | Embedding 模型 | Chat 模型(LLM) |
|---|---|---|
| 输入 | 文本 | 文本(对话) |
| 输出 | 固定长度的 float 数组 | 自然语言文本 |
| 目的 | 把文本编码成可计算的数字 | 理解意图并生成回答 |
| 用途 | 语义搜索、RAG 检索 | 对话、写作、推理 |
| 举例 | text-embedding-3-small |
gpt-4o、glm-4 |
| 能否对话 | ❌ 不能 | ✅ 可以 |
| 成本 | 极低(约 Chat 的 1/100) | 较高 |
// Chat 模型:输入文本 → 输出文本
String answer = chatModel.call("什么是RAG?");
// answer = "RAG是检索增强生成..."(自然语言)
// Embedding 模型:输入文本 → 输出向量
float[] vector = embeddingModel.embed("什么是RAG?");
// vector = [0.012, -0.034, 0.078, ...](浮点数组)
4.3 主流 Embedding 模型对比
| 模型 | 提供商 | 维度 | 最大 Token | 特点 |
|---|---|---|---|---|
text-embedding-3-small |
OpenAI | 1536 | 8191 | 性价比高,推荐 |
text-embedding-3-large |
OpenAI | 3072 | 8191 | 精度更高,成本更高 |
text-embedding-ada-002 |
OpenAI | 1536 | 8191 | 旧版,仍在广泛使用 |
embedding-3 |
智谱 AI | 2048 | 8192 | 中文效果好 |
BAAI/bge-large-zh |
BAAI | 1024 | 512 | 开源,中文专优 |
mxbai-embed-large |
Ollama | 1024 | 512 | 本地部署,免费 |
nomic-embed-text |
Ollama | 768 | 8192 | 本地部署,长文本 |
选型建议:
- 中文场景优先选择智谱
embedding-3或开源bge-large-zh- 英文场景优先选择 OpenAI
text-embedding-3-small- 预算有限或数据敏感,选择 Ollama 本地部署方案
5. 相似度计算——向量之间怎么比"像不像"?
有了向量之后,如何判断两个向量(即两段文本)的语义是否相近?这就需要"相似度计算"。
5.1 余弦相似度(Cosine Similarity)
最常用的相似度度量方式。
直觉理解:两个向量之间"夹角"的余弦值。夹角越小(方向越一致),余弦值越接近 1,表示越相似。
B 向量
/
/ θ(夹角)
/
/
─────────────────── A 向量
余弦相似度 = cos(θ)
- θ = 0° → cos(0°) = 1.0 → 完全相同
- θ = 90° → cos(90°) = 0.0 → 完全无关
- θ = 180° → cos(180°) = -1.0 → 完全相反
数学公式(了解即可):
A · B Σ(Ai × Bi)
cos(θ) = ─────── = ──────────────────────
|A|×|B| √(Σ Ai²) × √(Σ Bi²)
代码示例:
/**
* 计算两个向量的余弦相似度
* @param vectorA 向量A
* @param vectorB 向量B
* @return 相似度值,范围 [-1, 1],越接近1越相似
*/
public static double cosineSimilarity(float[] vectorA, float[] vectorB) {
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
for (int i = 0; i < vectorA.length; i++) {
dotProduct += vectorA[i] * vectorB[i];
normA += vectorA[i] * vectorA[i];
normB += vectorB[i] * vectorB[i];
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
实际效果:
"猫咪很可爱" vs "小猫真萌" → 余弦相似度 ≈ 0.92(很相似)
"猫咪很可爱" vs "今天天气不错" → 余弦相似度 ≈ 0.15(不相关)
"猫咪很可爱" vs "我讨厌猫" → 余弦相似度 ≈ 0.45(有关联但语义不同)
5.2 欧氏距离(Euclidean Distance)
直觉理解:两个点之间的"直线距离"。距离越小,越相似。
在二维空间中:
A = (1, 2), B = (4, 6)
欧氏距离 = √((4-1)² + (6-2)²) = √(9+16) = 5.0
特点:不仅考虑方向(像余弦),还考虑向量的长度(幅度)。
5.3 点积(Dot Product)
直觉理解:两个向量对应维度相乘再求和。同时包含了方向和长度信息。
A = [1, 2, 3], B = [4, 5, 6]
点积 = 1×4 + 2×5 + 3×6 = 4 + 10 + 18 = 32
5.4 三种方法怎么选?
| 方法 | 取值范围 | 含义 | 适用场景 | RAG 中的使用频率 |
|---|---|---|---|---|
| 余弦相似度 | [-1, 1] | 方向一致性 | 文本语义匹配 | ⭐⭐⭐⭐⭐ 最常用 |
| 欧氏距离 | [0, +∞) | 绝对距离 | 图像特征、聚类 | ⭐⭐ |
| 点积 | (-∞, +∞) | 方向+长度 | 推荐系统 | ⭐⭐⭐ |
小结:RAG 场景一般用余弦相似度,因为我们关心的是语义方向是否一致,而不是向量的绝对大小。Embedding 模型输出的向量通常已经归一化,此时余弦相似度和点积的结果等价。
6. 文本分块(Chunking)——RAG 质量的第一个关键
文本分块是 RAG 流水线中最容易被忽视但影响最大的环节。
6.1 为什么要分块?
三个原因:
原因一:Embedding 模型有 Token 上限
├─ text-embedding-3-small 最多处理 8191 tokens
├─ bge-large-zh 最多处理 512 tokens
└─ 一篇文档可能有几万 tokens → 必须切分
原因二:检索粒度问题
├─ 如果整篇文档作为一个向量,检索太粗
├─ 用户问"Agent的Tool Calling怎么实现?"
├─ 整篇文档的向量包含了太多无关内容
└─ 小块(如一个段落)的向量更精准地表示局部语义
原因三:LLM 上下文窗口限制
├─ 检索到的文档要塞进 Prompt 送给 LLM
├─ 如果每个块太大,Top-5 就可能超出窗口
└─ 小块可以塞更多来源,回答更全面
6.2 四种常见分块策略
策略一:固定大小分块
原文:[AAAA|BBBB|CCCC|DDDD|EEEE]
↓ 每 4 个字符切一刀
块1: [AAAA]
块2: [BBBB]
块3: [CCCC]
块4: [DDDD]
块5: [EEEE]
| 优点 | 缺点 |
|---|---|
| 实现简单 | 可能从句子中间切断 |
| 块大小均匀 | 语义完整性无法保证 |
策略二:按段落/标题分块
原文:
# 第一章 Agent概述 ←── 遇到标题切分
Agent是一种智能体...
它可以自主决策...
# 第二章 Tool Calling ←── 遇到标题切分
Tool Calling是指...
结果:
块1: "第一章 Agent概述\nAgent是一种智能体...\n它可以自主决策..."
块2: "第二章 Tool Calling\nTool Calling是指..."
| 优点 | 缺点 |
|---|---|
| 保留文档结构 | 块大小不均匀 |
| 语义完整性好 | 某些块可能太大或太小 |
策略三:滑动窗口分块(推荐)
原文:[A B C D E F G H I J]
↓ 窗口大小=5,重叠=2
块1: [A B C D E]
块2: [ D E F G H] ← D、E 重叠
块3: [ G H I J] ← G、H 重叠
| 优点 | 缺点 |
|---|---|
| 避免边界语义丢失 | 存储空间略增(有重叠) |
| 兼顾完整性和粒度 | 需要调两个参数 |
| 业界最常用 |
Spring AI 中的实现:
// TokenTextSplitter 就是滑动窗口分块器
TokenTextSplitter splitter = new TokenTextSplitter(
500, // defaultChunkSize: 每块大约 500 tokens
100, // minChunkSizeChars: 最小块字符数
200, // minChunkLengthToEmbed: 最小需要 Embedding 的长度
1000, // maxNumChunks: 最大块数
true // keepSeparator: 保留分隔符
);
List<Document> chunks = splitter.split(document);
策略四:语义分块
原文:[关于猫的介绍。猫很可爱。| 今天天气很好。适合出门。]
↓ 用模型检测语义边界(话题变化处切分)
块1: "关于猫的介绍。猫很可爱。"
块2: "今天天气很好。适合出门。"
| 优点 | 缺点 |
|---|---|
| 语义完整性最好 | 需要额外模型调用,成本高 |
| 切分质量最高 | 速度慢 |
6.3 分块参数怎么调?
| 参数 | 推荐范围 | 说明 |
|---|---|---|
| chunk_size | 300-500 tokens | 太大检索不精准,太小丢失上下文 |
| chunk_overlap | chunk_size 的 10%-20% | 即 50-100 tokens 的重叠 |
| 分隔符 | \n\n、。、. |
优先在自然断句处切分 |
经验法则:
- 技术文档:500 tokens,overlap 50
- 对话记录:200-300 tokens,overlap 30
- 法律/合同:300-400 tokens,overlap 80(重叠多一些避免条款截断)
7. 向量数据库(Vector Database)——专门为向量设计的存储引擎
7.1 为什么不用 MySQL?
MySQL 能做的:
SELECT * FROM documents WHERE content LIKE '%Agent%'
→ 关键词匹配,"Agent"和"智能体"匹配不上
MySQL 做不到的:
"找出和 [0.23, -0.45, 0.89, ...] 这个 1536 维向量最相似的 5 条记录"
→ MySQL 没有高维向量的索引和相似度计算能力
→ 即使暴力遍历,百万级数据要几十秒
向量数据库是专门为高维向量的存储和近似最近邻(ANN)搜索而设计的数据库。
7.2 主流向量数据库对比
| 数据库 | 类型 | 特点 | 适合场景 | Spring AI 支持 |
|---|---|---|---|---|
| Chroma | 轻量级嵌入式 | 零配置,开箱即用 | 开发测试、小数据量 | ✅ ChromaVectorStore |
| Milvus | 分布式 | 高性能,支持十亿级 | 生产环境、大规模 | ✅ MilvusVectorStore |
| Pinecone | 全托管 SaaS | 无需运维 | 快速上线、不想管基础设施 | ✅ PineconeVectorStore |
| Weaviate | 混合搜索 | 内置向量化,支持语义+关键词 | 混合搜索场景 | ✅ WeaviateVectorStore |
| pgvector | PG 扩展 | 在已有 PostgreSQL 上加向量能力 | 已有 PG 基础设施 | ✅ PgVectorStore |
| Redis Stack | 内存 | 速度极快 | 实时性要求高 | ✅ RedisVectorStore |
| SimpleVectorStore | 内存(Spring AI 内置) | 零依赖,用于 Demo | 学习和测试 | ✅ 内置 |
选型建议:
- 学习入门:
SimpleVectorStore(零依赖,内存存储)- 开发测试:Chroma(轻量,Docker 一键启动)
- 生产部署:Milvus 或 pgvector
7.3 向量检索的底层算法
面试中可能被问到,了解即可:
| 算法 | 原理 | 时间复杂度 | 精度 | 适用场景 |
|---|---|---|---|---|
| 暴力搜索(Flat) | 逐一比对所有向量 | O(n) | 100% 精确 | 小数据量(<10万) |
| IVF(倒排文件索引) | 先聚类,查询时只搜部分簇 | O(n/k) | 近似 | 大数据量 |
| HNSW(层次导航小世界图) | 构建多层图索引,图上跳跃搜索 | O(log n) | 近似(>95%) | 最常用,速度/精度平衡最好 |
| PQ(乘积量化) | 压缩向量减少内存 | O(n) | 近似 | 超大规模,内存有限 |
HNSW 搜索过程(直觉理解):
层3(最稀疏): A ─────────── B ──────────── C
│
层2: A ── D ─── B ── E ── C ── F
│
层1: A-D-G-B-H-E-I-C-F-J
│
层0(最稠密): A-D-K-G-L-B-M-H-N-E-O-I-P-C-Q-F-R-J
搜索时:从顶层(稀疏)快速定位大致区域,逐层下降(细化),
在底层(稠密)找到精确的最近邻。
小结:HNSW 是目前向量检索最主流的索引算法,它通过构建多层图结构实现 O(log n) 的搜索复杂度,在精度和速度之间取得了最佳平衡。
7.4 向量数据库的核心操作
// 伪代码:向量数据库的核心 CRUD
// 1. 写入(Insert)
vectorStore.add(List.of(
new Document("Spring AI支持Tool Calling", metadata),
new Document("RAG是检索增强生成", metadata)
));
// 2. 相似度搜索(核心操作)
List<Document> results = vectorStore.similaritySearch(
SearchRequest.query("Agent如何调用工具?")
.withTopK(5) // 返回最相似的5条
.withSimilarityThreshold(0.7) // 相似度阈值,低于0.7的不返回
);
// 3. 删除
vectorStore.delete(List.of("doc-id-1", "doc-id-2"));
8. Token 与上下文窗口——理解 LLM 的"胃口"
8.1 什么是 Token?
Token 是 LLM 处理文本的最小单位,不是字,不是词,而是模型自己的分词结果。
英文:
"Hello world" → ["Hello", " world"] → 2 tokens
中文:
"你好世界" → ["你好", "世界"] → 2 tokens(约 1 个汉字 ≈ 1-2 个 token)
代码:
"System.out.println" → ["System", ".", "out", ".", "print", "ln"] → 6 tokens
粗略估算:
- 英文:1 token ≈ 4 个字符 ≈ 0.75 个单词
- 中文:1 token ≈ 1-2 个汉字
- 1000 个中文汉字 ≈ 500-1000 tokens
8.2 上下文窗口(Context Window)
上下文窗口是 LLM 一次请求能处理的最大 Token 总数,包括输入 + 输出。
| 模型 | 上下文窗口 | 约等于中文字数 |
|---|---|---|
| GPT-3.5 | 4K / 16K tokens | 约 8K-16K 字 |
| GPT-4o | 128K tokens | 约 64K-128K 字 |
| GLM-4 | 128K tokens | 约 64K-128K 字 |
| Claude 3 | 200K tokens | 约 100K-200K 字 |
| DeepSeek V3 | 64K tokens | 约 32K-64K 字 |
8.3 为什么上下文窗口很大了还需要 RAG?
很多初学者会问:“GPT-4o 有 128K 窗口,直接把所有文档塞进去不就行了?”
答案是不行,原因如下:
| 原因 | 说明 |
|---|---|
| 成本问题 | Token 按量计费,128K 输入一次调用就要花好几块钱。RAG 只检索相关片段(Top-5 约 2K tokens),成本降低 90%+ |
| "大海捞针"问题 | 研究表明,当输入超长时,LLM 对中间位置的信息关注度下降(Lost in the Middle 现象)。信息越精准,回答越好 |
| 延迟问题 | 输入 Token 越多,首次响应延迟越高。128K 输入可能要等几十秒 |
| 知识库规模 | 企业知识库可能有几百万篇文档,远超任何模型的窗口 |
| 动态更新 | 新文档随时添加到向量数据库,无需重新训练模型 |
小结:长上下文和 RAG 不是替代关系,而是互补关系。RAG 负责从海量数据中精准检索,长上下文让模型能处理更多检索结果。两者结合才是最优方案。
9. Prompt Engineering 与 RAG 的关系
RAG 的最后一步是把检索到的文档注入 Prompt 中,这就涉及到 Prompt 的构造方式。
RAG 的增强 Prompt 模板
┌──────────────── 增强后的 Prompt ────────────────┐
│ │
│ 【系统提示词】 │
│ 你是一个专业的技术助手。请根据以下参考资料 │
│ 回答用户的问题。如果参考资料中没有相关信息, │
│ 请明确告知用户你不知道,不要编造。 │
│ │
│ 【检索到的参考资料】(由 RAG 检索步骤提供) │
│ --- │
│ [文档1] Agent 通过 Tool Calling 机制自主决策, │
│ 模型会根据用户问题选择合适的工具... │
│ --- │
│ [文档2] Spring AI 的 @Tool 注解可以将 Java │
│ 方法注册为模型可调用的工具... │
│ --- │
│ │
│ 【用户问题】 │
│ Agent 是怎么调用工具的? │
│ │
└──────────────────────────────────────────────────┘
关键设计原则:
| 原则 | 说明 | 为什么重要 |
|---|---|---|
| 明确要求基于资料回答 | “请根据以下参考资料回答” | 减少幻觉,让模型依赖事实 |
| 允许说不知道 | “如果资料中没有相关信息,请明确告知” | 防止编造 |
| 标注来源 | 给每个文档块编号或标注出处 | 方便用户溯源验证 |
| 控制回答格式 | “用中文回答”、“分点列出” | 提升回答质量 |
10. Spring AI 中的 RAG 组件映射
如果你使用 Spring AI 框架,以下是 RAG 每个步骤对应的组件:
| RAG 步骤 | Spring AI 组件 | 说明 |
|---|---|---|
| ① 文档加载 | DocumentReader |
支持 PDF、TXT、Markdown、JSON 等格式 |
| ② 文本分块 | TextSplitter(如 TokenTextSplitter) |
按 Token 数切分,支持重叠 |
| ③ 向量化 | EmbeddingModel(如 ZhiPuAiEmbeddingModel) |
调用 Embedding API 将文本转为向量 |
| ④ 存入向量库 | VectorStore(如 SimpleVectorStore) |
支持多种向量数据库 |
| ⑤ 查询向量化 | EmbeddingModel(同上) |
用同一个模型将查询文本转为向量 |
| ⑥ 相似度检索 | VectorStore.similaritySearch() |
返回 Top-K 最相似文档 |
| ⑦ 注入 Prompt | QuestionAnswerAdvisor |
自动将检索结果注入 Prompt |
| ⑧ LLM 生成 | ChatModel / ChatClient |
基于增强后的 Prompt 生成回答 |
代码示意(完整 RAG 流程)
// === 离线阶段:构建知识库索引 ===
// 1. 加载文档
DocumentReader reader = new TextReader(new ClassPathResource("knowledge.txt"));
List<Document> documents = reader.get();
// 2. 分块
TokenTextSplitter splitter = new TokenTextSplitter();
List<Document> chunks = splitter.split(documents);
// 3-4. 向量化 + 存入向量库(VectorStore 内部自动调用 EmbeddingModel)
vectorStore.add(chunks);
// === 在线阶段:用户查询 ===
// 5-8. ChatClient + QuestionAnswerAdvisor 自动完成:检索 → 注入 → 生成
ChatClient chatClient = chatClientBuilder
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore)) // 核心:RAG Advisor
.build();
String answer = chatClient.prompt()
.user("Agent 是怎么调用工具的?")
.call()
.content();
// 内部流程:
// user问题 → EmbeddingModel → 查询向量 → VectorStore检索Top-K
// → 拼接到Prompt → ChatModel生成回答
11. RAG 检索质量优化(进阶预览)
基础 RAG 跑通之后,你会发现检索质量往往不够理想。以下是常见的优化方向,作为后续学习的路线图:
| 优化策略 | 原理 | 效果 |
|---|---|---|
| Query 改写 | 用 LLM 先扩展/改写用户问题,再检索 | 提升召回率 |
| HyDE | 让 LLM 先"假设性回答",用回答去检索 | 检索更精准(反直觉但有效) |
| Reranker(重排序) | 检索后用交叉编码器对结果重新打分排序 | 过滤低质量结果 |
| 多路召回 | 同时用向量检索 + 关键词检索(BM25),合并去重 | 兼顾语义和关键词匹配 |
| Parent-Child 分块 | 小块用于检索(精准),返回时给大块(完整上下文) | 兼顾精度和上下文 |
| 元数据过滤 | 在向量检索前先按类型、日期等元数据缩小范围 | 减少噪声 |
基础 RAG 流程:
问题 → Embedding → 向量检索 → Top-K → LLM
优化后的 RAG 流程:
问题 → Query改写 → Embedding → 向量检索 + BM25检索
→ 合并去重 → Reranker重排序 → Top-K → LLM
这些优化在后续的 RAG 实战文章中会逐一详细讲解。
12. 总结
本文覆盖了学习 RAG 之前必须掌握的所有核心前置知识:
| 知识模块 | 核心要点 | 重要程度 |
|---|---|---|
| 向量(Vector) | 用有序数字表示语义,语义相近→距离近 | ⭐⭐⭐⭐⭐ |
| Embedding | 通过预训练模型将文本编码为固定维度向量 | ⭐⭐⭐⭐⭐ |
| 相似度计算 | 余弦相似度最常用,衡量语义方向一致性 | ⭐⭐⭐⭐ |
| 文本分块 | 滑动窗口分块最推荐,chunk_size 300-500 | ⭐⭐⭐⭐⭐ |
| 向量数据库 | HNSW 索引 + 近似最近邻搜索,毫秒级检索 | ⭐⭐⭐⭐ |
| Token 与上下文窗口 | 理解 LLM 的容量限制,解释 RAG 的必要性 | ⭐⭐⭐⭐ |
| Prompt Engineering | RAG 的最后一步:检索结果 + 用户问题 → 增强 Prompt | ⭐⭐⭐ |
核心认知:
- RAG = 检索(R)+ 增强(A)+ 生成(G),本质是给 LLM 做"开卷考试"
- 向量是桥梁——连接"人类语言"和"数学计算",让计算机能理解语义
- Embedding 是关键技术——将变长文本压缩为定长向量,是 RAG 检索的基础
- 分块决定上限——再好的模型、再好的向量数据库,如果分块策略不对,检索结果就不精准
- RAG 不是替代长上下文——两者互补,RAG 负责精准检索,长上下文负责处理更多结果
13. 参考资料
Spring AI 官方文档
- Spring AI RAG 文档 — RAG 核心文档
- Spring AI VectorStore — 向量数据库集成
- Spring AI Embedding Models — Embedding 模型文档
- Spring AI ETL Pipeline — 文档加载与分块
学术与技术文章
- Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks — RAG 原始论文(Meta,2020)
- Lost in the Middle: How Language Models Use Long Contexts — 长上下文注意力下降问题
- Efficient and robust approximate nearest neighbor search using HNSW — HNSW 算法论文
向量数据库官方文档
- Milvus 文档 — 分布式向量数据库
- Chroma 文档 — 轻量级向量数据库
- pgvector 文档 — PostgreSQL 向量扩展
其他推荐阅读
- OpenAI Embedding 指南 — 官方 Embedding 最佳实践
- Chunking Strategies for LLM Applications — Pinecone 分块策略指南
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)