向量数据库
向量数据库是一种专门的数据库类型,在 AI 应用程序中扮演着重要的角色。
在向量数据库中,查询与传统关系型数据库不同。它们执行的是相似性搜索,而不是精确匹配。当给定一个向量作为查询时,向量数据库会返回与该查询向量“相似”的向量。关于这种相似性如何在高层次上计算的更多细节,请参阅向量相似性。
向量数据库用于将您的数据与 AI 模型集成。使用它们的第一步是将您的数据加载到向量数据库中。然后,当用户查询需要发送到 AI 模型时,首先会检索一组相似的文档。这些文档随后作为用户问题的上下文,并与用户查询一起发送到 AI 模型。这种技术被称为检索增强生成(RAG)。
以下部分描述了 Spring AI 接口,用于使用多种向量数据库实现以及一些高层次的示例用法。
最后一部分旨在揭开向量数据库中相似性搜索底层方法的神秘面纱。
API 概述
本节将作为 Spring AI 框架中 VectorStore 接口及其相关类的指南。
Spring AI 提供了一个抽象的 API,用于通过 VectorStore 接口及其只读对应物 VectorStoreRetriever 接口与向量数据库进行交互。
VectorStoreRetriever 接口
Spring AI 提供了一个只读接口,名为 VectorStoreRetriever,它仅暴露文档检索功能。
@FunctionalInterface
public interface VectorStoreRetriever {
List<Document> similaritySearch(SearchRequest request);
default List<Document> similaritySearch(String query) {
return this.similaritySearch(SearchRequest.builder().query(query).build());
}
}
此函数式接口设计用于您只需从向量存储中检索文档而无需执行任何修改操作的用例。它遵循最小权限原则,仅暴露文档检索所需的必要功能。
VectorStore 接口
VectorStore 接口扩展了 VectorStoreRetriever 并增加了修改功能。
public interface VectorStore extends DocumentWriter, VectorStoreRetriever {
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) { ... }
default <T> Optional<T> getNativeClient() {
return Optional.empty();
}
}
VectorStore 接口结合了读写操作,允许您在向量数据库中添加、删除和搜索文档。
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 文档)的内容,并包含表示为字符串的文本。它还包含键值对形式的元数据,包括文件名等详细信息。
插入向量数据库后,文本内容会使用嵌入模型转换为数字数组(即 float[]),称为向量嵌入。嵌入模型,例如 Word2Vec、GLoVE 和 BERT,或 OpenAI 的 text-embedding-ada-002,用于将单词、句子或段落转换为这些向量嵌入。
向量数据库的作用是存储这些嵌入并促进相似性搜索。它本身不生成嵌入。要创建向量嵌入,应使用 EmbeddingModel。
接口中的 similaritySearch 方法允许检索与给定查询字符串相似的文档。这些方法可以通过使用以下参数进行微调:
-
k:一个整数,指定要返回的相似文档的最大数量。这通常被称为“top K”搜索或“K 最近邻”(KNN)。 -
threshold:一个介于 0 到 1 之间的双精度值,值越接近 1 表示相似度越高。默认情况下,例如,如果您将阈值设置为 0.75,则只返回相似度高于此值的文档。 -
Filter.Expression:一个用于传递流式 DSL(领域特定语言)表达式的类,其功能类似于 SQL 中的“where”子句,但它专门应用于Document的元数据键值对。 -
filterExpression:基于 ANTLR4 的外部 DSL,接受字符串形式的过滤表达式。例如,对于国家、年份和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.1 // Set the reserve percentage
);
}
}
在此配置中:
-
EncodingType.CL100K_BASE:指定用于分词的编码类型。此编码类型由JTokkitTokenCountEstimator用于准确估计令牌计数。 -
8000:设置最大输入令牌计数。此值应小于或等于嵌入模型的最大上下文窗口大小。 -
0.1:设置保留百分比。从最大输入令牌计数中保留的令牌百分比。这为处理过程中潜在的令牌计数增加创建了一个缓冲区。
默认情况下,此构造函数使用 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
);
使用自动截断
某些嵌入模型(如 Vertex AI 文本嵌入)支持 auto_truncate 功能。启用此功能后,模型会悄无声息地截断超出最大大小的文本输入并继续处理;禁用时,它会为过大的输入抛出明确的错误。
当使用批处理策略进行自动截断时,您必须将批处理策略配置为比模型实际最大值高得多的输入令牌计数。这可以防止批处理策略因大文档而引发异常,从而允许嵌入模型在内部处理截断。
自动截断配置
启用自动截断时,将批处理策略的最大输入令牌计数设置得远高于模型的实际限制。这可以防止批处理策略因大文档而引发异常,从而允许嵌入模型在内部处理截断。
以下是使用 Vertex AI 自动截断和自定义 BatchingStrategy,然后将其用于 PgVectorStore 的示例配置:
@Configuration
public class AutoTruncationEmbeddingConfig {
@Bean
public VertexAiTextEmbeddingModel vertexAiEmbeddingModel(
VertexAiEmbeddingConnectionDetails connectionDetails) {
VertexAiTextEmbeddingOptions options = VertexAiTextEmbeddingOptions.builder()
.model(VertexAiTextEmbeddingOptions.DEFAULT_MODEL_NAME)
.autoTruncate(true) // Enable auto-truncation
.build();
return new VertexAiTextEmbeddingModel(connectionDetails, options);
}
@Bean
public BatchingStrategy batchingStrategy() {
// Only use a high token limit if auto-truncation is enabled in your embedding model.
// Set a much higher token count than the model actually supports
// (e.g., 132,900 when Vertex AI supports only up to 20,000)
return new TokenCountBatchingStrategy(
EncodingType.CL100K_BASE,
132900, // Artificially high limit
0.1 // 10% reserve
);
}
@Bean
public VectorStore vectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel, BatchingStrategy batchingStrategy) {
return PgVectorStore.builder(jdbcTemplate, embeddingModel)
// other properties omitted here
.build();
}
}
在此配置中:
-
嵌入模型已启用自动截断功能,使其能够优雅地处理过大的输入。
-
批处理策略使用了一个人为设置的高令牌限制(132,900),远大于实际模型限制(20,000)。
-
向量存储使用配置好的嵌入模型和自定义的
BatchingStrategybean。
工作原理
这种方法有效的原因是:
-
TokenCountBatchingStrategy检查任何单个文档是否超过配置的最大值,如果超过则抛出IllegalArgumentException。 -
通过在批处理策略中设置一个非常高的限制,我们确保此检查永远不会失败。
-
超出模型限制的文档或批次会通过嵌入模型的自动截断功能进行静默截断和处理。
最佳实践
使用自动截断时:
-
将批处理策略的最大输入令牌计数设置为至少比模型实际限制大 5-10 倍,以避免批处理策略过早抛出异常。
-
监控日志中嵌入模型的截断警告(注意:并非所有模型都记录截断事件)。
-
考虑静默截断对嵌入质量的影响。
-
使用示例文档进行测试,以确保截断的嵌入仍然符合您的要求。
-
为将来的维护者记录此配置,因为它是非标准的。
| 虽然自动截断可以防止错误,但它可能导致不完整的嵌入。长文档末尾的重要信息可能会丢失。如果您的应用程序要求嵌入所有内容,请在嵌入之前将文档分成更小的块。 |
Spring Boot 自动配置
如果您正在使用 Spring Boot 自动配置,您必须提供一个自定义的 BatchingStrategy bean 来覆盖 Spring AI 附带的默认 bean
@Bean
public BatchingStrategy customBatchingStrategy() {
// This bean will override the default BatchingStrategy
return new TokenCountBatchingStrategy(
EncodingType.CL100K_BASE,
132900, // Much higher than model's actual limit
0.1
);
}
您的应用程序上下文中存在此 bean 将自动替换所有向量存储使用的默认批处理策略。
自定义实现
虽然 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 向量存储。
-
MariaDB 向量存储 - MariaDB 向量存储。
-
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 的模型。
Spring Boot 启动器为 OpenAI 提供的自动配置使得 EmbeddingModel 的实现可以在 Spring 应用程序上下文中进行依赖注入。
写入向量存储
将数据加载到向量存储中的一般用法类似于批处理作业,首先将数据加载到 Spring AI 的 Document 类中,然后调用 VectorStore 接口上的 add 方法。
给定一个表示 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 模型时,会进行相似性搜索以检索相似文档,然后将这些文档“填充”到提示中,作为用户问题的上下文。
对于只读操作,您可以使用 VectorStore 接口或更集中的 VectorStoreRetriever 接口。
@Autowired
VectorStoreRetriever retriever; // Could also use VectorStore here
String question = "<question from user>";
List<Document> similarDocuments = retriever.similaritySearch(question);
// Or with more specific search parameters
SearchRequest request = SearchRequest.builder()
.query(question)
.topK(5) // Return top 5 results
.similarityThreshold(0.7) // Only return results with similarity score >= 0.7
.build();
List<Document> filteredDocuments = retriever.similaritySearch(request);
其他选项可以传递到 similaritySearch 方法中,以定义要检索的文档数量和相似性搜索的阈值。
读写操作分离
使用单独的接口可以清晰地定义哪些组件需要写入权限,哪些组件只需要读取权限。
// Write operations in a service that needs full access
@Service
class DocumentIndexer {
private final VectorStore vectorStore;
DocumentIndexer(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
public void indexDocuments(List<Document> documents) {
vectorStore.add(documents);
}
}
// Read-only operations in a service that only needs retrieval
@Service
class DocumentRetriever {
private final VectorStoreRetriever retriever;
DocumentRetriever(VectorStoreRetriever retriever) {
this.retriever = retriever;
}
public List<Document> findSimilar(String query) {
return retriever.similaritySearch(query);
}
}
这种关注点分离有助于创建更易于维护和更安全的应用程序,通过将对修改操作的访问限制在真正需要它们的组件。
使用 VectorStoreRetriever 进行检索操作
VectorStoreRetriever 接口提供了向量存储的只读视图,仅公开相似性搜索功能。这遵循最小权限原则,在 RAG(检索增强生成)应用程序中特别有用,在这些应用程序中,您只需要检索文档而无需修改底层数据。
使用 VectorStoreRetriever 的好处
-
关注点分离:清晰地分离读操作和写操作。
-
接口隔离:只需要检索功能的客户端不会暴露于修改方法。
-
函数式接口:对于简单的用例,可以通过 Lambda 表达式或方法引用实现。
-
减少依赖:只需要执行搜索的组件不需要依赖完整的
VectorStore接口。
示例用法
当您只需要执行相似性搜索时,可以直接使用 VectorStoreRetriever。
@Service
public class DocumentRetrievalService {
private final VectorStoreRetriever retriever;
public DocumentRetrievalService(VectorStoreRetriever retriever) {
this.retriever = retriever;
}
public List<Document> findSimilarDocuments(String query) {
return retriever.similaritySearch(query);
}
public List<Document> findSimilarDocumentsWithFilters(String query, String country) {
SearchRequest request = SearchRequest.builder()
.query(query)
.topK(5)
.filterExpression("country == '" + country + "'")
.build();
return retriever.similaritySearch(request);
}
}
在此示例中,该服务仅依赖于 VectorStoreRetriever 接口,明确表明它只执行检索操作,而不修改向量存储。
与 RAG 应用程序集成
VectorStoreRetriever 接口在 RAG 应用程序中特别有用,您需要在其中检索相关文档以为 AI 模型提供上下文。
@Service
public class RagService {
private final VectorStoreRetriever retriever;
private final ChatModel chatModel;
public RagService(VectorStoreRetriever retriever, ChatModel chatModel) {
this.retriever = retriever;
this.chatModel = chatModel;
}
public String generateResponse(String userQuery) {
// Retrieve relevant documents
List<Document> relevantDocs = retriever.similaritySearch(userQuery);
// Extract content from documents to use as context
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
// Generate response using the retrieved context
String prompt = "Context information:\n" + context + "\n\nUser query: " + userQuery;
return chatModel.generate(prompt);
}
}
此模式允许在 RAG 应用程序中检索组件和生成组件之间实现清晰的分离。
元数据过滤器
本节描述了可用于查询结果的各种过滤器。
筛选字符串
您可以将类似 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.in("genre", "drama", "documentary"), b.not(b.lt("year", 2020))).build();
从向量存储中删除文档
向量存储接口提供了多种删除文档的方法,允许您通过特定的文档 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
调用删除 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));