《Spring AI 2.0实战》第6招 Embedding是什么
## 01、痛点 我是老李,最近帮一个业务团队排查知识库检索质量下降的问题。现象很典型:同样一个问题,以前能召回准确的制度文档,现在却返回一堆不相关内容。团队第一反应是向量库是不是满了、索引是不是坏了,甚至有人建议直接换一个更贵的向量数据库。 排查一圈后发现,根源不在向量库,而在 Embedding 模型被“顺手”升级了。旧的几百万条向量是用 A 模型生成的,新的查询请求却用 B 模型生成向量。两者根本不在同一个向量空间里,余弦相似度自然失真。用户看到的结果就是:搜得到,但不相关;相关的内容排不上去。 这其实不是个例。很多人进入 RAG 后,很快学会了调 VectorStore API,写入、查询、删除都顺手,但始终没搞懂一件事:为什么能搜到?为什么相近的文本会排在一起?如果不理解 Embedding,就只能停留在“会调 API,不会定位问题”的状态。向量库只是一个存储和相似度计算的引擎,真正决定“语义”的,是 Embedding 模型。 我们得把这个问题讲透。 ## 02、是什么 Embedding 做的事情,是把文本映射到向量空间。映射后的结果是一组浮点数,比如 768 维、1024 维或 1536 维的数组。这个映射有一个关键性质:语义接近的内容,在向量空间中距离也更接近。 这里要分清几个概念。 第一,维度。维度是向量长度,由模型决定。不同模型输出维度不同,维度越高通常表达能力越强,但计算和存储成本也更高。一个向量不是随便几个数字,而是模型对文本语义的压缩表示。 第二,相似度。向量之间怎么算“接近”?常用余弦相似度、内积和欧氏距离。文本检索中,余弦相似度最常见,因为它更关注方向,不受向量长度影响。两个向量夹角越小,余弦值越接近 1,语义越接近。 第三,Chunk。Chunk 是进入 Embedding 之前的文本单元。大段文档要先切分成长度合适的块,再逐块生成向量。切多大、重不重叠,都会影响召回效果。一个 chunk 如果太大,语义被稀释;太小又可能丢失上下文。 第四,职责区分。Embedding Model 和 Chat Model 是两回事。Embedding Model 负责把文本变成向量,用于检索、聚类、去重;Chat Model 负责根据上下文生成自然语言回复。两者不能互换。很多误用来自把 Chat Model 生成的内容直接当 Embedding,或者用 Embedding 的输出去做对话生成。 所以,Embedding 是 RAG 的前置能力,不是简单的“文本转数字”。你必须知道向量从哪来、维度多少、用什么相似度、喂进去的文本粒度是什么。 ## 03、为什么 我为什么现在要讲这件事?因为 RAG 正在从“玩具项目”走向“生产系统”。过去大家只关心能不能跑通,现在要关心召回质量、稳定性、模型升级、数据治理。 问题也出现在这里。向量数据库的普及让 Embedding 的接入成本变得很低,低到很多人把它当成一个标准件。可 Embedding 不是标准件,不同模型的训练数据、目标函数、输出维度、归一化方式都不一样。两个模型的向量,哪怕维度相同,也不能直接比较。比如模型 A 和模型 B 都能输出 1024 维向量,但它们的空间几何不同,相同的文本会落在完全不同的位置。 如果只把向量当成“数据”,就会忽略一件事:向量是模型产生的检索索引。索引的作用是加速查询,但索引规则由模型决定。模型变了,索引就必须重建。这个道理和数据库索引很像。你给一张表按列 A 建了 B+ 树,现在想按列 B 查,就不能复用原来的索引;同样,你换了 Embedding 模型,就必须重新生成所有向量。 RAG 的完整链路是:原始文档 -> 切分 -> Embedding -> 向量库 -> 相似度检索 -> 生成。中间任何一环变化,都会影响最终结果。但大多数人对 Embedding 这一环最不敏感,因为它没有界面,只有一串看不出含义的数字。 ## 04、架构 我们可以把 RAG 检索链路画成一个简单的图。由于本文用 Markdown 表达,我用文字描述: ``` 原始文档 -> 解析、清洗 Chunk 列表 -> Embedding Model 向量列表 + 元数据 -> 写入 向量数据库 同一个 Embedding Model 查询向量 -> 相似度检索 TopK 文档 -> Chat Model 生成回答 ``` 这个链路里有一个极其关键但容易被忽视的约束:写入和查询必须使用同一个 Embedding Model。更准确地说,是同一个模型版本、同一个维度、同一套预处理逻辑。只要有一处不一致,相似度计算就会失真。 很多人以为“换个更好的模型”只是提升效果,其实不是。换模型后,旧向量和新查询向量不再处于同一个向量空间。除非你全量重灌旧数据,否则查询端换模型一定会造成召回质量下降。 因此,向量库里的向量并不是原始事实。它只是模型对文本的一种投影。真正的事实是原始文档。向量是检索索引,而索引必须与查询端的投影方式保持一致。理解了这一点,架构设计就会更清晰:原始文档存储和向量检索存储要分离,向量可以随时重建,但原始文档不能丢。 另外,检索质量不只取决于 Embedding 模型,还取决于切分策略、元数据过滤、重排序等。但 Embedding 是地基,地基不稳,后面都是补救。 ## 05、实战 我们做一个小实验。取三个文本: 1. Redis 缓存雪崩 2. 大量 Key 同时过期 3. Java GC 直觉上,前两个都属于 Redis 缓存相关问题,相似度应该较高;第三个是 JVM 垃圾回收,和前两个语义距离较远。我们用某个 Embedding 模型把三个文本转成向量,然后计算两两余弦相似度。 伪代码如下: ```python import numpy as np texts = [ 'Redis 缓存雪崩', '大量 Key 同时过期', 'Java GC' ] vectors = embedding_model.encode(texts) def cosine(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) print('1 vs 2:', cosine(vectors[0], vectors[1])) print('1 vs 3:', cosine(vectors[0], vectors[2])) print('2 vs 3:', cosine(vectors[1], vectors[2])) ``` 实际跑出来,结果大致会是:1 和 2 的相似度在 0.7 以上,1 和 3、2 和 3 的相似度通常低于 0.4。不同模型的具体数值不同,但相对关系稳定。这说明 Embedding 确实捕捉到了“缓存雪崩”和“大量 Key 同时过期”之间的语义关联,而不是简单的字面重叠。 再做一个实验:用另一个版本的 Embedding 模型生成同样三个文本的向量,再和第一个模型的向量做相似度比较。你会发现,即使文本完全相同,跨模型的相似度也可能很低。也就是说,向量空间的坐标系变了,同一句话的坐标值完全不同。这很好理解:两个不同的地图投影方式,不能互相比较经纬度。 这个实验揭示了两个事实:第一,Embedding 能表达语义相似度;第二,这种表达高度依赖模型。脱离模型谈相似度,是没有意义的。 ## 06、洞见 到这里可以得出一个更本质的判断:向量不是原始事实,而是一种由模型产生的检索索引。 这个判断听起来简单,但它会改变我们管理知识库的方式。 如果把向量当成事实,你会倾向于只存向量,忽略原始文本,或者认为换模型只是“升级索引”。但如果把向量当成模型产生的索引,你就会意识到:模型版本同样属于数据版本。数据版本不只是文档的更新时间,还包括切分策略、Embedding 模型、维度、归一化方式等。任何一项变化,都会导致检索行为变化。 你可以类比数据库索引。索引的价值在于加速查询,但它依附于表结构和数据。数据变了,索引要重建;查询模式变了,索引也要调整。Embedding 模型就是向量索引的“结构定义”。模型版本不同,索引结构就不同。 在 RAG 场景中,检索索引不是数据库自动维护的,而是由你的数据流水线生成的。因此,模型版本必须纳入版本管理。否则,一旦模型升级,线上就会出现新旧向量混杂,召回质量不可解释。 这个洞见也能帮助我们更好地定位问题。当检索结果异常时,先不要急着调 TopK 或换向量库,而是先问:写入和查询用的是不是同一个 Embedding 模型?模型版本是否一致?维度是否一致?切分策略是否一致?如果答案是否定的,问题往往就在这里。 ## 07、行动 既然模型版本属于数据版本,我们的行动就很明确:把 Embedding Model、维度、版本纳入知识库元数据。 具体来说,每一条向量入库时,都应该带上以下元数据: - model_name:模型名称 - model_version:模型版本号 - dimensions:向量维度 - chunk_size:切块大小 - chunk_overlap:切块重叠长度 - preprocess:预处理方式,例如是否去除空白、是否统一小写 - created_at:向量生成时间 查询时,必须根据元数据选择相同的模型和预处理逻辑。不要把模型名写死在代码里,也不要默认“线上就是最新版”。如果你更换模型,应该: 1. 先保留旧模型,保证旧库查询不断。 2. 用新模型对全量文档重新生成向量,写入新的集合或命名空间。 3. 在查询端做灰度切换,对比新旧召回效果。 4. 确认新模型稳定后,再下线旧集合。 5. 记录模型版本变更日志,把变更时间和原因写清楚。 对于正在建设知识库的团队,我建议把 Embedding 模型管理当成数据治理的一部分。可以建立一张“模型版本表”,记录模型名称、版本、维度、适用集合、状态。每次发布新版本,都先在样本集上评估召回率和准确率,再决定是否全量切换。 另外,原始文档必须保留。向量只是索引,索引可以重建,原始事实一旦丢失就无法恢复。很多知识库系统只存向量不存原文,这在后期会带来很大麻烦。合理的做法是原文和向量双写,向量库只负责检索,原文存储负责最终呈现和证据留存。 ## 08、小结 把这篇文章的核心观点收拢一下。 第一,进入 RAG 之前,必须先搞懂 Embedding。它会决定你能搜到什么,以及为什么搜到。不会排查 Embedding 的人,只能停留在调 API 的阶段。 第二,Embedding 把文本映射到向量空间,语义接近的文本在空间中接近。要理解维度、相似度、Chunk,以及 Embedding Model 和 Chat Model 的不同职责。 第三,向量不是原始事实,而是模型产生的检索索引。模型版本同样属于数据版本。这意味着,更换 Embedding 模型不是简单的参数调整,而是一次索引重建。 第四,行动上要把 Embedding Model、维度、版本纳入知识库元数据。写入和查询必须使用同一个模型版本。模型升级要走重建索引和灰度切换流程。 做到这几点,你的 RAG 系统才不会在模型升级时突然“变傻”。 ## 09、链接与概念说明 为了帮你快速回顾,我把本文涉及的核心概念列一下: - Embedding:将文本映射为稠密向量的过程,语义相近的文本在向量空间中距离更近。 - 向量相似度:衡量两个向量接近程度的指标,常用余弦相似度、内积、欧氏距离。 - Chunk:文本切分后的最小处理单元,是 Embedding 的输入粒度。 - Embedding Model:负责把文本转成向量的模型,用于检索、聚类等。 - Chat Model:负责生成自然语言回复的模型,与 Embedding Model 职责不同。 - 向量数据库:用于存储向量并执行相似度检索的数据库,例如腾讯云向量数据库。 - 元数据:描述数据的数据,这里指模型名称、版本、维度、切分策略等信息。 更多内容可以参考腾讯云向量数据库官方文档:https://cloud.tencent.com/product/vdb 如果你正在设计知识库的 Embedding 管理方案,建议先确定模型版本和维度,再设计表结构和元数据字段,不要等数据量上来后再补。