# 18 - 向量数据库与 Embedding 实战

---

**本章课程目标：**

- 理解 **向量（Vector）**、**向量化（Embedding）**、**向量数据库（Vector Store / Vector Database）** 这三件事分别是什么，以及它们之间的关系。
- 建立一条清晰主线：**文本先向量化，再写入向量库，再做相似性检索**；学完本章后，可以自然衔接到 [第 19 章 RAG 检索增强生成](19-RAG检索增强生成.md)。

**学习建议：** 这章先画链路，再看代码：文本经过 Embedding 模型变成向量，向量进入数据库，查询时再用相似度找回来。API 名称可以后记，但要弄清“存进去的是什么、查出来的是什么、相似度为什么能代表相关性”。如果有余力，用两三条短文本手算或打印一次相似度，会比空背概念扎实。

**官方文档与资源**：详见 [工具导航与参考资料索引 - RAG与向量检索](工具导航与参考资料索引.md#RAG与向量检索)。

---

## 1、向量与向量化

### 1.1 向量的定义

**向量（Vector）** 本来是数学概念，指的是：**用一组有顺序的数字来表示某个对象在空间中的位置或特征。**

比如：

- 二维向量可以写成 `(x, y)`
- 三维向量可以写成 `(x, y, z)`
- 在 AI 场景里，常见的是几百维、上千维的高维向量，例如 `1024` 维、`1536` 维

入门阶段不必先纠结高维空间的数学细节，先建立这个直觉即可：

**向量就是一串数字，而这串数字可以拿来表示一段文本、一张图片，或者其他对象的“特征”。**

### 1.2 向量化的定义

把文本、图片、音频等原始内容转换成向量的过程，通常叫 **向量化**；在大模型与检索领域里，更常见的名字是 **Embedding（嵌入）**。

平时说 Embedding，有时指“向量化这个过程”，有时也指“向量化之后得到的向量表示”。结合上下文判断即可，不需要把它们拆成两个完全无关的概念。

LangChain 官方文档把文本 Embedding 说得很清楚：**Embedding 模型会把句子、段落等原始文本转换成固定长度的数字向量，并尽量让语义相近的文本在向量空间里靠得更近。**

下图可以帮助你建立整体印象：左边是原始内容，中间是嵌入模型，右边是一串串向量。

![文本、图像等通过嵌入模型转为向量，便于相似性计算与检索](images/18/18-1-2-1.jpeg)

用更直白的话说，Embedding 解决的是：

- 文本本身不能直接做“语义上的数学比较”
- 向量可以做距离和相似度计算
- 所以只要把文本变成向量，就能做“按意思查找相近内容”

这也是为什么 Embedding 常被用在：语义搜索、推荐系统、文本聚类、去重与相似内容识别、RAG 检索增强生成。

### 1.3 语义相近与向量相近

Embedding 的核心价值，不是“把文本编码成数字”这么简单，而是：**它希望把“语义关系”映射成“空间关系”。**

也就是说：语义接近的文本，向量更接近；语义差异大的文本，向量更远。比如：“我喜欢吃苹果”、“苹果是我最喜欢吃的水果”。

这两句话虽然字面不完全一样，但语义接近，所以向量通常也会更接近。  
而“我喜欢用苹果手机”虽然也有“苹果”两个字，但语义已经部分转向“手机品牌”，和前两句的关系就没那么近。

这正是向量检索比关键词检索更强的地方：**它找的是“意思接近”，不只是“词面一致”。**

### 1.4 向量维度的定义

Embedding 输出的向量，一般是固定长度的浮点数列表。这个长度就叫 **维度（dimension）**。

例如：

- 某个模型输出 1024 维向量
- 就意味着每段文本最终会变成一个长度为 1024 的数组

这里先记住两个结论：

1. **不同 Embedding 模型输出维度可能不同。**
2. **即便维度相同，不同模型的向量空间通常也不能直接混用。**

所以真实项目里要保证：**建库时用的 Embedding 模型，和查询时用的 Embedding 模型保持一致。**

否则要么维度不匹配直接报错，要么即使能算，相似度结果也没有意义。

### 1.5 进一步建立直觉

下面两张图分别帮助你理解“图像也可以向量化”和“向量空间会影响检索效果”。

![图像向量化与对比示意](images/18/18-1-5-1.jpeg)

> **图意说明：** 同品种毛色不同、同毛色体型不同——类比向量检索中「按特征维度」区分样本；用于建立「**向量 = 特征坐标**」的直觉（本章主线仍以文本 Embedding 为主）。

![向量维度与检索效果](images/18/18-1-5-2.jpeg)

> **图意说明：** 高维 Embedding 可理解为「更多维度的特征轴」；维度与模型设计相关，**检索效果依赖模型与索引质量**，而非单看维数高低。

**这一节先记住一句话：** Embedding 不是为了“把文本变难懂”，而是为了让计算机能在数学空间里比较语义。

---

## 2、向量数据库

当文本已经变成向量后，下一个问题就来了：**这些向量存到哪里？又怎么高效地查“谁和我最像”？**这就是向量数据库存在的原因。

### 2.1 定义

LangChain 官方概括非常直接：**向量存储（Vector Store）就是存储嵌入后的数据，并支持相似性搜索。**Redis 官方文档则强调了另一个重点：**向量检索不是只存向量，而是围绕向量建立索引，并结合元数据做搜索与过滤。**

所以，可以先把“向量数据库”理解成：**一种专门面向“相似度检索”的存储系统。**

### 2.2 与传统数据库的区别

最核心的区别不在“能不能存数据”，而在“怎么查数据”。

| 对比维度     | 传统关系型数据库 / 普通 KV 存储 | 向量数据库                           |
| ------------ | ------------------------------- | ------------------------------------ |
| 核心查询方式 | 精确匹配、范围查询、条件过滤    | 相似性搜索、最近邻检索               |
| 典型问题     | “id=1001 的记录是什么？”        | “和这段文本语义最接近的内容是什么？” |
| 主要依据     | 字段值相等、大小、排序          | 向量距离 / 相似度                    |
| 常见用途     | 订单、用户、交易、配置          | RAG、搜索、推荐、去重、聚类          |

![传统数据库与向量数据库的查询方式对比：字段精确查询侧重值匹配，向量检索侧重语义相似](images/18/18-2-2-1.png)

这并不代表传统数据库没用，而是说：**当你的问题从“查字段”变成“查意思”时，就要引入向量数据库。**

### 2.3 向量数据库里存什么

这是很多人第一次学时最容易混淆的地方。向量数据库里通常不只是“一个向量数组”，而是至少会包含下面几类信息：

- **原始内容**：例如文本正文 `page_content`
- **向量值**：Embedding 模型算出来的高维数组
- **元数据**：例如 `source`、`page`、`segment_id`、业务标签等
- **索引结构**：用于加速相似度检索

也就是说，向量数据库真正做的是：**把“内容 + 向量 + 元数据”组织起来，并支持按相似度查最相关内容。**

这件事对 RAG 非常关键，因为检索阶段不仅要拿到“最像的向量”，还要拿回对应的文本片段和元数据，最后才能交给大模型生成答案。

### 2.4 稠密向量、稀疏向量、标量字段定义

学到这里，很多人会先入为主地以为：向量数据库里只存“一个文本向量字段”。真实项目里当然可以这么做，但更完整的理解是：**一个可检索的数据对象，往往同时包含稠密向量、标量字段，某些系统里还会额外使用稀疏向量。**

- **稠密向量（Dense Vector）**：最常见，就是 Embedding 模型输出的一长串浮点数。它擅长表达整体语义，所以“意思相近”的文本通常会更接近。
- **稀疏向量（Sparse Vector）**：不是每一维都有值，而是只有少数维度非零。它更像“关键词及权重”的表达方式，常和词项匹配、倒排索引、BM25 一类思路联系在一起。
- **标量字段（Scalar Fields）**：例如 `source`、`author`、`category`、`created_at`、`doc_id` 这类普通字段。它们不参与语义向量计算，但常用于过滤、排序、权限控制和结果展示。

可以先把它们看成三类互补能力：

- 稠密向量负责“按语义找相近内容”
- 稀疏向量更偏“按关键词和词权重找相关内容”
- 标量字段负责“按业务条件过滤结果”

![稠密向量与稀疏向量对比：稠密向量表达整体语义，稀疏向量更接近关键词权重](images/18/18-2-4-1.png)

**扩展：BGE-M3 与混合检索**

以 BGE-M3 这类 Embedding 模型为例，它可以同时支持稠密向量、稀疏向量等多种表示方式。放到检索系统里，就可以形成这样的搭配：

| 检索信号 | 更擅长解决的问题                               |
| -------- | ---------------------------------------------- |
| 稠密向量 | 用户换一种说法提问时，仍然能按语义找到相关内容 |
| 稀疏向量 | 产品型号、错误码、专有名词、精确关键词匹配     |
| 标量字段 | 权限、分类、时间、来源、业务标签过滤           |

所以生产 RAG 里经常会看到“稠密检索 + 稀疏检索 + metadata filter + rerank”的组合。它不是为了把系统做复杂，而是因为单一路径很难同时兼顾语义、关键词和业务约束。

本章的 Redis 案例主线，主要聚焦在**文本 Embedding 生成的稠密向量 + 元数据字段**。这样已经足够搭建一条清晰的入门链路。但你后面如果看到“混合检索”“稠密+稀疏召回”“metadata filter”这些词，就不会觉得陌生了。

### 2.5 常见的向量数据库分类

入门阶段不需要全学，但至少要知道生态里有哪些常见方案：

| 名称                           | 简要说明                                 |
| ------------------------------ | ---------------------------------------- |
| **FAISS**                      | 偏本地、偏算法库，适合快速做向量检索实验 |
| **Chroma**                     | 轻量、好上手，适合本地原型验证           |
| **Milvus**                     | 开源专业向量数据库，适合大规模生产环境   |
| **Pgvector**                   | PostgreSQL 扩展，适合已有 PG 体系的项目  |
| **Redis / Redis Stack**        | 既能做缓存，也能做向量检索，适合工程整合 |
| **Elasticsearch / OpenSearch** | 搜索体系成熟，也支持向量搜索             |

### 2.6 RAG 与向量数据库的关系

因为大多数人第一次接触向量数据库，往往就是在做 RAG。

RAG 的底层链路通常是：

1. 把文档切成片段
2. 用 Embedding 模型把片段转成向量
3. 把“片段 + 向量 + 元数据”写入向量数据库
4. 用户提问时，把问题也转成向量
5. 在向量数据库里查最相关片段
6. 把片段和问题一起交给大模型生成答案

也就是说，**向量数据库是 RAG 的底层基础设施之一，但它本身不等于完整 RAG。**

![向量与向量库在知识体系中的位置](images/18/18-2-6-1.jpeg)

> **图意说明：** 上半为**索引/入库**链路，下半为**查询**链路；向量库处于「分段文本向量化之后、检索 top-K 片段之后交给模型」的关键位置。完整加载器、切分器与生成环节见 [第 19 章 RAG](19-RAG检索增强生成.md)。

### 2.7 Redis 与 Milvus 怎么选

本教程选择 Redis Stack，是因为它容易和缓存、会话、队列等工程场景放在一起，安装和演示成本也低。入门阶段用它理解“写入向量、建立索引、相似检索”非常合适。

但如果你的项目进入更大规模，例如数据量很大、索引策略复杂、混合检索和高并发检索要求更高，就可以把 **Milvus** 作为生产级扩展方向。先记一个简单判断：

| 场景                                         | 更适合      |
| -------------------------------------------- | ----------- |
| 课程学习、个人项目、和 Redis 生态整合        | Redis Stack |
| 大规模向量检索、专业索引管理、生产级检索平台 | Milvus      |

---

## 3、用 Redis Stack 作为向量存储

本教程选择 **Redis / Redis Stack** 来做向量存储，不是因为它是唯一方案，而是因为它适合教学和工程入门：

- 项目前面已经在 [第 16 章](16-记忆与对话历史（含Redis基础）.md) 引入了 Redis
- Redis Stack 在 Redis 基础上集成了搜索与向量检索能力
- 对入门学习来说，一套环境就能同时覆盖缓存、消息、历史记录、向量检索，工程连贯性会更强

### 3.1 Redis、Redis Stack、RediSearch 三者关系

- **Redis**：基础内存数据库 / 键值存储
- **Redis Stack**：在 Redis 基础上打包了搜索、JSON、时间序列、布隆过滤器等扩展能力
- **RediSearch**：Redis Stack 中和搜索、全文检索、向量检索强相关的模块

从本章角度看，直接记住一句话就够了：**我们之所以能用 Redis 做向量检索，关键是 Redis Stack / RediSearch 提供了向量字段、索引和 KNN 搜索能力。**

Redis 官方文档里也明确提到，Redis 可以：

- 创建向量索引
- 存储向量和元数据
- 做 KNN（最近邻）向量搜索
- 结合元数据过滤做混合检索

### 3.2 本教程为什么选 Redis

从真实项目角度看，Redis 作为向量存储有几个很实用的优点：

- **上手成本低**：很多团队本来就已经有 Redis
- **工程整合方便**：缓存、会话、历史记录、向量检索可以放在同一套基础设施里
- **适合中小型知识库**：做课程演示、内部工具、轻量 RAG 很顺手

当然，工程里也要知道它的边界：如果向量规模非常大、检索需求非常复杂，团队也可能选择 Milvus、Weaviate、Pinecone 等更专门的方案；这不是“谁绝对更好”的问题，而是“你的场景更适合哪种方案”。

如果你还没有装好 Redis Stack，请先回看 [第 16 章](16-记忆与对话历史（含Redis基础）.md) 里关于 Docker、Redis Stack、RedisInsight 的部分；本章不重复安装过程，只聚焦“Embedding + 向量检索”这条主线。

> 先区分一个容易误解的点
>
> - 做向量检索时，真正写入 Redis 的不是“裸向量”而已，通常是：文本内容、文本对应的向量、元数据、与索引相关的结构信息。
> - 所以你在 RedisInsight 里看到的记录，往往不是一个简单数字数组，而是多个字段组成的结构化数据。这一点到了本章第 6 节看案例时会更直观。

---

## 4、Embedding 文本向量化

这一节是本章的核心。因为后面不管是算相似度，还是写入 Redis，本质上都依赖同一步：**先把文本变成向量。**

### 4.1 定义

**Embedding（嵌入）** 是将文本字符串表示为**向量（浮点数列表）**的过程。通过计算向量之间的距离或相似度，可以衡量文本之间的相关性：**距离越小（或相似度越高），相关性越高**；距离越大，相关性越低。

**一句话概括：** Embeddings 用来衡量文本之间的相关性。

**常见应用包括：**

- **搜索**：按与查询的相关性对结果排序
- **聚类**：按文本相似性分组
- **推荐**：根据相关文本推荐内容
- **异常检测**：找出与多数内容相关性较低的异常点
- **多样性测量**：分析相似性分布
- **分类**：按与标签的相似性对文本分类

LangChain 官方则进一步强调了两个 API：

- `embed_query(text)`：把单条查询文本转成向量
- `embed_documents(texts)`：把多条文档文本批量转成向量

这一组区分正好对应真实项目里的两个阶段：

- **索引阶段**：把文档片段批量向量化，通常用 `embed_documents`
- **查询阶段**：把用户问题向量化，通常用 `embed_query`

### 4.2 重要实践规则

跑案例前，先看清这几条经验：

1. Embedding 模型输出的是向量，不是自然语言。
2. 不同 Embedding 模型的维度可能不同。
3. 建索引和查询必须使用同一套 Embedding 模型。
4. Embedding 适合做语义相似度计算，但它本身不负责“回答问题”。
5. 本章主线是文本 Embedding；多模态 Embedding 属于进阶扩展。

第 5 条专门解释一下。LangChain 当前官方 Embeddings 总览页主要聚焦 **文本 Embedding**；本章保留的多模态案例是通过 **DashScope 原生 SDK** 来展示扩展能力，目的是帮助你建立“Embedding 不只可用于文本”的认知，但本章的学习主线仍然是 **文本向量化**。

### 4.3 案例：DashScope 原生调用，先看到“向量长什么样”

这是最简单的 Hello 级示例，目标不是马上做检索，而是先看到：

- 文本是怎么传给 Embedding 模型的
- 返回结果结构大概长什么样
- 向量通常是怎样的一串浮点数

【案例源码】`案例与源码-2-LangChain框架/09-embedding/Text2Embedding_DashScopeHello.py`

[Text2Embedding_DashScopeHello.py](案例与源码-2-LangChain框架/09-embedding/Text2Embedding_DashScopeHello.py ":include :type=code")

学习这个案例时，建议你重点观察：

- `model` 指定的是哪个 Embedding 模型
- 返回对象里 `embedding` 在哪一层
- 这一条文本最终输出的是多长的向量

### 4.4 案例：OpenAI 兼容写法，理解“同一能力，不同接法”

很多平台虽然不是 OpenAI，但会提供 **OpenAI 兼容接口**。这意味着你可以继续用 OpenAI SDK 的调用方式，只是把：`base_url`、`api_key`、`model`改成对应平台的配置即可。

这个案例的价值在真实项目里非常大，因为它会让你意识到：**很多时候你真正学的不是“某一家厂商的私有 SDK”，而是一种通用接入模式。**

【案例源码】`案例与源码-2-LangChain框架/09-embedding/Text2Embedding_OpenAiHello.py`

[Text2Embedding_OpenAiHello.py](案例与源码-2-LangChain框架/09-embedding/Text2Embedding_OpenAiHello.py ":include :type=code")

### 4.5 案例：用 LangChain 的统一接口做单条与批量向量化

当你开始做 LangChain 项目时，更推荐理解这一层封装。因为后面不管是检索器、向量库、RAG，很多组件都是围绕 LangChain 的 Embeddings 接口来协作的。

这个案例非常关键，因为它直接演示了：

- `embed_query(text)`：单条查询文本向量化
- `embed_documents(texts)`：多条文档文本批量向量化

这两个方法，基本就是后面做检索的“索引阶段”和“查询阶段”的缩影。

【案例源码】`案例与源码-2-LangChain框架/09-embedding/Text2Embedding_DashScope.py`

[Text2Embedding_DashScope.py](案例与源码-2-LangChain框架/09-embedding/Text2Embedding_DashScope.py ":include :type=code")

运行时你可以特别留意：单条返回的是一个向量；批量返回的是“向量列表”；len(向量)对应维度。

### 4.6 案例：进阶扩展，多模态 Embedding

这一节不作为本章主线要求，主要帮助你知道：Embedding 不只可以处理文本，也可以扩展到图文等多模态输入。不过要注意，本案例使用的是 **DashScope 原生多模态 Embedding 接口**，并不是 LangChain 统一文本 Embeddings 接口本身。

初学者先知道两件事就够了：

1. **Embedding 的思想不只适用于文本。**
2. **本章先把文本 Embedding 学稳，多模态后面按需拓展。**

【案例源码】`案例与源码-2-LangChain框架/09-embedding/Text2Embedding_DashScopePro.py`

[Text2Embedding_DashScopePro.py](案例与源码-2-LangChain框架/09-embedding/Text2Embedding_DashScopePro.py ":include :type=code")

---

## 5、通过向量计算语义相似度

当我们已经能把文本变成向量之后，就可以继续做下一步：**比较两段文本在语义上有多接近。**

### 5.1 为什么常用余弦相似度

在向量相似度计算里，常见指标有：

- **余弦相似度（Cosine Similarity）**
- **欧氏距离（Euclidean Distance）**
- **点积（Dot Product）**

其中最适合入门先建立直觉的，通常是 **余弦相似度**。它关注的重点不是“两个向量长度有多像”，而是“两个向量方向有多接近”。对语义检索来说，这通常很有价值，因为我们更关心“意思是否接近”。

余弦相似度公式：

```text
cos(theta) = (A · B) / (|A| |B|)
```

它的结果通常在 `[-1, 1]` 范围内：

- 越接近 `1`，通常表示越相似
- 越接近 `0`，表示相关性较弱
- 越接近 `-1`，表示方向相反

OpenAI 官方文档里也明确提到：在很多 Embedding 场景里，**余弦相似度是非常常见、非常实用的选择**。

### 5.2 检索里常见的距离度量怎么选

前面我们用余弦相似度建立了直觉，但真实项目里，你在向量库、索引或 SDK 参数中还会经常看到这几个名字：

- **COSINE**：更关注向量方向是否接近，是文本语义检索里最常见、也最容易理解的一类度量。
- **L2**：欧氏距离，关注两个点在空间里的直线距离。距离越小，通常表示越接近。
- **IP（Inner Product）**：内积。某些模型或系统会直接用它做相似度计算；在向量已归一化时，它和余弦相似度往往会非常接近。

这里不要纠结“谁永远最好”，先看这条规则：

**Embedding 模型的特性、索引建立时选择的度量方式、查询时传入的 metric，三者必须保持一致。**

否则常见问题有两个：轻则结果变差，明明是相关内容却排不上来；重则索引配置和查询配置不匹配，直接报错。

所以当你看到某个向量库示例里写的是 `COSINE`、`L2` 或 `IP`，不要把它当成无关紧要的参数；它本质上决定了系统如何理解“相似”。

### 5.3 精确检索、近似检索与索引

另一个很值得尽早建立的工程概念是：**不是所有相似度检索，都会老老实实把查询向量和库里每一个向量逐个比较。**

从思路上看，大致可以分成两类：

- **精确检索（Exact KNN / FLAT）**：把查询向量和库里的所有向量都算一遍，结果最直接，也最容易理解。
- **近似检索（ANN, Approximate Nearest Neighbor）**：通过索引结构加速搜索，只近似地找到“最可能接近”的一批结果。

为什么需要近似检索？因为数据一旦上规模，逐个比较会越来越慢。于是很多向量数据库会建立索引，用空间换时间。你在不同系统里经常会见到：

- **FLAT**：更偏暴力搜索，准确但慢
- **HNSW**：工程里很常见的 ANN 索引，通常能在召回率和延迟之间取得较好平衡

![HNSW 索引示意：通过分层近邻图减少全量比较，在召回率与查询速度之间折中](images/18/18-5-3-1.png)

入门阶段可以先记住一句话：**索引的作用不是改变“语义相似”的定义，而是让“找最相似内容”这件事在大规模数据下也能跑得动。**

这也是为什么本章前面一直强调：检索效果不只取决于 Embedding 模型，还和索引质量、参数配置、数据切分方式一起决定最终结果。

### 5.4 案例：把多句话转成向量，再两两比较

这个案例会：

1. 准备多句文本
2. 调用 Embedding 接口拿到每句文本的向量
3. 用 `numpy` 手动计算余弦相似度
4. 打印两两比较结果

【案例源码】`案例与源码-2-LangChain框架/09-embedding/Text2Embedding_CosSimilarity.py`

[Text2Embedding_CosSimilarity.py](案例与源码-2-LangChain框架/09-embedding/Text2Embedding_CosSimilarity.py ":include :type=code")

学习这个案例时，建议重点抓住两件事：

- 重点不是“必须用哪一个模型”，而是**拿到向量后怎么做相似度计算**
- 当两句话主题更接近时，余弦相似度通常会更高

这个案例里使用的是多模态 Embedding 接口来处理文本输入，课程想演示的是“**向量一旦拿到手，就可以做数学比较**”这件事。这并不表示相似度计算必须用多模态模型；真实项目里，你完全可以用常规文本 Embedding 模型完成同样的计算。

### 5.5 相似度结果在项目里怎么用

这类分数在真实项目里最常见的用途有：

- **语义检索排序**：把最相关的文本排在前面
- **文本去重**：判断两段内容是否高度重复
- **推荐**：找相似商品、相似文章、相似问题
- **聚类分析**：把语义相近的内容分成一组

所以，不要把“余弦相似度”只理解成一道数学题。在工程里，它通常直接决定了：**检索结果排前面的内容是不是你真正想要的内容。**

---

## 6、向量库的写入与检索（RAG 的底层能力）

前面我们完成了两件事：1. 把文本转成向量；2. 学会了计算向量之间的相似度。

这一节把这两件事往前再推一步，进入真正的工程链路：**把文本和向量写入 Redis，然后按相似度做检索。**

先强调一个边界：**这一节还不是完整 RAG。**这里演示的是 RAG 的底层能力，也就是：建索引，做相似度检索。但还没有加入：文档加载器，文本切分器，检索结果交给大模型生成最终答案。

这些会在 [第 19 章 RAG 检索增强生成](19-RAG检索增强生成.md) 继续展开。

### 6.1 案例：把 Document 列表写入 Redis，再用检索器取回结果

这是本章最适合作为“第一眼向量库实战”的案例。

它做的事情可以概括成：

1. 先准备若干 `Document`（`page_content` + `metadata`，在完整 RAG 中常由加载器与切分器产生，见 [第 19 章](19-RAG检索增强生成.md)）
2. 用 `DashScopeEmbeddings` 把 `page_content` 向量化
3. 用 `Redis.from_documents(...)` 一次性写入 Redis
4. 再通过 `as_retriever()` 生成检索器
5. 对查询文本做相似度检索

这个案例最值得初学者理解的地方是：

- 向量库里存的不是孤零零的数字，而是和 `Document` 结构结合起来的内容
- 检索出来的结果不是“向量本身”，而是对应的 `Document`
- 这正是 RAG 后面能把检索结果塞回 Prompt 的基础

【案例源码】`案例与源码-2-LangChain框架/09-embedding/EmbeddingStoreRedis.py`

[EmbeddingStoreRedis.py](案例与源码-2-LangChain框架/09-embedding/EmbeddingStoreRedis.py ":include :type=code")

运行后，你可以在 RedisInsight 里看到类似下图的结构：

![RedisInsight 中查看写入的文档与向量](images/18/18-6-1-1.jpg)

看到这张图时，建议你重点理解：

- `content` 一类字段是原始文本
- `content_vector` 一类字段是 Embedding 后的向量
- `source` 等字段是元数据

也就是说，Redis 在这里保存的是一份“可检索的语义索引”，而不是只存了一组浮点数。

### 6.2 案例：使用 langchain_redis 的 RedisVectorStore 写入文本

除了 `langchain_community.vectorstores.Redis`，项目里还保留了另一套更贴近专门 Redis 集成包的写法：

- `RedisConfig`
- `RedisVectorStore`
- `add_texts(...)`

这个案例适合帮助你理解另一种常见思路：**先创建一个向量库实例，再持续往里面追加文本。**

【案例源码】`案例与源码-2-LangChain框架/10-rag/RedisVectorStore.py`

[RedisVectorStore.py](案例与源码-2-LangChain框架/10-rag/RedisVectorStore.py ":include :type=code")

这个案例有几个学习重点：

- `add_texts()` 适合写字符串列表
- 每条文本可以带自己的 `metadata`
- 返回的 `ids` 可用于后续删除、更新或追踪

这类写法在真实项目里很常见，因为很多时候数据并不是一次性导入，而是：

- 批量导入
- 增量追加
- 定时重建索引

### 6.3 案例：连接已有索引，做相似性检索

这个案例和上一节是一组配套案例。它假设你已经写入过数据，然后再去做查询。

核心动作是：

- 连接已有的 `index_name`
- 把查询文本向量化
- 在 Redis 中找最相近的若干条文本
- 返回 `(Document, score)` 结果

【案例源码】`案例与源码-2-LangChain框架/10-rag/RedisVectorStore_SimilaritySearch.py`

[RedisVectorStore_SimilaritySearch.py](案例与源码-2-LangChain框架/10-rag/RedisVectorStore_SimilaritySearch.py ":include :type=code")

这里需要先区分 `score` 的含义：

**`similarity_search_with_score()` 返回的 `score`，在不少实现里表示“距离”，不一定是“越大越相似”的分数。**

所以：

- 有些场景下，`score` 越小反而表示越相似
- 代码里把它换算成 `1 - score`，更多是为了课程演示时方便直觉理解
- 实际项目中，要以当前向量库、距离度量方式和返回值定义为准

如果后续要设置召回阈值，第一步不是直接套用某个分数，而是先确认这个 `score` 表示距离还是相似度。

### 6.4 元数据过滤、混合检索与重排序

很多人在跑完第一个向量检索案例后，会误以为“检索 = 把问题转成向量，然后直接搜 top-k”。这条主线当然没错，但真实项目里，通常还会再叠加三类能力：

- **元数据过滤（metadata filter）**：先用 `source`、`category`、时间范围、权限字段等条件缩小候选范围，再做向量检索。
- **混合检索（hybrid retrieval）**：同时结合语义向量检索和关键词检索，或者结合不同类型的召回路径。
- **重排序（rerank）**：先召回一批候选结果，再用更精细的模型或策略重新排序。

三者各自解决的问题很清楚：

- 过滤，解决“只在某个业务范围内找”
- 混合，解决“既想看语义，也不想丢掉关键词命中”
- 重排序，解决“已经找回来了，但顺序还不够好”

其中“重排序”可以单独看一下。它并不是重新去全库里搜索，而是对**已经召回的一小批候选结果**做二次排序优化。很多系统里，向量检索负责第一阶段召回，reranker 负责第二阶段排序，这样往往能兼顾速度和效果。

本章的 Redis 实战主要演示的是**稠密向量检索的第一阶段能力**。你后面在更完整的 RAG 系统里，如果发现“能搜到，但排序不够稳”或者“关键词信息丢了”，就可以优先从这三类增强手段入手。

### 6.5 from_documents 和 add_texts 怎么理解

这两个方法都能“把内容写入向量库”，但适合的场景不完全一样：

| 方法                  | 更适合什么场景                                 | 直观理解                     |
| --------------------- | ---------------------------------------------- | ---------------------------- |
| `from_documents(...)` | 你手里已经有一批 `Document` 对象，想一次性建库 | 更像“一步到位建索引”         |
| `add_texts(...)`      | 你有字符串列表，或想持续追加数据               | 更像“在已有向量库上增量写入” |

初学阶段你不用纠结谁绝对更高级，只要知道：

- 它们都在做“文本向量化 + 写入向量库”
- 区别主要是入参结构和工程组织方式

### 6.6 本章与第19章的关系

这一节结束后，你已经掌握了 RAG 中非常关键的底层能力：

- 文本向量化
- 向量写入 Redis
- 相似性检索

而 [第 19 章](19-RAG检索增强生成.md) 会在此基础上补齐剩余三步：

1. 用加载器把 PDF / Word / Markdown 等文档读成 `Document`
2. 用文本分割器把长文档切成片段
3. 把检索结果和用户问题一起交给大模型生成答案

### 6.7 生产级扩展方向

学完 Redis 向量检索后，可以把后续提升分成三条线：

- **数据线**：增量写入、删除更新、索引重建、版本管理。
- **检索线**：metadata filter、混合检索、rerank、阈值策略。
- **平台线**：从轻量 Redis / Chroma 过渡到 Milvus、OpenSearch 等更专业的检索平台。

这三条线不用在本章全部展开，但它们会帮助你理解：向量数据库不是“存一下向量”这么简单，而是 RAG 系统效果和性能的底座。

所以，本章和第 19 章的关系可以概括为：

- **第 18 章**：先把“向量化 + 存储 + 检索”练熟
- **第 19 章**：再把它们和文档处理、Prompt、LLM 串成完整 RAG

---

**章节思考题：**

1. 为什么 Embedding 适合做语义检索，而普通关键词匹配不够？

   **参考思路：** Embedding 把文本映射到语义空间，能捕捉同义表达和相近含义；关键词匹配更依赖字面重合。比如“退款规则”和“怎么退钱”字面不同，但语义接近。

2. 向量写入数据库时，除了向量本身，还应该保存哪些信息？

   **参考思路：** 至少要保存原文片段、来源、文档 ID、段落位置、业务标签、更新时间等 metadata。否则检索回来只有一串向量，无法展示、追溯或过滤。

3. 相似度高是否一定代表答案可用？为什么？

   **参考思路：** 不一定。向量相似只能说明语义接近，可能仍然答非所问、缺少关键条件或不是最新资料。真实 RAG 还要结合阈值、过滤、Rerank 和答案生成约束。

4. 如果查询结果总是不相关，你会先排查哪些环节？

   **参考思路：** 查文档是否写入、Embedding 模型是否一致、向量维度是否匹配、查询文本是否合理、metadata 是否过滤过严、相似度计算和 Top K 是否设置得当。

**本章小结：**

- **向量与 Embedding**：Embedding 模型会把文本转换成固定长度向量，让“语义相近”可以转化成“向量接近”。
- **向量数据库**：它解决的不是普通字段查询，而是“按语义找最接近内容”的问题；这也是 RAG 能成立的基础。
- **Redis Stack**：本教程用 Redis 做向量存储，是为了让你在已有项目基础设施上快速理解向量索引与检索。
- **案例主线**：本章已经完整走通了“文本向量化 → 相似度计算 → 写入 Redis → 相似性检索”这条链路；而真正影响效果的，除了模型本身，还包括切块方式、元数据设计、阈值和是否需要混合检索。
- 学完本章后，你至少应该：能清楚说出 **向量、Embedding、向量数据库** 三者分别是什么，以及它们之间的先后关系；知道为什么“语义相近”可以映射成“向量相近”，以及为什么建库和查询要尽量使用同一个 Embedding 模型；能把“向量化 → 相似度计算 → 写入向量库 → 相似性检索”这条主线和后续 RAG 联系起来，并意识到检索效果不只由向量库本身决定。

**建议下一步：**

1. 先把 `09-embedding` 目录下的案例按顺序跑一遍，尤其是 `Text2Embedding_DashScope.py`、`Text2Embedding_CosSimilarity.py`、`EmbeddingStoreRedis.py`。
2. 再运行 `10-rag` 目录下的 `RedisVectorStore.py` 和 `RedisVectorStore_SimilaritySearch.py`，对比两种 Redis 向量库写法。
3. 学完本章后继续阅读 [第 19 章 RAG 检索增强生成](19-RAG检索增强生成.md)，把本章的向量能力接到完整 RAG 流程里。
