Day 18:Embedding 与向量数据库

学习目标

前两天我们把文档解析干净、切成了合适的 Chunk。这些 Chunk 现在还是普通的文本字符串。今天要把它们变成可以计算相似度的数学表示——向量。这就是 Embedding(嵌入)做的事情。

然后这些向量需要一个”家”来存储和检索,这就是向量数据库。

今天是 RAG 系统中最”数学”的一天,但不要被数学吓到。我们的目标是理解 Embedding 的工程意义、知道怎么选择模型和数据库、能完成从文本到向量到检索的完整闭环。底层的数学原理知道概念就够了,不需要推导公式。

学完今天的内容,你应该能够解释”为什么两段文本语义相似,它们的向量就接近”,能够在几个主流向量数据库中做出选型决策,并且完成 Chunk 向量化和语义检索的完整流程。


核心概念

一、Embedding 工作原理

Embedding 的核心任务是把文本映射成数值向量。一段文本(一个词、一句话、一个 Chunk)经过 Embedding 模型处理后,输出一个固定长度的浮点数数组,比如 [0.023, -0.157, 0.891, …]。这个数组就是这段文本的向量表示。

向量的维度。 常见的 Embedding 模型输出的向量维度从 384 到 3072 不等。维度越高,理论上能表达越多的语义信息,但计算和存储成本也越高。大多数实用场景中,768 到 1536 维是一个合理的范围。

语义空间的直觉。 Embedding 把文本映射到一个高维空间中,这个空间中语义相似的文本距离近,语义不同的文本距离远。你可以把它想象成一张地图:北京和天津在地图上离得很近(地理上相似),北京和纽约离得很远(地理上不同)。同理,“退款流程”和”退货步骤”在语义空间中离得近(意思相近),“退款流程”和”天气预报”离得远(意思不相关)。

这是怎么做到的? Embedding 模型在海量文本上训练,学习到了词语和句子之间的语义关系。训练过程中,经常一起出现的词(比如”退款”和”退货”)被映射到相近的位置,而不相关的词(比如”退款”和”天气”)被映射到较远的位置。经过大量训练后,整个语义空间形成了一种”结构”,使得语义关系可以用几何距离来衡量。

关键洞察:Embedding 是一种压缩表示。 一段 500 字符的文本,包含的信息量远超 768 个数字能精确表达的。Embedding 做的是一种有损压缩——保留了最核心的语义信息,丢弃了细节。这就是为什么两段文本的向量相似并不等于两段文本完全一样,它们只是在核心语义上接近。

二、向量生成

在 RAG 系统中,向量生成有两个场景:离线生成和在线生成。

离线生成。 这是在文档入库阶段,把所有 Chunk 都转换成向量。通常是一个批处理任务:把所有 Chunk 文本收集起来,批量调用 Embedding API,把返回的向量和原始文本一起存入向量数据库。

离线生成需要考虑的工程问题:

  • 批处理效率。 Embedding API 通常支持批量请求(一次发送多条文本),比逐条请求效率高得多。但批量大小有上限(比如一次最多 2048 条),需要分批处理。
  • 成本控制。 Embedding API 按输入 Token 数计费。百万级 Chunk 的向量化是一笔不小的费用。需要估算总成本,可能的话在测试阶段先用小数据集验证效果。
  • 缓存策略。 同一个 Chunk 的 Embedding 结果是确定性的(给定同一个模型)。如果 Chunk 没有变化,不需要重新生成向量。可以通过 Chunk 的哈希值来判断是否需要重新生成。
  • 错误处理。 API 调用可能失败(网络错误、速率限制、服务不可用)。需要实现重试机制和断点续处理。

在线生成。 这是在用户提问时,把用户的问题转换成向量,用于检索。这是一个实时操作,对延迟敏感。

在线生成的工程要求:

  • 低延迟。 用户等不了太久。Embedding API 的调用时间通常在几十到几百毫秒,需要选择响应快的 API 端点。
  • 一致性。 离线和在线必须使用同一个 Embedding 模型。如果文档用模型 A 生成向量,用户问题用模型 B 生成向量,两个模型生成的向量空间不同,相似度计算就没有意义。

三、向量存储

向量存储需要解决两个问题:存储向量本身,以及存储对应的原始文本和元数据。

