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 向量的”方向”比”长度”更重要。两段语义相似的文本,它们的向量方向应该接近,但长度可能不同(因为文本长度不同)。余弦相似度只关注方向,忽略了长度的影响,所以更适合衡量语义相似性。
检索过程。
- 用户的查询文本通过 Embedding 模型转换为查询向量
- 查询向量发送给向量数据库
- 向量数据库通过索引快速定位候选集
- 在候选集中逐一计算与查询向量的相似度
- 按相似度从高到低排序,返回前 k 个结果
- 每个结果包含向量对应的原始文本和元数据
精确检索 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 结果中。
任务三:实现语义检索
实现从问题到检索结果的完整流程。
步骤:
- 接收用户问题文本
- 用同一个 Embedding 模型将问题转换为向量
- 在向量数据库中检索 top-k
- 返回检索结果(每个结果包含文本、元数据、相似度分数)
检索结果的质量评估。 对每个检索结果,关注两个指标:
- 相似度分数。 余弦相似度在 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 阶段不需要考虑,但在做生产级系统时可能是必要的。
自测问题
-
用自己的话解释 Embedding 是什么,以及它为什么能让计算机理解”语义相似”。
-
向量检索中,余弦相似度和欧氏距离的区别是什么?为什么 RAG 场景通常用余弦相似度?
-
离线向量生成和在线向量生成分别发生在什么阶段?为什么必须使用同一个 Embedding 模型?
-
列举至少 4 种主流向量数据库,说明各自的适用场景。
-
Top-k 中的 k 值怎么选择?k 太大和 k 太小分别有什么问题?
-
元数据过滤的作用是什么?举一个具体例子说明过滤如何提升检索质量。
-
为什么说”向量数据库不等同于搜索引擎”?纯向量检索有什么结构性弱点?
-
如果你的知识库包含中文和英文混合的文档,选择 Embedding 模型时需要注意什么?
-
解释 Embedding 模型版本不一致会导致什么问题。怎么避免?
-
一个 RAG 系统的检索效果不好,可能的原因有哪些?怎么区分是 Embedding 的问题还是切分的问题?
关键词
- Embedding(嵌入):将文本转换为固定长度的数值向量,使文本的语义关系可以用向量距离来衡量
- 向量(Vector):一组有序的浮点数,是文本在高维语义空间中的坐标表示
- 向量数据库(Vector Database):专门存储和检索向量的数据库系统,支持相似度检索和元数据过滤
- 余弦相似度:衡量两个向量方向接近程度的指标,是 RAG 中最常用的相似度计算方式
- Top-k 检索:返回与查询向量最相似的前 k 个结果
- 近似最近邻(ANN):通过索引结构加速向量检索,以少量精度损失换取数量级的速度提升
- HNSW:分层可导航小世界图,一种高效的向量索引算法,大多数向量数据库的默认索引
- Metadata Filtering:在向量检索基础上按元数据条件过滤结果,如按文档类型、时间范围过滤
- 语义空间:Embedding 模型将文本映射到的高维空间,语义相似的文本在此空间中距离近
- 批量向量化:将大量文本一次性或分批发送给 Embedding API 生成向量的过程