Spring AI 学习篇(五)| 嵌入模型与向量表示的本质
一、本章核心学习目标
学完本章,你将能够:
- 深刻理解嵌入(Embedding)的本质和核心原理
- 掌握向量相似度计算的三种常用方法
- 熟练使用Spring AI
EmbeddingClient统一API - 独立完成主流嵌入模型的对比与选型
- 实现本地Ollama嵌入模型与商业API的无缝切换
- 避开嵌入模型使用中的常见误区
- 掌握嵌入模型的企业级性能优化技巧
二、前置知识准备
- 已经完成前4篇的学习,熟练掌握ChatClient和提示词工程
- 了解大模型的知识截止和幻觉问题
- 熟悉Spring Boot的配置和依赖注入
三、为什么我们需要嵌入模型?
在前面的学习中,我们已经能够让大模型回答问题,但大模型有三个无法通过提示词工程解决的原生缺陷:
- 知识截止限制:所有大模型都有训练数据截止日期,无法回答训练之后发生的事情
- 幻觉问题:大模型会编造不存在的事实和数据
- 私有数据不可用:企业内部文档、客户数据等不能上传给大模型训练
嵌入模型就是解决这三个问题的核心钥匙,它是RAG(检索增强生成)技术的基础。
嵌入的本质:把文本变成机器可理解的数字向量
通俗地说,嵌入就是把人类语言的文本转换成一串固定长度的数字向量。它的神奇之处在于:语义相似的文本,生成的向量在空间中距离也会很近。
举个直观的例子:
- “我喜欢吃苹果” 和 “我爱吃苹果” → 向量距离非常近
- “我喜欢吃苹果” 和 “我喜欢吃香蕉” → 向量距离较近(都是水果)
- “我喜欢吃苹果” 和 “今天天气很好” → 向量距离非常远
这样一来,机器就可以通过计算向量之间的距离,来判断两段文本的语义相似度。
预告式提及:下一章我们会学习向量数据库,它就是专门用来存储这些向量并快速计算相似度的数据库。
四、向量相似度计算原理
向量相似度计算是检索的核心,常用的有三种方法:
1. 余弦相似度(最常用)
计算两个向量之间的夹角余弦值,取值范围是[-1, 1]。值越接近1,相似度越高;越接近-1,相似度越低;0表示完全无关。
特点:不受向量长度的影响,只关心向量的方向,是文本相似度计算的首选方法。
2. 点积相似度
计算两个向量对应位置元素的乘积之和。取值范围没有限制,值越大相似度越高。
特点:同时考虑向量的方向和长度,适合需要考虑文本重要性的场景。
3. 欧氏距离
计算两个向量在空间中的直线距离。取值范围是[0, +∞),值越小相似度越高。
特点:适合比较向量的绝对差异,在文本相似度计算中使用较少。
企业级最佳实践:中文RAG场景首选余弦相似度。
五、Spring AI EmbeddingClient统一API详解
和ChatClient一样,Spring AI为所有嵌入模型提供了统一的EmbeddingClient接口。无论你使用的是商业API还是本地模型,代码完全相同。
1. 基础依赖配置
(1) 对接DeepSeek商业嵌入API
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>
(2) 对接本地Ollama嵌入模型
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
2. 基础配置
(1) DeepSeek商业API配置
spring:
ai:
deepseek:
api-key: 你的DeepSeek API Key
embedding:
options:
model: deepseek-embedding
(2) Ollama本地模型配置
spring:
ai:
ollama:
base-url: http://localhost:11434
embedding:
options:
model: bge-m4
重要说明:只需要添加对应的依赖和配置,Spring AI会自动为你创建EmbeddingClient实例,不需要编写任何额外代码。
3. 基础调用示例
import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class EmbeddingController {
private final EmbeddingClient embeddingClient;
public EmbeddingController(EmbeddingClient embeddingClient) {
this.embeddingClient = embeddingClient;
}
// 生成单个文本的嵌入向量(返回 float[])
@GetMapping("/embed")
public float[] embed(@RequestParam String text) {
return embeddingClient.embed(text);
}
// 批量生成嵌入向量
@GetMapping("/embed/batch")
public List<float[]> embedBatch(@RequestParam List<String> texts) {
return embeddingClient.embed(texts);
}
// 获取嵌入向量的维度
@GetMapping("/embed/dimension")
public int getDimension() {
return embeddingClient.dimensions();
}
}
4. 计算两个文本的相似度
Spring AI 不提供内置的向量计算工具类,需要手动实现余弦相似度:
@GetMapping("/similarity")
public double calculateSimilarity(@RequestParam String text1,
@RequestParam String text2) {
float[] vector1 = embeddingClient.embed(text1);
float[] vector2 = embeddingClient.embed(text2);
return cosineSimilarity(vector1, vector2);
}
/**
* 手动实现余弦相似度:cos(θ) = (A·B) / (|A| × |B|)
*/
private double cosineSimilarity(float[] a, float[] b) {
double dotProduct = 0, normA = 0, normB = 0;
for (int i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
测试示例:
- 输入:text1=“我喜欢吃苹果”,text2=“我爱吃苹果” → 相似度≈0.95
- 输入:text1=“我喜欢吃苹果”,text2=“今天天气很好” → 相似度≈0.1
六、2026年主流嵌入模型对比与选型
这是本章最核心的内容,也是你在实际开发中必须做出的决策。
1. 嵌入模型核心参数说明
| 参数 | 含义 | 影响 |
|---|---|---|
| 向量维度 | 向量包含的数字个数 | 维度越高,语义表达能力越强,但存储和计算成本也越高 |
| 最大序列长度 | 模型一次能处理的最大token数 | 超过这个长度的文本会被截断,导致语义丢失 |
| 中文性能 | 模型对中文语义的理解能力 | 直接决定RAG系统的检索准确率 |
| 商用许可证 | 是否可以免费用于商业用途 | 关系到企业的法律风险 |
2. 主流嵌入模型横向对比
| 模型名称 | 厂商/机构 | 类型 | 向量维度 | 最大长度 | 100万tokens成本 | 中文评分 | 商用许可 |
|---|---|---|---|---|---|---|---|
| BGE-M4 | 智源研究院 | 开源本地 | 1024 | 8192 | 0元 | 95 | Apache 2.0(免费商用) |
| M3E-v2 | 阿里达摩院 | 开源本地 | 1024 | 512 | 0元 | 90 | Apache 2.0 |
| Jina Embeddings v3 | Jina AI | 开源本地 | 1024 | 8192 | 0元 | 85 | Apache 2.0 |
| DeepSeek-embedding-v2 | 深度求索 | 商业API | 1536 | 4096 | 1元 | 85 | 商业 |
| 智谱embedding-3 | 智谱AI | 商业API | 1024 | 4096 | 5元 | 88 | 商业 |
| OpenAI text-embedding-3-small | OpenAI | 商业API | 1536 | 8191 | 0.14元 | 80 | 商业 |
3. 嵌入模型选型决策树
嵌入模型选型决策树:
├── 数据敏感,绝对不能上传到第三方服务器 → 必须选择开源本地模型
│ ├── 有GPU(4GB以上显存) → BGE-M4(首选,中文性能天花板)
│ └── 只有CPU → M3E-v2(推理速度快,CPU友好)
└── 数据不敏感,可以上传 → 商业API
├── 预算有限 → DeepSeek-embedding-v2(性价比之王)
├── 已经在使用智谱生态 → 智谱embedding-3
└── 多语言需求 → OpenAI text-embedding-3-small
重要纠正:之前有说法"DeepSeek没有向量模型",这是完全错误的。DeepSeek不仅有官方的嵌入API,而且价格极低,是商业API的首选。
2026年最新进展:BGE-M4已经内置了稀疏向量支持,不需要再单独部署Splade等稀疏向量模型,可以直接实现混合检索。我们会在第8篇详细讲解混合检索技术。
4. 本地Ollama部署BGE-M4
如果你选择本地部署BGE-M4,只需要一行命令:
ollama pull bge-m4
然后修改application.yml配置为Ollama模式,不需要修改任何Java代码。
七、嵌入模型的常见误区
1. ❌ 误区一:用聊天模型做嵌入
很多新手会误以为"同一个模型既可以聊天也可以做嵌入",这是完全错误的。
聊天模型和嵌入模型的训练目标完全不同:
- 聊天模型的训练目标是生成下一个token
- 嵌入模型的训练目标是让语义相似的文本生成相似的向量
用聊天模型生成的向量,相似度计算结果会非常不准确。
2. ❌ 误区二:混用不同嵌入模型的向量
每个嵌入模型生成的向量空间是完全不同的。你不能把BGE-M4生成的向量和DeepSeek生成的向量放在同一个向量数据库中进行比较,结果会完全没有意义。
企业级最佳实践:一个RAG系统只能使用一个嵌入模型,所有向量都必须由同一个模型生成。
3. ❌ 误区三:向量维度越高越好
虽然维度越高语义表达能力越强,但1024维已经足够应对绝大多数中文场景。更高的维度(如1536维)只会增加存储和计算成本,带来的性能提升非常有限。
4. ❌ 误区四:嵌入模型越新越好
不要盲目追求最新的嵌入模型。BGE-M4已经经过了大量企业的验证,稳定性和效果都有保障。新模型可能存在各种问题,不建议直接用于生产环境。
八、企业级性能优化技巧
1. 批量生成嵌入向量
不要每次只生成一个文本的向量,尽量批量生成,这样可以大幅提升效率。
import java.util.Arrays;
import java.util.List;
// 推荐:批量生成
List<String> texts = Arrays.asList("文本1", "文本2", "文本3");
List<float[]> embeddings = embeddingClient.embed(texts);
// 不推荐:逐个生成
for (String text : texts) {
float[] embedding = embeddingClient.embed(text);
}
2. 缓存嵌入向量
对于不会变化的文本(如知识库文档),生成一次向量后就缓存起来,不要重复生成。可以使用Redis作为缓存。
3. 限制文本长度
不要把过长的文本直接传给嵌入模型,应该先进行切分。我们会在第7篇详细讲解文本切分策略。
4. 选择合适的批量大小
商业API一般都有批量大小限制,比如DeepSeek的最大批量大小是2048。超过这个限制会导致调用失败。
九、常见坑与解决方案
1. ❌ 向量维度不匹配
问题:嵌入模型生成的向量维度和向量数据库要求的维度不一致
解决方案:确保嵌入模型和向量数据库使用相同的维度。BGE-M4是1024维,DeepSeek是1536维。
2. ❌ 中文效果差
问题:使用OpenAI等国外模型,中文检索准确率低
解决方案:中文场景优先使用BGE-M4、M3E-v2等国产模型,它们的中文性能远超国外模型。
3. ❌ 批量调用报错
问题:批量调用时提示"请求过大"
解决方案:减小批量大小,一般每次处理10-100个文本比较合适。
4. ❌ 本地模型速度慢
问题:使用CPU运行BGE-M4,生成速度很慢
解决方案:如果有NVIDIA显卡,Ollama会自动启用GPU加速,推理速度大幅提升。
十、本章总结与下章预告
本章总结
- 嵌入模型的本质是将文本转换为数字向量,语义相似的文本向量距离更近
- 余弦相似度是文本相似度计算的首选方法
- Spring AI提供了统一的
EmbeddingClient接口,支持商业API和本地模型 - 中文RAG场景首选BGE-M4开源本地模型,商业API首选DeepSeek-embedding-v2
- 不要用聊天模型做嵌入,不要混用不同模型的向量
- 批量生成和缓存可以大幅提升嵌入模型的性能
预告式提及:我们现在已经学会了如何生成向量,但如何高效地存储和检索这些向量呢?下一章我们将学习向量数据库,它是RAG系统的核心存储组件。
下章预告
下一章我们将深入学习向量数据库的选择与Spring AI集成。你将学会:
- 什么是向量数据库?为什么不用MySQL存向量?
- 主流向量数据库对比:Milvus vs Qdrant vs Chroma
- Spring AI向量数据库统一API详解
- 本地向量数据库Chroma的快速上手
- 向量的增删改查与批量导入导出
十一、课后练习
- 分别使用DeepSeek商业API和本地Ollama BGE-M4生成"Java是最好的编程语言"的向量,观察它们的维度差异
- 计算三对文本的相似度:“Java开发"和"Java编程”、“Java开发"和"Python开发”、“Java开发"和"打篮球”
- 实现一个批量生成嵌入向量的接口,支持一次处理最多50个文本
- 尝试修改Ollama配置,使用M3E-v2模型,对比它和BGE-M4的生成速度和相似度计算结果
- 思考一下:为什么嵌入模型能够捕捉到文本的语义信息?
| 嵌入模型与向量表示的本质&spm=1001.2101.3001.5002&articleId=162204477&d=1&t=3&u=8c0f656bb63a4d62b26081147a896c1a)
1890

被折叠的 条评论
为什么被折叠?