向量本身。 一个 1536 维的向量,每个维度是一个 32 位浮点数(float32),占 4 字节。一个向量占 6144 字节(约 6KB)。一百万个 Chunk 的向量约 6GB。这个规模用内存完全可以承受。

原始文本和元数据。 除了向量,还需要存储每个 Chunk 的原始文本(用于检索后拼接到 Prompt 中)和元数据(来源、章节、页码等,用于引用标注和过滤)。这些数据通常以 JSON 格式存储。

存储格式的选择。 向量数据库通常使用优化的二进制格式存储向量(比如使用 float16 而不是 float32 减半存储空间,或者使用量化技术进一步压缩)。元数据则用 JSON 或结构化存储。

四、向量数据库

向量数据库是专门为向量检索设计的存储系统。它和传统数据库的区别在于:传统数据库通过精确匹配(“名字等于’张三’“)或范围查询(“年龄大于 30”)来检索数据,而向量数据库通过相似度计算(“和这段文本语义最接近的 5 条记录”)来检索数据。

向量数据库的核心能力:

  • 向量索引。 为向量建立索引结构,使得检索时不需要和数据库中的每一条记录逐一计算相似度(那太慢了),而是通过索引快速定位到最可能相似的候选集。常见的索引算法有 HNSW(分层可导航小世界图)、IVF(倒排文件索引)、PQ(乘积量化)等。
  • 相似度计算。 支持不同的相似度度量方式。最常用的是余弦相似度(衡量两个向量方向的接近程度)和欧氏距离(衡量两个向量端点之间的直线距离)。
  • 元数据过滤。 在向量检索的基础上,支持按元数据条件过滤结果。比如”只在’产品 A 手册’这篇文档中搜索”,或者”只搜索 2024 年更新的文档”。
  • CRUD 操作。 支持向量的增删改查,可以动态更新知识库。

主流向量数据库对比:

Chroma。 开源、轻量、上手极快。适合原型验证和小规模项目。不需要独立部署服务器,嵌入到应用进程中即可使用。缺点是生产环境下的性能和可靠性未经大规模验证。

FAISS。 Meta 开源的向量检索库。严格来说不是数据库,而是一个向量索引库。需要自己实现存储和管理逻辑。性能极强,适合对延迟有极高要求的场景。缺点是不支持元数据过滤(需要自己实现),不支持网络服务(需要自己封装 API)。

Milvus。 开源分布式向量数据库,支持大规模部署。功能齐全,支持多种索引类型、元数据过滤、分布式部署。适合企业级生产环境。缺点是架构复杂,部署和维护门槛较高。

Pinecone。 全托管的向量数据库服务(SaaS)。不需要自己部署和维护,开箱即用。性能稳定,支持元数据过滤。缺点是数据存在第三方服务器上(可能不符合某些企业的合规要求),且按使用量计费,大规模使用成本较高。

Weaviate。 开源向量数据库,支持混合检索(向量 + 关键词)。内置了一些 AI 能力(如自动向量化)。适合需要混合检索的场景。

Qdrant。 用 Rust 编写的开源向量数据库,性能优异。支持丰富的过滤条件和负载管理。近两年发展迅速,社区活跃。

选型建议:

  • 学习和原型阶段:Chroma,零配置上手
  • 需要高性能但不想自己搭集群:Qdrant 或 Weaviate
  • 大规模生产环境:Milvus
  • 不想自己运维:Pinecone
  • 已有 PostgreSQL 基础设施:pgvector(PostgreSQL 的向量扩展)

五、相似度检索

相似度检索是向量数据库的核心操作:给定一个查询向量,找到数据库中和它最相似的 k 个向量。

余弦相似度。 最常用的相似度度量方式。它计算两个向量之间夹角的余弦值,结果在 -1 到 1 之间。值越接近 1 表示方向越一致(语义越相似),接近 0 表示无关,接近 -1 表示相反。

为什么用余弦相似度而不是欧氏距离?因为 Embedding 向量的”方向”比”长度”更重要。两段语义相似的文本,它们的向量方向应该接近,但长度可能不同(因为文本长度不同)。余弦相似度只关注方向,忽略了长度的影响,所以更适合衡量语义相似性。

检索过程。

  1. 用户的查询文本通过 Embedding 模型转换为查询向量
  2. 查询向量发送给向量数据库
  3. 向量数据库通过索引快速定位候选集
  4. 在候选集中逐一计算与查询向量的相似度
  5. 按相似度从高到低排序,返回前 k 个结果
  6. 每个结果包含向量对应的原始文本和元数据

精确检索 vs 近似检索。 精确检索会计算查询向量与数据库中所有向量的相似度,确保返回的确实是 top-k。但数据库中有百万甚至千万级向量时,逐一计算太慢了。

近似最近邻(ANN,Approximate Nearest Neighbor)检索通过索引结构(如 HNSW、IVF)来加速,牺牲少量精度换取数量级的速度提升。在大多数 RAG 场景中,近似检索的精度损失可以忽略不计,但速度提升非常显著。

六、Top-k

Top-k 指的是检索返回的 Chunk 数量。k 的选择直接影响 RAG 系统的效果。

k 太小(比如 k=1)。 只返回 1 个最相似的 Chunk。如果检索结果恰好包含了答案,很好。但如果答案分布在多个 Chunk 中,或者最相似的 Chunk 不是包含答案的那个(检索有偏差),就没救了。风险太高。

k 太大(比如 k=20)。 返回 20 个 Chunk。覆盖面广了,但引入了大量噪声。模型需要从 20 个 Chunk 中筛选有用信息,注意力被稀释。同时,20 个 Chunk 的文本量可能超过 Prompt 的合理长度。

k 的推荐值。 通常 k=3 到 k=5 是一个不错的起点。这个范围既能覆盖多个相关 Chunk,又不会引入太多噪声。具体值需要根据实际场景调优:

  • 简单事实型问答(答案集中在一段文本中):k=3
  • 复杂流程型问答(答案可能分散在多个段落中):k=5-10
  • 开放式总结型问答(需要综合多个来源):k=10+

k 的值也可以动态调整:先用 k=3 检索,如果检索结果的相似度都很低(说明没找到特别相关的),再扩大 k 值做二次检索。

七、Metadata Filtering

元数据过滤是在向量检索的基础上,增加额外的筛选条件。

为什么需要元数据过滤。 想象一个企业知识库,里面有产品文档、人事制度、财务报告、技术规范等不同类型的文档。用户问”产品的保修期是多久”,如果不过滤,检索可能会在所有文档中搜索,可能返回一条人事制度中提到”保修”的无关 Chunk。通过元数据过滤,可以限制只在”产品文档”类型中搜索,提高检索精准度。

常见的过滤条件:

  • 按文档类型过滤:只在产品文档中搜索
  • 按部门过滤:只在法务部门的文档中搜索
  • 按时间过滤:只搜索最近一年更新的文档
  • 按关键词过滤:只搜索标题中包含”退货”的章节

过滤的实现方式。 大多数向量数据库支持在检索请求中附加过滤条件。过滤可以发生在检索之前(先缩小搜索范围再做向量匹配)或检索之后(先做向量匹配再过滤结果)。前者效率更高,但不是所有数据库都支持。

过滤的注意事项。 过滤条件太严格可能导致检索不到任何结果。比如用户限定”只在 2024 年 1 月更新的文档中搜索”,但相关内容其实在 2023 年 12 月的文档中。过滤条件应该有合理的默认值,并且在结果为空时给出有用的反馈(比如”在指定范围内未找到相关内容,已扩大搜索范围”)。

八、向量库选型

向量数据库的选型不只是技术决策,还涉及团队、成本、运维等多个维度。

选型决策维度:

数据规模。 几千到几万条 Chunk,任何数据库都能轻松应对。百万级需要考虑索引算法的选择和内存配置。千万级以上需要考虑分布式方案。

查询频率。 每秒几十次查询,单节点就够。每秒几百次需要考虑缓存和读写分离。每秒上千次需要分布式部署。

功能需求。 需不需要元数据过滤?需不需要混合检索(向量 + 关键词)?需不需要实时更新?需不需要多租户隔离?

运维能力。 团队有没有数据库运维经验?有没有 Kubernetes 集群?如果团队小、运维能力有限,全托管服务(Pinecone)或者轻量方案(Chroma、pgvector)更合适。

成本预算。 自建开源方案的成本主要在人力(部署和维护),托管方案的成本主要在服务费。小规模时托管方案通常更便宜,大规模时自建方案更经济。

