Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Redis embedding store spring boot starter #2

Merged
merged 14 commits into from
Jul 2, 2024
2 changes: 1 addition & 1 deletion .sdkmanrc
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion langchain4j-qianfan-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring</artifactId>
<version>0.30.0</version>
<version>0.32.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
91 changes: 91 additions & 0 deletions langchain4j-redis-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring</artifactId>
<version>0.32.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>langchain4j-redis-spring-boot-starter</artifactId>
<name>LangChain4j Spring Boot starter for Redis</name>
<packaging>jar</packaging>

<dependencies>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-redis</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>

<!-- should be listed before spring-boot-configuration-processor -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<!-- needed to generate automatic metadata about available config properties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-embeddings-all-minilm-l6-v2-q</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-tests</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<type>test-jar</type>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.redis.testcontainers</groupId>
<artifactId>testcontainers-redis</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.tinylog</groupId>
<artifactId>tinylog-impl</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.tinylog</groupId>
<artifactId>slf4j-tinylog</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -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<String> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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<String> metadataKeys;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=dev.langchain4j.store.embedding.redis.spring.RedisEmbeddingStoreAutoConfiguration
Original file line number Diff line number Diff line change
@@ -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<? extends EmbeddingStore<TextSegment>> 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";
}
}
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@
<module>langchain4j-azure-open-ai-spring-boot-starter</module>
<module>langchain4j-vertex-ai-gemini-spring-boot-starter</module>
<module>langchian4j-elasticsearch-spring-boot-starter</module>
<module>langchain4j-redis-spring-boot-starter</module>
<module>langchain4j-qianfan-spring-boot-starter</module>
</modules>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<tinylog.version>2.6.2</tinylog.version>
<spring.boot.version>3.2.6</spring.boot.version>
<testcontainers.version>1.19.8</testcontainers.version>
<tinylog.version>2.6.2</tinylog.version>
Expand Down
Loading