diff --git a/.sdkmanrc b/.sdkmanrc index 4e7acfe..95502c0 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -3,4 +3,4 @@ # See https://sdkman.io/usage#config # A summary is to add the following to ~/.sdkman/etc/config # sdkman_auto_env=true -java=17.0.11-tem +java=17.0.11-tem \ No newline at end of file diff --git a/langchain4j-qianfan-spring-boot-starter/pom.xml b/langchain4j-qianfan-spring-boot-starter/pom.xml index 4e58a54..b59d121 100644 --- a/langchain4j-qianfan-spring-boot-starter/pom.xml +++ b/langchain4j-qianfan-spring-boot-starter/pom.xml @@ -6,7 +6,7 @@ dev.langchain4j langchain4j-spring - 0.30.0 + 0.32.0-SNAPSHOT ../pom.xml diff --git a/langchain4j-redis-spring-boot-starter/pom.xml b/langchain4j-redis-spring-boot-starter/pom.xml new file mode 100644 index 0000000..7227a0e --- /dev/null +++ b/langchain4j-redis-spring-boot-starter/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + dev.langchain4j + langchain4j-spring + 0.32.0-SNAPSHOT + ../pom.xml + + + langchain4j-redis-spring-boot-starter + LangChain4j Spring Boot starter for Redis + jar + + + + + dev.langchain4j + langchain4j-redis + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-autoconfigure-processor + true + + + + + org.projectlombok + lombok + provided + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + dev.langchain4j + langchain4j-embeddings-all-minilm-l6-v2-q + test + + + + dev.langchain4j + langchain4j-spring-boot-tests + ${project.version} + tests + test-jar + test + + + + com.redis.testcontainers + testcontainers-redis + 1.6.4 + test + + + + org.tinylog + tinylog-impl + test + + + + org.tinylog + slf4j-tinylog + test + + + + + \ No newline at end of file diff --git a/langchain4j-redis-spring-boot-starter/src/main/java/dev/langchain4j/store/embedding/redis/spring/RedisEmbeddingStoreAutoConfiguration.java b/langchain4j-redis-spring-boot-starter/src/main/java/dev/langchain4j/store/embedding/redis/spring/RedisEmbeddingStoreAutoConfiguration.java new file mode 100644 index 0000000..bff93ac --- /dev/null +++ b/langchain4j-redis-spring-boot-starter/src/main/java/dev/langchain4j/store/embedding/redis/spring/RedisEmbeddingStoreAutoConfiguration.java @@ -0,0 +1,47 @@ +package dev.langchain4j.store.embedding.redis.spring; + +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.lang.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static dev.langchain4j.store.embedding.redis.spring.RedisEmbeddingStoreProperties.PREFIX; + +@AutoConfiguration +@EnableConfigurationProperties(RedisEmbeddingStoreProperties.class) +@ConditionalOnProperty(prefix = PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) +public class RedisEmbeddingStoreAutoConfiguration { + + private static final String DEFAULT_HOST = "localhost"; + private static final int DEFAULT_PORT = 6379; + private static final String DEFAULT_INDEX_NAME = "langchain4j-index"; + + @Bean + @ConditionalOnMissingBean + public RedisEmbeddingStore redisEmbeddingStore(RedisEmbeddingStoreProperties properties, + @Nullable EmbeddingModel embeddingModel) { + String host = Optional.ofNullable(properties.getHost()).orElse(DEFAULT_HOST); + int port = Optional.ofNullable(properties.getPort()).orElse(DEFAULT_PORT); + String indexName = Optional.ofNullable(properties.getIndexName()).orElse(DEFAULT_INDEX_NAME); + Integer dimension = Optional.ofNullable(properties.getDimension()).orElseGet(() -> embeddingModel == null ? null : embeddingModel.dimension()); + List metadataKeys = Optional.ofNullable(properties.getMetadataKeys()).orElse(new ArrayList<>()); + + return RedisEmbeddingStore.builder() + .host(host) + .port(port) + .user(properties.getUser()) + .password(properties.getPassword()) + .indexName(indexName) + .dimension(dimension) + .metadataKeys(metadataKeys) + .build(); + } +} diff --git a/langchain4j-redis-spring-boot-starter/src/main/java/dev/langchain4j/store/embedding/redis/spring/RedisEmbeddingStoreProperties.java b/langchain4j-redis-spring-boot-starter/src/main/java/dev/langchain4j/store/embedding/redis/spring/RedisEmbeddingStoreProperties.java new file mode 100644 index 0000000..daf0796 --- /dev/null +++ b/langchain4j-redis-spring-boot-starter/src/main/java/dev/langchain4j/store/embedding/redis/spring/RedisEmbeddingStoreProperties.java @@ -0,0 +1,24 @@ +package dev.langchain4j.store.embedding.redis.spring; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.ArrayList; +import java.util.List; + +@ConfigurationProperties(prefix = RedisEmbeddingStoreProperties.PREFIX) +@Getter +@Setter +public class RedisEmbeddingStoreProperties { + + static final String PREFIX = "langchain4j.redis"; + + private String host; + private Integer port; + private String user; + private String password; + private String indexName; + private Integer dimension; + private List metadataKeys; +} diff --git a/langchain4j-redis-spring-boot-starter/src/main/resources/META-INF/spring.factories b/langchain4j-redis-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..c03ec78 --- /dev/null +++ b/langchain4j-redis-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=dev.langchain4j.store.embedding.redis.spring.RedisEmbeddingStoreAutoConfiguration \ No newline at end of file diff --git a/langchain4j-redis-spring-boot-starter/src/test/java/dev/langchain4j/store/embedding/redis/spring/RedisEmbeddingStoreAutoConfigurationIT.java b/langchain4j-redis-spring-boot-starter/src/test/java/dev/langchain4j/store/embedding/redis/spring/RedisEmbeddingStoreAutoConfigurationIT.java new file mode 100644 index 0000000..aa05e4a --- /dev/null +++ b/langchain4j-redis-spring-boot-starter/src/test/java/dev/langchain4j/store/embedding/redis/spring/RedisEmbeddingStoreAutoConfigurationIT.java @@ -0,0 +1,61 @@ +package dev.langchain4j.store.embedding.redis.spring; + +import com.redis.testcontainers.RedisContainer; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.store.embedding.EmbeddingStore; +import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore; +import dev.langchain4j.store.embedding.spring.EmbeddingStoreAutoConfigurationIT; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.testcontainers.containers.wait.strategy.Wait; +import redis.clients.jedis.JedisPooled; + +import static com.redis.testcontainers.RedisStackContainer.DEFAULT_IMAGE_NAME; +import static com.redis.testcontainers.RedisStackContainer.DEFAULT_TAG; + +class RedisEmbeddingStoreAutoConfigurationIT extends EmbeddingStoreAutoConfigurationIT { + + static RedisContainer redis = new RedisContainer(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)) + .waitingFor(Wait.defaultWaitStrategy()); + + @BeforeAll + static void beforeAll() { + redis.start(); + } + + @AfterAll + static void afterAll() { + redis.stop(); + } + + @BeforeEach + void beforeEach() { + try (JedisPooled jedis = new JedisPooled(redis.getHost(), redis.getFirstMappedPort())) { + jedis.flushDB(); // TODO fix: why redis returns embeddings from different indexes? + } + } + + @Override + protected Class autoConfigurationClass() { + return RedisEmbeddingStoreAutoConfiguration.class; + } + + @Override + protected Class> embeddingStoreClass() { + return RedisEmbeddingStore.class; + } + + @Override + protected String[] properties() { + return new String[]{ + "langchain4j.redis.host=" + redis.getHost(), + "langchain4j.redis.port=" + redis.getFirstMappedPort() + }; + } + + @Override + protected String dimensionPropertyKey() { + return "langchain4j.redis.dimension"; + } +} diff --git a/pom.xml b/pom.xml index 3fac0e7..b5df5ed 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ langchain4j-azure-open-ai-spring-boot-starter langchain4j-vertex-ai-gemini-spring-boot-starter langchian4j-elasticsearch-spring-boot-starter + langchain4j-redis-spring-boot-starter langchain4j-qianfan-spring-boot-starter @@ -31,6 +32,7 @@ 17 17 UTF-8 + 2.6.2 3.2.6 1.19.8 2.6.2