From a8110bb3da0ffa9a57640dd859176cea4eacdcf3 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Thu, 29 Feb 2024 09:27:19 +0100 Subject: [PATCH] Qdrant improvements - Update docs - Allow colleciton auto-creation if missing - Add cloud IT : QdrantVectorStoreCloudAutoConfigurationIT - Add auto-configuration properties tests: PgVectorStorePropertiesTests --- .../ROOT/pages/api/vectordbs/qdrant.adoc | 116 ++++++-------- .../QdrantVectorStoreAutoConfiguration.java | 2 +- .../qdrant/QdrantVectorStoreProperties.java | 2 +- .../qdrant/PgVectorStorePropertiesTests.java | 56 +++++++ .../QdrantVectorStoreAutoConfigurationIT.java | 37 ++--- ...ntVectorStoreCloudAutoConfigurationIT.java | 147 ++++++++++++++++++ vector-stores/spring-ai-qdrant/README.md | 23 ++- .../vectorstore/qdrant/QdrantVectorStore.java | 27 +++- 8 files changed, 312 insertions(+), 98 deletions(-) create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/PgVectorStorePropertiesTests.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreCloudAutoConfigurationIT.java diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/qdrant.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/qdrant.adoc index f4df0fe0e..9adc9e3ea 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/qdrant.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/qdrant.adoc @@ -9,49 +9,14 @@ link:https://www.qdrant.tech/[Qdrant] is an open-source, high-performance vector * 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[EmbeddingClient] to generate the embeddings stored by the `QdrantVectorStore`. -== Configuration +To set up `QdrantVectorStore`, you'll need the following information from your Qdrant instance: `Host`, `GRPC Port`, `Collection Name`, and `API Key` (if required). -To set up `QdrantVectorStore`, you'll need the following information from your Qdrant instance: - -* Qdrant Host -* Qdrant GRPC Port -* Qdrant Collection Name -* Optional Qdrant API Key (not required for local development) - -[NOTE] -==== -A Qdrant collection has to be link:https://qdrant.tech/documentation/concepts/collections/#create-a-collection[created] in advance with the appropriate dimensions and configurations. - -For example if using the OpenAI `text-embedding-ada-002` embedding model, create a collection with a vector size of `1536`. -==== +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 `EmbeddingClient`. == Dependencies -* The Vector Store requires an `EmbeddingClient` instance to calculate embeddings for the documents. -You can pick one of the available xref:api/embeddings.adoc#available-implementations[EmbeddingClient Implementations]. For example ou can use the OpenAI boot starter: - -[source,xml] ----- - - org.springframework.ai - spring-ai-openai-spring-boot-starter - ----- - -or to your Gradle `build.gradle` build file. - -[source,groovy] ----- -dependencies { - implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter' -} ----- - -TIP: Additionally, you'll need to provide your OpenAI API Key. Set it as an environment variable like so: -`export SPRING_AI_OPENAI_API_KEY='Your_OpenAI_API_Key` - - -* Add the Qdrant Boot Starter dependency to your project: +Then add the Qdrant boot starter dependency to your project: [source,xml] ---- @@ -70,21 +35,55 @@ dependencies { } ---- +The Vector Store, also requires an `EmbeddingClient` instance to calculate embeddings for the documents. +You can pick one of the available xref:api/embeddings.adoc#available-implementations[EmbeddingClient Implementations]. + +For example to use the xref:api/embeddings/openai-embeddings.adoc[OpenAI EmbeddingClient] add the following dependency to your project: + +[source,xml] +---- + + org.springframework.ai + spring-ai-openai-spring-boot-starter + +---- + +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. -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. +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_, -TIP: Refer to the xref:getting-started.adoc#repositories[Repositories] section to add Milestone and/or Snapshot Repositories to your build file. +[source,properties] +---- +spring.ai.vectorstore.qdrant.host= +spring.ai.vectorstore.qdrant.port= +spring.ai.vectorstore.qdrant.api-key= +spring.ai.vectorstore.qdrant.collection-name= +# API key if needed, e.g. OpenAI +spring.ai.openai.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 [source,java] ---- -@Autowired -VectorStore vectorStore; +@Autowired VectorStore vectorStore; + +// ... -... List 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"), @@ -97,24 +96,7 @@ vectorStore.add(List.of(document)); List results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(5)); ---- -== Configuration - -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= -spring.ai.vectorstore.qdrant.port= -spring.ai.vectorstore.qdrant.api-key= -spring.ai.vectorstore.qdrant.collection-name= - -# API key if needed, e.g. OpenAI -spring.ai.openai.api.key= ----- - - -== Manual Configuration +=== 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` dependency to your project: @@ -162,7 +144,7 @@ public VectorStore vectorStore(QdrantVectorStoreConfig config, EmbeddingClient e } ---- -=== Metadata filtering +== Metadata filtering 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. @@ -195,18 +177,18 @@ vectorStore.similaritySearch(SearchRequest.defaults() NOTE: These filter expressions are converted into the equivalent Qdrant link:https://qdrant.tech/documentation/concepts/filtering/[filters]. - [[qdrant-vectorstore-properties]] -== Qdrant VectorStore properties +== Configuration properties You can use the following properties in your Spring Boot configuration to customize the Qdrant vector store. +[cols="3,5,1"] |=== |Property| Description | Default value |`spring.ai.vectorstore.qdrant.host`| The host of the Qdrant server. | localhost -|`spring.ai.vectorstore.qdrant.port`| The port of the Qdrant server. | 6334 +|`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). Defaults to false. | false +|`spring.ai.vectorstore.qdrant.use-tls`| Whether to use TLS(HTTPS). | false |=== diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfiguration.java index ad2e0143c..f0f9beda9 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfiguration.java @@ -43,7 +43,7 @@ public class QdrantVectorStoreAutoConfiguration { .withCollectionName(properties.getCollectionName()) .withHost(properties.getHost()) .withPort(properties.getPort()) - .withTls(properties.useTls()) + .withTls(properties.isUseTls()) .withApiKey(properties.getApiKey()) .build(); diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreProperties.java index 46ee9b5d5..d67c537ab 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreProperties.java @@ -76,7 +76,7 @@ public class QdrantVectorStoreProperties { this.port = port; } - public boolean useTls() { + public boolean isUseTls() { return this.useTls; } diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/PgVectorStorePropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/PgVectorStorePropertiesTests.java new file mode 100644 index 000000000..8eb6a555d --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/PgVectorStorePropertiesTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023-2023 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.autoconfigure.vectorstore.qdrant; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Tzolov + */ +public class PgVectorStorePropertiesTests { + + @Test + public void defaultValues() { + var props = new QdrantVectorStoreProperties(); + + assertThat(props.getCollectionName()).isNull(); + assertThat(props.getHost()).isEqualTo("localhost"); + assertThat(props.getPort()).isEqualTo(6334); + assertThat(props.isUseTls()).isFalse(); + assertThat(props.getApiKey()).isNull(); + } + + @Test + public void customValues() { + var props = new QdrantVectorStoreProperties(); + + props.setCollectionName("MY_COLLECTION"); + props.setHost("MY_HOST"); + props.setPort(999); + props.setUseTls(true); + props.setApiKey("MY_API_KEY"); + + assertThat(props.getCollectionName()).isEqualTo("MY_COLLECTION"); + assertThat(props.getHost()).isEqualTo("MY_HOST"); + assertThat(props.getPort()).isEqualTo(999); + assertThat(props.isUseTls()).isTrue(); + assertThat(props.getApiKey()).isEqualTo("MY_API_KEY"); + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfigurationIT.java index 35d3239e9..9146852a1 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfigurationIT.java @@ -54,8 +54,6 @@ public class QdrantVectorStoreAutoConfigurationIT { private static final String COLLECTION_NAME = "test_collection"; - private static final int EMBEDDING_DIMENSION = 384; - private static final int QDRANT_GRPC_PORT = 6334; @Container @@ -66,31 +64,6 @@ public class QdrantVectorStoreAutoConfigurationIT { new Document(getText("classpath:/test/data/time.shelter.txt")), new Document(getText("classpath:/test/data/great.depression.txt"), Map.of("depression", "bad"))); - @BeforeAll - static void setup() throws InterruptedException, ExecutionException { - - String host = qdrantContainer.getHost(); - int port = qdrantContainer.getMappedPort(QDRANT_GRPC_PORT); - QdrantClient client = new QdrantClient(QdrantGrpcClient.newBuilder(host, port, false).build()); - - client - .createCollectionAsync(COLLECTION_NAME, - VectorParams.newBuilder().setDistance(Distance.Cosine).setSize(EMBEDDING_DIMENSION).build()) - .get(); - - client.close(); - } - - public static String getText(String uri) { - var resource = new DefaultResourceLoader().getResource(uri); - try { - return resource.getContentAsString(StandardCharsets.UTF_8); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(QdrantVectorStoreAutoConfiguration.class)) .withUserConfiguration(Config.class) @@ -121,6 +94,16 @@ public class QdrantVectorStoreAutoConfigurationIT { }); } + public static String getText(String uri) { + var resource = new DefaultResourceLoader().getResource(uri); + try { + return resource.getContentAsString(StandardCharsets.UTF_8); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + @Configuration(proxyBeanMethods = false) static class Config { diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreCloudAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreCloudAutoConfigurationIT.java new file mode 100644 index 000000000..6573b723f --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreCloudAutoConfigurationIT.java @@ -0,0 +1,147 @@ +/* + * Copyright 2024-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.autoconfigure.vectorstore.qdrant; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import io.qdrant.client.QdrantClient; +import io.qdrant.client.QdrantGrpcClient; +import io.qdrant.client.grpc.Collections.Distance; +import io.qdrant.client.grpc.Collections.VectorParams; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.EmbeddingClient; +import org.springframework.ai.transformers.TransformersEmbeddingClient; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test using a free tier Qdrant Cloud instance: https://cloud.qdrant.io + * + * @author Christian Tzolov + * @since 0.8.1 + */ +// NOTE: The free Qdrant Cluster and the QDRANT_API_KEY expire after 4 weeks of +// inactivity. +@EnabledIfEnvironmentVariable(named = "QDRANT_API_KEY", matches = ".+") +@EnabledIfEnvironmentVariable(named = "QDRANT_HOST", matches = ".+") +public class QdrantVectorStoreCloudAutoConfigurationIT { + + private static final String COLLECTION_NAME = "test_collection"; + + // Because we pre-create the collection. + private static final int EMBEDDING_DIMENSION = 384; + + private static final String CLOUD_API_KEY = System.getenv("QDRANT_API_KEY"); + + private static final String CLOUD_HOST = System.getenv("QDRANT_HOST"); + + // NOTE: The GRPC port (usually 6334) is different from the HTTP port (usually 6333)! + private static final int CLOUD_GRPC_PORT = 6334; + + List documents = List.of( + new Document(getText("classpath:/test/data/spring.ai.txt"), Map.of("spring", "great")), + new Document(getText("classpath:/test/data/time.shelter.txt")), + new Document(getText("classpath:/test/data/great.depression.txt"), Map.of("depression", "bad"))); + + @BeforeAll + static void setup() throws InterruptedException, ExecutionException { + + // Create a new test collection + try (QdrantClient client = new QdrantClient( + QdrantGrpcClient.newBuilder(CLOUD_HOST, CLOUD_GRPC_PORT, true).withApiKey(CLOUD_API_KEY).build())) { + + if (client.listCollectionsAsync().get().stream().anyMatch(c -> c.equals(COLLECTION_NAME))) { + client.deleteCollectionAsync(COLLECTION_NAME).get(); + } + + var vectorParams = VectorParams.newBuilder() + .setDistance(Distance.Cosine) + .setSize(EMBEDDING_DIMENSION) + .build(); + + client.createCollectionAsync(COLLECTION_NAME, vectorParams).get(); + } + } + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(QdrantVectorStoreAutoConfiguration.class)) + .withUserConfiguration(Config.class) + .withPropertyValues("spring.ai.vectorstore.qdrant.port=" + CLOUD_GRPC_PORT, + "spring.ai.vectorstore.qdrant.host=" + CLOUD_HOST, + "spring.ai.vectorstore.qdrant.api-key=" + CLOUD_API_KEY, + "spring.ai.vectorstore.qdrant.collection-name=" + COLLECTION_NAME, + "spring.ai.vectorstore.qdrant.use-tls=true"); + + @Test + public void addAndSearch() { + contextRunner.run(context -> { + + VectorStore vectorStore = context.getBean(VectorStore.class); + + vectorStore.add(documents); + + List results = vectorStore + .similaritySearch(SearchRequest.query("What is Great Depression?").withTopK(1)); + + assertThat(results).hasSize(1); + Document resultDoc = results.get(0); + assertThat(resultDoc.getId()).isEqualTo(documents.get(2).getId()); + assertThat(resultDoc.getMetadata()).containsKeys("depression", "distance"); + + // Remove all documents from the store + vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); + results = vectorStore.similaritySearch(SearchRequest.query("Great Depression").withTopK(1)); + assertThat(results).hasSize(0); + }); + } + + public static String getText(String uri) { + var resource = new DefaultResourceLoader().getResource(uri); + try { + return resource.getContentAsString(StandardCharsets.UTF_8); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + public EmbeddingClient embeddingClient() { + return new TransformersEmbeddingClient(); + } + + } + +} diff --git a/vector-stores/spring-ai-qdrant/README.md b/vector-stores/spring-ai-qdrant/README.md index 00bf74013..ea76c677e 100644 --- a/vector-stores/spring-ai-qdrant/README.md +++ b/vector-stores/spring-ai-qdrant/README.md @@ -1 +1,22 @@ -Qdrant Vector Store \ No newline at end of file +# Qdrant Vector Store + +[Reference Documentation](https://docs.spring.io/spring-ai/reference/0.8-SNAPSHOT/api/vectordbs/qdrant.html#qdrant-vectorstore-properties) + +## Run locally + +### Accessing the Web UI + +First, run the Docker container: + +``` +docker run -p 6333:6333 -p 6334:6334 \ + -v $(pwd)/qdrant_storage:/qdrant/storage:z \ + qdrant/qdrant +``` + +The GUI is available at http://localhost:6333/dashboard + +## Qdrant references + +- https://qdrant.tech/documentation/interfaces/ +- https://github.com/qdrant/java-client diff --git a/vector-stores/spring-ai-qdrant/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStore.java b/vector-stores/spring-ai-qdrant/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStore.java index aea487d5c..9ddcca6be 100644 --- a/vector-stores/spring-ai-qdrant/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStore.java +++ b/vector-stores/spring-ai-qdrant/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStore.java @@ -30,10 +30,13 @@ import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingClient; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; import io.qdrant.client.QdrantClient; import io.qdrant.client.QdrantGrpcClient; +import io.qdrant.client.grpc.Collections.Distance; +import io.qdrant.client.grpc.Collections.VectorParams; import io.qdrant.client.grpc.JsonWithInt.Value; import io.qdrant.client.grpc.Points.Filter; import io.qdrant.client.grpc.Points.PointId; @@ -47,9 +50,10 @@ import io.qdrant.client.grpc.Points.UpdateStatus; * and similarity searching of documents in a Qdrant collection. * * @author Anush Shetty + * @author Christian Tzolov * @since 0.8.1 */ -public class QdrantVectorStore implements VectorStore { +public class QdrantVectorStore implements VectorStore, InitializingBean { private static final String CONTENT_FIELD_NAME = "doc_content"; @@ -326,4 +330,25 @@ public class QdrantVectorStore implements VectorStore { return doubleList.stream().map(d -> d.floatValue()).toList(); } + @Override + public void afterPropertiesSet() throws Exception { + // Create the collection if it does not exist. + if (!isCollectionExists()) { + var vectorParams = VectorParams.newBuilder() + .setDistance(Distance.Cosine) + .setSize(this.embeddingClient.dimensions()) + .build(); + this.qdrantClient.createCollectionAsync(this.collectionName, vectorParams).get(); + } + } + + private boolean isCollectionExists() { + try { + return this.qdrantClient.listCollectionsAsync().get().stream().anyMatch(c -> c.equals(this.collectionName)); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }