向量数据库
向量数据库是一种专门类型的数据库,在 AI 应用中扮演着至关重要的角色。
在向量数据库中,查询与传统的关系型数据库有所不同。它们不进行精确匹配,而是执行相似性搜索。当给定一个向量作为查询时,向量数据库会返回与查询向量“相似”的向量。关于如何在高层次上计算这种相似性的更多详细信息,请参阅 Vector Similarity。
向量数据库用于将你的数据与 AI 模型集成。使用的第一步是将你的数据加载到向量数据库中。然后,当用户查询要发送到 AI 模型时,首先会检索一组相似的文档。这些文档作为用户问题的上下文,并与用户的查询一起发送到 AI 模型。这种技术被称为 检索增强生成 (Retrieval Augmented Generation, RAG)。
以下部分描述了 Spring AI 接口,用于使用多种向量数据库实现以及一些高级示例用法。
最后一节旨在揭秘向量数据库中相似性搜索的基本方法。
API 概览
本节作为指南,介绍 Spring AI 框架中的 VectorStore
接口及其相关类。
Spring AI 提供了一个抽象化的 API,通过 VectorStore
接口与向量数据库进行交互。
以下是 VectorStore
接口定义:
public interface VectorStore extends DocumentWriter {
default String getName() {
return this.getClass().getSimpleName();
}
void add(List<Document> documents);
void delete(List<String> idList);
void delete(Filter.Expression filterExpression);
default void delete(String filterExpression) { ... };
List<Document> similaritySearch(String query);
List<Document> similaritySearch(SearchRequest request);
default <T> Optional<T> getNativeClient() {
return Optional.empty();
}
}
以及相关的 SearchRequest
构建器:
public class SearchRequest {
public static final double SIMILARITY_THRESHOLD_ACCEPT_ALL = 0.0;
public static final int DEFAULT_TOP_K = 4;
private String query = "";
private int topK = DEFAULT_TOP_K;
private double similarityThreshold = SIMILARITY_THRESHOLD_ACCEPT_ALL;
@Nullable
private Filter.Expression filterExpression;
public static Builder from(SearchRequest originalSearchRequest) {
return builder().query(originalSearchRequest.getQuery())
.topK(originalSearchRequest.getTopK())
.similarityThreshold(originalSearchRequest.getSimilarityThreshold())
.filterExpression(originalSearchRequest.getFilterExpression());
}
public static class Builder {
private final SearchRequest searchRequest = new SearchRequest();
public Builder query(String query) {
Assert.notNull(query, "Query can not be null.");
this.searchRequest.query = query;
return this;
}
public Builder topK(int topK) {
Assert.isTrue(topK >= 0, "TopK should be positive.");
this.searchRequest.topK = topK;
return this;
}
public Builder similarityThreshold(double threshold) {
Assert.isTrue(threshold >= 0 && threshold <= 1, "Similarity threshold must be in [0,1] range.");
this.searchRequest.similarityThreshold = threshold;
return this;
}
public Builder similarityThresholdAll() {
this.searchRequest.similarityThreshold = 0.0;
return this;
}
public Builder filterExpression(@Nullable Filter.Expression expression) {
this.searchRequest.filterExpression = expression;
return this;
}
public Builder filterExpression(@Nullable String textExpression) {
this.searchRequest.filterExpression = (textExpression != null)
? new FilterExpressionTextParser().parse(textExpression) : null;
return this;
}
public SearchRequest build() {
return this.searchRequest;
}
}
public String getQuery() {...}
public int getTopK() {...}
public double getSimilarityThreshold() {...}
public Filter.Expression getFilterExpression() {...}
}
要将数据插入到向量数据库中,需要将其封装在一个 Document
对象中。Document
类封装了来自数据源(如 PDF 或 Word 文档)的内容,并以字符串形式表示文本。它还包含以键值对形式存储的元数据,其中包括文件名等详细信息。
向量数据库的作用是存储这些嵌入向量并促进相似性搜索。它本身并不生成嵌入向量。要创建向量嵌入,应使用 EmbeddingModel
。
接口中的 similaritySearch
方法允许检索与给定查询字符串相似的文档。这些方法可以通过使用以下参数进行微调:
-
k
: 一个整数,指定返回的相似文档的最大数量。这通常被称为“前 K 个”搜索或“K 最近邻”(KNN)。 -
threshold
: 一个范围在 0 到 1 之间的双精度值,值越接近 1 表示相似度越高。默认情况下,如果你设置阈值为 0.75,例如,只有相似度高于此值的文档才会被返回。 -
Filter.Expression
: 一个用于传递流畅 DSL(领域特定语言)表达式的类,其功能类似于 SQL 中的“where”子句,但它仅适用于Document
的元数据键值对。 -
filterExpression
: 一个基于 ANTLR4 的外部 DSL,接受字符串形式的过滤表达式。例如,对于元数据键如 country、year 和isActive
,你可以使用如下表达式:country == 'UK' && year >= 2020 && isActive == true
。
在 元数据过滤器 部分查找更多关于 Filter.Expression
的信息。
模式初始化
一些向量存储在使用前需要初始化其后端架构。默认情况下,它不会自动为你初始化。你必须通过为适当的构造函数参数传递一个 boolean
值来选择启用,或者如果使用 Spring Boot,则在 application.properties
或 application.yml
中将相应的 initialize-schema
属性设置为 true
。请查阅你使用的向量存储的文档以获取具体的属性名称。
批处理策略
在使用向量存储时,通常需要嵌入大量文档。虽然一次性调用嵌入所有文档看似简单,但这种方法可能会导致问题。嵌入模型将文本处理为 token,并且有一个最大 token 限制,通常称为上下文窗口大小。这一限制限制了单次嵌入请求中可以处理的文本量。如果尝试在一次调用中嵌入过多的 token,可能会导致错误或截断的嵌入。
为了解决这一令牌限制问题,Spring AI 实现了一种分批处理策略。该方法将大量文档集分解为较小的批次,以适应嵌入模型的最大上下文窗口。分批处理不仅解决了令牌限制问题,还可以提高性能并更有效地利用 API 速率限制。
Spring AI 通过 BatchingStrategy
接口提供了这一功能,该接口允许根据文档的 token 数量对文档进行子批次处理。
核心的 BatchingStrategy
接口定义如下:
public interface BatchingStrategy {
List<List<Document>> batch(List<Document> documents);
}
该接口定义了一个单一方法 batch
,该方法接收一个文档列表并返回一个文档批次列表。
默认实现
Spring AI 提供了一个名为 TokenCountBatchingStrategy
的默认实现。该策略根据文档的 token 数量进行批处理,确保每个批次不会超过计算得出的最大输入 token 数量。
TokenCountBatchingStrategy
的主要特性:
-
使用 OpenAI 的最大输入 token 数(8191)作为默认上限。
-
包含一个保留百分比(默认 10%),为潜在的额外开销提供缓冲。
-
计算实际的最大输入 token 数为:
actualMaxInputTokenCount = originalMaxInputTokenCount * (1 - RESERVE_PERCENTAGE)
该策略估计每个文档的 token 数量,将它们分组为不超过最大输入 token 数量的批次,如果单个文档超过此限制,则抛出异常。
你也可以自定义 TokenCountBatchingStrategy
以更好地满足你的特定需求。这可以通过在 Spring Boot 的 @Configuration
类中创建一个带有自定义参数的新实例来实现。
以下是一个如何创建自定义 TokenCountBatchingStrategy
bean 的示例:
@Configuration
public class EmbeddingConfig {
@Bean
public BatchingStrategy customTokenCountBatchingStrategy() {
return new TokenCountBatchingStrategy(
EncodingType.CL100K_BASE, // Specify the encoding type
8000, // Set the maximum input token count
0.1 // Set the reserve percentage
);
}
}
在此配置中:
-
EncodingType.CL100K_BASE
: 指定用于分词的编码类型。该编码类型由JTokkitTokenCountEstimator
使用,以准确估计 token 数量。 -
8000
: 设置最大输入 token 数量。该值应小于或等于您的嵌入模型的最大上下文窗口大小。 -
0.1
: 设置保留百分比。从最大输入 token 数量中保留的 token 百分比。这为处理过程中潜在的 token 数量增加创建了一个缓冲区。
默认情况下,此构造函数使用 Document.DEFAULT_CONTENT_FORMATTER
进行内容格式化,并使用 MetadataMode.NONE
进行元数据处理。如果你需要自定义这些参数,可以使用带有额外参数的完整构造函数。
一旦定义了这个自定义的 TokenCountBatchingStrategy
bean,它将会被应用程序中的 EmbeddingModel
实现自动使用,从而替换掉默认的策略。
TokenCountBatchingStrategy
内部使用 TokenCountEstimator
(特别是 JTokkitTokenCountEstimator
)来计算 token 数量,以实现高效的批处理。这确保了基于指定编码类型的准确 token 估计。
此外,TokenCountBatchingStrategy
提供了灵活性,允许你传入自己实现的 TokenCountEstimator
接口。这一特性使你能够使用根据特定需求定制的自定义 token 计数策略。例如:
TokenCountEstimator customEstimator = new YourCustomTokenCountEstimator();
TokenCountBatchingStrategy strategy = new TokenCountBatchingStrategy(
this.customEstimator,
8000, // maxInputTokenCount
0.1, // reservePercentage
Document.DEFAULT_CONTENT_FORMATTER,
MetadataMode.NONE
);
自定义实现
虽然 TokenCountBatchingStrategy
提供了一个强大的默认实现,但你可以根据特定需求自定义批处理策略。这可以通过 Spring Boot 的自动配置来实现。
要自定义批处理策略,请在您的 Spring Boot 应用程序中定义一个 BatchingStrategy
bean:
@Configuration
public class EmbeddingConfig {
@Bean
public BatchingStrategy customBatchingStrategy() {
return new CustomBatchingStrategy();
}
}
这个自定义的 BatchingStrategy
将会自动被应用程序中的 EmbeddingModel
实现使用。
Spring AI 支持的向量存储配置为使用默认的 TokenCountBatchingStrategy
。SAP Hana 向量存储目前未配置为批处理。
VectorStore 实现
以下是 VectorStore
接口的可用实现:
-
Azure Vector Search - Azure 向量存储。
-
Apache Cassandra - Apache Cassandra 向量存储。
-
Chroma Vector Store - Chroma 向量存储。
-
GemFire Vector Store - GemFire 向量存储。
-
MariaDB Vector Store - MariaDB 向量存储。
-
Milvus Vector Store - Milvus 向量存储。
-
Neo4j Vector Store - Neo4j 向量存储。
-
OpenSearch Vector Store - OpenSearch 向量存储。
-
Oracle Vector Store - Oracle Database 向量存储。
-
PgVector Store - PostgreSQL/PGVector 向量存储。
-
Pinecone Vector Store - PineCone 向量存储。
-
Qdrant Vector Store - Qdrant 向量存储。
-
Redis Vector Store - Redis 向量存储。
-
SAP Hana Vector Store - SAP HANA 向量存储。
-
Typesense Vector Store - Typesense 向量存储。
-
Weaviate Vector Store - Weaviate 向量存储。
-
SimpleVectorStore - 一个简单的持久化向量存储实现,适合教育用途。
在未来的版本中可能会支持更多的实现。
如果你有一个需要 Spring AI 支持的向量数据库,请在 GitHub 上提交一个 issue,或者更好的是,提交一个带有实现的 pull request。
有关每个 VectorStore
实现的信息可以在本章的各个小节中找到。
示例用法
为了计算向量数据库的嵌入,你需要选择一个与使用的高级 AI 模型相匹配的嵌入模型。
例如,使用 OpenAI 的 ChatGPT 时,我们使用 OpenAiEmbeddingModel
和一个名为 text-embedding-ada-002
的模型。
Spring Boot 启动器对 OpenAI 的自动配置使得 EmbeddingModel
的实现可以在 Spring 应用程序上下文中进行依赖注入。
将数据加载到向量存储中的一般用法类似于批量作业,首先将数据加载到 Spring AI 的 Document
类中,然后调用 save
方法。
给定一个指向表示 JSON 文件的源文件的 String
引用,该 JSON 文件包含我们希望加载到向量数据库中的数据,我们使用 Spring AI 的 JsonReader
来加载 JSON 中的特定字段,将其分割成小块,然后将这些小片段传递给向量存储实现。VectorStore
实现计算嵌入并将 JSON 和嵌入存储在向量数据库中:
@Autowired
VectorStore vectorStore;
void load(String sourceFile) {
JsonReader jsonReader = new JsonReader(new FileSystemResource(sourceFile),
"price", "name", "shortDescription", "description", "tags");
List<Document> documents = jsonReader.get();
this.vectorStore.add(documents);
}
之后,当用户的问题被传递到 AI 模型中时,会进行相似性搜索以检索类似的文档,然后这些文档会被“塞入”提示中,作为用户问题的上下文。
String question = <question from user>
List<Document> similarDocuments = store.similaritySearch(this.question);
可以在 similaritySearch
方法中传入额外的参数来定义要检索的文档数量以及相似性搜索的阈值。
元数据过滤器
本节描述了可用于查询结果的各种过滤器。
过滤字符串
你可以将一个类似 SQL 的过滤表达式作为 String
传递给 similaritySearch
的重载方法之一。
考虑以下示例:
-
"country == 'BG'"
"国家 == 'BG'"
-
"genre == 'drama' && year >= 2020"
"类型 == 'drama' && 年份 >= 2020"
-
"genre in ['comedy', 'documentary', 'drama']"
"类型 in ['comedy', 'documentary', 'drama']"
Filter.Expression
你可以使用 FilterExpressionBuilder
创建一个 Filter.Expression
实例,它提供了一个流畅的 API。一个简单的示例如下:
FilterExpressionBuilder b = new FilterExpressionBuilder();
Expression expression = this.b.eq("country", "BG").build();
你可以使用以下运算符构建复杂的表达式:
EQUALS: '=='
MINUS : '-'
PLUS: '+'
GT: '>'
GE: '>='
LT: '<'
LE: '<='
NE: '!='
你可以使用以下运算符来组合表达式:
AND: 'AND' | 'and' | '&&';
OR: 'OR' | 'or' | '||';
考虑以下示例:
Expression exp = b.and(b.eq("genre", "drama"), b.gte("year", 2020)).build();
你也可以使用以下运算符:
IN: 'IN' | 'in';
NIN: 'NIN' | 'nin';
NOT: 'NOT' | 'not';
考虑以下示例:
Expression exp = b.and(b.eq("genre", "drama"), b.gte("year", 2020)).build();
从向量存储中删除文档
Vector Store 接口提供了多种删除文档的方法,允许您通过特定的文档 ID 或使用过滤表达式来移除数据。
按文档 ID 删除
删除文档的最简单方法是提供文档 ID 列表:
void delete(List<String> idList);
此方法会移除所有 ID 与提供的列表中匹配的文档。如果列表中任何 ID 在存储中不存在,它将被忽略。
// Create and add document
Document document = new Document("The World is Big",
Map.of("country", "Netherlands"));
vectorStore.add(List.of(document));
// Delete document by ID
vectorStore.delete(List.of(document.getId()));
通过过滤表达式删除
对于更复杂的删除条件,你可以使用过滤表达式:
void delete(Filter.Expression filterExpression);
此方法接受一个 Filter.Expression
对象,该对象定义了应删除哪些文档的条件。当您需要根据文档的元数据属性删除文档时,此方法特别有用。
// Create test documents with different metadata
Document bgDocument = new Document("The World is Big",
Map.of("country", "Bulgaria"));
Document nlDocument = new Document("The World is Big",
Map.of("country", "Netherlands"));
// Add documents to the store
vectorStore.add(List.of(bgDocument, nlDocument));
// Delete documents from Bulgaria using filter expression
Filter.Expression filterExpression = new Filter.Expression(
Filter.ExpressionType.EQ,
new Filter.Key("country"),
new Filter.Value("Bulgaria")
);
vectorStore.delete(filterExpression);
// Verify deletion with search
SearchRequest request = SearchRequest.builder()
.query("World")
.filterExpression("country == 'Bulgaria'")
.build();
List<Document> results = vectorStore.similaritySearch(request);
// results will be empty as Bulgarian document was deleted
通过字符串过滤表达式删除
为了方便,你也可以使用基于字符串的过滤表达式来删除文档:
void delete(String filterExpression);
此方法将提供的字符串过滤器在内部转换为 Filter.Expression
对象。当您有字符串格式的过滤条件时,此方法非常有用。
// Create and add documents
Document bgDocument = new Document("The World is Big",
Map.of("country", "Bulgaria"));
Document nlDocument = new Document("The World is Big",
Map.of("country", "Netherlands"));
vectorStore.add(List.of(bgDocument, nlDocument));
// Delete Bulgarian documents using string filter
vectorStore.delete("country == 'Bulgaria'");
// Verify remaining documents
SearchRequest request = SearchRequest.builder()
.query("World")
.topK(5)
.build();
List<Document> results = vectorStore.similaritySearch(request);
// results will only contain the Netherlands document
调用 Delete API 时的错误处理
所有删除方法在发生错误时都可能抛出异常:
最佳实践是将删除操作包裹在 try-catch
块中:
try {
vectorStore.delete("country == 'Bulgaria'");
}
catch (Exception e) {
logger.error("Invalid filter expression", e);
}
文档版本控制用例
一个常见的场景是管理文档版本,其中你需要上传文档的新版本并删除旧版本。以下是使用过滤表达式处理这种情况的方法:
// Create initial document (v1) with version metadata
Document documentV1 = new Document(
"AI and Machine Learning Best Practices",
Map.of(
"docId", "AIML-001",
"version", "1.0",
"lastUpdated", "2024-01-01"
)
);
// Add v1 to the vector store
vectorStore.add(List.of(documentV1));
// Create updated version (v2) of the same document
Document documentV2 = new Document(
"AI and Machine Learning Best Practices - Updated",
Map.of(
"docId", "AIML-001",
"version", "2.0",
"lastUpdated", "2024-02-01"
)
);
// First, delete the old version using filter expression
Filter.Expression deleteOldVersion = new Filter.Expression(
Filter.ExpressionType.AND,
Arrays.asList(
new Filter.Expression(
Filter.ExpressionType.EQ,
new Filter.Key("docId"),
new Filter.Value("AIML-001")
),
new Filter.Expression(
Filter.ExpressionType.EQ,
new Filter.Key("version"),
new Filter.Value("1.0")
)
)
);
vectorStore.delete(deleteOldVersion);
// Add the new version
vectorStore.add(List.of(documentV2));
// Verify only v2 exists
SearchRequest request = SearchRequest.builder()
.query("AI and Machine Learning")
.filterExpression("docId == 'AIML-001'")
.build();
List<Document> results = vectorStore.similaritySearch(request);
// results will contain only v2 of the document
你也可以使用字符串过滤器表达式来完成相同的操作:
// Delete old version using string filter
vectorStore.delete("docId == 'AIML-001' AND version == '1.0'");
// Add new version
vectorStore.add(List.of(documentV2));
删除文档时的性能考虑
-
当您确切知道要删除哪些文档时,按 ID 列表删除通常更快。
-
基于过滤器的删除可能需要扫描索引以查找匹配的文档;然而,这取决于向量存储的具体实现。
-
大型删除操作应分批进行,以避免给系统带来过大压力。
-
在根据文档属性删除时,考虑使用过滤器表达式,而不是先收集 ID。
理解向量
章节摘要
📄️ Azure AI 服务
本节将引导您设置 AzureVectorStore,以存储文档嵌入并使用 Azure AI Search 服务执行相似性搜索。
📄️ Azure Cosmos DB
本节将指导您如何设置 CosmosDBVectorStore 以存储文档嵌入并执行相似性搜索。
📄️ Apache Cassandra 向量存储
本节将指导您如何设置 CassandraVectorStore 以存储文档嵌入并执行相似性搜索。
📄️ Chroma
本节将引导您设置 Chroma VectorStore 以存储文档嵌入并执行相似性搜索。
📄️ Elasticsearch
本节将指导你设置 Elasticsearch VectorStore 以存储文档嵌入并执行相似性搜索。
📄️ GemFire
本节将引导您完成设置 GemFireVectorStore 以存储文档嵌入并执行相似性搜索的过程。
📄️ MariaDB 向量存储
本节将指导您如何设置 MariaDBVectorStore 来存储文档嵌入并执行相似性搜索。
📄️ Milvus
Milvus 是一个开源的向量数据库,在数据科学和机器学习领域引起了广泛关注。其显著特点之一在于其对向量索引和查询的强大支持。Milvus 采用了最先进的算法来加速搜索过程,使其在处理大规模数据集时,依然能够高效地检索相似的向量。
📄️ MongoDB Atlas
本节将引导您设置 MongoDB Atlas 作为向量存储,以便与 Spring AI 一起使用。
📄️ Neo4j
本节将指导您如何设置 Neo4jVectorStore 以存储文档嵌入并执行相似性搜索。
📄️ OpenSearch
本节将引导您设置 OpenSearchVectorStore,以存储文档嵌入并执行相似性搜索。
📄️ Oracle
Oracle 数据库 23ai(23.4+)的 AI 向量搜索功能现已作为 Spring AI VectorStore 提供,以帮助您存储文档嵌入并执行相似性搜索。当然,所有其他功能也同样可用。
📄️ PGvector
本节将引导您设置 PGvector VectorStore 以存储文档嵌入并执行相似性搜索。
📄️ Pinecone
本节将引导您设置 Pinecone VectorStore,以存储文档嵌入并执行相似性搜索。
📄️ Qdrant
本节将引导您设置 Qdrant VectorStore,用于存储文档嵌入并执行相似性搜索。
📄️ Redis
本节将指导您如何设置 RedisVectorStore 来存储文档嵌入并执行相似性搜索。
📄️ SAP HANA
你需要一个 SAP HANA Cloud 向量引擎账户 - 参考 SAP HANA Cloud 向量引擎 - 创建试用账户指南来创建一个试用账户。
📄️ Typesense
本节将引导你设置 TypesenseVectorStore 来存储文档嵌入并执行相似性搜索。
📄️ Weaviate
本节将引导您设置 Weaviate VectorStore,以存储文档嵌入并执行相似性搜索。