From d77c950ea625a51688747b57b83f1ebf2fda050b Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Thu, 12 Dec 2024 17:04:23 -0500 Subject: [PATCH] Add builder pattern to WeaviateVectorStore and refactor package name Introduces a builder pattern for configuring WeaviateVectorStore instances and moves the implementation to the org.springframework.ai.vectorstore.weaviate package. This change: - Makes configuration more flexible and type-safe through builder methods - Improves code organization by moving to a dedicated vector store package - Deprecates old constructors in favor of the builder pattern - Adds builder tests - Enables better IDE support through method chaining --- .../ROOT/pages/api/vectordbs/weaviate.adoc | 400 ++++++++-------- .../WeaviateVectorStoreAutoConfiguration.java | 26 +- .../WeaviateVectorStoreProperties.java | 8 +- ...eaviateVectorStoreAutoConfigurationIT.java | 2 +- ...teContainerConnectionDetailsFactoryIT.java | 2 +- .../WeaviateFilterExpressionConverter.java | 2 +- .../{ => weaviate}/WeaviateVectorStore.java | 429 ++++++++++++++++-- ...eaviateFilterExpressionConverterTests.java | 2 +- .../{ => weaviate}/WeaviateImage.java | 2 +- .../WeaviateVectorStoreBuilderTests.java | 146 ++++++ .../{ => weaviate}/WeaviateVectorStoreIT.java | 18 +- .../WeaviateVectorStoreObservationIT.java | 17 +- 12 files changed, 759 insertions(+), 295 deletions(-) rename vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/{ => weaviate}/WeaviateFilterExpressionConverter.java (99%) rename vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/{ => weaviate}/WeaviateVectorStore.java (52%) rename vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/{ => weaviate}/WeaviateFilterExpressionConverterTests.java (99%) rename vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/{ => weaviate}/WeaviateImage.java (94%) create mode 100644 vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreBuilderTests.java rename vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/{ => weaviate}/WeaviateVectorStoreIT.java (94%) rename vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/{ => weaviate}/WeaviateVectorStoreObservationIT.java (94%) diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/weaviate.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/weaviate.adoc index 5c1ce1be0..56eaec93d 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/weaviate.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/weaviate.adoc @@ -1,200 +1,20 @@ = Weaviate -This section will walk you through setting up the Weaviate VectorStore to store document embeddings and perform similarity searches. +This section walks you through setting up the Weaviate VectorStore to store document embeddings and perform similarity searches. -== What is Weaviate? - -link:https://weaviate.io/[Weaviate] is an open-source vector database. -It allows you to store data objects and vector embeddings from your favorite ML-models and scale seamlessly into billions of data objects. +link:https://weaviate.io/[Weaviate] is an open-source vector database that allows you to store data objects and vector embeddings from your favorite ML-models and scale seamlessly into billions of data objects. It provides tools to store document embeddings, content, and metadata and to search through those embeddings, including metadata filtering. == Prerequisites -1. `EmbeddingModel` instance to compute the document embeddings. Several options are available: +* A running Weaviate instance. The following options are available: +** link:https://console.weaviate.cloud/[Weaviate Cloud Service] (requires account creation and API key) +** link:https://weaviate.io/developers/weaviate/installation/docker[Docker container] +* If required, an API key for the xref:api/embeddings.adoc#available-implementations[EmbeddingModel] to generate the embeddings stored by the `WeaviateVectorStore`. -- `Transformers Embedding` - computes the embedding in your local environment. Follow the ONNX Transformers Embedding instructions. -- `OpenAI Embedding` - uses the OpenAI embedding endpoint. You need to create an account at link:https://platform.openai.com/signup[OpenAI Signup] and generate the api-key token at link:https://platform.openai.com/account/api-keys[API Keys]. -- You can also use the `Azure OpenAI Embedding` or the `PostgresML Embedding Model`. -2. `Weaviate cluster`. You can set up a cluster locally in a Docker container or create a link:https://console.weaviate.cloud/[Weaviate Cloud Service]. For the latter, you need to create a Weaviate account, set up a cluster, and get your access API key from the link:https://console.weaviate.cloud/dashboard[dashboard details]. +== Dependencies -On startup, the `WeaviateVectorStore` creates the required `SpringAiWeaviate` object schema if it's not already provisioned. - -== Auto-configuration - -Then add the WeaviateVectorStore boot starter dependency to your project: - -[source,xml] ----- - - org.springframework.ai - spring-ai-weaviate-store-spring-boot-starter - ----- - -or to your Gradle `build.gradle` build file. - -[source,groovy] ----- -dependencies { - implementation 'org.springframework.ai:spring-ai-weaviate-store-spring-boot-starter' -} ----- - -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. - -NOTE: this is a breaking change! In earlier versions of Spring AI, this schema initialization happened by default. - - -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] ----- - - 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. - -To connect to Weaviate and use the `WeaviateVectorStore`, 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.weaviate.host= -spring.ai.vectorstore.weaviate.api-key= -spring.ai.vectorstore.weaviate.scheme=http - -# API key if needed, e.g. OpenAI -spring.ai.openai.api.key= ----- - -TIP: Check the list of xref:#weaviate-vectorstore-properties[configuration parameters] to learn about the default values and configuration options. - -Now you can Auto-wire the Weaviate Vector Store in your application and use it - -[source,java] ----- -@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"), - new Document("You walk forward facing the past and you turn back toward the future.", Map.of("meta2", "meta2"))); - -// Add the documents -vectorStore.add(documents); - -// Retrieve documents similar to a query -List results = this.vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(5)); ----- - -[[weaviate-vectorstore-properties]] -=== Configuration properties - -You can use the following properties in your Spring Boot configuration to customize the weaviate vector store. - -[cols="3,5,1",stripes=even] -|=== -|Property| Description | Default value - -|`spring.ai.vectorstore.weaviate.host`| The host of the Weaviate server. | localhost:8080 -|`spring.ai.vectorstore.weaviate.scheme`| Connection schema. | http -|`spring.ai.vectorstore.weaviate.api-key`| The API key to use for authentication with the Weaviate server. | - -|`spring.ai.vectorstore.weaviate.object-class`| | "SpringAiWeaviate" -|`spring.ai.vectorstore.weaviate.consistency-level`| Desired tradeoff between consistency and speed | ConsistentLevel.ONE -|`spring.ai.vectorstore.weaviate.filter-field`| spring.ai.vectorstore.weaviate.filter-field.= | - -|`spring.ai.vectorstore.weaviate.headers`| | - -|`spring.ai.vectorstore.weaviate.initialize-schema`| Whether to initialize the required schema | `false` -|=== - -== Metadata filtering - -You can leverage the generic, portable link:https://docs.spring.io/spring-ai/reference/api/vectordbs.html#_metadata_filters[metadata filters] with WeaviateVectorStore as well. - -For example, you can use either the text expression language: - -[source,java] ----- -vectorStore.similaritySearch( - SearchRequest - .query("The World") - .withTopK(TOP_K) - .withSimilarityThreshold(SIMILARITY_THRESHOLD) - .withFilterExpression("country in ['UK', 'NL'] && year >= 2020")); ----- - -or programmatically using the expression DSL: - -[source,java] ----- -FilterExpressionBuilder b = new FilterExpressionBuilder(); - -vectorStore.similaritySearch( - SearchRequest - .query("The World") - .withTopK(TOP_K) - .withSimilarityThreshold(SIMILARITY_THRESHOLD) - .withFilterExpression(b.and( - b.in("country", "UK", "NL"), - b.gte("year", 2020)).build())); ----- - -The portable filter expressions get automatically converted into the proprietary Weaviate link:https://weaviate.io/developers/weaviate/api/graphql/filters[where filters]. -For example, the following portable filter expression: - -[source,sql] ----- -country in ['UK', 'NL'] && year >= 2020 ----- - -is converted into Weaviate GraphQL link:https://weaviate.io/developers/weaviate/api/graphql/filters[where filter expression]: - -[source,graphql] ----- -operator:And - operands: - [{ - operator:Or - operands: - [{ - path:["meta_country"] - operator:Equal - valueText:"UK" - }, - { - path:["meta_country"] - operator:Equal - valueText:"NL" - }] - }, - { - path:["meta_year"] - operator:GreaterThanEqual - valueNumber:2020 - }] ----- - -== Manual Configuration - -Instead of using the Spring Boot auto-configuration, you can manually configure the `WeaviateVectorStore`. -For this you need to add the `spring-ai-weaviate-store` dependency to your project: +Add the Weaviate Vector Store dependency to your project: [source,xml] ---- @@ -213,46 +33,202 @@ dependencies { } ---- -To configure Weaviate in your application, you can create a WeaviateClient: +TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file. + +== Configuration + +To connect to Weaviate and use the `WeaviateVectorStore`, 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.weaviate.host= +spring.ai.vectorstore.weaviate.scheme= +spring.ai.vectorstore.weaviate.api-key= +# API key if needed, e.g. OpenAI +spring.ai.openai.api-key= +---- + +environment variables, + +[source,bash] +---- +export SPRING_AI_VECTORSTORE_WEAVIATE_HOST= +export SPRING_AI_VECTORSTORE_WEAVIATE_SCHEME= +export SPRING_AI_VECTORSTORE_WEAVIATE_API_KEY= +# API key if needed, e.g. OpenAI +export SPRING_AI_OPENAI_API_KEY= +---- + +or can be a mix of those. + +NOTE: If you choose to create a shell script for ease in future work, be sure to run it prior to starting your application by "sourcing" the file, i.e. `source .sh`. + +== Auto-configuration + +Spring AI provides Spring Boot auto-configuration for the Weaviate Vector Store. +To enable it, add the following dependency to your project's Maven `pom.xml` file: + +[source,xml] +---- + + org.springframework.ai + spring-ai-weaviate-store-spring-boot-starter + +---- + +or to your Gradle `build.gradle` build file. + +[source,groovy] +---- +dependencies { + implementation 'org.springframework.ai:spring-ai-weaviate-store-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. + +Please have a look at the list of xref:#_weaviatevectorstore_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. + +Additionally, you will need a configured `EmbeddingModel` bean. Refer to the xref:api/embeddings.adoc#available-implementations[EmbeddingModel] section for more information. + +Here is an example of the needed bean: + +[source,java] +---- +@Bean +public EmbeddingModel embeddingModel() { + // Can be any other Embeddingmodel implementation. + return new OpenAiEmbeddingModel(new OpenAiApi(System.getenv("SPRING_AI_OPENAI_API_KEY"))); +} +---- + +Now you can auto-wire the `WeaviateVectorStore` as a vector store in your application. + +== Manual Configuration + +Instead of using Spring Boot auto-configuration, you can manually configure the `WeaviateVectorStore` using the builder pattern: [source,java] ---- @Bean public WeaviateClient weaviateClient() { - try { - return WeaviateAuthClient.apiKey( - new Config(, , ), - ); - } - catch (AuthException e) { - throw new IllegalArgumentException("WeaviateClient could not be created.", e); - } + return new WeaviateClient(new Config("http", "localhost:8080")); +} + +@Bean +public VectorStore vectorStore(EmbeddingModel embeddingModel, WeaviateClient weaviateClient) { + return WeaviateVectorStore.builder() + .weaviateClient(weaviateClient) + .embeddingModel(embeddingModel) + .objectClass("CustomClass") // Optional: defaults to "SpringAiWeaviate" + .consistencyLevel(ConsistentLevel.QUORUM) // Optional: defaults to ConsistentLevel.ONE + .filterMetadataFields(List.of( // Optional: fields that can be used in filters + MetadataField.text("country"), + MetadataField.number("year"))) + .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: +== Metadata filtering + +You can leverage the generic, portable xref:api/vectordbs.adoc#metadata-filters[metadata filters] with Weaviate store as well. + +For example, you can use either the text expression language: [source,java] ---- -@Bean -public WeaviateVectorStore vectorStore(EmbeddingModel embeddingModel, WeaviateClient weaviateClient) { - - WeaviateVectorStoreConfig.Builder configBuilder = WeaviateVectorStore.WeaviateVectorStoreConfig.builder() - .withObjectClass() - .withConsistencyLevel(); - - return new WeaviateVectorStore(configBuilder.build(), embeddingModel, weaviateClient); -} +vectorStore.similaritySearch( + SearchRequest.defaults() + .withQuery("The World") + .withTopK(TOP_K) + .withSimilarityThreshold(SIMILARITY_THRESHOLD) + .withFilterExpression("country in ['UK', 'NL'] && year >= 2020")); ---- -== Run Weaviate cluster in docker container +or programmatically using the `Filter.Expression` DSL: -Start Weaviate in a docker container: +[source,java] +---- +FilterExpressionBuilder b = new FilterExpressionBuilder(); + +vectorStore.similaritySearch(SearchRequest.defaults() + .withQuery("The World") + .withTopK(TOP_K) + .withSimilarityThreshold(SIMILARITY_THRESHOLD) + .withFilterExpression(b.and( + b.in("country", "UK", "NL"), + b.gte("year", 2020)).build())); +---- + +NOTE: Those (portable) filter expressions get automatically converted into the proprietary Weaviate link:https://weaviate.io/developers/weaviate/api/graphql/filters[where filters]. + +For example, this portable filter expression: + +[source,sql] +---- +country in ['UK', 'NL'] && year >= 2020 +---- + +is converted into the proprietary Weaviate GraphQL filter format: + +[source,graphql] +---- +operator: And +operands: + [{ + operator: Or + operands: + [{ + path: ["meta_country"] + operator: Equal + valueText: "UK" + }, + { + path: ["meta_country"] + operator: Equal + valueText: "NL" + }] + }, + { + path: ["meta_year"] + operator: GreaterThanEqual + valueNumber: 2020 + }] +---- + +== Run Weaviate in Docker + +To quickly get started with a local Weaviate instance, you can run it in Docker: [source,bash] ---- -docker run -it --rm --name weaviate -e AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true -e PERSISTENCE_DATA_PATH=/var/lib/weaviate -e QUERY_DEFAULTS_LIMIT=25 -e DEFAULT_VECTORIZER_MODULE=none -e CLUSTER_HOSTNAME=node1 -p 8080:8080 semitechnologies/weaviate:1.22.4 +docker run -it --rm --name weaviate \ + -e AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true \ + -e PERSISTENCE_DATA_PATH=/var/lib/weaviate \ + -e QUERY_DEFAULTS_LIMIT=25 \ + -e DEFAULT_VECTORIZER_MODULE=none \ + -e CLUSTER_HOSTNAME=node1 \ + -p 8080:8080 \ + semitechnologies/weaviate:1.22.4 ---- -Starts a Weaviate cluster at http://localhost:8080/v1 with scheme=http, host=localhost:8080, and apiKey="". Then follow the usage instructions. \ No newline at end of file +This starts a Weaviate instance accessible at http://localhost:8080. + +== WeaviateVectorStore properties + +You can use the following properties in your Spring Boot configuration to customize the Weaviate vector store. + +[stripes=even] +|=== +|Property|Description|Default value + +|`spring.ai.vectorstore.weaviate.host`|The host of the Weaviate server|localhost:8080 +|`spring.ai.vectorstore.weaviate.scheme`|Connection schema|http +|`spring.ai.vectorstore.weaviate.api-key`|The API key for authentication| +|`spring.ai.vectorstore.weaviate.object-class`|The class name for storing documents|SpringAiWeaviate +|`spring.ai.vectorstore.weaviate.consistency-level`|Desired tradeoff between consistency and speed|ConsistentLevel.ONE +|`spring.ai.vectorstore.weaviate.filter-field`|Configures metadata fields that can be used in filters. Format: spring.ai.vectorstore.weaviate.filter-field.=| +|=== diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfiguration.java index cc0fec826..1e7101700 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfiguration.java @@ -25,9 +25,9 @@ import io.weaviate.client.v1.auth.exception.AuthException; import org.springframework.ai.embedding.BatchingStrategy; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.TokenCountBatchingStrategy; -import org.springframework.ai.vectorstore.WeaviateVectorStore; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore.WeaviateVectorStoreConfig; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField; import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -81,18 +81,20 @@ public class WeaviateVectorStoreAutoConfiguration { ObjectProvider customObservationConvention, BatchingStrategy batchingStrategy) { - WeaviateVectorStoreConfig.Builder configBuilder = WeaviateVectorStore.WeaviateVectorStoreConfig.builder() - .withObjectClass(properties.getObjectClass()) - .withFilterableMetadataFields(properties.getFilterField() + return WeaviateVectorStore.builder() + .weaviateClient(weaviateClient) + .embeddingModel(embeddingModel) + .objectClass(properties.getObjectClass()) + .filterMetadataFields(properties.getFilterField() .entrySet() .stream() - .map(e -> new MetadataField(e.getKey(), e.getValue())) + .map(e -> new WeaviateVectorStore.MetadataField(e.getKey(), e.getValue())) .toList()) - .withConsistencyLevel(properties.getConsistencyLevel()); - - return new WeaviateVectorStore(configBuilder.build(), embeddingModel, weaviateClient, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP), - customObservationConvention.getIfAvailable(() -> null), batchingStrategy); + .consistencyLevel(WeaviateVectorStore.ConsistentLevel.valueOf(properties.getConsistencyLevel().name())) + .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) + .customObservationConvention(customObservationConvention.getIfAvailable(() -> null)) + .batchingStrategy(batchingStrategy) + .build(); } static class PropertiesWeaviateConnectionDetails implements WeaviateConnectionDetails { diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreProperties.java index a39a0f43e..08a41eb09 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreProperties.java @@ -18,9 +18,9 @@ package org.springframework.ai.autoconfigure.vectorstore.weaviate; import java.util.Map; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.ConsistentLevel; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore.ConsistentLevel; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore.MetadataField; import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -41,7 +41,7 @@ public class WeaviateVectorStoreProperties { private String objectClass = "SpringAiWeaviate"; - private ConsistentLevel consistencyLevel = WeaviateVectorStoreConfig.ConsistentLevel.ONE; + private ConsistentLevel consistencyLevel = WeaviateVectorStore.ConsistentLevel.ONE; /** * spring.ai.vectorstore.weaviate.filter-field.= diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfigurationIT.java index 02d3b2a3e..da0e32b35 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfigurationIT.java @@ -32,7 +32,7 @@ import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore.MetadataField; import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactoryIT.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactoryIT.java index bb81ace4f..fe145af97 100644 --- a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactoryIT.java +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactoryIT.java @@ -32,7 +32,7 @@ import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.ai.vectorstore.WeaviateVectorStore; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; diff --git a/vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/WeaviateFilterExpressionConverter.java b/vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/weaviate/WeaviateFilterExpressionConverter.java similarity index 99% rename from vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/WeaviateFilterExpressionConverter.java rename to vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/weaviate/WeaviateFilterExpressionConverter.java index 2f368d678..c3c711193 100644 --- a/vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/WeaviateFilterExpressionConverter.java +++ b/vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/weaviate/WeaviateFilterExpressionConverter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.vectorstore; +package org.springframework.ai.vectorstore.weaviate; import java.util.Date; import java.util.List; diff --git a/vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/WeaviateVectorStore.java b/vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStore.java similarity index 52% rename from vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/WeaviateVectorStore.java rename to vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStore.java index cd2110f4f..824a64e4f 100644 --- a/vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/WeaviateVectorStore.java +++ b/vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStore.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.vectorstore; +package org.springframework.ai.vectorstore.weaviate; import java.util.ArrayList; import java.util.Arrays; @@ -52,8 +52,8 @@ 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.WeaviateVectorStore.WeaviateVectorStoreConfig.ConsistentLevel; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField; +import org.springframework.ai.vectorstore.AbstractVectorStoreBuilder; +import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.filter.Filter; import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore; import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; @@ -63,18 +63,36 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** - * A VectorStore implementation backed by Weaviate vector database. + * A vector store implementation that stores and retrieves vectors in a Weaviate database. * * Note: You can assign arbitrary metadata fields with your Documents. Later will be * persisted and managed as Document fields. But only the metadata keys listed in * {@link WeaviateVectorStore#filterMetadataFields} can be used for similarity search * expression filters. * + *