合规要求。 数据能不能存在第三方服务器上?有些行业(金融、医疗、政府)要求数据必须本地存储。这种情况下只能选择自建的开源方案。


概念关系图

Embedding 工作原理
=========================================================

[文本: "退款流程是什么"]
        |
        v
[Embedding 模型]
        |
        v
[向量: [0.023, -0.157, 0.891, ..., 0.042]]  (1536 维)
        |
        v
[语义空间中的位置]

语义空间(简化为 2D 示意)
=========================================================

                    * "退货条件"
                   /
    * "退款步骤"  /
       |        /
       |      /   * "退货流程"
       |    /    (和"退款步骤"距离近)
       |  /
       |/
       |
-------+-----------------* "天气预报"
       |
       |
       |            * "篮球比赛"
       |

距离近 = 语义相似    距离远 = 语义不同


RAG 向量检索流程
=========================================================

[离线入库]
[Chunk 文本] --> [Embedding API] --> [向量] --> [向量数据库]
                                           + [原始文本]
                                           + [元数据]

[在线检索]
[用户问题] --> [Embedding API] --> [问题向量]
                                       |
                                       v
                              [向量数据库检索]
                                       |
                                       v
                              [Top-k 相似 Chunk]
                                       |
                                       v
                              [元数据过滤(可选)]
                                       |
                                       v
                              [检索结果: 文本 + 元数据]


向量数据库选型矩阵
=========================================================

方案        数据规模     运维复杂度    混合检索    适用阶段
-----------------------------------------------------------------
Chroma     万级以内     低            不支持      学习/原型
FAISS      百万级       中            不支持      高性能场景
Qdrant     百万级       中            支持        中等项目
Weaviate   百万级       中            支持        混合检索
Milvus     千万级       高            支持        大型生产
Pinecone   百万级       低(托管)      支持        快速上线
pgvector   百万级       低            有限        已有PG基建

实战分析

任务一:对 Chunks 生成 Embedding

将 Day 17 产出的 Chunks 批量向量化。

选择 Embedding 模型。 对于中文场景,需要选择对中文支持好的模型。主流选择包括 OpenAI 的 text-embedding-3-small/large、Cohere 的 embed-multilingual、以及 BGE(BAAI General Embedding)等开源模型。

选择时考虑三个因素:

  • 中文效果(在中文文本的语义相似度任务上的表现)
  • 维度(影响存储和计算成本)
  • 价格(按 Token 计费,不同模型差异很大)

批量处理策略。 将所有 Chunk 文本收集成一个列表,按 Embedding API 的批量上限分批调用。每批调用后检查结果是否完整(有没有遗漏或错误的向量)。记录每批处理的时间和 Token 消耗,用于成本估算。

一致性保证。 记录使用的 Embedding 模型名称和版本。后续的在线查询必须使用同一个模型。模型更换意味着需要重新向量化所有 Chunk。

任务二:存入向量数据库

选择一个向量数据库,将向量和对应的文本、元数据存入。

初始选型建议。 学习阶段建议用 Chroma。它零配置、Python 原生、文档丰富。等系统需要上线时再考虑迁移到生产级数据库。

存储时的注意事项:

  • 为每个向量关联完整的元数据(来源、章节、页码等)
  • 设计合理的 Collection 命名(按文档类型或项目分组)
  • 测试写入性能,记录索引构建时间
  • 验证写入后能否正确检索到

测试数据。 写入后,用几个已知答案的问题测试检索效果。比如你知道某个 Chunk 包含”退款 SLA 是 3 个工作日”,就用”退款需要多久”来检索,看这个 Chunk 是否出现在 top-k 结果中。

任务三:实现语义检索

实现从问题到检索结果的完整流程。

步骤:

  1. 接收用户问题文本
  2. 用同一个 Embedding 模型将问题转换为向量
  3. 在向量数据库中检索 top-k
  4. 返回检索结果(每个结果包含文本、元数据、相似度分数)

检索结果的质量评估。 对每个检索结果,关注两个指标:

  • 相似度分数。 余弦相似度在 0.8 以上通常表示高度相关,0.5-0.8 表示部分相关,0.5 以下可能不相关。具体阈值需要根据实际数据校准。
  • 人工判断。 抽样检查检索结果是否真的和问题相关。分数高但内容不相关,说明 Embedding 模型在这个场景下的效果不好。

