第十一章 Embedding 向量化与向量数据库
版本标注
- Spring AI:
1.1.2- Spring AI Alibaba:
1.1.2.0章节定位
EmbeddingModel与VectorStore是 RAG 的核心基础设施。- 学习本章时,重点应放在统一抽象:
EmbeddingModel负责向量化,VectorStore负责检索;至于底层是 Redis 还是其他向量库,属于实现选型问题。
s01 > s02 > s03 > s04 > s05 > s06 > s07 > s08 > s09 > s10 > [ s11 ] s12 > s13 > s14 > s15 > s16 > s17 > s18
"Embedding 不负责回答问题, 它负责让机器看懂相似度" -- 向量化是检索、推荐和 RAG 的基础设施。
一、为什么需要 Embedding?
1.1 计算机 vs 人类
作为人类,我们天然理解文字的含义:
"猫" ──→ 毛茸茸的、会喵喵叫、养来抓老鼠...
"狗" ──→ 忠诚的、会看家、是人类的好朋友...
但对计算机来说,"猫"和"狗"只是两个字符串,它不知道它们有什么关系。
1.2 Embedding 的思想
Embedding(向量化) 的核心思想是:把文字转换成一串数字(向量)。
Embedding 思想
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"猫" ──→ [0.12, -0.34, 0.56, 0.78, ...] (1536维向量)
"狗" ──→ [0.15, -0.31, 0.53, 0.80, ...] (1536维向量)
"汽车" ──→[0.89, 0.12, -0.34, 0.21, ...] (1536维向量)
↓ 向量距离很近(相似)
↓ 向量距离很远(不相似)
如果两个向量的距离很近,说明对应的文本在语义上也相似。
1.3 Embedding 的应用场景
| 应用场景 | 说明 |
|---|---|
| 语义搜索 | 搜"手机"也能找到"智能手机"相关内容 |
| 相似推荐 | 找到和当前内容最相似的其他内容 |
| 分类聚类 | 把相似的内容自动归类 |
| 去重判断 | 判断两段文字是否重复 |
二、向量数据库
2.1 传统数据库的局限
传统数据库存储的是精确值:
SELECT * FROM products WHERE name = '手机'; -- 精确匹配
但如果你搜"手机",就搜不到"智能手机"。
2.2 向量数据库的优势
向量数据库可以存储向量,并支持相似度检索:
// 向量数据库查询
SearchRequest request = SearchRequest.builder()
.query("手机") // 搜索关键词
.topK(5) // 返回最相似的5个结果
.build();
vectorStore.similaritySearch(request); // 返回语义相似的结果
即使搜索词不完全一样,也能找到语义上相关的内容!
2.3 Redis Stack
本章示例使用的向量数据库是 Redis Stack:
- 本身就是内存数据库,性能很快
- 支持向量存储和检索
- 安装使用简单
三、项目代码详解
3.1 VectorStore 配置方式
这一章有一个很容易忽略、但实际运行时一定会踩到的点:VectorStore 只是一个接口,Spring 容器不会无条件帮你变出一个可注入的实现对象。
也就是说,如果你在控制器里直接写:
@Resource
private VectorStore vectorStore;
但项目里既没有自动装配出某个向量库实现,也没有自己手动声明 Bean,启动时就会报:
A component required a bean of type 'org.springframework.ai.vectorstore.VectorStore' that could not be found.
在学习阶段,最简单的做法之一,是先手动提供一个 SimpleVectorStore Bean:
package com.atguigu.study.config;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class VectorStoreConfig {
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return SimpleVectorStore.builder(embeddingModel).build();
}
}
这段配置类的意思很简单:
- 容器里已经有
EmbeddingModel - 现在我们基于它手动创建一个
SimpleVectorStore - 再把这个对象注册成
VectorStoreBean
这样后面的 Controller 才能顺利注入:
@Resource
private VectorStore vectorStore;
不过,这里还有一个很重要的补充:
如果你引入的是某个具体向量库的 Starter,并且配置也写全了,那么
VectorStore也可能由自动配置直接提供,不需要你再手动写@Bean。
比如 Redis 向量存储场景中,如果你已经引入:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>
并且在 application.yml 中配置好了:
spring:
ai:
vectorstore:
redis:
initialize-schema: true
index-name: custom-index
prefix: custom-prefix
data:
redis:
host: localhost
port: 6379
那么在很多情况下,Spring Boot 就会自动帮你创建 Redis 对应的 VectorStore Bean,此时就不需要再写一个手动的 VectorStoreConfig。
所以这一节真正要记住的是:
- 如果你用的是
SimpleVectorStore这种本地实现,通常自己写@Bean最直接 - 如果你用的是 Redis 这类带 Starter 的向量库实现,往往可以依赖自动装配
3.2 向量化控制器
package com.atguigu.study.controller;
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingRequest;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
/**
* 向量化控制器
* 展示文本Embedding和向量检索的使用
*/
@RestController
@Slf4j
public class Embed2VectorController
{
// 注入 Embedding 模型(用于文本→向量)
@Resource
private EmbeddingModel embeddingModel;
// 注入向量存储器(用于存储和检索向量)
@Resource
private VectorStore vectorStore;
/**
* 文本转向量(Text to Embedding)
*
* 接口:http://localhost:8011/text2embed?msg=射雕英雄传
*
* @param msg 要转换的文本
* @return EmbeddingResponse 包含向量结果
*/
@GetMapping("/text2embed")
public EmbeddingResponse text2Embed(String msg)
{
// 方法1:使用默认配置进行向量化
// EmbeddingResponse response = embeddingModel.call(
// new EmbeddingRequest(List.of(msg), null));
// 方法2:显式指定使用 text-embedding-v3 模型(推荐)
// DashScopeEmbeddingOptions 包含阿里云特有的配置
EmbeddingResponse embeddingResponse = embeddingModel.call(
new EmbeddingRequest(
List.of(msg), // 要向量化的文本列表(可以一次批量处理多个)
DashScopeEmbeddingOptions.builder()
.withModel("text-embedding-v3") // 指定使用 embedding v3 模型
.build()
)
);
// 打印向量结果(可以看到生成了1536维的向量数组)
System.out.println(Arrays.toString(embeddingResponse.getResult().getOutput()));
// 返回完整的 EmbeddingResponse(包含向量数据)
return embeddingResponse;
}
/**
* 向量入库:将文本转为向量并存储到向量数据库
*
* 接口:http://localhost:8011/embed2vector/add
*
* 这个方法将文本转换为向量,并存入 VectorStore
*/
@GetMapping("/embed2vector/add")
public void add()
{
// 1. 准备要存储的文档
// Document 是 Spring AI 定义的文档对象
// 包含 content(内容)、id、metadata(额外信息)
List<Document> documents = List.of(
new Document("i study LLM"), // 英语:我学习大语言模型
new Document("i love java") // 英语:我喜欢Java
);
// 2. 调用 VectorStore.add() 方法
// 内部会自动:
// - 用 EmbeddingModel 把文本转为向量
// - 将向量和原始文本一起存入底层向量库
vectorStore.add(documents);
// 3. 简单测试:查看底层向量库中是否已经写入文档
}
/**
* 向量检索:从向量数据库中查找相似内容
*
* 接口:http://localhost:8011/embed2vector/get?msg=LLM
*
* @param msg 搜索关键词
* @return 和 msg 语义最相似的文档列表
*/
@GetMapping("/embed2vector/get")
public List<Document> getAll(@RequestParam(name = "msg") String msg)
{
// 1. 构建搜索请求
// SearchRequest 是 Spring AI 的搜索请求构建器
SearchRequest searchRequest = SearchRequest.builder()
.query(msg) // 搜索词(会自动转为向量)
.topK(2) // 返回最相似的2个结果
.build();
// 2. 执行相似度搜索
// 传入搜索请求,返回匹配度最高的文档列表
List<Document> list = vectorStore.similaritySearch(searchRequest);
// 3. 打印结果
System.out.println(list);
// 4. 返回结果
return list;
}
}
四、核心原理详解
4.1 为什么有时要写配置类,有时又不用?
这个问题表面上看是在问“为什么有时要写 @Configuration”,其实本质上是在问:
为什么
EmbeddingModel往往可以直接注入,而VectorStore却经常需要我自己注册成 Bean?
EmbeddingModel通常是 框架已经帮你自动装配好的 BeanVectorStore通常是 你还需要自己决定具体实现的 Bean
所以两者最大的区别不在于“是不是接口”,而在于:
这个对象到底是谁放进 Spring 容器里的。
1. 为什么 EmbeddingModel 经常可以直接注入?
当你引入了 Spring AI Alibaba 的 DashScope Starter,并且配置好了 API Key 之后,框架通常会自动帮你创建好一个 EmbeddingModel Bean。
所以在控制器里这样写:
@Resource
private EmbeddingModel embeddingModel;
往往就能直接成功。
这是因为:
- Starter 已经知道你在使用 DashScope
- 它也知道该创建哪种 Embedding 实现
- 所以自动配置类会替你把这个对象注册进 Spring 容器
也就是说,这里的 EmbeddingModel 并不是“凭空出现的”,而是 框架提前帮你准备好的。
2. 为什么 VectorStore 有时不能直接注入?
因为 VectorStore 虽然也是一个接口,但它背后的实现选择并不唯一。
比如你完全可以选择:
SimpleVectorStore- Redis VectorStore
- Milvus
- PGVector
- Elasticsearch
- 你自己写的实现
Spring 不知道你到底想用哪一个,所以在没有更具体条件时,它通常不会替你“猜一个”然后自动注册。
这时如果你直接写:
@Resource
private VectorStore vectorStore;
但项目里又没有任何一个 VectorStore Bean,Spring 启动时就会报:
A component required a bean of type 'org.springframework.ai.vectorstore.VectorStore' that could not be found.
3. 这时为什么要写配置类?
当你用的是 SimpleVectorStore 这种不依赖外部中间件、也没有专门 Starter 自动装配的实现时,你就需要明确告诉 Spring:
“这个项目里我决定使用
SimpleVectorStore作为VectorStore的实现。”
最常见的写法就是:
@Configuration
public class VectorStoreConfig {
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return SimpleVectorStore.builder(embeddingModel).build();
}
}
这段代码的意思是:
EmbeddingModel已经由框架自动提供- 现在我们基于它手动创建一个
SimpleVectorStore - 再把这个对象注册成
VectorStoreBean
这样后面谁再写:
@Resource
private VectorStore vectorStore;
Spring 就能顺利注入了。
但这里要补一句非常关键的话:
并不是所有
VectorStore都必须手动写配置类。
如果你已经引入了某个向量库的 Starter,例如 Redis 向量存储:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>
并且相关配置也写全了,那么 VectorStore 可能就会由自动配置直接提供。这时你只需要像下面这样注入即可:
@Resource
private VectorStore vectorStore;
不需要你再额外写:
@Bean
public VectorStore vectorStore(...) {
... }
4. 那什么时候可以不写配置类?
如果某个对象只是你在当前方法里临时用一下,其实完全可以自己创建:
VectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build();
这种写法没有错,只是它有一个前提:
- 这个对象只在当前方法里用
- 你不打算让它被别的 Controller / Service 复用
- 你也不需要 Spring 去管理它
一旦这个对象要在整个项目里复用,或者要被注入到别的类里,通常还是应该把它写成 @Bean。
5. 最终怎么判断该不该写配置类?
可以用一个非常实用的判断标准:
- 框架已经自动装配好的对象:通常直接注入就行,例如
EmbeddingModel - 没有 Starter 自动装配、需要你自己决定实现的对象:通常要自己注册 Bean,例如
SimpleVectorStore - 已经有专门 Starter 且配置完整的对象:很多时候也可以直接注入,例如 Redis
VectorStore
所以一句话总结就是:
不是“有的类能直接注入,有的类必须写配置类”,而是“谁已经被框架自动放进 Spring 容器里了,谁还需要你自己决定实现并注册进去”。
4.2 Embedding 的维度
// 阿里云的 text-embedding-v3 模型生成的向量维度是 1536
// 每个文本都会被转换成 1536 个数字的数组
//
// 就好像把文字映射到一个1536维的空间中的一点
// 语义相似的文字,在这个空间中的距离也会比较近
4.3 相似度计算
向量数据库通常使用以下几种相似度算法:
| 算法 | 说明 | 适用范围 |
|---|---|---|
| 余弦相似度 (Cosine) | 夹角越小越相似 | 方向敏感场景 |
| 欧氏距离 (L2) | 直线距离 | 数值敏感场景 |
| 内积 (IP) | 点积运算 | 排序优化 |
很多向量库默认采用余弦相似度或相近策略,具体行为取决于底层实现和索引配置。
五、本章小结
5.1 核心概念
| 概念 | 说明 |
|---|---|
| Embedding | 把文本转为向量的技术 |
| VectorStore | 向量数据库存储接口 |
| 维度/Vector Dimension | 向量的长度(如1536) |
| 相似度检索 | 根据向量距离找相似内容 |
| text-embedding-v3 | 阿里云的向量化模型 |
5.2 使用流程
文本 → EmbeddingModel → 向量 → VectorStore.add() → 存入数据库
↑
搜索词 → 自动转向量 → similaritySearch() → 返回相似结果
5.3 拓展:RAG 的基础
Embedding + VectorStore = RAG(检索增强生成) 的核心技术!
后面章节我们会学习完整的 RAG 流程。
本章重点:
- 理解 Embedding 的核心思想(文字→数字)
- 掌握文本向量化的方法
- 学会使用向量数据库进行相似度检索
下章剧透(s12):
掌握了向量化技术,下一章我们将学习 RAG(检索增强生成)——让 AI 能够"查阅"知识库来回答专业问题!
💡 TIP:不要把 VectorStore 只理解成 Redis
本章示例使用 Redis 只是因为它简单、容易上手。但在实际项目里,VectorStore 更重要的价值在于统一抽象:
| 抽象 | 作用 |
|---|---|
EmbeddingModel |
把文本转成向量 |
VectorStore |
存储向量并做相似度检索 |
SearchRequest |
描述一次检索请求 |
这样你后续切换底层实现时,业务代码改动会小很多。
📝 编辑者:Flittly
📅 更新时间:2026年4月