Use RedisAutoConfiguration in RedisVectorStoreAutoConfiguration
Currently, `RedisVectorStoreAutoConfiguration` creates its own configuration to connect with Redis. This commit reuse `RedisAutoConfiguration` from spring boot project. It's limited to Jedis.
This commit is contained in:
committed by
Christian Tzolov
parent
40d8671f3e
commit
0a07f65d6a
@@ -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`
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -202,6 +202,12 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Override Jedis version -->
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Document> results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1));
|
||||
List<Document> 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)
|
||||
|
||||
@@ -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:");
|
||||
}
|
||||
|
||||
@@ -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<RedisConnectionDetails> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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://");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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://");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
services:
|
||||
redis:
|
||||
image: '{imageName}'
|
||||
ports:
|
||||
- '6379'
|
||||
@@ -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<RedisStackContainer, RedisConnectionDetails> {
|
||||
|
||||
@Override
|
||||
public RedisConnectionDetails getContainerConnectionDetails(ContainerConnectionSource<RedisStackContainer> source) {
|
||||
return new RedisContainerConnectionDetails(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RedisConnectionDetails} backed by a {@link ContainerConnectionSource}.
|
||||
*/
|
||||
private static final class RedisContainerConnectionDetails extends ContainerConnectionDetails<RedisStackContainer>
|
||||
implements RedisConnectionDetails {
|
||||
|
||||
private RedisContainerConnectionDetails(ContainerConnectionSource<RedisStackContainer> source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUri() {
|
||||
return getContainer().getRedisURI();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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<Document> 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<Document> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,6 +34,11 @@
|
||||
<version>${parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Document> 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
|
||||
|
||||
Reference in New Issue
Block a user