任务四:支持按 Metadata 过滤

在检索接口中增加元数据过滤功能。

实现几个典型过滤场景:

  • 按文档名过滤:“只在《产品手册 v2》中搜索”
  • 按章节过滤:“只在’退货政策’章节中搜索”
  • 按内容类型过滤:“只搜索表格类型的 Chunk”
  • 组合过滤:“在产品文档中,只搜索最近更新的内容”

过滤效果测试。 对同一个问题,分别做有过滤和无过滤的检索,对比结果差异。过滤应该能去除不相关的结果,但不应该把正确结果也过滤掉。


当日产物说明

产物一:《Embedding 入库脚本》

这是一个完整的向量化入库方案。

应该包含:

  • Embedding 模型的选择说明(为什么选这个模型、参数是什么)
  • 批量向量化的流程(怎么分批、怎么处理错误、怎么记录进度)
  • 向量数据库的连接和写入方式
  • 元数据的存储格式
  • 入库后的验证方法(怎么确认入库成功)

质量标准: 按照这个方案,能把一批 Chunk 成功向量化并存入数据库。成本估算和时间估算要合理。

产物二:《向量检索 Demo》

这是一个端到端的检索演示。

应该包含:

  • 10 个测试问题和预期的正确答案
  • 每个测试问题的 top-3 检索结果
  • 每个检索结果的相似度分数
  • 检索结果是否包含正确答案的判断
  • 检索成功的案例和失败的案例分析

质量标准: 10 个测试问题中,至少 7 个的正确答案出现在 top-3 检索结果中。如果低于这个水平,需要分析原因(是切分问题还是 Embedding 模型问题还是问题表述问题)。

产物三:《向量库选型记录》

这是一份选型决策文档。

应该包含:

  • 候选方案列表(至少 3 个)
  • 每个方案的评估维度(功能、性能、成本、运维)
  • 最终选择和理由
  • 备选方案(如果首选方案遇到问题,切换到哪个)

质量标准: 决策有理有据,不是”听别人说这个好”。每个方案的优势和局限都说清楚了。


常见误区与避坑

误区一:Embedding 模型随便选一个就行

不同 Embedding 模型的效果差异很大,尤其是在中文场景下。有些模型是主要针对英文训练的,中文语义理解能力弱。比如”苹果公司”和”苹果手机”在好的中文模型里应该距离很近(都是苹果公司相关),但在英文为主的模型里可能距离较远(“apple company” vs “apple phone”的语义差异在英文中更大)。

选模型之前,用你自己的数据做一个小规模测试:准备 20 组文本对,人工标注每组是否语义相似,然后用候选模型计算相似度,看哪个模型和人工标注最一致。

误区二:向量维度越高越好

高维向量理论上能编码更多信息,但实际效果不一定更好。维度高意味着计算量增大、存储空间增大、检索延迟增大。如果模型的训练数据不够支撑高维表示,高维向量中可能包含大量噪声维度,反而影响效果。

对于大多数企业知识库场景,768-1536 维已经足够。不要为了”可能更好”而盲目追求高维。

误区三:向量数据库等同于搜索引擎

向量数据库做的是语义相似度检索,不是关键词精确匹配。“语义相似”和”内容匹配”不是一回事。

一个例子:用户问”退货的电话号码是多少”,文档中写的是”如需退货,请拨打客服热线 400-xxx-xxxx”。向量检索可能找到这个 Chunk(因为”退货”语义相关),但如果你问的是”400-xxx-xxxx 这个号码归属哪里”,纯语义检索很难回答,因为模型可能不认为”电话号码归属”和”退货电话”语义相近。

对于需要精确匹配的场景(搜索特定编号、特定日期、特定人名),关键词检索比向量检索更靠谱。这就是为什么 Day 20 要学混合检索。

误区四:忽略了 Embedding 模型的版本一致性

这是一个非常容易犯的错误:开发时用 v1 版本的模型生成文档向量,上线后 API 升级到 v2 版本,用户问题的向量是用 v2 生成的。v1 和 v2 的向量空间不同,相似度计算完全失准。

解决方案:在数据库中记录每个向量使用的模型名称和版本号。在线查询时校验模型版本。模型升级时,需要重新向量化所有 Chunk。

