此版本仍在开发中,尚不被认为是稳定的。对于最新的快照版本,请使用 Spring AI 1.0.1! |
矢量数据库
矢量数据库是一种特殊类型的数据库,在人工智能应用中发挥着至关重要的作用。
在向量数据库中,查询与传统的关系数据库不同。 他们执行相似性搜索,而不是完全匹配。 当给定向量作为查询时,向量数据库返回与查询向量“相似”的向量。 有关如何在高级上计算此相似性的更多详细信息,请参阅向量相似度。
矢量数据库用于将您的数据与 AI 模型集成。 使用它们的第一步是将数据加载到矢量数据库中。 然后,当用户查询要发送到 AI 模型时,首先检索一组相似的文档。 然后,这些文档作为用户问题的上下文,并与用户的查询一起发送到 AI 模型。 这种技术被称为检索增强生成 (RAG)。
以下部分描述了用于使用多个矢量数据库实现的 Spring AI 接口和一些高级示例用法。
最后一节旨在揭开向量数据库中相似性搜索的基本方法的神秘面纱。
API 概述
本节作为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
:指定要返回的类似文档的最大数量的整数。这通常称为“前 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,则设置适当的initialize-schema
属性设置为true
在application.properties
或application.yml
. 检查您正在使用的矢量存储的文档,了解特定属性名称。
批处理策略
使用矢量存储时,通常需要嵌入大量文档。虽然一次调用一次嵌入所有文档似乎很简单,但这种方法可能会导致问题。嵌入模型将文本作为标记处理,并具有最大标记限制,通常称为上下文窗口大小。此限制限制了单个嵌入请求中可以处理的文本量。尝试在一次调用中嵌入过多Tokens可能会导致错误或嵌入被截断。
为了解决此Tokens限制,Spring AI 实施了批处理策略。这种方法将大量文档分解为适合嵌入模型最大上下文窗口的较小批次。批处理不仅解决了Tokens限制问题,还可以提高性能并更有效地使用 API 速率限制。
Spring AI 通过BatchingStrategy
接口,允许根据Tokens计数分子批处理文档。
核心BatchingStrategy
接口定义如下:
public interface BatchingStrategy {
List<List<Document>> batch(List<Document> documents);
}
此接口定义了一个方法,batch
,它采用文档列表并返回文档批列表。
默认实现
Spring AI 提供了一个默认实现,名为TokenCountBatchingStrategy
. 此策略根据文档的Tokens计数对文档进行批处理,确保每个批次不超过计算出的最大输入Tokens计数。
主要特点TokenCountBatchingStrategy
:
-
使用 OpenAI 的最大输入Tokens数 (8191) 作为默认上限。
-
包含预留百分比(默认为 10%),为潜在开销提供缓冲。
-
计算实际的最大输入Tokens计数如下:
actualMaxInputTokenCount = originalMaxInputTokenCount * (1 - RESERVE_PERCENTAGE)
该策略估计每个文档的Tokens计数,将它们分组为批次,而不超过最大输入Tokens计数,并在单个文档超过此限制时引发异常。
您还可以自定义TokenCountBatchingStrategy
以更好地满足您的特定要求。这可以通过在 Spring Boot 中使用自定义参数创建一个新实例来完成@Configuration
类。
下面是如何创建自定义TokenCountBatchingStrategy
豆:
@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
以准确估计Tokens计数。 -
8000
:设置最大输入Tokens计数。此值应小于或等于嵌入模型的最大上下文窗口大小。 -
0.1
:设置预留百分比。从最大输入Tokens计数中要预留的Tokens百分比。这会为处理期间潜在的Tokens计数增加创建一个缓冲区。
默认情况下,此构造函数使用Document.DEFAULT_CONTENT_FORMATTER
用于内容格式和MetadataMode.NONE
用于元数据处理。如果您需要自定义这些参数,则可以使用带有其他参数的完整构造函数。
一旦定义,这个习俗TokenCountBatchingStrategy
bean 将被EmbeddingModel
实现,替换默认策略。
这TokenCountBatchingStrategy
内部使用TokenCountEstimator
(具体来说,JTokkitTokenCountEstimator
) 来计算Tokens计数以实现高效批处理。这可确保根据指定的编码类型进行准确的Tokens估计。
此外TokenCountBatchingStrategy
通过允许您传入自己的TokenCountEstimator
接口。 此功能使您能够使用根据您的特定需求量身定制的自定义Tokens计数策略。 例如:
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
特征。 启用后,模型会以静默方式截断超过最大大小的文本输入并继续处理;禁用后,它会为太大的输入抛出显式错误。
将自动截断与批处理策略一起使用时,必须使用远高于模型实际最大值的输入Tokens计数来配置批处理策略。这可以防止批处理策略为大型文档引发异常,从而允许嵌入模型在内部处理截断。
自动截断的配置
启用自动截断时,请将批处理策略的最大输入Tokens计数设置为远高于模型的实际限制。这可以防止批处理策略为大型文档引发异常,从而允许嵌入模型在内部处理截断。
以下是将 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();
}
}
在此配置中:
-
嵌入模型启用了自动截断,使其能够优雅地处理超大输入。
-
批处理策略使用人为的高Tokens限制 (132,900),远大于实际模型限制 (20,000)。
-
向量存储使用配置的嵌入模型和自定义
BatchingStrategy
豆。
为什么这有效
这种方法之所以有效,是因为:
-
这
TokenCountBatchingStrategy
检查是否有任何单个文档超过配置的最大值,并抛出IllegalArgumentException
如果是的话。 -
通过在批处理策略中设置非常高的限制,我们确保此检查永远不会失败。
-
超过模型限制的文档或批次将由嵌入模型的自动截断功能静默截断和处理。
最佳实践
使用自动截断时:
-
将批处理策略的最大输入Tokens计数设置为至少比模型的实际限制大 5-10 倍,以避免批处理策略过早出现异常。
-
监视日志中是否有来自嵌入模型的截断警告(注意:并非所有模型都会记录截断事件)。
-
考虑静默截断对嵌入质量的影响。
-
使用示例文档进行测试,以确保截断的嵌入仍满足您的要求。
-
为未来的维护者记录此配置,因为它是非标准的。
虽然自动截断可以防止错误,但它可能会导致嵌入不完整。长文档末尾的重要信息可能会丢失。如果您的应用程序需要嵌入所有内容,请在嵌入之前将文档拆分为更小的块。 |
Spring Boot 自动配置
如果您使用的是 Spring Boot 自动配置,则必须提供自定义BatchingStrategy
bean 来覆盖 Spring AI 附带的默认版本:
@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 的自动配置来完成。
要自定义批处理策略,请定义一个BatchingStrategy
bean 在你的 Spring Boot 应用程序中:
@Configuration
public class EmbeddingConfig {
@Bean
public BatchingStrategy customBatchingStrategy() {
return new CustomBatchingStrategy();
}
}
这个习俗BatchingStrategy
然后将被EmbeddingModel
实现。
Spring AI 支持的矢量存储配置为使用默认的TokenCountBatchingStrategy .
SAP Hana 矢量存储当前未配置为批处理。 |
VectorStore 实现
这些是VectorStore
接口:
-
Azure 矢量搜索 - Azure 矢量存储。
-
Apache Cassandra - Apache Cassandra 矢量存储。
-
Chroma Vector Store - 色度矢量存储。
-
Elasticsearch 矢量存储 - Elasticsearch 矢量存储。
-
GemFire 矢量存储 - GemFire矢量存储。
-
MariaDB 矢量存储 - MariaDB 矢量存储。
-
Milvus Vector Store - Milvus 矢量存储。
-
Neo4j 矢量商店 - Neo4j 矢量商店。
-
OpenSearch 矢量存储 - OpenSearch 矢量存储。
-
Oracle 矢量存储 - Oracle 数据库矢量存储。
-
PgVector Store - PostgreSQL/PGVector 向量存储。
-
Pinecone Vector Store - Pinecone矢量存储。
-
Qdrant Vector Store - 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 Starters对 OpenAI 的自动配置实现了EmbeddingModel
在 Spring 应用程序上下文中可用于依赖注入。
写入矢量存储
将数据加载到向量存储中的一般用法是您在类似批处理的作业中执行的作,方法是首先将数据加载到 Spring AI 的Document
类,然后调用add
方法VectorStore
接口。
给定一个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 模型中时,会进行相似性搜索以检索相似的文档,然后将其“塞”到提示中作为用户问题的上下文。
对于只读作,您可以使用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.表达式
您可以创建Filter.Expression
使用FilterExpressionBuilder
它公开了一个流畅的 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 匹配的所有文档。 如果列表中的任何 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));