Add builder pattern to QdrantVectorStore
Introduces a builder pattern for configuring QdrantVectorStore instances to provide a more flexible and type-safe way to create and configure vector stores. This change: - Makes configuration more intuitive through fluent builder methods - Improves validation by enforcing required parameters at compile time - Deprecates old constructors in favor of the builder pattern - Adds comprehensive builder tests to ensure reliability - Updates reference documentation with builder usage examples - Maintains backward compatibility while providing a clear migration path The builder pattern simplifies QdrantVectorStore configuration by providing clear method names, proper validation, and better IDE support through method chaining. This makes the API more user-friendly and helps prevent configuration errors at compile time rather than runtime.
This commit is contained in:
committed by
Mark Pollack
parent
19e61bf71a
commit
48bcbd1555
@@ -2,27 +2,26 @@
|
||||
|
||||
This section walks you through setting up the Qdrant `VectorStore` to store document embeddings and perform similarity searches.
|
||||
|
||||
link:https://www.qdrant.tech/[Qdrant] is an open-source, high-performance vector search engine/database.
|
||||
link:https://www.qdrant.tech/[Qdrant] is an open-source, high-performance vector search engine/database. It uses HNSW (Hierarchical Navigable Small World) algorithm for efficient k-NN search operations and provides advanced filtering capabilities for metadata-based queries.
|
||||
|
||||
== Prerequisites
|
||||
|
||||
* Qdrant Instance: Set up a Qdrant instance by following the link:https://qdrant.tech/documentation/guides/installation/[installation instructions] in the Qdrant documentation.
|
||||
* If required, an API key for the xref:api/embeddings.adoc#available-implementations[EmbeddingModel] to generate the embeddings stored by the `QdrantVectorStore`.
|
||||
|
||||
To set up `QdrantVectorStore`, you'll need the following information from your Qdrant instance: `Host`, `GRPC Port`, `Collection Name`, and `API Key` (if required).
|
||||
|
||||
NOTE: It is recommended that the Qdrant collection is link:https://qdrant.tech/documentation/concepts/collections/#create-a-collection[created] in advance with the appropriate dimensions and configurations.
|
||||
If the collection is not created, the `QdrantVectorStore` will attempt to create one using the `Cosine` similarity and the dimension of the configured `EmbeddingModel`.
|
||||
|
||||
== Auto-configuration
|
||||
|
||||
Then add the Qdrant boot starter dependency to your project:
|
||||
Spring AI provides Spring Boot auto-configuration for the Qdrant Vector Store.
|
||||
To enable it, add the following dependency to your project's Maven `pom.xml` file:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-qdrant-store-spring-boot-starter</artifactId>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-qdrant-store-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
@@ -35,53 +34,19 @@ dependencies {
|
||||
}
|
||||
----
|
||||
|
||||
The vector store implementation can initialize the requisite schema for you, but you must opt-in by specifying the `initializeSchema` boolean in the appropriate constructor or by setting `...initialize-schema=true` in the `application.properties` file.
|
||||
TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file.
|
||||
|
||||
Please have a look at the list of xref:#qdrant-vectorstore-properties[configuration parameters] for the vector store to learn about the default values and configuration options.
|
||||
|
||||
TIP: Refer to the xref:getting-started.adoc#repositories[Repositories] section to add Milestone and/or Snapshot Repositories to your build file.
|
||||
|
||||
The vector store implementation can initialize the requisite schema for you, but you must opt-in by specifying the `initializeSchema` boolean in the builder or by setting `...initialize-schema=true` in the `application.properties` file.
|
||||
|
||||
NOTE: this is a breaking change! In earlier versions of Spring AI, this schema initialization happened by default.
|
||||
|
||||
Additionally, you will need a configured `EmbeddingModel` bean. Refer to the xref:api/embeddings.adoc#available-implementations[EmbeddingModel] section for more information.
|
||||
|
||||
The Vector Store, also requires an `EmbeddingModel` instance to calculate embeddings for the documents.
|
||||
You can pick one of the available xref:api/embeddings.adoc#available-implementations[EmbeddingModel Implementations].
|
||||
|
||||
For example to use the xref:api/embeddings/openai-embeddings.adoc[OpenAI EmbeddingModel] add the following dependency to your project:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
or to your Gradle `build.gradle` build file.
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
dependencies {
|
||||
implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'
|
||||
}
|
||||
----
|
||||
|
||||
TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file.
|
||||
Refer to the xref:getting-started.adoc#repositories[Repositories] section to add Milestone and/or Snapshot Repositories to your build file.
|
||||
|
||||
To connect to Qdrant and use the `QdrantVectorStore`, you need to provide access details for your instance.
|
||||
A simple configuration can either be provided via Spring Boot's _application.properties_,
|
||||
|
||||
[source,properties]
|
||||
----
|
||||
spring.ai.vectorstore.qdrant.host=<host of your qdrant instance>
|
||||
spring.ai.vectorstore.qdrant.port=<the GRPC port of your qdrant instance>
|
||||
spring.ai.vectorstore.qdrant.api-key=<your api key>
|
||||
spring.ai.vectorstore.qdrant.collection-name=<The name of the collection to use in Qdrant>
|
||||
|
||||
# API key if needed, e.g. OpenAI
|
||||
spring.ai.openai.api.key=<api-key>
|
||||
----
|
||||
|
||||
TIP: Check the list of xref:#qdrant-vectorstore-properties[configuration parameters] to learn about the default values and configuration options.
|
||||
|
||||
Now you can Auto-wire the Qdrant Vector Store in your application and use it
|
||||
Now you can auto-wire the `QdrantVectorStore` as a vector store in your application.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -89,7 +54,7 @@ Now you can Auto-wire the Qdrant Vector Store in your application and use it
|
||||
|
||||
// ...
|
||||
|
||||
List <Document> documents = List.of(
|
||||
List<Document> documents = List.of(
|
||||
new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("meta1", "meta1")),
|
||||
new Document("The World is Big and Salvation Lurks Around the Corner"),
|
||||
new Document("You walk forward facing the past and you turn back toward the future.", Map.of("meta2", "meta2")));
|
||||
@@ -98,29 +63,109 @@ List <Document> documents = List.of(
|
||||
vectorStore.add(documents);
|
||||
|
||||
// Retrieve documents similar to a query
|
||||
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(5));
|
||||
List<Document> results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(5));
|
||||
----
|
||||
|
||||
[[qdrant-vectorstore-properties]]
|
||||
=== Configuration properties
|
||||
=== Configuration Properties
|
||||
|
||||
You can use the following properties in your Spring Boot configuration to customize the Qdrant vector store.
|
||||
To connect to Qdrant and use the `QdrantVectorStore`, you need to provide access details for your instance.
|
||||
A simple configuration can be provided via Spring Boot's `application.yml`:
|
||||
|
||||
[cols="3,5,1",stripes=even]
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
ai:
|
||||
vectorstore:
|
||||
qdrant:
|
||||
host: <qdrant host>
|
||||
port: <qdrant grpc port>
|
||||
api-key: <qdrant api key>
|
||||
collection-name: <collection name>
|
||||
use-tls: false
|
||||
initialize-schema: true
|
||||
batching-strategy: TOKEN_COUNT # Optional: Controls how documents are batched for embedding
|
||||
----
|
||||
|
||||
Properties starting with `spring.ai.vectorstore.qdrant.*` are used to configure the `QdrantVectorStore`:
|
||||
|
||||
[cols="2,5,1",stripes=even]
|
||||
|===
|
||||
|Property| Description | Default value
|
||||
|Property | Description | Default Value
|
||||
|
||||
|`spring.ai.vectorstore.qdrant.host`| The host of the Qdrant server. | localhost
|
||||
|`spring.ai.vectorstore.qdrant.port`| The gRPC port of the Qdrant server. | 6334
|
||||
|`spring.ai.vectorstore.qdrant.api-key`| The API key to use for authentication with the Qdrant server. | -
|
||||
|`spring.ai.vectorstore.qdrant.collection-name`| The name of the collection to use in Qdrant. | -
|
||||
|`spring.ai.vectorstore.qdrant.use-tls`| Whether to use TLS(HTTPS). | false
|
||||
|`spring.ai.vectorstore.qdrant.initialize-schema`| Whether to initialize the backend schema or not | false
|
||||
|`spring.ai.vectorstore.qdrant.host`| The host of the Qdrant server | `localhost`
|
||||
|`spring.ai.vectorstore.qdrant.port`| The gRPC port of the Qdrant server | `6334`
|
||||
|`spring.ai.vectorstore.qdrant.api-key`| The API key to use for authentication | -
|
||||
|`spring.ai.vectorstore.qdrant.collection-name`| The name of the collection to use | `vector_store`
|
||||
|`spring.ai.vectorstore.qdrant.use-tls`| Whether to use TLS(HTTPS) | `false`
|
||||
|`spring.ai.vectorstore.qdrant.initialize-schema`| Whether to initialize the schema | `false`
|
||||
|`spring.ai.vectorstore.qdrant.batching-strategy`| Strategy for batching documents when calculating embeddings. Options are `TOKEN_COUNT` or `FIXED_SIZE` | `TOKEN_COUNT`
|
||||
|===
|
||||
|
||||
== Metadata filtering
|
||||
== Manual Configuration
|
||||
|
||||
You can leverage the generic, portable link:https://docs.spring.io/spring-ai/reference/api/vectordbs.html#_metadata_filters[metadata filters] with the Qdrant vector store.
|
||||
Instead of using the Spring Boot auto-configuration, you can manually configure the Qdrant vector store. For this you need to add the `spring-ai-qdrant-store` to your project:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-qdrant-store</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
or to your Gradle `build.gradle` build file.
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
dependencies {
|
||||
implementation 'org.springframework.ai:spring-ai-qdrant-store'
|
||||
}
|
||||
----
|
||||
|
||||
TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file.
|
||||
|
||||
Create a Qdrant client bean:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public QdrantClient qdrantClient() {
|
||||
QdrantGrpcClient.Builder grpcClientBuilder =
|
||||
QdrantGrpcClient.newBuilder(
|
||||
"<QDRANT_HOSTNAME>",
|
||||
<QDRANT_GRPC_PORT>,
|
||||
<IS_TLS>);
|
||||
grpcClientBuilder.withApiKey("<QDRANT_API_KEY>");
|
||||
|
||||
return new QdrantClient(grpcClientBuilder.build());
|
||||
}
|
||||
----
|
||||
|
||||
Then create the `QdrantVectorStore` bean using the builder pattern:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public VectorStore vectorStore(QdrantClient qdrantClient, EmbeddingModel embeddingModel) {
|
||||
return QdrantVectorStore.builder(qdrantClient)
|
||||
.embeddingModel(embeddingModel)
|
||||
.collectionName("custom-collection") // Optional: defaults to "vector_store"
|
||||
.initializeSchema(true) // Optional: defaults to false
|
||||
.batchingStrategy(new TokenCountBatchingStrategy()) // Optional: defaults to TokenCountBatchingStrategy
|
||||
.build();
|
||||
}
|
||||
|
||||
// This can be any EmbeddingModel implementation
|
||||
@Bean
|
||||
public EmbeddingModel embeddingModel() {
|
||||
return new OpenAiEmbeddingModel(new OpenAiApi(System.getenv("OPENAI_API_KEY")));
|
||||
}
|
||||
----
|
||||
|
||||
== Metadata Filtering
|
||||
|
||||
You can leverage the generic, portable xref:api/vectordbs.adoc#metadata-filters[metadata filters] with Qdrant store as well.
|
||||
|
||||
For example, you can use either the text expression language:
|
||||
|
||||
@@ -128,10 +173,10 @@ For example, you can use either the text expression language:
|
||||
----
|
||||
vectorStore.similaritySearch(
|
||||
SearchRequest.defaults()
|
||||
.withQuery("The World")
|
||||
.withTopK(TOP_K)
|
||||
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
|
||||
.withFilterExpression("author in ['john', 'jill'] && article_type == 'blog'"));
|
||||
.withQuery("The World")
|
||||
.withTopK(TOP_K)
|
||||
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
|
||||
.withFilterExpression("author in ['john', 'jill'] && article_type == 'blog'"));
|
||||
----
|
||||
|
||||
or programmatically using the `Filter.Expression` DSL:
|
||||
@@ -149,54 +194,4 @@ vectorStore.similaritySearch(SearchRequest.defaults()
|
||||
b.eq("article_type", "blog")).build()));
|
||||
----
|
||||
|
||||
NOTE: These filter expressions are converted into the equivalent Qdrant link:https://qdrant.tech/documentation/concepts/filtering/[filters].
|
||||
|
||||
== Manual Configuration
|
||||
|
||||
Instead of using the Spring Boot auto-configuration, you can manually configure the `QdrantVectorStore`. For this you need to add the `spring-ai-qdrant-store` dependency to your project:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-qdrant-store</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
or to your Gradle `build.gradle` build file.
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
dependencies {
|
||||
implementation 'org.springframework.ai:spring-ai-qdrant'
|
||||
}
|
||||
----
|
||||
|
||||
To configure Qdrant in your application, you can create a QdrantClient:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public QdrantClient qdrantClient() {
|
||||
|
||||
QdrantGrpcClient.Builder grpcClientBuilder =
|
||||
QdrantGrpcClient.newBuilder(
|
||||
"<QDRANT_HOSTNAME>",
|
||||
<QDRANT_GRPC_PORT>,
|
||||
<IS_TSL>);
|
||||
grpcClientBuilder.withApiKey("<QDRANT_API_KEY>");
|
||||
|
||||
return new QdrantClient(grpcClientBuilder.build());
|
||||
}
|
||||
----
|
||||
|
||||
Integrate with OpenAI's embeddings by adding the Spring Boot OpenAI starter to your project.
|
||||
This provides you with an implementation of the Embeddings client:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public QdrantVectorStore vectorStore(EmbeddingModel embeddingModel, QdrantClient qdrantClient) {
|
||||
return new QdrantVectorStore(qdrantClient, "<QDRANT_COLLECTION_NAME>", embeddingModel);
|
||||
}
|
||||
----
|
||||
NOTE: These (portable) filter expressions get automatically converted into the proprietary Qdrant link:https://qdrant.tech/documentation/concepts/filtering/[filter expressions].
|
||||
|
||||
@@ -77,9 +77,14 @@ public class QdrantVectorStoreAutoConfiguration {
|
||||
QdrantClient qdrantClient, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
|
||||
BatchingStrategy batchingStrategy) {
|
||||
return new QdrantVectorStore(qdrantClient, properties.getCollectionName(), embeddingModel,
|
||||
properties.isInitializeSchema(), observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
|
||||
customObservationConvention.getIfAvailable(() -> null), batchingStrategy);
|
||||
return QdrantVectorStore.builder(qdrantClient)
|
||||
.collectionName(properties.getCollectionName())
|
||||
.embeddingModel(embeddingModel)
|
||||
.initializeSchema(properties.isInitializeSchema())
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.customObservationConvention(customObservationConvention.getIfAvailable(() -> null))
|
||||
.batchingStrategy(batchingStrategy)
|
||||
.build();
|
||||
}
|
||||
|
||||
static class PropertiesQdrantConnectionDetails implements QdrantConnectionDetails {
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.ai.embedding.EmbeddingOptionsBuilder;
|
||||
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
|
||||
import org.springframework.ai.model.EmbeddingUtils;
|
||||
import org.springframework.ai.observation.conventions.VectorStoreProvider;
|
||||
import org.springframework.ai.vectorstore.AbstractVectorStoreBuilder;
|
||||
import org.springframework.ai.vectorstore.SearchRequest;
|
||||
import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore;
|
||||
import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
|
||||
@@ -53,6 +54,71 @@ import org.springframework.util.Assert;
|
||||
* Qdrant vectorStore implementation. This store supports creating, updating, deleting,
|
||||
* and similarity searching of documents in a Qdrant collection.
|
||||
*
|
||||
* <p>
|
||||
* The store uses Qdrant's vector search functionality to persist and query vector
|
||||
* embeddings along with their associated document content and metadata. The
|
||||
* implementation leverages Qdrant's HNSW (Hierarchical Navigable Small World) algorithm
|
||||
* for efficient k-NN search operations.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Features:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Automatic schema initialization with configurable collection creation</li>
|
||||
* <li>Support for cosine similarity distance metric</li>
|
||||
* <li>Metadata filtering using Qdrant's filter expressions</li>
|
||||
* <li>Configurable similarity thresholds for search results</li>
|
||||
* <li>Batch processing support with configurable strategies</li>
|
||||
* <li>Observation and metrics support through Micrometer</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Basic usage example:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* QdrantVectorStore vectorStore = QdrantVectorStore.builder(qdrantClient)
|
||||
* .embeddingModel(embeddingModel)
|
||||
* .initializeSchema(true)
|
||||
* .build();
|
||||
*
|
||||
* // Add documents
|
||||
* vectorStore.add(List.of(
|
||||
* new Document("content1", Map.of("key1", "value1")),
|
||||
* new Document("content2", Map.of("key2", "value2"))
|
||||
* ));
|
||||
*
|
||||
* // Search with filters
|
||||
* List<Document> results = vectorStore.similaritySearch(
|
||||
* SearchRequest.query("search text")
|
||||
* .withTopK(5)
|
||||
* .withSimilarityThreshold(0.7)
|
||||
* .withFilterExpression("key1 == 'value1'")
|
||||
* );
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Advanced configuration example:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* QdrantVectorStore vectorStore = QdrantVectorStore.builder(qdrantClient)
|
||||
* .embeddingModel(embeddingModel)
|
||||
* .collectionName("custom-collection")
|
||||
* .initializeSchema(true)
|
||||
* .batchingStrategy(new TokenCountBatchingStrategy())
|
||||
* .observationRegistry(observationRegistry)
|
||||
* .customObservationConvention(customConvention)
|
||||
* .build();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Requirements:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Running Qdrant instance accessible via gRPC</li>
|
||||
* <li>Collection with vector size matching the embedding model dimensions</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Anush Shetty
|
||||
* @author Christian Tzolov
|
||||
* @author Eddú Meléndez
|
||||
@@ -67,8 +133,6 @@ public class QdrantVectorStore extends AbstractObservationVectorStore implements
|
||||
|
||||
private static final String CONTENT_FIELD_NAME = "doc_content";
|
||||
|
||||
private final EmbeddingModel embeddingModel;
|
||||
|
||||
private final QdrantClient qdrantClient;
|
||||
|
||||
private final String collectionName;
|
||||
@@ -85,7 +149,9 @@ public class QdrantVectorStore extends AbstractObservationVectorStore implements
|
||||
* @param collectionName The name of the collection to use in Qdrant.
|
||||
* @param embeddingModel The client for embedding operations.
|
||||
* @param initializeSchema A boolean indicating whether to initialize the schema.
|
||||
* @deprecated Use {@link #builder(QdrantClient)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.0.0-M5")
|
||||
public QdrantVectorStore(QdrantClient qdrantClient, String collectionName, EmbeddingModel embeddingModel,
|
||||
boolean initializeSchema) {
|
||||
this(qdrantClient, collectionName, embeddingModel, initializeSchema, ObservationRegistry.NOOP, null,
|
||||
@@ -100,22 +166,48 @@ public class QdrantVectorStore extends AbstractObservationVectorStore implements
|
||||
* @param initializeSchema A boolean indicating whether to initialize the schema.
|
||||
* @param observationRegistry The observation registry to use.
|
||||
* @param customObservationConvention The custom search observation convention to use.
|
||||
* @deprecated Use {@link #builder(QdrantClient)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.0.0-M5")
|
||||
public QdrantVectorStore(QdrantClient qdrantClient, String collectionName, EmbeddingModel embeddingModel,
|
||||
boolean initializeSchema, ObservationRegistry observationRegistry,
|
||||
VectorStoreObservationConvention customObservationConvention, BatchingStrategy batchingStrategy) {
|
||||
|
||||
super(observationRegistry, customObservationConvention);
|
||||
this(builder(qdrantClient).embeddingModel(embeddingModel)
|
||||
.collectionName(collectionName)
|
||||
.initializeSchema(initializeSchema)
|
||||
.observationRegistry(observationRegistry)
|
||||
.customObservationConvention(customObservationConvention)
|
||||
.batchingStrategy(batchingStrategy));
|
||||
}
|
||||
|
||||
Assert.notNull(qdrantClient, "QdrantClient must not be null");
|
||||
Assert.notNull(collectionName, "collectionName must not be null");
|
||||
Assert.notNull(embeddingModel, "EmbeddingModel must not be null");
|
||||
/**
|
||||
* Protected constructor for creating a QdrantVectorStore instance using the builder
|
||||
* pattern.
|
||||
* @param builder the {@link QdrantBuilder} containing all configuration settings
|
||||
* @throws IllegalArgumentException if qdrant client is missing
|
||||
* @see QdrantBuilder
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected QdrantVectorStore(QdrantBuilder builder) {
|
||||
super(builder);
|
||||
|
||||
this.initializeSchema = initializeSchema;
|
||||
this.embeddingModel = embeddingModel;
|
||||
this.collectionName = collectionName;
|
||||
this.qdrantClient = qdrantClient;
|
||||
this.batchingStrategy = batchingStrategy;
|
||||
Assert.notNull(builder.qdrantClient, "QdrantClient must not be null");
|
||||
|
||||
this.qdrantClient = builder.qdrantClient;
|
||||
this.collectionName = builder.collectionName;
|
||||
this.initializeSchema = builder.initializeSchema;
|
||||
this.batchingStrategy = builder.batchingStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new QdrantBuilder instance. This is the recommended way to instantiate a
|
||||
* QdrantVectorStore.
|
||||
* @param qdrantClient the client for interfacing with Qdrant
|
||||
* @return a new QdrantBuilder instance
|
||||
*/
|
||||
public static QdrantBuilder builder(QdrantClient qdrantClient) {
|
||||
return new QdrantBuilder(qdrantClient);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,4 +364,80 @@ public class QdrantVectorStore extends AbstractObservationVectorStore implements
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating instances of {@link QdrantVectorStore}. This builder provides
|
||||
* a fluent API for configuring all aspects of the vector store.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static final class QdrantBuilder extends AbstractVectorStoreBuilder<QdrantBuilder> {
|
||||
|
||||
private final QdrantClient qdrantClient;
|
||||
|
||||
private String collectionName = DEFAULT_COLLECTION_NAME;
|
||||
|
||||
private boolean initializeSchema = false;
|
||||
|
||||
private BatchingStrategy batchingStrategy = new TokenCountBatchingStrategy();
|
||||
|
||||
/**
|
||||
* Creates a new builder instance with the required QdrantClient and
|
||||
* EmbeddingModel.
|
||||
* @param qdrantClient the client for Qdrant operations
|
||||
* @throws IllegalArgumentException if qdrantClient is null
|
||||
*/
|
||||
QdrantBuilder(QdrantClient qdrantClient) {
|
||||
Assert.notNull(qdrantClient, "QdrantClient must not be null");
|
||||
this.qdrantClient = qdrantClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the Qdrant collection name.
|
||||
* @param collectionName the name of the collection to use (defaults to
|
||||
* {@value DEFAULT_COLLECTION_NAME})
|
||||
* @return this builder instance
|
||||
* @throws IllegalArgumentException if collectionName is null or empty
|
||||
*/
|
||||
public QdrantBuilder collectionName(String collectionName) {
|
||||
Assert.hasText(collectionName, "collectionName must not be empty");
|
||||
this.collectionName = collectionName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures whether to initialize the collection schema.
|
||||
* @param initializeSchema true to initialize schema automatically
|
||||
* @return this builder instance
|
||||
*/
|
||||
public QdrantBuilder initializeSchema(boolean initializeSchema) {
|
||||
this.initializeSchema = initializeSchema;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the strategy for batching operations.
|
||||
* @param batchingStrategy the batching strategy to use
|
||||
* @return this builder instance
|
||||
* @throws IllegalArgumentException if batchingStrategy is null
|
||||
*/
|
||||
public QdrantBuilder batchingStrategy(BatchingStrategy batchingStrategy) {
|
||||
Assert.notNull(batchingStrategy, "BatchingStrategy must not be null");
|
||||
this.batchingStrategy = batchingStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a new QdrantVectorStore instance with the configured
|
||||
* settings.
|
||||
* @return a new QdrantVectorStore instance
|
||||
* @throws IllegalStateException if the builder configuration is invalid
|
||||
*/
|
||||
@Override
|
||||
public QdrantVectorStore build() {
|
||||
validate();
|
||||
return new QdrantVectorStore(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.vectorstore.qdrant;
|
||||
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.embedding.EmbeddingModel;
|
||||
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link QdrantVectorStore.QdrantBuilder}.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
*/
|
||||
class QdrantVectorStoreBuilderTests {
|
||||
|
||||
private QdrantClient qdrantClient;
|
||||
|
||||
private EmbeddingModel embeddingModel;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.qdrantClient = mock(QdrantClient.class);
|
||||
this.embeddingModel = mock(EmbeddingModel.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultConfiguration() {
|
||||
QdrantVectorStore vectorStore = QdrantVectorStore.builder(qdrantClient).embeddingModel(embeddingModel).build();
|
||||
|
||||
// Verify default values
|
||||
assertThat(vectorStore).hasFieldOrPropertyWithValue("collectionName", "vector_store");
|
||||
assertThat(vectorStore).hasFieldOrPropertyWithValue("initializeSchema", false);
|
||||
assertThat(vectorStore).hasFieldOrPropertyWithValue("batchingStrategy.class", TokenCountBatchingStrategy.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customConfiguration() {
|
||||
QdrantVectorStore vectorStore = QdrantVectorStore.builder(qdrantClient)
|
||||
.embeddingModel(embeddingModel)
|
||||
.collectionName("custom_collection")
|
||||
.initializeSchema(true)
|
||||
.batchingStrategy(new TokenCountBatchingStrategy())
|
||||
.build();
|
||||
|
||||
assertThat(vectorStore).hasFieldOrPropertyWithValue("collectionName", "custom_collection");
|
||||
assertThat(vectorStore).hasFieldOrPropertyWithValue("initializeSchema", true);
|
||||
assertThat(vectorStore).hasFieldOrPropertyWithValue("batchingStrategy.class", TokenCountBatchingStrategy.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullQdrantClientInConstructorShouldThrowException() {
|
||||
assertThatThrownBy(() -> QdrantVectorStore.builder(null)).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("QdrantClient must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullEmbeddingModelShouldThrowException() {
|
||||
assertThatThrownBy(() -> QdrantVectorStore.builder(qdrantClient).embeddingModel(null).build())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("EmbeddingModel must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyCollectionNameShouldThrowException() {
|
||||
assertThatThrownBy(
|
||||
() -> QdrantVectorStore.builder(qdrantClient).embeddingModel(embeddingModel).collectionName("").build())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("collectionName must not be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullBatchingStrategyShouldThrowException() {
|
||||
assertThatThrownBy(() -> QdrantVectorStore.builder(qdrantClient)
|
||||
.embeddingModel(embeddingModel)
|
||||
.batchingStrategy(null)
|
||||
.build()).isInstanceOf(IllegalArgumentException.class).hasMessage("BatchingStrategy must not be null");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -254,7 +254,11 @@ public class QdrantVectorStoreIT {
|
||||
|
||||
@Bean
|
||||
public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel, QdrantClient qdrantClient) {
|
||||
return new QdrantVectorStore(qdrantClient, COLLECTION_NAME, embeddingModel, true);
|
||||
return QdrantVectorStore.builder(qdrantClient)
|
||||
.collectionName(COLLECTION_NAME)
|
||||
.embeddingModel(embeddingModel)
|
||||
.initializeSchema(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -195,8 +195,14 @@ public class QdrantVectorStoreObservationIT {
|
||||
@Bean
|
||||
public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel, QdrantClient qdrantClient,
|
||||
ObservationRegistry observationRegistry) {
|
||||
return new QdrantVectorStore(qdrantClient, COLLECTION_NAME, embeddingModel, true, observationRegistry, null,
|
||||
new TokenCountBatchingStrategy());
|
||||
return QdrantVectorStore.builder(qdrantClient)
|
||||
.collectionName(COLLECTION_NAME)
|
||||
.embeddingModel(embeddingModel)
|
||||
.initializeSchema(true)
|
||||
.observationRegistry(observationRegistry)
|
||||
.customObservationConvention(null)
|
||||
.batchingStrategy(new TokenCountBatchingStrategy())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
Reference in New Issue
Block a user