Embedding & VectorStore
向量化与语义检索是 RAG 系统的核心基础设施,理解 Embedding 和 VectorStore 是构建知识库应用的关键。
什么是 Embedding?
Embedding(向量嵌入)是将文本转换为高维数值向量的过程,语义相似的文本在向量空间中距离更近:
"Java 虚拟线程" → [0.23, -0.45, 0.67, ..., 0.12] (1536 维)
"JVM 轻量级线程" → [0.25, -0.43, 0.65, ..., 0.11] (语义相近,向量相近)
"Python 协程" → [0.18, -0.38, 0.71, ..., 0.09] (相关但不同)
"今天天气真好" → [-0.12, 0.34, -0.23, ..., 0.56] (语义无关,向量差异大)EmbeddingModel 接口
java
public interface EmbeddingModel extends Model<EmbeddingRequest, EmbeddingResponse> {
// 单文本向量化
float[] embed(String text);
// 批量向量化(推荐,减少 API 调用次数)
EmbeddingResponse call(EmbeddingRequest request);
// 获取向量维度
default int dimensions() {
return embed("test").length;
}
}使用 DashScope Embedding
java
@Autowired
private EmbeddingModel embeddingModel;
// 单文本
float[] vector = embeddingModel.embed("Spring AI Alibaba 是什么?");
System.out.println("向量维度:" + vector.length); // 1536
// 批量(推荐)
EmbeddingRequest request = new EmbeddingRequest(
List.of("文本1", "文本2", "文本3"),
EmbeddingOptions.EMPTY
);
EmbeddingResponse response = embeddingModel.call(request);
List<Embedding> embeddings = response.getResults();配置 Embedding 模型
yaml
spring:
ai:
dashscope:
embedding:
options:
model: text-embedding-v3 # 推荐最新版本| 模型 | 维度 | 特点 |
|---|---|---|
| text-embedding-v3 | 1024/2048 | 最新,效果最好 |
| text-embedding-v2 | 1536 | 稳定版本 |
| text-embedding-async-v2 | 1536 | 异步批量处理 |
VectorStore 接口
VectorStore 是向量数据库的统一抽象,屏蔽了不同数据库的实现差异:
java
public interface VectorStore {
// 存储文档(自动向量化)
void add(List<Document> documents);
// 删除文档
Optional<Boolean> delete(List<String> idList);
// 相似度搜索
List<Document> similaritySearch(String query);
// 带过滤条件的搜索
List<Document> similaritySearch(SearchRequest request);
}Document 数据模型
java
// Document 是 VectorStore 的基本单元
Document doc = new Document(
"Spring AI Alibaba 是阿里云推出的 Java AI 框架...", // 内容
Map.of( // 元数据
"source", "官方文档",
"chapter", "概述",
"version", "1.1.0",
"url", "https://java2ai.com/docs/overview"
)
);
// 访问文档属性
String id = doc.getId(); // 自动生成的 UUID
String content = doc.getContent(); // 文本内容
Map<String, Object> metadata = doc.getMetadata(); // 元数据
float[] embedding = doc.getEmbedding(); // 向量(存储后填充)SearchRequest 高级搜索
java
// 基础搜索
List<Document> results = vectorStore.similaritySearch("什么是 RAG?");
// 高级搜索
SearchRequest request = SearchRequest.builder()
.query("Spring AI 如何配置?")
.topK(5) // 返回最相似的 5 条
.similarityThreshold(0.75) // 相似度阈值(0-1)
.filterExpression("version == '1.1.0' && source == '官方文档'")
.build();
List<Document> filtered = vectorStore.similaritySearch(request);支持的 VectorStore 实现
1. SimpleVectorStore(开发/测试)
java
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
return new SimpleVectorStore(embeddingModel);
// 内存存储,重启丢失,仅用于开发测试
}2. Redis VectorStore(生产推荐)
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store-spring-boot-autoconfigure</artifactId>
</dependency>yaml
spring:
ai:
vectorstore:
redis:
uri: redis://localhost:6379
index: spring-ai-index
prefix: "doc:"
initialize-schema: truejava
// 自动装配,无需手动创建
@Autowired
private VectorStore vectorStore; // RedisVectorStore3. Elasticsearch VectorStore
yaml
spring:
elasticsearch:
uris: http://localhost:9200
ai:
vectorstore:
elasticsearch:
index-name: spring-ai-docs
dimensions: 1536
similarity: cosine4. PGVector(PostgreSQL)
yaml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/vectordb
ai:
vectorstore:
pgvector:
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 15365. 阿里云向量检索服务
yaml
spring:
ai:
vectorstore:
dashvector:
api-key: ${DASHVECTOR_API_KEY}
endpoint: ${DASHVECTOR_ENDPOINT}
collection-name: my-collection文档处理流水线
在存入 VectorStore 之前,通常需要对文档进行预处理:
java
@Service
public class DocumentIngestionService {
@Autowired
private VectorStore vectorStore;
@Autowired
private TikaDocumentReader tikaReader;
public void ingestDocument(Resource resource) {
// 1. 读取文档(支持 PDF、Word、HTML 等)
List<Document> rawDocs = new TikaDocumentReader(resource).get();
// 2. 文本分块(Chunking)
TextSplitter splitter = new TokenTextSplitter(
512, // chunk size(tokens)
128, // overlap(重叠 token 数,保持上下文连贯)
5, // min chunk size
10000, // max chunk size
true // keep separator
);
List<Document> chunks = splitter.apply(rawDocs);
// 3. 添加元数据
chunks.forEach(doc -> {
doc.getMetadata().put("ingested_at", LocalDateTime.now().toString());
doc.getMetadata().put("source", resource.getFilename());
});
// 4. 向量化并存储(自动调用 EmbeddingModel)
vectorStore.add(chunks);
log.info("成功导入 {} 个文档块", chunks.size());
}
}相似度算法
VectorStore 支持多种相似度计算方式:
余弦相似度(Cosine Similarity):最常用
cos(θ) = (A·B) / (|A| × |B|)
范围:[-1, 1],1 表示完全相同
欧氏距离(Euclidean Distance):
d = √(Σ(ai - bi)²)
距离越小,越相似
点积(Dot Product):
A·B = Σ(ai × bi)
适用于归一化向量向量索引优化
java
// HNSW 索引(Hierarchical Navigable Small World)
// 适合大规模数据,查询速度快,但内存占用较高
@Bean
public VectorStore pgVectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel em) {
return PgVectorStore.builder(jdbcTemplate, em)
.dimensions(1536)
.distanceType(PgVectorStore.PgDistanceType.COSINE_DISTANCE)
.indexType(PgVectorStore.PgIndexType.HNSW)
.initializeSchema(true)
.build();
}实战:构建文档知识库
java
@RestController
@RequestMapping("/knowledge")
public class KnowledgeController {
@Autowired
private VectorStore vectorStore;
@Autowired
private ChatClient chatClient;
// 上传文档
@PostMapping("/upload")
public ResponseEntity<String> upload(@RequestParam MultipartFile file) {
Resource resource = file.getResource();
List<Document> docs = new TikaDocumentReader(resource).get();
TextSplitter splitter = new TokenTextSplitter(512, 128);
List<Document> chunks = splitter.apply(docs);
vectorStore.add(chunks);
return ResponseEntity.ok("成功导入 " + chunks.size() + " 个文档块");
}
// 语义搜索
@GetMapping("/search")
public List<Document> search(@RequestParam String query) {
return vectorStore.similaritySearch(
SearchRequest.builder()
.query(query)
.topK(5)
.similarityThreshold(0.7)
.build()
);
}
// 知识库问答(RAG)
@GetMapping("/ask")
public String ask(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.advisors(new QuestionAnswerAdvisor(vectorStore))
.call()
.content();
}
}性能调优建议
生产环境建议
- 批量导入:使用
vectorStore.add(List<Document>)批量操作,减少 API 调用 - 合理分块:chunk size 512-1024 tokens,overlap 10-20%
- 索引选择:大数据量用 HNSW,小数据量用 IVFFlat
- 缓存 Embedding:相同文本的向量结果可以缓存,避免重复计算
- 异步处理:文档导入使用异步任务,避免阻塞主线程