李眉在服务器上装好 PGVector 后吐槽:"Spring AI 把向量存储也抽象成接口了,连 Redis 和 Pinecone 都能用同一套 API——这就是我喜欢的 SpringStyle。"
向量存储抽象与配置
定义与作用
VectorStore 是 Spring AI 的向量数据库抽象接口,屏蔽了 PGVector、Redis、Pinecone、Milvus 等不同后端的差异。
public interface VectorStore {
void add(List<Document> documents); // 存储文档
List<Document> similaritySearch(SearchRequest request); // 相似搜索
void delete(List<String> ids); // 删除文档
}
核心价值:换向量数据库只需改 YAML + 换 Starter,代码 0 改动。
核心原理:文档写入与查询的完整流程
图释:VectorStore 在内部自动调用 EmbeddingModel 做文本向量化,应用代码不需要手动处理向量。
SearchRequest 参数详解
SearchRequest request = SearchRequest.builder()
.query("人工智能选修课有哪些") // 查询文本
.topK(5) // 返回前 5 个最相似结果
.similarityThreshold(0.7) // 只返回相似度 > 0.7 的结果
.filterExpression("category == 'CS'") // 元数据过滤:只查 CS 类课程
.build();
List<Document> results = vectorStore.similaritySearch(request);
| 参数 | 说明 | 典型值 |
|---|---|---|
| query | 查询文本 | 用户问题 |
| topK | 返回数量 | 3-5(太少丢失信息,太多引入噪声) |
| similarityThreshold | 相似度阈值 | 0.7-0.8(低于此值的结果被过滤) |
| filterExpression | 元数据过滤 | 按分类/部门/时间过滤 |
相似度距离类型对比
| 距离类型 | 取值范围 | 特点 | 推荐场景 |
|---|---|---|---|
| COSINE_DISTANCE | [0, 2] | 只关注方向,忽略向量长度 | 语义搜索(推荐) |
| EUCLIDEAN_DISTANCE | [0, +∞) | 真实的欧几里得距离 | 需要绝对距离的场景 |
| DOT_PRODUCT | (-∞, +∞) | 向量内积 | 适用于归一化向量 |
完整示例:飞翔科技文档库的查询
PGVector 完整配置
# application.yml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/feixiang_vector
username: postgres
password: ${DB_PASSWORD}
ai:
vectorstore:
pgvector:
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 1536
initialize-schema: true
依赖引入
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
关键代码
@Service
public class DocumentSearchService {
private final VectorStore vectorStore;
public DocumentSearchService(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
/**
* 查询飞翔科技产品手册
*/
public String searchProductManual(String question) {
SearchRequest request = SearchRequest.builder()
.query(question)
.topK(3)
.similarityThreshold(0.7)
.build();
List<Document> results = vectorStore.similaritySearch(request);
if (results.isEmpty()) {
return "未在文档库中找到相关信息";
}
// 拼接搜索结果
StringBuilder sb = new StringBuilder();
for (int i = 0; i < results.size(); i++) {
Document doc = results.get(i);
sb.append(String.format("【匹配 %d】相似度: %.2f\n",
i + 1, doc.getScore()));
sb.append(doc.getContent()).append("\n\n");
}
return sb.toString();
}
}
易错场景与面试考点
易错场景一:向量维度不匹配
# ❌ 错误:PGVector 配置的 dimensions 与 Embedding 模型不一致
spring:
ai:
vectorstore:
pgvector:
dimensions: 768 # 错误:OpenAI text-embedding-3-small 是 1536 维
问题分析
PGVector 在创建表时会强制执行向量维度约束。维度不匹配会导致 INSERT 时抛异常:
ERROR: expected 1536 dimensions, not 768
解决:确认 Embedding 模型的实际维度后再配置。OpenAI 的 text-embedding-3-small 为 1536 维,Ollama 的 nomic-embed-text 为 768 维。
易错场景二:similarityThreshold 过高导致空结果
// ❌ 错误:阈值设为 0.95,几乎所有结果都被过滤
SearchRequest.builder()
.similarityThreshold(0.95) // 太高了
.build();
// 结果:empty list
问题分析
0.7-0.8 是一个合理的取值范围。超过 0.9 会过滤掉大部分相关结果;不设置阈值会返回语义无关的结果。
面试高频题
Q1:VectorStore.add() 方法是同步的还是异步的?内部发生了什么?
同步调用。内部流程:①调用 EmbeddingModel.embed() 将 Document.content 转为向量;②将 content + metadata + embedding 写入底层数据库(如 PGVector)。这个过程通常 100ms-1s,取决于 Embedding API 的响应速度。
Q2:为什么推荐 COSINE_DISTANCE 而不是 EUCLIDEAN_DISTANCE?
余弦距离只衡量向量方向(语义方向),忽略向量长度(文本长度)。两段语义相同但长度不同的文本(如"你好"和"你好你好你好")在余弦距离下高度相似,但在欧氏距离下距离很大。语义搜索场景下余弦距离更合理。
本章小结
- VectorStore.add() 写入时自动调用 EmbeddingModel 向量化
- similaritySearch() 的核心参数是 topK、similarityThreshold、filterExpression
- 推荐使用 COSINE_DISTANCE 做语义搜索
- PGVector 配置的 dimensions 必须与 Embedding 模型输出维度一致