向量数据库
向量数据库是一种专门类型的数据库,在 AI 应用程序中发挥着至关重要的作用。
在向量数据库中,查询与传统的关联数据库不同。它们执行的是相似性搜索,而不是精确匹配。当给定一个向量作为查询时,向量数据库会返回与查询向量“相似”的向量。关于如何高层计算这种相似性的更多细节,请参阅向量相似性。
向量数据库用于将您的数据与 AI 模型集成。使用它们的第一个步骤是将您的数据加载到向量数据库中。然后,当用户查询要发送到 AI 模型时,首先会检索一组相似的文档。这些文档随后作为用户问题的上下文,并与用户的查询一起发送到 AI 模型。这种技术被称为检索增强生成 (RAG)。
以下部分描述了 Spring AI 用于使用多个向量数据库实现的接口以及一些高级示例用法。
最后一部分旨在揭开向量数据库中相似性搜索的底层方法的神秘面纱。
API 概述
本节作为VectorStore
接口及其在 Spring AI 框架中的相关类的指南。
Spring AI 提供了一个抽象的 API,用于通过VectorStore
接口与向量数据库交互。
以下是VectorStore
接口定义
public interface VectorStore {
void add(List<Document> documents);
Optional<Boolean> delete(List<String> idList);
List<Document> similaritySearch(String query);
List<Document> similaritySearch(SearchRequest request);
}
以及相关的SearchRequest
构建器
public class SearchRequest {
public final String query;
private int topK = 4;
private double similarityThreshold = SIMILARITY_THRESHOLD_ALL;
private Filter.Expression filterExpression;
public static SearchRequest query(String query) { return new SearchRequest(query); }
private SearchRequest(String query) { this.query = query; }
public SearchRequest withTopK(int topK) {...}
public SearchRequest withSimilarityThreshold(double threshold) {...}
public SearchRequest withSimilarityThresholdAll() {...}
public SearchRequest withFilterExpression(Filter.Expression expression) {...}
public SearchRequest withFilterExpression(String textExpression) {...}
public String getQuery() {...}
public int getTopK() {...}
public double getSimilarityThreshold() {...}
public Filter.Expression getFilterExpression() {...}
}
要将数据插入向量数据库,请将其封装在Document
对象中。Document
类封装来自数据源(例如 PDF 或 Word 文档)的内容,并包含以字符串表示的文本。它还包含以键值对形式表示的元数据,包括文件名等详细信息。
插入向量数据库后,文本内容将使用嵌入模型转换为数值数组或float[]
,称为向量嵌入。嵌入模型,例如Word2Vec、GLoVE和BERT,或 OpenAI 的text-embedding-ada-002
,用于将单词、句子或段落转换为这些向量嵌入。
向量数据库的作用是存储和促进这些嵌入的相似性搜索。它本身不会生成嵌入。要创建向量嵌入,应使用EmbeddingModel
。
接口中的similaritySearch
方法允许检索与给定查询字符串相似的文档。可以使用以下参数微调这些方法
-
k
:指定要返回的相似文档的最大数量的整数。这通常称为“top K”搜索或“K 近邻”(KNN)。 -
threshold
:一个介于 0 到 1 之间的双精度值,其中更接近 1 的值表示更高的相似性。例如,默认情况下,如果您设置阈值为 0.75,则仅返回相似度高于此值的文档。 -
Filter.Expression
:一个用于传递类似于 SQL 中“where”子句的流畅 DSL(领域特定语言)表达式的类,但它仅适用于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
。检查您正在使用的向量存储的文档以获取特定属性名称。
批处理策略
在使用向量存储时,通常需要嵌入大量文档。虽然一次性调用嵌入所有文档看起来很简单,但这种方法可能会导致问题。嵌入模型将文本处理为标记,并且具有最大标记限制,通常称为上下文窗口大小。此限制限制了可以在单个嵌入请求中处理的文本量。尝试在一个调用中嵌入太多标记会导致错误或截断的嵌入。
为了解决此标记限制,Spring AI 实现了批处理策略。这种方法将大型文档集分解成适合嵌入模型最大上下文窗口的较小批次。批处理不仅解决了标记限制问题,还可以提高性能并更有效地利用 API 速率限制。
Spring AI 通过 `BatchingStrategy` 接口提供此功能,该接口允许根据标记计数以子批次处理文档。
核心 `BatchingStrategy` 接口定义如下:
public interface BatchingStrategy {
List<List<Document>> batch(List<Document> documents);
}
此接口定义了一个方法 `batch`,它接受一个文档列表并返回一个文档批次列表。
默认实现
Spring AI 提供了一个名为 `TokenCountBatchingStrategy` 的默认实现。此策略根据文档的标记计数对文档进行批处理,确保每个批次都不会超过计算出的最大输入标记计数。
`TokenCountBatchingStrategy` 的主要功能:
-
使用 OpenAI 的最大输入标记计数 (8191) 作为默认上限。
-
包含一个保留百分比(默认 10%),以提供潜在开销的缓冲。
-
将实际最大输入标记计数计算为:`actualMaxInputTokenCount = originalMaxInputTokenCount * (1 - RESERVE_PERCENTAGE)`
该策略估计每个文档的标记计数,将它们分组到批次中,而不超过最大输入标记计数,如果单个文档超过此限制,则抛出异常。
您还可以自定义 `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.9 // Set the threshold factor
);
}
}
在此配置中:
-
`EncodingType.CL100K_BASE`:指定用于标记化的编码类型。此编码类型由 `JTokkitTokenCountEstimator` 用于准确估计标记计数。
-
`8000`:设置最大输入标记计数。此值应小于或等于嵌入模型的最大上下文窗口大小。
-
`0.9`:设置阈值因子。此因子确定在开始新批次之前批次可以有多满。值为 0.9 表示每个批次将填充到最大输入标记计数的 90%。
默认情况下,此构造函数使用 `Document.DEFAULT_CONTENT_FORMATTER` 进行内容格式化,并使用 `MetadataMode.NONE` 进行元数据处理。如果您需要自定义这些参数,则可以使用带有其他参数的完整构造函数。
定义后,此自定义 `TokenCountBatchingStrategy` bean 将由应用程序中的 `EmbeddingModel` 实现自动使用,替换默认策略。
`TokenCountBatchingStrategy` 在内部使用 `TokenCountEstimator`(具体来说是 `JTokkitTokenCountEstimator`)来计算标记计数以进行高效的批处理。这确保了根据指定的编码类型进行准确的标记估计。
此外, `TokenCountBatchingStrategy` 提供了灵活性,允许您传入您自己的 `TokenCountEstimator` 接口实现。此功能使您能够使用针对特定需求定制的自定义标记计数策略。例如:
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` 接口的可用实现:
-
Azure 向量搜索 - Azure 向量存储。
-
Apache Cassandra - Apache Cassandra 向量存储。
-
Chroma 向量存储 - Chroma 向量存储。
-
Elasticsearch 向量存储 - Elasticsearch 向量存储。
-
GemFire 向量存储 - GemFire 向量存储。
-
Milvus 向量存储 - Milvus 向量存储。
-
MongoDB Atlas 向量存储 - MongoDB Atlas 向量存储。
-
Neo4j 向量存储 - Neo4j 向量存储。
-
OpenSearch 向量存储 - OpenSearch 向量存储。
-
Oracle 向量存储 - Oracle 数据库 向量存储。
-
PgVector 存储 - PostgreSQL/PGVector 向量存储。
-
Pinecone 向量存储 - PineCone 向量存储。
-
Qdrant 向量存储 - Qdrant 向量存储。
-
Redis 向量存储 - Redis 向量存储。
-
SAP Hana 向量存储 - SAP HANA 向量存储。
-
Typesense 向量存储 - Typesense 向量存储。
-
Weaviate 向量存储 - Weaviate 向量存储。
-
SimpleVectorStore - 持久向量存储的简单实现,适合教育目的。
未来版本可能会支持更多实现。
如果您有一个需要 Spring AI 支持的向量数据库,请在 GitHub 上提交问题,或者更好的是,提交包含实现的拉取请求。
有关每个 `VectorStore` 实现的信息可以在本章的子部分中找到。
示例用法
要计算向量数据库的嵌入,您需要选择一个与正在使用的更高级别 AI 模型匹配的嵌入模型。
例如,对于 OpenAI 的 ChatGPT,我们使用 `OpenAiEmbeddingModel` 和一个名为 `text-embedding-ada-002` 的模型。
OpenAI 的 Spring Boot 启动程序的自动配置使 `EmbeddingModel` 的实现在 Spring 应用程序上下文中可用于依赖注入。
将数据加载到向量存储中的常规用法是在类似批处理的任务中完成的,方法是首先将数据加载到 Spring AI 的 `Document` 类中,然后调用 `save` 方法。
给定一个表示 JSON 文件的源文件的 `String` 引用,其中包含我们想要加载到向量数据库中的数据,我们使用 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'"
-
"genre == 'drama' && year >= 2020"
-
"genre 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();