误区五:向量数据库不需要维护

向量数据库不是”建好就不管了”。以下情况需要维护:

  • 数据更新:文档增删改后,对应的向量需要同步更新
  • 索引优化:随着数据增长,索引可能需要重建或优化参数
  • 监控:检索延迟、命中率、错误率需要持续监控
  • 备份:和任何数据库一样,需要定期备份

把向量数据库当作一个需要持续维护的基础设施组件,而不是一个黑盒。


延伸思考

Embedding 与 Day 17 Chunking 的协同

Embedding 的效果受 Chunk 质量的直接影响。如果 Chunk 太大,向量是对大量信息的平均表示,会模糊具体细节。如果 Chunk 太小,向量缺乏足够的上下文信息来生成有意义的表示。

一个好的经验法则是:Chunk 的大小应该和 Embedding 模型”擅长处理”的文本长度大致匹配。大多数 Embedding 模型在 100-1000 Token 的文本上效果最好。如果你的 Chunk 远小于 100 Token 或远大于 1000 Token,可能需要重新考虑切分策略。

向量检索之外:混合检索的必要性

纯向量检索在很多场景下效果不错,但有一个结构性弱点:它擅长捕捉语义相似性,但不擅长精确匹配。当用户的查询包含专有名词、产品型号、日期、人名时,关键词检索更准确。

明天的 RAG 问答链路 v1 会先用纯向量检索搭建最小闭环。Day 20 的优化环节会引入混合检索,把向量检索和关键词检索结合起来。但在引入混合检索之前,先把纯向量检索的基线效果测清楚,这样优化后才能量化改进幅度。

Embedding 模型的领域适配

通用 Embedding 模型在特定领域(半导体、法律、医疗)的效果可能不理想,因为这些领域的专有术语、缩写、概念关系在通用训练数据中出现频率低。

解决方案是对 Embedding 模型做领域微调:用领域内的文本对(比如”半导体良率”和”wafer yield”应该语义相近)来训练模型,使其在特定领域的语义空间更准确。这是一个进阶话题,在 MVP 阶段不需要考虑,但在做生产级系统时可能是必要的。


自测问题

  1. 用自己的话解释 Embedding 是什么,以及它为什么能让计算机理解”语义相似”。

  2. 向量检索中,余弦相似度和欧氏距离的区别是什么?为什么 RAG 场景通常用余弦相似度?

  3. 离线向量生成和在线向量生成分别发生在什么阶段?为什么必须使用同一个 Embedding 模型?

  4. 列举至少 4 种主流向量数据库,说明各自的适用场景。

  5. Top-k 中的 k 值怎么选择?k 太大和 k 太小分别有什么问题?

  6. 元数据过滤的作用是什么?举一个具体例子说明过滤如何提升检索质量。

  7. 为什么说”向量数据库不等同于搜索引擎”?纯向量检索有什么结构性弱点?

  8. 如果你的知识库包含中文和英文混合的文档,选择 Embedding 模型时需要注意什么?

  9. 解释 Embedding 模型版本不一致会导致什么问题。怎么避免?

  10. 一个 RAG 系统的检索效果不好,可能的原因有哪些?怎么区分是 Embedding 的问题还是切分的问题?


关键词

  • Embedding(嵌入):将文本转换为固定长度的数值向量,使文本的语义关系可以用向量距离来衡量
  • 向量(Vector):一组有序的浮点数,是文本在高维语义空间中的坐标表示
  • 向量数据库(Vector Database):专门存储和检索向量的数据库系统,支持相似度检索和元数据过滤
  • 余弦相似度:衡量两个向量方向接近程度的指标,是 RAG 中最常用的相似度计算方式
  • Top-k 检索:返回与查询向量最相似的前 k 个结果
  • 近似最近邻(ANN):通过索引结构加速向量检索,以少量精度损失换取数量级的速度提升
  • HNSW:分层可导航小世界图,一种高效的向量索引算法,大多数向量数据库的默认索引
  • Metadata Filtering:在向量检索基础上按元数据条件过滤结果,如按文档类型、时间范围过滤
  • 语义空间:Embedding 模型将文本映射到的高维空间,语义相似的文本在此空间中距离近
  • 批量向量化:将大量文本一次性或分批发送给 Embedding API 生成向量的过程