diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc index 432a06e4c..50745c1e3 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc @@ -43,9 +43,6 @@ The following service connection factories are provided in the `spring-ai-spring | `QdrantConnectionDetails` | Containers named `qdrant/qdrant` -| `RedisConnectionDetails` -| Containers named `redis/redis-stack-server`, `redis/redis-stack` - | `TypesenseConnectionDetails` | Containers named `typesense/typesense` 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 index c1966544a..e646be203 100644 --- 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 @@ -46,9 +46,6 @@ The following service connection factories are provided in the `spring-ai-spring | `QdrantConnectionDetails` | Containers of type `QdrantContainer` -| `RedisConnectionDetails` -| Containers of type `RedisStackContainer` - | `TypesenseConnectionDetails` | Containers named "typesense/typesense" diff --git a/spring-ai-spring-boot-autoconfigure/pom.xml b/spring-ai-spring-boot-autoconfigure/pom.xml index 6ac477d1a..7a21efbfd 100644 --- a/spring-ai-spring-boot-autoconfigure/pom.xml +++ b/spring-ai-spring-boot-autoconfigure/pom.xml @@ -202,6 +202,12 @@ true + + org.springframework.data + spring-data-redis + true + + redis.clients 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 deleted file mode 100644 index df98d9522..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisConnectionDetails.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 4f203ed19..98b047153 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 @@ -19,53 +19,38 @@ import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.vectorstore.RedisVectorStore; import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import redis.clients.jedis.JedisPooled; /** * @author Christian Tzolov * @author Eddú Meléndez */ -@AutoConfiguration -@ConditionalOnClass({ RedisVectorStore.class, EmbeddingModel.class }) +@AutoConfiguration(after = RedisAutoConfiguration.class) +@ConditionalOnClass({ JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class }) +@ConditionalOnBean(JedisConnectionFactory.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(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties, - RedisConnectionDetails redisConnectionDetails) { + JedisConnectionFactory jedisConnectionFactory) { var config = RedisVectorStoreConfig.builder() - .withURI(redisConnectionDetails.getUri()) .withIndexName(properties.getIndex()) .withPrefix(properties.getPrefix()) .build(); - return new RedisVectorStore(config, embeddingModel, properties.isInitializeSchema()); - } - - static class PropertiesRedisConnectionDetails implements RedisConnectionDetails { - - private final RedisVectorStoreProperties properties; - - public PropertiesRedisConnectionDetails(RedisVectorStoreProperties properties) { - this.properties = properties; - } - - @Override - public String getUri() { - return this.properties.getUri(); - } - + return new RedisVectorStore(config, embeddingModel, + new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()), + properties.isInitializeSchema()); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreProperties.java index eb79a1a40..4799afb80 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreProperties.java @@ -20,26 +20,17 @@ import org.springframework.boot.context.properties.ConfigurationProperties; /** * @author Julien Ruaux + * @author Eddú Meléndez */ @ConfigurationProperties(RedisVectorStoreProperties.CONFIG_PREFIX) public class RedisVectorStoreProperties extends CommonVectorStoreProperties { public static final String CONFIG_PREFIX = "spring.ai.vectorstore.redis"; - private String uri = "redis://localhost:6379"; - private String index = "default-index"; private String prefix = "default:"; - public String getUri() { - return this.uri; - } - - public void setUri(String uri) { - this.uri = uri; - } - public String getIndex() { return this.index; } diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfigurationIT.java index b5721db77..78122a520 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfigurationIT.java @@ -28,6 +28,7 @@ import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -38,6 +39,7 @@ import com.redis.testcontainers.RedisStackContainer; /** * @author Julien Ruaux + * @author Eddú Meléndez */ @Testcontainers class RedisVectorStoreAutoConfigurationIT { @@ -52,32 +54,31 @@ class RedisVectorStoreAutoConfigurationIT { ResourceUtils.getText("classpath:/test/data/great.depression.txt"), Map.of("depression", "bad"))); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RedisVectorStoreAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, RedisVectorStoreAutoConfiguration.class)) .withUserConfiguration(Config.class) - .withPropertyValues("spring.ai.vectorstore.redis.index=myIdx") - .withPropertyValues("spring.ai.vectorstore.redis.prefix=doc:"); + .withPropertyValues("spring.data.redis.url=" + redisContainer.getRedisURI(), + "spring.ai.vectorstore.redis.index=myIdx", "spring.ai.vectorstore.redis.prefix=doc:"); @Test void addAndSearch() { - contextRunner.withPropertyValues("spring.ai.vectorstore.redis.uri=" + redisContainer.getRedisURI()) - .run(context -> { - VectorStore vectorStore = context.getBean(VectorStore.class); - vectorStore.add(documents); + contextRunner.run(context -> { + VectorStore vectorStore = context.getBean(VectorStore.class); + vectorStore.add(documents); - List results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); + 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(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()); + // 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(); - }); + results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); + assertThat(results).isEmpty(); + }); } @Configuration(proxyBeanMethods = false) diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStorePropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStorePropertiesTests.java index 14852b7e8..cb3691031 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStorePropertiesTests.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStorePropertiesTests.java @@ -21,13 +21,13 @@ import org.junit.jupiter.api.Test; /** * @author Julien Ruaux + * @author Eddú Meléndez */ class RedisVectorStorePropertiesTests { @Test void defaultValues() { var props = new RedisVectorStoreProperties(); - assertThat(props.getUri()).isEqualTo("redis://localhost:6379"); assertThat(props.getIndex()).isEqualTo("default-index"); assertThat(props.getPrefix()).isEqualTo("default:"); } @@ -35,11 +35,9 @@ class RedisVectorStorePropertiesTests { @Test void customValues() { var props = new RedisVectorStoreProperties(); - props.setUri("redis://redis.com:12345"); props.setIndex("myIdx"); props.setPrefix("doc:"); - assertThat(props.getUri()).isEqualTo("redis://redis.com:12345"); assertThat(props.getIndex()).isEqualTo("myIdx"); assertThat(props.getPrefix()).isEqualTo("doc:"); } diff --git a/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java b/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java deleted file mode 100644 index b0266aca7..000000000 --- a/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/redis/RedisDockerComposeConnectionDetailsFactory.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.docker.compose.service.connection.redis; - -import org.springframework.ai.autoconfigure.vectorstore.redis.RedisConnectionDetails; -import org.springframework.boot.docker.compose.core.RunningService; -import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; -import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; - -/** - * @author Eddú Meléndez - */ -class RedisDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { - - private static final String[] REDIS_IMAGE_NAMES = { "redis/redis-stack", "redis/redis-stack-server" }; - - private static final int REDIS_PORT = 6379; - - protected RedisDockerComposeConnectionDetailsFactory() { - super(REDIS_IMAGE_NAMES); - } - - @Override - protected RedisConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { - return new RedisDockerComposeConnectionDetails(source.getRunningService()); - } - - /** - * {@link RedisConnectionDetails} backed by a {@code Redis} {@link RunningService}. - */ - static class RedisDockerComposeConnectionDetails extends DockerComposeConnectionDetails - implements RedisConnectionDetails { - - private final String uri; - - RedisDockerComposeConnectionDetails(RunningService service) { - super(service); - this.uri = "redis://" + service.host() + ":" + service.ports().get(REDIS_PORT); - } - - @Override - public String getUri() { - return this.uri; - } - - } - -} diff --git a/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories b/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories index c0ee2af6b..218716860 100644 --- a/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories +++ b/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories @@ -3,6 +3,5 @@ org.springframework.ai.docker.compose.service.connection.chroma.ChromaDockerComp org.springframework.ai.docker.compose.service.connection.ollama.OllamaDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.opensearch.OpenSearchDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.qdrant.QdrantDockerComposeConnectionDetailsFactory,\ -org.springframework.ai.docker.compose.service.connection.redis.RedisDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.typesense.TypesenseDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.weaviate.WeaviateDockerComposeConnectionDetailsFactory \ No newline at end of file diff --git a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/redis/RedisStackDockerComposeConnectionDetailsFactoryTests.java b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/redis/RedisStackDockerComposeConnectionDetailsFactoryTests.java deleted file mode 100644 index 70ef16c29..000000000 --- a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/redis/RedisStackDockerComposeConnectionDetailsFactoryTests.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.docker.compose.service.connection.redis; - -import org.junit.jupiter.api.Test; -import org.springframework.ai.autoconfigure.vectorstore.redis.RedisConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.testcontainers.utility.DockerImageName; - -import static org.assertj.core.api.Assertions.assertThat; - -class RedisStackDockerComposeConnectionDetailsFactoryTests extends AbstractDockerComposeIntegrationTests { - - RedisStackDockerComposeConnectionDetailsFactoryTests() { - super("redis-compose.yaml", DockerImageName.parse("redis/redis-stack")); - } - - @Test - void runCreatesConnectionDetails() { - RedisConnectionDetails connectionDetails = run(RedisConnectionDetails.class); - assertThat(connectionDetails.getUri()).startsWith("redis://"); - } - -} diff --git a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/redis/RedisStackServerDockerComposeConnectionDetailsFactoryTests.java b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/redis/RedisStackServerDockerComposeConnectionDetailsFactoryTests.java deleted file mode 100644 index c7df8a8c7..000000000 --- a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/redis/RedisStackServerDockerComposeConnectionDetailsFactoryTests.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.docker.compose.service.connection.redis; - -import org.junit.jupiter.api.Test; -import org.springframework.ai.autoconfigure.vectorstore.redis.RedisConnectionDetails; -import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; -import org.testcontainers.utility.DockerImageName; - -import static org.assertj.core.api.Assertions.assertThat; - -class RedisStackServerDockerComposeConnectionDetailsFactoryTests extends AbstractDockerComposeIntegrationTests { - - RedisStackServerDockerComposeConnectionDetailsFactoryTests() { - super("redis-compose.yaml", DockerImageName.parse("redis/redis-stack-server")); - } - - @Test - void runCreatesConnectionDetails() { - RedisConnectionDetails connectionDetails = run(RedisConnectionDetails.class); - assertThat(connectionDetails.getUri()).startsWith("redis://"); - } - -} diff --git a/spring-ai-spring-boot-docker-compose/src/test/resources/org/springframework/ai/docker/compose/service/connection/redis/redis-compose.yaml b/spring-ai-spring-boot-docker-compose/src/test/resources/org/springframework/ai/docker/compose/service/connection/redis/redis-compose.yaml deleted file mode 100644 index 9511c464d..000000000 --- a/spring-ai-spring-boot-docker-compose/src/test/resources/org/springframework/ai/docker/compose/service/connection/redis/redis-compose.yaml +++ /dev/null @@ -1,5 +0,0 @@ -services: - redis: - image: '{imageName}' - ports: - - '6379' 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 deleted file mode 100644 index 72007c4e6..000000000 --- a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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/resources/META-INF/spring.factories b/spring-ai-spring-boot-testcontainers/src/main/resources/META-INF/spring.factories index 2eab8182b..8fa2b068b 100644 --- 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 @@ -4,6 +4,5 @@ org.springframework.ai.testcontainers.service.connection.milvus.MilvusContainerC org.springframework.ai.testcontainers.service.connection.ollama.OllamaContainerConnectionDetailsFactory,\ org.springframework.ai.testcontainers.service.connection.opensearch.OpenSearchContainerConnectionDetailsFactory,\ org.springframework.ai.testcontainers.service.connection.qdrant.QdrantContainerConnectionDetailsFactory,\ -org.springframework.ai.testcontainers.service.connection.redis.RedisContainerConnectionDetailsFactory,\ org.springframework.ai.testcontainers.service.connection.typesense.TypesenseContainerConnectionDetailsFactory,\ 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/redis/RedisContainerConnectionDetailsFactoryTest.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTest.java deleted file mode 100644 index aa53e64fd..000000000 --- a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/redis/RedisContainerConnectionDetailsFactoryTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.EmbeddingModel; -import org.springframework.ai.transformers.TransformersEmbeddingModel; -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 EmbeddingModel embeddingModel() { - return new TransformersEmbeddingModel(); - } - - } - -} \ No newline at end of file diff --git a/vector-stores/spring-ai-redis-store/pom.xml b/vector-stores/spring-ai-redis-store/pom.xml index 729ba0fe8..aee0919a0 100644 --- a/vector-stores/spring-ai-redis-store/pom.xml +++ b/vector-stores/spring-ai-redis-store/pom.xml @@ -34,6 +34,11 @@ ${parent.version} + + org.springframework.data + spring-data-redis + + redis.clients jedis diff --git a/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java index e28589618..990bee6f5 100644 --- a/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java +++ b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java @@ -67,6 +67,7 @@ import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm; * * @author Julien Ruaux * @author Christian Tzolov + * @author Eddú Meléndez * @see VectorStore * @see RedisVectorStoreConfig * @see EmbeddingModel @@ -100,8 +101,6 @@ public class RedisVectorStore implements VectorStore, InitializingBean { */ public static final class RedisVectorStoreConfig { - private final String uri; - private final String indexName; private final String prefix; @@ -119,7 +118,6 @@ public class RedisVectorStore implements VectorStore, InitializingBean { } private RedisVectorStoreConfig(Builder builder) { - this.uri = builder.uri; this.indexName = builder.indexName; this.prefix = builder.prefix; this.contentFieldName = builder.contentFieldName; @@ -147,8 +145,6 @@ public class RedisVectorStore implements VectorStore, InitializingBean { public static class Builder { - private String uri = DEFAULT_URI; - private String indexName = DEFAULT_INDEX_NAME; private String prefix = DEFAULT_PREFIX; @@ -164,16 +160,6 @@ public class RedisVectorStore implements VectorStore, InitializingBean { private Builder() { } - /** - * Configures the Redis URI to use. - * @param uri the Redis URI to use - * @return this builder - */ - public Builder withURI(String uri) { - this.uri = uri; - return this; - } - /** * Configures the Redis index name to use. * @param name the index name to use @@ -247,8 +233,6 @@ public class RedisVectorStore implements VectorStore, InitializingBean { private final boolean initializeSchema; - public static final String DEFAULT_URI = "redis://localhost:6379"; - public static final String DEFAULT_INDEX_NAME = "spring-ai-index"; public static final String DEFAULT_CONTENT_FIELD_NAME = "content"; @@ -287,13 +271,14 @@ public class RedisVectorStore implements VectorStore, InitializingBean { private FilterExpressionConverter filterExpressionConverter; - public RedisVectorStore(RedisVectorStoreConfig config, EmbeddingModel embeddingModel, boolean initializeSchema) { + public RedisVectorStore(RedisVectorStoreConfig config, EmbeddingModel embeddingModel, JedisPooled jedis, + boolean initializeSchema) { Assert.notNull(config, "Config must not be null"); Assert.notNull(embeddingModel, "Embedding model must not be null"); this.initializeSchema = initializeSchema; - this.jedis = new JedisPooled(config.uri); + this.jedis = jedis; this.embeddingModel = embeddingModel; this.config = config; this.filterExpressionConverter = new RedisFilterExpressionConverter(this.config.metadataFields); diff --git a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/RedisVectorStoreIT.java b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/RedisVectorStoreIT.java index c766c1af4..44497602d 100644 --- a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/RedisVectorStoreIT.java +++ b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/RedisVectorStoreIT.java @@ -32,18 +32,23 @@ import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.RedisVectorStore.MetadataField; import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig; import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import com.redis.testcontainers.RedisStackContainer; +import redis.clients.jedis.JedisPooled; /** * @author Julien Ruaux + * @author Eddú Meléndez */ @Testcontainers class RedisVectorStoreIT { @@ -53,7 +58,9 @@ class RedisVectorStoreIT { RedisStackContainer.DEFAULT_IMAGE_NAME.withTag(RedisStackContainer.DEFAULT_TAG)); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withUserConfiguration(TestApplication.class); + .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withUserConfiguration(TestApplication.class) + .withPropertyValues("spring.data.redis.url=" + redisContainer.getRedisURI()); List documents = List.of( new Document("1", getText("classpath:/test/data/spring.ai.txt"), Map.of("meta1", "meta1")), @@ -243,12 +250,15 @@ class RedisVectorStoreIT { public static class TestApplication { @Bean - public RedisVectorStore vectorStore(EmbeddingModel embeddingModel) { - return new RedisVectorStore(RedisVectorStoreConfig.builder() - .withURI(redisContainer.getRedisURI()) - .withMetadataFields(MetadataField.tag("meta1"), MetadataField.tag("meta2"), - MetadataField.tag("country"), MetadataField.numeric("year")) - .build(), embeddingModel, true); + public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, + JedisConnectionFactory jedisConnectionFactory) { + return new RedisVectorStore( + RedisVectorStoreConfig.builder() + .withMetadataFields(MetadataField.tag("meta1"), MetadataField.tag("meta2"), + MetadataField.tag("country"), MetadataField.numeric("year")) + .build(), + embeddingModel, + new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()), true); } @Bean