diff --git a/document-readers/pdf-reader/pom.xml b/document-readers/pdf-reader/pom.xml index c870c9176c..37ebc83dd5 100644 --- a/document-readers/pdf-reader/pom.xml +++ b/document-readers/pdf-reader/pom.xml @@ -55,6 +55,7 @@ org.testcontainers junit-jupiter + ${testcontainers.version} test diff --git a/models/spring-ai-ollama/pom.xml b/models/spring-ai-ollama/pom.xml index e8f04527e7..b72cf88f94 100644 --- a/models/spring-ai-ollama/pom.xml +++ b/models/spring-ai-ollama/pom.xml @@ -71,6 +71,7 @@ org.testcontainers junit-jupiter + ${testcontainers.version} test diff --git a/models/spring-ai-postgresml/pom.xml b/models/spring-ai-postgresml/pom.xml index 78ed1e8f2c..aac3aee315 100644 --- a/models/spring-ai-postgresml/pom.xml +++ b/models/spring-ai-postgresml/pom.xml @@ -54,12 +54,14 @@ org.testcontainers junit-jupiter + ${testcontainers.version} test org.testcontainers postgresql + ${testcontainers.version} test diff --git a/models/spring-ai-transformers/pom.xml b/models/spring-ai-transformers/pom.xml index fdc8f69035..bffd8123d5 100644 --- a/models/spring-ai-transformers/pom.xml +++ b/models/spring-ai-transformers/pom.xml @@ -82,6 +82,7 @@ org.testcontainers junit-jupiter + ${testcontainers.version} test diff --git a/pom.xml b/pom.xml index 83bd0e1ff5..97071095ad 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,7 @@ spring-ai-spring-boot-starters/spring-ai-starter-bedrock-ai spring-ai-spring-boot-starters/spring-ai-starter-mistral-ai spring-ai-retry + spring-ai-spring-boot-testcontainers @@ -133,7 +134,7 @@ 1.7.1 - 1.19.6 + 1.19.7 0.0.4 diff --git a/spring-ai-bom/pom.xml b/spring-ai-bom/pom.xml index 03655b644e..ac3d40e9d2 100644 --- a/spring-ai-bom/pom.xml +++ b/spring-ai-bom/pom.xml @@ -301,6 +301,12 @@ spring-ai-qdrant-store-spring-boot-starter ${project.version} + + + org.springframework.ai + spring-ai-spring-boot-testcontainers + ${project.version} + diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc index 7fa84af400..4891400b84 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc @@ -52,6 +52,7 @@ ** xref:api/etl-pipeline.adoc[] ** xref:api/testing.adoc[] ** xref:api/generic-model.adoc[] +* xref:api/testcontainers.adoc[Testcontainers] * xref:contribution-guidelines.adoc[Contribution Guidelines] * Appendices ** xref:upgrade-notes.adoc[] diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/testcontainers.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/testcontainers.adoc new file mode 100644 index 0000000000..f43fa75d87 --- /dev/null +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/testcontainers.adoc @@ -0,0 +1,28 @@ +[[testcontainers]] += Testcontainers + +== Service Connections + +The following service connection factories are provided in the spring-ai-spring-boot-testcontainers jar: + +[cols="|,|"] +|==== +| Connection Details | Matched on +| `ChromaConnectionDetails` +| Containers of type `ChromaDBContainer` + +| `MilvusServiceClientConnectionDetails` +| Containers of type `MilvusContainer` + +| `OllamaConnectionDetails` +| Containers of type `OllamaContainer` + +| `QdrantConnectionDetails` +| Containers of type `QdrantContainer` + +| `RedisConnectionDetails` +| Containers of type `RedisStackContainer` + +| `WeaviateConnectionDetails` +| Containers of type `WeaviateContainer` +|==== diff --git a/spring-ai-spring-boot-autoconfigure/pom.xml b/spring-ai-spring-boot-autoconfigure/pom.xml index 80c6aacf12..8b5df9d983 100644 --- a/spring-ai-spring-boot-autoconfigure/pom.xml +++ b/spring-ai-spring-boot-autoconfigure/pom.xml @@ -265,6 +265,7 @@ org.testcontainers postgresql + ${testcontainers.version} test @@ -298,7 +299,7 @@ org.testcontainers qdrant - 1.19.6 + ${testcontainers.version} test diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java index aa454fb838..d3378ec88e 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java @@ -31,6 +31,7 @@ * {@link AutoConfiguration Auto-configuration} for Ollama Chat Client. * * @author Christian Tzolov + * @author Eddú Meléndez * @since 0.8.0 */ @AutoConfiguration(after = RestClientAutoConfiguration.class) @@ -39,10 +40,16 @@ OllamaConnectionProperties.class }) public class OllamaAutoConfiguration { + @Bean + @ConditionalOnMissingBean(OllamaConnectionDetails.class) + public PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectionProperties properties) { + return new PropertiesOllamaConnectionDetails(properties); + } + @Bean @ConditionalOnMissingBean - public OllamaApi ollamaApi(OllamaConnectionProperties properties, RestClient.Builder restClientBuilder) { - return new OllamaApi(properties.getBaseUrl(), restClientBuilder); + public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails, RestClient.Builder restClientBuilder) { + return new OllamaApi(connectionDetails.getBaseUrl(), restClientBuilder); } @Bean @@ -65,4 +72,19 @@ public OllamaEmbeddingClient ollamaEmbeddingClient(OllamaApi ollamaApi, OllamaEm .withDefaultOptions(properties.getOptions()); } + private static class PropertiesOllamaConnectionDetails implements OllamaConnectionDetails { + + private final OllamaConnectionProperties properties; + + PropertiesOllamaConnectionDetails(OllamaConnectionProperties properties) { + this.properties = properties; + } + + @Override + public String getBaseUrl() { + return this.properties.getBaseUrl(); + } + + } + } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaConnectionDetails.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaConnectionDetails.java new file mode 100644 index 0000000000..6981097c3e --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaConnectionDetails.java @@ -0,0 +1,27 @@ +/* + * 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.autoconfigure.ollama; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * @author Eddú Meléndez + */ +public interface OllamaConnectionDetails extends ConnectionDetails { + + String getBaseUrl(); + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaConnectionDetails.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaConnectionDetails.java new file mode 100644 index 0000000000..4877fdca2c --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaConnectionDetails.java @@ -0,0 +1,29 @@ +/* + * 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.autoconfigure.vectorstore.chroma; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * @author Eddú Meléndez + */ +public interface ChromaConnectionDetails extends ConnectionDetails { + + String getHost(); + + int getPort(); + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreAutoConfiguration.java index 392ce9160a..a780313b7a 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreAutoConfiguration.java @@ -30,12 +30,19 @@ /** * @author Christian Tzolov + * @author Eddú Meléndez */ @AutoConfiguration @ConditionalOnClass({ EmbeddingClient.class, RestTemplate.class, ChromaVectorStore.class, ObjectMapper.class }) @EnableConfigurationProperties({ ChromaApiProperties.class, ChromaVectorStoreProperties.class }) public class ChromaVectorStoreAutoConfiguration { + @Bean + @ConditionalOnMissingBean(ChromaConnectionDetails.class) + PropertiesChromaConnectionDetails chromaConnectionDetails(ChromaApiProperties properties) { + return new PropertiesChromaConnectionDetails(properties); + } + @Bean @ConditionalOnMissingBean public RestTemplate restTemplate() { @@ -44,9 +51,10 @@ public RestTemplate restTemplate() { @Bean @ConditionalOnMissingBean - public ChromaApi chromaApi(ChromaApiProperties apiProperties, RestTemplate restTemplate) { + public ChromaApi chromaApi(ChromaApiProperties apiProperties, RestTemplate restTemplate, + ChromaConnectionDetails connectionDetails) { - String chromaUrl = String.format("%s:%s", apiProperties.getHost(), apiProperties.getPort()); + String chromaUrl = String.format("%s:%s", connectionDetails.getHost(), connectionDetails.getPort()); var chromaApi = new ChromaApi(chromaUrl, restTemplate, new ObjectMapper()); @@ -67,4 +75,24 @@ public ChromaVectorStore vectorStore(EmbeddingClient embeddingClient, ChromaApi return new ChromaVectorStore(embeddingClient, chromaApi, storeProperties.getCollectionName()); } + private static class PropertiesChromaConnectionDetails implements ChromaConnectionDetails { + + private final ChromaApiProperties properties; + + PropertiesChromaConnectionDetails(ChromaApiProperties properties) { + this.properties = properties; + } + + @Override + public String getHost() { + return this.properties.getHost(); + } + + @Override + public int getPort() { + return this.properties.getPort(); + } + + } + } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusServiceClientConnectionDetails.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusServiceClientConnectionDetails.java new file mode 100644 index 0000000000..b6d015630b --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusServiceClientConnectionDetails.java @@ -0,0 +1,29 @@ +/* + * 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.autoconfigure.vectorstore.milvus; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * @author Eddú Meléndez + */ +public interface MilvusServiceClientConnectionDetails extends ConnectionDetails { + + String getHost(); + + int getPort(); + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfiguration.java index dd744cd6ac..c1b2ab70a9 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfiguration.java @@ -34,12 +34,20 @@ /** * @author Christian Tzolov + * @author Eddú Meléndez */ @AutoConfiguration @ConditionalOnClass({ MilvusVectorStore.class, EmbeddingClient.class }) @EnableConfigurationProperties({ MilvusServiceClientProperties.class, MilvusVectorStoreProperties.class }) public class MilvusVectorStoreAutoConfiguration { + @Bean + @ConditionalOnMissingBean(MilvusServiceClientConnectionDetails.class) + PropertiesMilvusServiceClientConnectionDetails milvusServiceClientConnectionDetails( + MilvusServiceClientProperties properties) { + return new PropertiesMilvusServiceClientConnectionDetails(properties); + } + @Bean @ConditionalOnMissingBean public MilvusVectorStore vectorStore(MilvusServiceClient milvusClient, EmbeddingClient embeddingClient, @@ -60,11 +68,11 @@ public MilvusVectorStore vectorStore(MilvusServiceClient milvusClient, Embedding @Bean @ConditionalOnMissingBean public MilvusServiceClient milvusClient(MilvusVectorStoreProperties serverProperties, - MilvusServiceClientProperties clientProperties) { + MilvusServiceClientProperties clientProperties, MilvusServiceClientConnectionDetails connectionDetails) { var builder = ConnectParam.newBuilder() - .withHost(clientProperties.getHost()) - .withPort(clientProperties.getPort()) + .withHost(connectionDetails.getHost()) + .withPort(connectionDetails.getPort()) .withDatabaseName(serverProperties.getDatabaseName()) .withConnectTimeout(clientProperties.getConnectTimeoutMs(), TimeUnit.MILLISECONDS) .withKeepAliveTime(clientProperties.getKeepAliveTimeMs(), TimeUnit.MILLISECONDS) @@ -105,4 +113,25 @@ public MilvusServiceClient milvusClient(MilvusVectorStoreProperties serverProper return new MilvusServiceClient(builder.build()); } + private static class PropertiesMilvusServiceClientConnectionDetails + implements MilvusServiceClientConnectionDetails { + + private final MilvusServiceClientProperties properties; + + PropertiesMilvusServiceClientConnectionDetails(MilvusServiceClientProperties properties) { + this.properties = properties; + } + + @Override + public String getHost() { + return this.properties.getHost(); + } + + @Override + public int getPort() { + return this.properties.getPort(); + } + + } + } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantConnectionDetails.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantConnectionDetails.java new file mode 100644 index 0000000000..f07a701e2b --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantConnectionDetails.java @@ -0,0 +1,29 @@ +/* + * 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.autoconfigure.vectorstore.qdrant; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * @author Eddú Meléndez + */ +public interface QdrantConnectionDetails extends ConnectionDetails { + + String getHost(); + + int getPort(); + +} 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 d887d9731a..5540f29776 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 @@ -26,6 +26,7 @@ /** * @author Anush Shetty + * @author Eddú Meléndez * @since 0.8.1 */ @AutoConfiguration @@ -33,14 +34,21 @@ @EnableConfigurationProperties(QdrantVectorStoreProperties.class) public class QdrantVectorStoreAutoConfiguration { + @Bean + @ConditionalOnMissingBean(QdrantConnectionDetails.class) + PropertiesQdrantConnectionDetails qdrantConnectionDetails(QdrantVectorStoreProperties properties) { + return new PropertiesQdrantConnectionDetails(properties); + } + @Bean @ConditionalOnMissingBean - public QdrantVectorStore vectorStore(EmbeddingClient embeddingClient, QdrantVectorStoreProperties properties) { + public QdrantVectorStore vectorStore(EmbeddingClient embeddingClient, QdrantVectorStoreProperties properties, + QdrantConnectionDetails connectionDetails) { var config = QdrantVectorStoreConfig.builder() .withCollectionName(properties.getCollectionName()) - .withHost(properties.getHost()) - .withPort(properties.getPort()) + .withHost(connectionDetails.getHost()) + .withPort(connectionDetails.getPort()) .withTls(properties.isUseTls()) .withApiKey(properties.getApiKey()) .build(); @@ -48,4 +56,24 @@ public QdrantVectorStore vectorStore(EmbeddingClient embeddingClient, QdrantVect return new QdrantVectorStore(config, embeddingClient); } + private static class PropertiesQdrantConnectionDetails implements QdrantConnectionDetails { + + private final QdrantVectorStoreProperties properties; + + PropertiesQdrantConnectionDetails(QdrantVectorStoreProperties properties) { + this.properties = properties; + } + + @Override + public String getHost() { + return this.properties.getHost(); + } + + @Override + public int getPort() { + return this.properties.getPort(); + } + + } + } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisConnectionDetails.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisConnectionDetails.java new file mode 100644 index 0000000000..df98d95227 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisConnectionDetails.java @@ -0,0 +1,27 @@ +/* + * 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.autoconfigure.vectorstore.redis; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * @author Eddú Meléndez + */ +public interface RedisConnectionDetails extends ConnectionDetails { + + String getUri(); + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java index 67e807d82e..e17d76e7da 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java @@ -26,18 +26,26 @@ /** * @author Christian Tzolov + * @author Eddú Meléndez */ @AutoConfiguration @ConditionalOnClass({ RedisVectorStore.class, EmbeddingClient.class }) @EnableConfigurationProperties(RedisVectorStoreProperties.class) public class RedisVectorStoreAutoConfiguration { + @Bean + @ConditionalOnMissingBean(RedisConnectionDetails.class) + public PropertiesRedisConnectionDetails redisConnectionDetails(RedisVectorStoreProperties properties) { + return new PropertiesRedisConnectionDetails(properties); + } + @Bean @ConditionalOnMissingBean - public RedisVectorStore vectorStore(EmbeddingClient embeddingClient, RedisVectorStoreProperties properties) { + public RedisVectorStore vectorStore(EmbeddingClient embeddingClient, RedisVectorStoreProperties properties, + RedisConnectionDetails redisConnectionDetails) { var config = RedisVectorStoreConfig.builder() - .withURI(properties.getUri()) + .withURI(redisConnectionDetails.getUri()) .withIndexName(properties.getIndex()) .withPrefix(properties.getPrefix()) .build(); @@ -45,4 +53,19 @@ public RedisVectorStore vectorStore(EmbeddingClient embeddingClient, RedisVector return new RedisVectorStore(config, embeddingClient); } + private static class PropertiesRedisConnectionDetails implements RedisConnectionDetails { + + private final RedisVectorStoreProperties properties; + + public PropertiesRedisConnectionDetails(RedisVectorStoreProperties properties) { + this.properties = properties; + } + + @Override + public String getUri() { + return this.properties.getUri(); + } + + } + } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateConnectionDetails.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateConnectionDetails.java new file mode 100644 index 0000000000..154271c6f1 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateConnectionDetails.java @@ -0,0 +1,24 @@ +/* + * 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.autoconfigure.vectorstore.weaviate; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +public interface WeaviateConnectionDetails extends ConnectionDetails { + + String getHost(); + +} 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 3f3e706060..3d5c84534f 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 @@ -27,20 +27,28 @@ /** * @author Christian Tzolov + * @author Eddú Meléndez */ @AutoConfiguration @ConditionalOnClass({ EmbeddingClient.class, WeaviateVectorStore.class }) @EnableConfigurationProperties({ WeaviateVectorStoreProperties.class }) public class WeaviateVectorStoreAutoConfiguration { + @Bean + @ConditionalOnMissingBean(WeaviateConnectionDetails.class) + public PropertiesWeaviateConnectionDetails weaviateConnectionDetails(WeaviateVectorStoreProperties properties) { + return new PropertiesWeaviateConnectionDetails(properties); + } + @Bean @ConditionalOnMissingBean - public WeaviateVectorStore vectorStore(EmbeddingClient embeddingClient, WeaviateVectorStoreProperties properties) { + public WeaviateVectorStore vectorStore(EmbeddingClient embeddingClient, WeaviateVectorStoreProperties properties, + WeaviateConnectionDetails connectionDetails) { WeaviateVectorStoreConfig.Builder configBuilder = WeaviateVectorStore.WeaviateVectorStoreConfig.builder() .withScheme(properties.getScheme()) .withApiKey(properties.getApiKey()) - .withHost(properties.getHost()) + .withHost(connectionDetails.getHost()) .withHeaders(properties.getHeaders()) .withObjectClass(properties.getObjectClass()) .withFilterableMetadataFields(properties.getFilterField() @@ -53,4 +61,19 @@ public WeaviateVectorStore vectorStore(EmbeddingClient embeddingClient, Weaviate return new WeaviateVectorStore(configBuilder.build(), embeddingClient); } + private static class PropertiesWeaviateConnectionDetails implements WeaviateConnectionDetails { + + private final WeaviateVectorStoreProperties properties; + + PropertiesWeaviateConnectionDetails(WeaviateVectorStoreProperties properties) { + this.properties = properties; + } + + @Override + public String getHost() { + return this.properties.getHost(); + } + + } + } diff --git a/spring-ai-spring-boot-testcontainers/pom.xml b/spring-ai-spring-boot-testcontainers/pom.xml new file mode 100644 index 0000000000..fca68b9112 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/pom.xml @@ -0,0 +1,206 @@ + + + 4.0.0 + + org.springframework.ai + spring-ai + 1.0.0-SNAPSHOT + + spring-ai-spring-boot-testcontainers + jar + Spring AI Testcontainers + Spring AI Testcontainers + https://github.com/spring-projects/spring-ai + + + https://github.com/spring-projects/spring-ai + git://github.com/spring-projects/spring-ai.git + git@github.com:spring-projects/spring-ai.git + + + + + + org.springframework.ai + spring-ai-spring-boot-autoconfigure + ${project.parent.version} + + + + com.google.protobuf + protobuf-java + ${protobuf-java.version} + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-testcontainers + + + + org.springframework.ai + spring-ai-openai + ${project.parent.version} + true + + + + org.springframework.ai + spring-ai-ollama + ${project.parent.version} + true + + + + + org.springframework.ai + spring-ai-transformers + ${project.parent.version} + true + + + + + org.springframework.ai + spring-ai-milvus-store + ${project.parent.version} + true + + + + + org.springframework.ai + spring-ai-chroma-store + ${project.parent.version} + true + + + + + org.springframework.ai + spring-ai-weaviate-store + ${project.parent.version} + true + + + + + org.springframework.ai + spring-ai-redis + ${project.parent.version} + true + + + + + redis.clients + jedis + 5.1.0 + + + + + org.springframework.ai + spring-ai-qdrant + ${project.parent.version} + true + + + + + + org.springframework.ai + spring-ai-test + ${project.parent.version} + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-jdbc + test + + + org.postgresql + postgresql + ${postgresql.version} + test + + + + org.testcontainers + testcontainers + ${testcontainers.version} + true + + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + + com.redis + testcontainers-redis + 2.0.1 + + + + org.awaitility + awaitility + test + + + + org.testcontainers + qdrant + ${testcontainers.version} + true + + + + org.testcontainers + weaviate + ${testcontainers.version} + true + + + + org.testcontainers + chromadb + ${testcontainers.version} + true + + + + org.testcontainers + milvus + ${testcontainers.version} + true + + + + org.testcontainers + ollama + ${testcontainers.version} + true + + + + + diff --git a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/chroma/ChromaContainerConnectionDetailsFactory.java b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/chroma/ChromaContainerConnectionDetailsFactory.java new file mode 100644 index 0000000000..cb859f400a --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/chroma/ChromaContainerConnectionDetailsFactory.java @@ -0,0 +1,56 @@ +/* + * 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.testcontainers.service.connection.chroma; + +import org.springframework.ai.autoconfigure.vectorstore.chroma.ChromaConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.testcontainers.chromadb.ChromaDBContainer; + +/** + * @author Eddú Meléndez + */ +class ChromaContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + public ChromaConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new ChromaDBContainerConnectionDetails(source); + } + + /** + * {@link ChromaConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class ChromaDBContainerConnectionDetails extends ContainerConnectionDetails + implements ChromaConnectionDetails { + + private ChromaDBContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getHost() { + return "http://%s".formatted(getContainer().getHost()); + } + + @Override + public int getPort() { + return getContainer().getMappedPort(8000); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/milvus/MilvusContainerConnectionDetailsFactory.java b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/milvus/MilvusContainerConnectionDetailsFactory.java new file mode 100644 index 0000000000..a44643c382 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/milvus/MilvusContainerConnectionDetailsFactory.java @@ -0,0 +1,58 @@ +/* + * 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.testcontainers.service.connection.milvus; + +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.testcontainers.milvus.MilvusContainer; + +/** + * @author Eddú Meléndez + */ +class MilvusContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + public MilvusServiceClientConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource source) { + return new MilvusContainerConnectionDetails(source); + } + + /** + * {@link MilvusServiceClientConnectionDetails} backed by a + * {@link ContainerConnectionSource}. + */ + private static final class MilvusContainerConnectionDetails extends ContainerConnectionDetails + implements MilvusServiceClientConnectionDetails { + + private MilvusContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getHost() { + return getContainer().getHost(); + } + + @Override + public int getPort() { + return getContainer().getMappedPort(19530); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactory.java b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactory.java new file mode 100644 index 0000000000..46174bc36f --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactory.java @@ -0,0 +1,51 @@ +/* + * 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.testcontainers.service.connection.ollama; + +import org.springframework.ai.autoconfigure.ollama.OllamaConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.testcontainers.ollama.OllamaContainer; + +/** + * @author Eddú Meléndez + */ +class OllamaContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + public OllamaConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new OllamaContainerConnectionDetails(source); + } + + /** + * {@link OllamaConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class OllamaContainerConnectionDetails extends ContainerConnectionDetails + implements OllamaConnectionDetails { + + private OllamaContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getBaseUrl() { + return getContainer().getEndpoint(); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/qdrant/QdrantContainerConnectionDetailsFactory.java b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/qdrant/QdrantContainerConnectionDetailsFactory.java new file mode 100644 index 0000000000..2b410c5161 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/qdrant/QdrantContainerConnectionDetailsFactory.java @@ -0,0 +1,56 @@ +/* + * 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.testcontainers.service.connection.qdrant; + +import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.testcontainers.qdrant.QdrantContainer; + +/** + * @author Eddú Meléndez + */ +class QdrantContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + public QdrantConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new QdrantContainerConnectionDetails(source); + } + + /** + * {@link QdrantConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class QdrantContainerConnectionDetails extends ContainerConnectionDetails + implements QdrantConnectionDetails { + + private QdrantContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getHost() { + return getContainer().getHost(); + } + + @Override + public int getPort() { + return getContainer().getMappedPort(6334); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java new file mode 100644 index 0000000000..72007c4e6d --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java @@ -0,0 +1,51 @@ +/* + * 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.testcontainers.service.connection.redis; + +import com.redis.testcontainers.RedisStackContainer; +import org.springframework.ai.autoconfigure.vectorstore.redis.RedisConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; + +/** + * @author Eddú Meléndez + */ +class RedisContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + public RedisConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new RedisContainerConnectionDetails(source); + } + + /** + * {@link RedisConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class RedisContainerConnectionDetails extends ContainerConnectionDetails + implements RedisConnectionDetails { + + private RedisContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getUri() { + return getContainer().getRedisURI(); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactory.java b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactory.java new file mode 100644 index 0000000000..601fb6244d --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactory.java @@ -0,0 +1,52 @@ +/* + * 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.testcontainers.service.connection.weaviate; + +import org.springframework.ai.autoconfigure.vectorstore.weaviate.WeaviateConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.testcontainers.weaviate.WeaviateContainer; + +/** + * @author Eddú Meléndez + */ +class WeaviateContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + public WeaviateConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource source) { + return new WeaviateContainerConnectionDetails(source); + } + + /** + * {@link WeaviateConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class WeaviateContainerConnectionDetails extends ContainerConnectionDetails + implements WeaviateConnectionDetails { + + private WeaviateContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String getHost() { + return getContainer().getHttpHostAddress(); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/main/resources/META-INF/spring.factories b/spring-ai-spring-boot-testcontainers/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..32f6c2d2c4 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/main/resources/META-INF/spring.factories @@ -0,0 +1,7 @@ +org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ +org.springframework.ai.testcontainers.service.connection.chroma.ChromaContainerConnectionDetailsFactory,\ +org.springframework.ai.testcontainers.service.connection.milvus.MilvusContainerConnectionDetailsFactory,\ +org.springframework.ai.testcontainers.service.connection.ollama.OllamaContainerConnectionDetailsFactory,\ +org.springframework.ai.testcontainers.service.connection.qdrant.QdrantContainerConnectionDetailsFactory,\ +org.springframework.ai.testcontainers.service.connection.redis.RedisContainerConnectionDetailsFactory,\ +org.springframework.ai.testcontainers.service.connection.weaviate.WeaviateContainerConnectionDetailsFactory diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/chroma/ChromaContainerConnectionDetailsFactoryTest.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/chroma/ChromaContainerConnectionDetailsFactoryTest.java new file mode 100644 index 0000000000..1a9db7bc8c --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/chroma/ChromaContainerConnectionDetailsFactoryTest.java @@ -0,0 +1,92 @@ +/* + * 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.testcontainers.service.connection.chroma; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.autoconfigure.vectorstore.chroma.ChromaVectorStoreAutoConfiguration; +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.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.chromadb.ChromaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringJUnitConfig +@Testcontainers +@TestPropertySource(properties = "spring.ai.vectorstore.chroma.store.collectionName=TestCollection") +class ChromaContainerConnectionDetailsFactoryTest { + + @Container + @ServiceConnection + static ChromaDBContainer chroma = new ChromaDBContainer("ghcr.io/chroma-core/chroma:0.4.15"); + + @Autowired + private VectorStore vectorStore; + + @Test + public void addAndSearchWithFilters() { + var bgDocument = new Document("The World is Big and Salvation Lurks Around the Corner", + Map.of("country", "Bulgaria")); + var nlDocument = new Document("The World is Big and Salvation Lurks Around the Corner", + Map.of("country", "Netherlands")); + + vectorStore.add(List.of(bgDocument, nlDocument)); + + var request = SearchRequest.query("The World").withTopK(5); + + List results = vectorStore.similaritySearch(request); + assertThat(results).hasSize(2); + + results = vectorStore + .similaritySearch(request.withSimilarityThresholdAll().withFilterExpression("country == 'Bulgaria'")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getId()).isEqualTo(bgDocument.getId()); + + results = vectorStore + .similaritySearch(request.withSimilarityThresholdAll().withFilterExpression("country == 'Netherlands'")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getId()).isEqualTo(nlDocument.getId()); + + // Remove all documents from the store + vectorStore.delete(List.of(bgDocument, nlDocument).stream().map(doc -> doc.getId()).toList()); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(ChromaVectorStoreAutoConfiguration.class) + static class Config { + + @Bean + public EmbeddingClient embeddingClient() { + return new TransformersEmbeddingClient(); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/milvus/MilvusContainerConnectionDetailsFactoryTest.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/milvus/MilvusContainerConnectionDetailsFactoryTest.java new file mode 100644 index 0000000000..ee43cf0019 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/milvus/MilvusContainerConnectionDetailsFactoryTest.java @@ -0,0 +1,99 @@ +/* + * 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.testcontainers.service.connection.milvus; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.ResourceUtils; +import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration; +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.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.milvus.MilvusContainer; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringJUnitConfig +@Testcontainers +@TestPropertySource(properties = { "spring.ai.vectorstore.milvus.metricType=COSINE", + "spring.ai.vectorstore.milvus.indexType=IVF_FLAT", "spring.ai.vectorstore.milvus.embeddingDimension=384", + "spring.ai.vectorstore.milvus.collectionName=myTestCollection" }) +class MilvusContainerConnectionDetailsFactoryTest { + + @Container + @ServiceConnection + static MilvusContainer milvusContainer = new MilvusContainer("milvusdb/milvus:v2.3.8"); + + List documents = List.of( + new Document(ResourceUtils.getText("classpath:/test/data/spring.ai.txt"), Map.of("spring", "great")), + new Document(ResourceUtils.getText("classpath:/test/data/time.shelter.txt")), new Document( + ResourceUtils.getText("classpath:/test/data/great.depression.txt"), Map.of("depression", "bad"))); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MilvusVectorStoreAutoConfiguration.class)) + .withUserConfiguration(Config.class); + + @Autowired + private VectorStore vectorStore; + + @Test + public void addAndSearch() { + vectorStore.add(documents); + + List results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); + + assertThat(results).hasSize(1); + Document resultDoc = results.get(0); + assertThat(resultDoc.getId()).isEqualTo(documents.get(0).getId()); + assertThat(resultDoc.getContent()) + .contains("Spring AI provides abstractions that serve as the foundation for developing AI applications."); + assertThat(resultDoc.getMetadata()).hasSize(2); + assertThat(resultDoc.getMetadata()).containsKeys("spring", "distance"); + + // Remove all documents from the store + vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); + + results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); + assertThat(results).hasSize(0); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(MilvusVectorStoreAutoConfiguration.class) + static class Config { + + @Bean + public EmbeddingClient embeddingClient() { + return new TransformersEmbeddingClient(); + } + + } + +} \ No newline at end of file diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactoryTest.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactoryTest.java new file mode 100644 index 0000000000..5b79e662f2 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactoryTest.java @@ -0,0 +1,82 @@ +/* + * 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.testcontainers.service.connection.ollama; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; +import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.ai.ollama.OllamaEmbeddingClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.ollama.OllamaContainer; + +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers +@TestPropertySource(properties = "spring.ai.ollama.embedding.options.model=" + + OllamaContainerConnectionDetailsFactoryTest.MODEL_NAME) +class OllamaContainerConnectionDetailsFactoryTest { + + private static final Log logger = LogFactory.getLog(OllamaContainerConnectionDetailsFactoryTest.class); + + static final String MODEL_NAME = "orca-mini"; + + @Container + @ServiceConnection + static OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.1.23"); + + @Autowired + private OllamaEmbeddingClient embeddingClient; + + @BeforeAll + public static void beforeAll() throws IOException, InterruptedException { + logger.info("Start pulling the '" + MODEL_NAME + " ' generative ... would take several minutes ..."); + ollama.execInContainer("ollama", "pull", MODEL_NAME); + logger.info(MODEL_NAME + " pulling competed!"); + } + + @Test + public void singleTextEmbedding() { + EmbeddingResponse embeddingResponse = this.embeddingClient.embedForResponse(List.of("Hello World")); + assertThat(embeddingResponse.getResults()).hasSize(1); + assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); + assertThat(this.embeddingClient.dimensions()).isEqualTo(3200); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ RestClientAutoConfiguration.class, OllamaAutoConfiguration.class }) + static class Config { + + } + +} \ No newline at end of file diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/qdrant/QdrantContainerConnectionDetailsFactoryTest.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/qdrant/QdrantContainerConnectionDetailsFactoryTest.java new file mode 100644 index 0000000000..09f3c1498d --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/qdrant/QdrantContainerConnectionDetailsFactoryTest.java @@ -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.testcontainers.service.connection.qdrant; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration; +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.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.qdrant.QdrantContainer; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringJUnitConfig +@Testcontainers +@TestPropertySource(properties = "spring.ai.vectorstore.qdrant.collectionName=test_collection") +public class QdrantContainerConnectionDetailsFactoryTest { + + @Container + @ServiceConnection + static QdrantContainer qdrantContainer = new QdrantContainer("qdrant/qdrant:v1.7.4"); + + 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"))); + + @Autowired + private VectorStore vectorStore; + + @Test + public void addAndSearch() { + 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) + @ImportAutoConfiguration(QdrantVectorStoreAutoConfiguration.class) + static class Config { + + @Bean + public EmbeddingClient embeddingClient() { + return new TransformersEmbeddingClient(); + } + + } + +} diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTest.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTest.java new file mode 100644 index 0000000000..b0888689e7 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTest.java @@ -0,0 +1,91 @@ +/* + * 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.testcontainers.service.connection.redis; + +import com.redis.testcontainers.RedisStackContainer; +import org.junit.jupiter.api.Test; +import org.springframework.ai.ResourceUtils; +import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreAutoConfiguration; +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.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringJUnitConfig +@Testcontainers +@TestPropertySource( + properties = { "spring.ai.vectorstore.redis.index=myIdx", "spring.ai.vectorstore.redis.prefix=doc:" }) +class RedisContainerConnectionDetailsFactoryTest { + + @Container + @ServiceConnection + static RedisStackContainer redisContainer = new RedisStackContainer( + RedisStackContainer.DEFAULT_IMAGE_NAME.withTag(RedisStackContainer.DEFAULT_TAG)); + + private List documents = List.of( + new Document(ResourceUtils.getText("classpath:/test/data/spring.ai.txt"), Map.of("spring", "great")), + new Document(ResourceUtils.getText("classpath:/test/data/time.shelter.txt")), new Document( + ResourceUtils.getText("classpath:/test/data/great.depression.txt"), Map.of("depression", "bad"))); + + @Autowired + private VectorStore vectorStore; + + @Test + void addAndSearch() { + vectorStore.add(documents); + + List results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); + + assertThat(results).hasSize(1); + Document resultDoc = results.get(0); + assertThat(resultDoc.getId()).isEqualTo(documents.get(0).getId()); + assertThat(resultDoc.getContent()) + .contains("Spring AI provides abstractions that serve as the foundation for developing AI applications."); + + // Remove all documents from the store + vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); + + results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); + assertThat(results).isEmpty(); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(RedisVectorStoreAutoConfiguration.class) + static class Config { + + @Bean + public EmbeddingClient embeddingClient() { + return new TransformersEmbeddingClient(); + } + + } + +} \ No newline at end of file diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactoryTest.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactoryTest.java new file mode 100644 index 0000000000..685bc1b794 --- /dev/null +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/weaviate/WeaviateContainerConnectionDetailsFactoryTest.java @@ -0,0 +1,125 @@ +/* + * 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.testcontainers.service.connection.weaviate; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.autoconfigure.vectorstore.weaviate.WeaviateVectorStoreAutoConfiguration; +import org.springframework.ai.autoconfigure.vectorstore.weaviate.WeaviateVectorStoreProperties; +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.ai.vectorstore.WeaviateVectorStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.weaviate.WeaviateContainer; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringJUnitConfig +@Testcontainers +@TestPropertySource(properties = { "spring.ai.vectorstore.weaviate.filter-field.country=TEXT", + "spring.ai.vectorstore.weaviate.filter-field.year=NUMBER", + "spring.ai.vectorstore.weaviate.filter-field.active=BOOLEAN", + "spring.ai.vectorstore.weaviate.filter-field.price=NUMBER" }) +class WeaviateContainerConnectionDetailsFactoryTest { + + @Container + @ServiceConnection + static WeaviateContainer weaviateContainer = new WeaviateContainer("semitechnologies/weaviate:1.22.4"); + + @Autowired + private WeaviateVectorStoreProperties properties; + + @Autowired + private VectorStore vectorStore; + + @Test + public void addAndSearchWithFilters() { + assertThat(properties.getFilterField()).hasSize(4); + + assertThat(properties.getFilterField().get("country")) + .isEqualTo(WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField.Type.TEXT); + assertThat(properties.getFilterField().get("year")) + .isEqualTo(WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField.Type.NUMBER); + assertThat(properties.getFilterField().get("active")) + .isEqualTo(WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField.Type.BOOLEAN); + assertThat(properties.getFilterField().get("price")) + .isEqualTo(WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField.Type.NUMBER); + + var bgDocument = new Document("The World is Big and Salvation Lurks Around the Corner", + Map.of("country", "Bulgaria", "price", 3.14, "active", true, "year", 2020)); + var nlDocument = new Document("The World is Big and Salvation Lurks Around the Corner", + Map.of("country", "Netherlands", "price", 1.57, "active", false, "year", 2023)); + + vectorStore.add(List.of(bgDocument, nlDocument)); + + var request = SearchRequest.query("The World").withTopK(5); + + List results = vectorStore.similaritySearch(request); + assertThat(results).hasSize(2); + + results = vectorStore + .similaritySearch(request.withSimilarityThresholdAll().withFilterExpression("country == 'Bulgaria'")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getId()).isEqualTo(bgDocument.getId()); + + results = vectorStore + .similaritySearch(request.withSimilarityThresholdAll().withFilterExpression("country == 'Netherlands'")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getId()).isEqualTo(nlDocument.getId()); + + results = vectorStore.similaritySearch( + request.withSimilarityThresholdAll().withFilterExpression("price > 1.57 && active == true")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getId()).isEqualTo(bgDocument.getId()); + + results = vectorStore + .similaritySearch(request.withSimilarityThresholdAll().withFilterExpression("year in [2020, 2023]")); + assertThat(results).hasSize(2); + + results = vectorStore + .similaritySearch(request.withSimilarityThresholdAll().withFilterExpression("year > 2020 && year <= 2023")); + assertThat(results).hasSize(1); + assertThat(results.get(0).getId()).isEqualTo(nlDocument.getId()); + + // Remove all documents from the store + vectorStore.delete(List.of(bgDocument, nlDocument).stream().map(doc -> doc.getId()).toList()); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(WeaviateVectorStoreAutoConfiguration.class) + static class Config { + + @Bean + public EmbeddingClient embeddingClient() { + return new TransformersEmbeddingClient(); + } + + } + +} \ No newline at end of file diff --git a/vector-stores/spring-ai-qdrant/pom.xml b/vector-stores/spring-ai-qdrant/pom.xml index 8b635c4c17..f5c4c36cd2 100644 --- a/vector-stores/spring-ai-qdrant/pom.xml +++ b/vector-stores/spring-ai-qdrant/pom.xml @@ -72,7 +72,7 @@ org.testcontainers qdrant - 1.19.6 + ${testcontainers.version} test diff --git a/vector-stores/spring-ai-redis/pom.xml b/vector-stores/spring-ai-redis/pom.xml index 8bf9b348e8..b735ded987 100644 --- a/vector-stores/spring-ai-redis/pom.xml +++ b/vector-stores/spring-ai-redis/pom.xml @@ -71,6 +71,7 @@ org.testcontainers junit-jupiter + ${testcontainers.version} test