+ * Example usage with builder: + *

+ *
{@code
+ * // Create the vector store with builder
+ * WeaviateVectorStore vectorStore = WeaviateVectorStore.builder()
+ *     .weaviateClient(weaviateClient)            // Required: Configure Weaviate client
+ *     .embeddingModel(embeddingModel)            // Required: Configure embedding model
+ *     .objectClass("CustomClass")                // Optional: Custom class name (default: SpringAiWeaviate)
+ *     .consistencyLevel(ConsistentLevel.QUORUM)  // Optional: Set consistency level (default: ONE)
+ *     .filterMetadataFields(List.of(             // Optional: Configure filterable metadata fields
+ *         MetadataField.text("country"),
+ *         MetadataField.number("year")
+ *     ))
+ *     .build();
+ * }
+ * * @author Christian Tzolov * @author EddĂș MelĂ©ndez * @author Josh Long * @author Soby Chacko * @author Thomas Vitale + * @since 1.0.0 */ public class WeaviateVectorStore extends AbstractObservationVectorStore { @@ -92,8 +110,6 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { private static final String ADDITIONAL_VECTOR_FIELD_NAME = "vector"; - private final EmbeddingModel embeddingModel; - private final WeaviateClient weaviateClient; private final ConsistentLevel consistencyLevel; @@ -131,11 +147,16 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { private final ObjectMapper objectMapper = new ObjectMapper(); /** - * Constructs a new WeaviateVectorStore. - * @param vectorStoreConfig The configuration for the store. - * @param embeddingModel The client for embedding operations. - * @param weaviateClient The client for Weaviate operations. + * Constructs a new WeaviateVectorStore with default settings. + * @param vectorStoreConfig The configuration for the store + * @param embeddingModel The client for embedding operations + * @param weaviateClient The client for Weaviate operations + * @deprecated Use {@link #builder()} instead to create instances of + * WeaviateVectorStore. This constructor will be removed in a future release. + * @see #builder() + * @since 1.0.0 */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public WeaviateVectorStore(WeaviateVectorStoreConfig vectorStoreConfig, EmbeddingModel embeddingModel, WeaviateClient weaviateClient) { this(vectorStoreConfig, embeddingModel, weaviateClient, ObservationRegistry.NOOP, null, @@ -143,31 +164,61 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { } /** - * Constructs a new WeaviateVectorStore. - * @param vectorStoreConfig The configuration for the store. - * @param embeddingModel The client for embedding operations. - * @param weaviateClient The client for Weaviate operations. - * @param observationRegistry The registry for observations. - * @param customObservationConvention The custom observation convention. + * Constructs a new WeaviateVectorStore with custom settings. + * @param vectorStoreConfig The configuration for the store + * @param embeddingModel The client for embedding operations + * @param weaviateClient The client for Weaviate operations + * @param observationRegistry The registry for observations + * @param customObservationConvention The custom observation convention + * @param batchingStrategy The strategy for batching operations + * @deprecated Use {@link #builder()} instead to create instances of + * WeaviateVectorStore. This constructor will be removed in a future release. + * @see #builder() + * @since 1.0.0 */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public WeaviateVectorStore(WeaviateVectorStoreConfig vectorStoreConfig, EmbeddingModel embeddingModel, WeaviateClient weaviateClient, ObservationRegistry observationRegistry, VectorStoreObservationConvention customObservationConvention, BatchingStrategy batchingStrategy) { - super(observationRegistry, customObservationConvention); + this(builder().embeddingModel(embeddingModel) + .weaviateClient(weaviateClient) + .observationRegistry(observationRegistry) + .customObservationConvention(customObservationConvention) + .batchingStrategy(batchingStrategy)); + } - Assert.notNull(vectorStoreConfig, "WeaviateVectorStoreConfig must not be null"); - Assert.notNull(embeddingModel, "EmbeddingModel must not be null"); + /** + * Protected constructor for creating a WeaviateVectorStore instance using the builder + * pattern. This constructor initializes the vector store with the configured settings + * from the builder and performs necessary validations. + * @param builder the {@link WeaviateBuilder} containing all configuration settings + * @throws IllegalArgumentException if the weaviateClient is null + * @see WeaviateBuilder + * @since 1.0.0 + */ + protected WeaviateVectorStore(WeaviateBuilder builder) { + super(builder); - this.embeddingModel = embeddingModel; - this.consistencyLevel = vectorStoreConfig.consistencyLevel; - this.weaviateObjectClass = vectorStoreConfig.weaviateObjectClass; - this.filterMetadataFields = vectorStoreConfig.filterMetadataFields; + Assert.notNull(builder.weaviateClient, "WeaviateClient must not be null"); + + this.weaviateClient = builder.weaviateClient; + this.consistencyLevel = builder.consistencyLevel; + this.weaviateObjectClass = builder.weaviateObjectClass; + this.filterMetadataFields = builder.filterMetadataFields; + this.batchingStrategy = builder.batchingStrategy; this.filterExpressionConverter = new WeaviateFilterExpressionConverter( this.filterMetadataFields.stream().map(MetadataField::name).toList()); - this.weaviateClient = weaviateClient; this.weaviateSimilaritySearchFields = buildWeaviateSimilaritySearchFields(); - this.batchingStrategy = batchingStrategy; + } + + /** + * Creates a new WeaviateBuilder instance. This is the recommended way to instantiate + * a WeaviateVectorStore. + * @return a new WeaviateBuilder instance + */ + public static WeaviateBuilder builder() { + return new WeaviateBuilder(); } private Field[] buildWeaviateSimilaritySearchFields() { @@ -402,8 +453,193 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { } /** - * Configuration class for the WeaviateVectorStore. + * Defines the consistency levels for Weaviate operations. + * + * @see Weaviate + * Consistency Strategies */ + public enum ConsistentLevel { + + /** + * Write must receive an acknowledgement from at least one replica node. This is + * the fastest (most available), but least consistent option. + */ + ONE, + + /** + * Write must receive an acknowledgement from at least QUORUM replica nodes. + * QUORUM is calculated as n / 2 + 1, where n is the number of replicas. + */ + QUORUM, + + /** + * Write must receive an acknowledgement from all replica nodes. This is the most + * consistent, but 'slowest'. + */ + ALL + + } + + /** + * Represents a metadata field configuration for Weaviate vector store. + * + * @param name the name of the metadata field + * @param type the type of the metadata field + */ + public record MetadataField(String name, Type type) { + + /** + * Creates a metadata field of type TEXT. + * @param name the name of the field + * @return a new MetadataField instance of type TEXT + * @throws IllegalArgumentException if name is null or empty + */ + public static MetadataField text(String name) { + Assert.hasText(name, "Text field must not be empty"); + return new MetadataField(name, Type.TEXT); + } + + /** + * Creates a metadata field of type NUMBER. + * @param name the name of the field + * @return a new MetadataField instance of type NUMBER + * @throws IllegalArgumentException if name is null or empty + */ + public static MetadataField number(String name) { + Assert.hasText(name, "Number field must not be empty"); + return new MetadataField(name, Type.NUMBER); + } + + /** + * Creates a metadata field of type BOOLEAN. + * @param name the name of the field + * @return a new MetadataField instance of type BOOLEAN + * @throws IllegalArgumentException if name is null or empty + */ + public static MetadataField bool(String name) { + Assert.hasText(name, "Boolean field name must not be empty"); + return new MetadataField(name, Type.BOOLEAN); + } + + /** + * Defines the supported types for metadata fields. + */ + public enum Type { + + TEXT, NUMBER, BOOLEAN + + } + } + + public static final class WeaviateBuilder extends AbstractVectorStoreBuilder { + + private String weaviateObjectClass = "SpringAiWeaviate"; + + private ConsistentLevel consistencyLevel = ConsistentLevel.ONE; + + private List filterMetadataFields = List.of(); + + private WeaviateClient weaviateClient; + + private BatchingStrategy batchingStrategy = new TokenCountBatchingStrategy(); + + /** + * Configures the Weaviate client. + * @param weaviateClient the client for Weaviate operations + * @return this builder instance + * @throws IllegalArgumentException if weaviateClient is null + */ + public WeaviateBuilder weaviateClient(WeaviateClient weaviateClient) { + Assert.notNull(weaviateClient, "weaviateClient must not be null"); + this.weaviateClient = weaviateClient; + return this; + } + + /** + * Configures the Weaviate object class. + * @param objectClass the object class to use + * @return this builder instance + * @throws IllegalArgumentException if objectClass is null or empty + */ + public WeaviateBuilder objectClass(String objectClass) { + Assert.hasText(objectClass, "objectClass must not be empty"); + this.weaviateObjectClass = objectClass; + return this; + } + + /** + * Configures the consistency level for Weaviate operations. + * @param consistencyLevel the consistency level to use + * @return this builder instance + * @throws IllegalArgumentException if consistencyLevel is null + */ + public WeaviateBuilder consistencyLevel(ConsistentLevel consistencyLevel) { + Assert.notNull(consistencyLevel, "consistencyLevel must not be null"); + this.consistencyLevel = consistencyLevel; + return this; + } + + /** + * Configures the filterable metadata fields. + * @param filterMetadataFields list of metadata fields that can be used in filters + * @return this builder instance + * @throws IllegalArgumentException if filterMetadataFields is null + */ + public WeaviateBuilder filterMetadataFields(List filterMetadataFields) { + Assert.notNull(filterMetadataFields, "filterMetadataFields must not be null"); + this.filterMetadataFields = filterMetadataFields; + return this; + } + + /** + * Configures the batching strategy. + * @param batchingStrategy the strategy for batching operations + * @return this builder instance + * @throws IllegalArgumentException if batchingStrategy is null + */ + public WeaviateBuilder batchingStrategy(BatchingStrategy batchingStrategy) { + Assert.notNull(batchingStrategy, "batchingStrategy must not be null"); + this.batchingStrategy = batchingStrategy; + return this; + } + + /** + * Builds and returns a new WeaviateVectorStore instance with the configured + * settings. + * @return a new WeaviateVectorStore instance + * @throws IllegalStateException if the builder configuration is invalid + */ + @Override + public WeaviateVectorStore build() { + validate(); + return new WeaviateVectorStore(this); + } + + } + + /** + * Configuration class for WeaviateVectorStore. + * + * @deprecated Use {@link WeaviateVectorStore#builder()} instead to configure and + * create instances of WeaviateVectorStore. This class will be removed in a future + * release. Example migration:
{@code
+	 * // Old approach:
+	 * WeaviateVectorStoreConfig config = WeaviateVectorStoreConfig.builder()
+	 *     .withObjectClass("CustomClass")
+	 *     .withConsistencyLevel(ConsistentLevel.QUORUM)
+	 *     .build();
+	 *
+	 * // New approach:
+	 * WeaviateVectorStore store = WeaviateVectorStore.builder()
+	 *     .objectClass("CustomClass")
+	 *     .consistencyLevel(ConsistentLevel.QUORUM)
+	 *     .build();
+	 * }
+ * @see WeaviateVectorStore#builder() + * @since 1.0.0 + */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public static final class WeaviateVectorStoreConfig { private final String weaviateObjectClass; @@ -421,8 +657,10 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { /** * Constructor using the builder. - * @param builder The configuration builder. + * @param builder The configuration builder + * @deprecated Use {@link WeaviateVectorStore#builder()} instead */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public WeaviateVectorStoreConfig(Builder builder) { this.weaviateObjectClass = builder.objectClass; this.consistencyLevel = builder.consistencyLevel; @@ -432,22 +670,43 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { /** * Start building a new configuration. - * @return The entry point for creating a new configuration. + * @return The entry point for creating a new configuration + * @deprecated Use {@link WeaviateVectorStore#builder()} instead to configure and + * create instances of WeaviateVectorStore */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public static Builder builder() { return new Builder(); } /** - * {@return the default config} + * Returns the default configuration. + * @return the default configuration + * @deprecated Use {@link WeaviateVectorStore#builder()} instead to configure and + * create instances of WeaviateVectorStore with default settings */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public static WeaviateVectorStoreConfig defaultConfig() { return builder().build(); } /** - * https://weaviate.io/developers/weaviate/concepts/replication-architecture/consistency#tunable-consistency-strategies + * Defines the consistency levels for Weaviate operations. + * + * @see Weaviate + * Consistency Strategies + * @deprecated Use {@link WeaviateVectorStore.ConsistentLevel} instead. This enum + * will be removed in a future release. Example migration:
{@code
+		 * // Old approach:
+		 * WeaviateVectorStoreConfig.ConsistentLevel level = WeaviateVectorStoreConfig.ConsistentLevel.QUORUM;
+		 *
+		 * // New approach:
+		 * WeaviateVectorStore.ConsistentLevel level = WeaviateVectorStore.ConsistentLevel.QUORUM;
+		 * }
+ * @since 1.0.0 */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public enum ConsistentLevel { /** @@ -470,33 +729,91 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { } + /** + * Represents a metadata field configuration for Weaviate vector store. + * + * @param name the name of the metadata field + * @param type the type of the metadata field + * @deprecated Use {@link WeaviateVectorStore.MetadataField} instead. This record + * will be removed in a future release. Example migration:
{@code
+		 * // Old approach:
+		 * WeaviateVectorStoreConfig.MetadataField field = WeaviateVectorStoreConfig.MetadataField.text("field");
+		 *
+		 * // New approach:
+		 * WeaviateVectorStore.MetadataField field = WeaviateVectorStore.MetadataField.text("field");
+		 * }
+ * @since 1.0.0 + */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public record MetadataField(String name, Type type) { + /** + * Creates a metadata field of type TEXT. + * @param name the name of the field + * @return a new MetadataField instance of type TEXT + * @throws IllegalArgumentException if name is null or empty + * @deprecated Use {@link WeaviateVectorStore.MetadataField#text(String)} + * instead + */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public static MetadataField text(String name) { return new MetadataField(name, Type.TEXT); } + /** + * Creates a metadata field of type NUMBER. + * @param name the name of the field + * @return a new MetadataField instance of type NUMBER + * @throws IllegalArgumentException if name is null or empty + * @deprecated Use {@link WeaviateVectorStore.MetadataField#number(String)} + * instead + */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public static MetadataField number(String name) { return new MetadataField(name, Type.NUMBER); } + /** + * Creates a metadata field of type BOOLEAN. + * @param name the name of the field + * @return a new MetadataField instance of type BOOLEAN + * @throws IllegalArgumentException if name is null or empty + * @deprecated Use {@link WeaviateVectorStore.MetadataField#bool(String)} + * instead + */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public static MetadataField bool(String name) { return new MetadataField(name, Type.BOOLEAN); } + /** + * Defines the supported types for metadata fields. + * + * @deprecated Use {@link WeaviateVectorStore.MetadataField.Type} instead. + * This enum will be removed in a future release. + * @since 1.0.0 + */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public enum Type { TEXT, NUMBER, BOOLEAN } - } + /** + * Builder for WeaviateVectorStoreConfig. + * + * @deprecated Use {@link WeaviateVectorStore#builder()} instead to configure and + * create instances of WeaviateVectorStore + * @since 1.0.0 + */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public static final class Builder { private String objectClass = "SpringAiWeaviate"; - private ConsistentLevel consistencyLevel = WeaviateVectorStoreConfig.ConsistentLevel.ONE; + private ConsistentLevel consistencyLevel = ConsistentLevel.ONE; private List filterMetadataFields = List.of(); @@ -506,10 +823,15 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { } /** - * Weaviate known, filterable metadata fields. - * @param filterMetadataFields known metadata fields to use. - * @return this builder. + * Configures the filterable metadata fields. + * @param filterMetadataFields known metadata fields to use + * @return this builder + * @throws IllegalArgumentException if filterMetadataFields is null + * @deprecated Use + * {@link WeaviateVectorStore.WeaviateBuilder#filterMetadataFields(List)} + * instead */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public Builder withFilterableMetadataFields(List filterMetadataFields) { Assert.notNull(filterMetadataFields, "The filterMetadataFields can not be null."); this.filterMetadataFields = filterMetadataFields; @@ -517,10 +839,14 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { } /** - * Weaviate config headers. - * @param headers config headers to use. - * @return this builder. + * Configures the Weaviate config headers. + * @param headers config headers to use + * @return this builder + * @throws IllegalArgumentException if headers is null + * @deprecated Use the new builder API in + * {@link WeaviateVectorStore#builder()} */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public Builder withHeaders(Map headers) { Assert.notNull(headers, "The headers can not be null."); this.headers = headers; @@ -528,10 +854,14 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { } /** - * Weaviate objectClass. - * @param objectClass objectClass to use. - * @return this builder. + * Configures the Weaviate objectClass. + * @param objectClass objectClass to use + * @return this builder + * @throws IllegalArgumentException if objectClass is empty or null + * @deprecated Use + * {@link WeaviateVectorStore.WeaviateBuilder#objectClass(String)} instead */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public Builder withObjectClass(String objectClass) { Assert.hasText(objectClass, "The objectClass can not be empty."); this.objectClass = objectClass; @@ -539,10 +869,15 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { } /** - * Weaviate consistencyLevel. - * @param consistencyLevel consistencyLevel to use. - * @return this builder. + * Configures the Weaviate consistencyLevel. + * @param consistencyLevel consistencyLevel to use + * @return this builder + * @throws IllegalArgumentException if consistencyLevel is null + * @deprecated Use + * {@link WeaviateVectorStore.WeaviateBuilder#consistencyLevel(WeaviateVectorStore.ConsistentLevel)} + * instead */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public Builder withConsistencyLevel(ConsistentLevel consistencyLevel) { Assert.notNull(consistencyLevel, "The consistencyLevel can not be null."); this.consistencyLevel = consistencyLevel; @@ -550,8 +885,12 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore { } /** - * {@return the immutable configuration} + * Builds and returns the immutable configuration. + * @return the immutable configuration + * @deprecated Use {@link WeaviateVectorStore#builder()} instead to configure + * and create instances of WeaviateVectorStore */ + @Deprecated(forRemoval = true, since = "1.0.0-M5") public WeaviateVectorStoreConfig build() { return new WeaviateVectorStoreConfig(this); } diff --git a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateFilterExpressionConverterTests.java b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateFilterExpressionConverterTests.java similarity index 99% rename from vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateFilterExpressionConverterTests.java rename to vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateFilterExpressionConverterTests.java index f06f2ad5d..8a56b8d93 100644 --- a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateFilterExpressionConverterTests.java +++ b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateFilterExpressionConverterTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.vectorstore; +package org.springframework.ai.vectorstore.weaviate; import java.util.List; diff --git a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateImage.java b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateImage.java similarity index 94% rename from vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateImage.java rename to vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateImage.java index 8d13d9b30..99a529974 100644 --- a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateImage.java +++ b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateImage.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.vectorstore; +package org.springframework.ai.vectorstore.weaviate; import org.testcontainers.utility.DockerImageName; diff --git a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreBuilderTests.java b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreBuilderTests.java new file mode 100644 index 000000000..739ed525a --- /dev/null +++ b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreBuilderTests.java @@ -0,0 +1,146 @@ +/* + * 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.weaviate; + +import io.weaviate.client.Config; +import io.weaviate.client.WeaviateClient; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore.ConsistentLevel; +import org.springframework.ai.vectorstore.weaviate.WeaviateVectorStore.MetadataField; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests for {@link WeaviateVectorStore.WeaviateBuilder}. + * + * @author Mark Pollack + */ +@ExtendWith(MockitoExtension.class) +class WeaviateVectorStoreBuilderTests { + + @Mock + private EmbeddingModel embeddingModel; + + @Test + void shouldBuildWithMinimalConfiguration() { + WeaviateClient weaviateClient = new WeaviateClient(new Config("http", "localhost:8080")); + + WeaviateVectorStore vectorStore = WeaviateVectorStore.builder() + .weaviateClient(weaviateClient) + .embeddingModel(embeddingModel) + .build(); + + assertThat(vectorStore).isNotNull(); + } + + @Test + void shouldBuildWithCustomConfiguration() { + WeaviateClient weaviateClient = new WeaviateClient(new Config("http", "localhost:8080")); + + WeaviateVectorStore vectorStore = WeaviateVectorStore.builder() + .weaviateClient(weaviateClient) + .embeddingModel(embeddingModel) + .objectClass("CustomClass") + .consistencyLevel(ConsistentLevel.QUORUM) + .filterMetadataFields(List.of(MetadataField.text("country"), MetadataField.number("year"))) + .build(); + + assertThat(vectorStore).isNotNull(); + } + + @Test + void shouldFailWithoutWeaviateClient() { + assertThatThrownBy(() -> WeaviateVectorStore.builder().embeddingModel(embeddingModel).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("WeaviateClient must not be null"); + } + + @Test + void shouldFailWithoutEmbeddingModel() { + WeaviateClient weaviateClient = new WeaviateClient(new Config("http", "localhost:8080")); + + assertThatThrownBy(() -> WeaviateVectorStore.builder().weaviateClient(weaviateClient).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("EmbeddingModel must be configured"); + } + + @Test + void shouldFailWithInvalidObjectClass() { + WeaviateClient weaviateClient = new WeaviateClient(new Config("http", "localhost:8080")); + + assertThatThrownBy(() -> WeaviateVectorStore.builder() + .weaviateClient(weaviateClient) + .embeddingModel(embeddingModel) + .objectClass("") + .build()).isInstanceOf(IllegalArgumentException.class).hasMessage("objectClass must not be empty"); + } + + @Test + void shouldFailWithNullConsistencyLevel() { + WeaviateClient weaviateClient = new WeaviateClient(new Config("http", "localhost:8080")); + + assertThatThrownBy(() -> WeaviateVectorStore.builder() + .weaviateClient(weaviateClient) + .embeddingModel(embeddingModel) + .consistencyLevel(null) + .build()).isInstanceOf(IllegalArgumentException.class).hasMessage("consistencyLevel must not be null"); + } + + @Test + void shouldFailWithNullFilterMetadataFields() { + WeaviateClient weaviateClient = new WeaviateClient(new Config("http", "localhost:8080")); + + assertThatThrownBy(() -> WeaviateVectorStore.builder() + .weaviateClient(weaviateClient) + .embeddingModel(embeddingModel) + .filterMetadataFields(null) + .build()).isInstanceOf(IllegalArgumentException.class).hasMessage("filterMetadataFields must not be null"); + } + + @Test + void shouldCreateMetadataFieldsWithValidation() { + assertThatThrownBy(() -> MetadataField.text("")).isInstanceOf(IllegalArgumentException.class) + .hasMessage("Text field must not be empty"); + + assertThatThrownBy(() -> MetadataField.number("")).isInstanceOf(IllegalArgumentException.class) + .hasMessage("Number field must not be empty"); + + assertThatThrownBy(() -> MetadataField.bool("")).isInstanceOf(IllegalArgumentException.class) + .hasMessage("Boolean field name must not be empty"); + + MetadataField textField = MetadataField.text("validName"); + assertThat(textField.name()).isEqualTo("validName"); + assertThat(textField.type()).isEqualTo(MetadataField.Type.TEXT); + + MetadataField numberField = MetadataField.number("validName"); + assertThat(numberField.name()).isEqualTo("validName"); + assertThat(numberField.type()).isEqualTo(MetadataField.Type.NUMBER); + + MetadataField boolField = MetadataField.bool("validName"); + assertThat(boolField.name()).isEqualTo("validName"); + assertThat(boolField.type()).isEqualTo(MetadataField.Type.BOOLEAN); + } + +} diff --git a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreIT.java b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreIT.java similarity index 94% rename from vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreIT.java rename to vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreIT.java index 7239b319b..b675b5f96 100644 --- a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreIT.java +++ b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.vectorstore; +package org.springframework.ai.vectorstore.weaviate; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -35,8 +35,8 @@ import org.testcontainers.weaviate.WeaviateContainer; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.transformers.TransformersEmbeddingModel; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -252,13 +252,13 @@ public class WeaviateVectorStoreIT { WeaviateClient weaviateClient = new WeaviateClient( new Config("http", weaviateContainer.getHttpHostAddress())); - WeaviateVectorStoreConfig config = WeaviateVectorStore.WeaviateVectorStoreConfig.builder() - .withFilterableMetadataFields(List.of(MetadataField.text("country"), MetadataField.number("year"))) - .withConsistencyLevel(WeaviateVectorStoreConfig.ConsistentLevel.ONE) + return WeaviateVectorStore.builder() + .weaviateClient(weaviateClient) + .embeddingModel(embeddingModel) + .filterMetadataFields(List.of(WeaviateVectorStore.MetadataField.text("country"), + WeaviateVectorStore.MetadataField.number("year"))) + .consistencyLevel(WeaviateVectorStore.ConsistentLevel.ONE) .build(); - - return new WeaviateVectorStore(config, embeddingModel, weaviateClient); - } @Bean diff --git a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreObservationIT.java b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreObservationIT.java similarity index 94% rename from vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreObservationIT.java rename to vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreObservationIT.java index 17b54c188..b3b8ea8f7 100644 --- a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/weaviate/WeaviateVectorStoreObservationIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.vectorstore; +package org.springframework.ai.vectorstore.weaviate; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -37,7 +37,8 @@ import org.springframework.ai.embedding.TokenCountBatchingStrategy; import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.transformers.TransformersEmbeddingModel; -import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention; import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.HighCardinalityKeyNames; import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.LowCardinalityKeyNames; @@ -165,13 +166,13 @@ public class WeaviateVectorStoreObservationIT { WeaviateClient weaviateClient = new WeaviateClient( new io.weaviate.client.Config("http", weaviateContainer.getHttpHostAddress())); - WeaviateVectorStoreConfig config = WeaviateVectorStore.WeaviateVectorStoreConfig.builder() - .withConsistencyLevel(WeaviateVectorStoreConfig.ConsistentLevel.ONE) + return WeaviateVectorStore.builder() + .weaviateClient(weaviateClient) + .embeddingModel(embeddingModel) + .consistencyLevel(WeaviateVectorStore.ConsistentLevel.ONE) + .observationRegistry(observationRegistry) + .batchingStrategy(new TokenCountBatchingStrategy()) .build(); - - return new WeaviateVectorStore(config, embeddingModel, weaviateClient, observationRegistry, null, - new TokenCountBatchingStrategy()); - } @Bean