Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@
package org.springframework.ai.vectorstore.redis.autoconfigure;

import io.micrometer.observation.ObservationRegistry;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisPooled;

import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
Expand All @@ -38,6 +33,10 @@
import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisPooled;

/**
* {@link AutoConfiguration Auto-configuration} for Redis Vector Store.
Expand All @@ -46,6 +45,7 @@
* @author Eddú Meléndez
* @author Soby Chacko
* @author Jihoon Kim
* @author Brian Sam-Bodden
*/
@AutoConfiguration(after = DataRedisAutoConfiguration.class)
@ConditionalOnClass({ JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class })
Expand All @@ -69,14 +69,27 @@ public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorSt
BatchingStrategy batchingStrategy) {

JedisPooled jedisPooled = this.jedisPooled(jedisConnectionFactory);
return RedisVectorStore.builder(jedisPooled, embeddingModel)
RedisVectorStore.Builder builder = RedisVectorStore.builder(jedisPooled, embeddingModel)
.initializeSchema(properties.isInitializeSchema())
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
.customObservationConvention(customObservationConvention.getIfAvailable(() -> null))
.batchingStrategy(batchingStrategy)
.indexName(properties.getIndexName())
.prefix(properties.getPrefix())
.build();
.prefix(properties.getPrefix());

// Configure HNSW parameters if available
hnswConfiguration(builder, properties);

return builder.build();
}

/**
* Configures the HNSW-related parameters on the builder
*/
private void hnswConfiguration(RedisVectorStore.Builder builder, RedisVectorStoreProperties properties) {
builder.hnswM(properties.getHnsw().getM())
.hnswEfConstruction(properties.getHnsw().getEfConstruction())
.hnswEfRuntime(properties.getHnsw().getEfRuntime());
}

private JedisPooled jedisPooled(JedisConnectionFactory jedisConnectionFactory) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,28 @@

import org.springframework.ai.vectorstore.properties.CommonVectorStoreProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

/**
* Configuration properties for Redis Vector Store.
*
* <p>
* Example application.properties:
* </p>
* <pre>
* spring.ai.vectorstore.redis.index-name=my-index
* spring.ai.vectorstore.redis.prefix=doc:
* spring.ai.vectorstore.redis.initialize-schema=true
*
* # HNSW algorithm configuration
* spring.ai.vectorstore.redis.hnsw.m=32
* spring.ai.vectorstore.redis.hnsw.ef-construction=100
* spring.ai.vectorstore.redis.hnsw.ef-runtime=50
* </pre>
*
* @author Julien Ruaux
* @author Eddú Meléndez
* @author Brian Sam-Bodden
*/
@ConfigurationProperties(RedisVectorStoreProperties.CONFIG_PREFIX)
public class RedisVectorStoreProperties extends CommonVectorStoreProperties {
Expand All @@ -34,6 +50,12 @@ public class RedisVectorStoreProperties extends CommonVectorStoreProperties {

private String prefix = "default:";

/**
* HNSW algorithm configuration properties.
*/
@NestedConfigurationProperty
private HnswProperties hnsw = new HnswProperties();

public String getIndexName() {
return this.indexName;
}
Expand All @@ -50,4 +72,64 @@ public void setPrefix(String prefix) {
this.prefix = prefix;
}

public HnswProperties getHnsw() {
return this.hnsw;
}

public void setHnsw(HnswProperties hnsw) {
this.hnsw = hnsw;
}

/**
* HNSW (Hierarchical Navigable Small World) algorithm configuration properties.
*/
public static class HnswProperties {

/**
* M parameter for HNSW algorithm. Represents the maximum number of connections
* per node in the graph. Higher values increase recall but also memory usage.
* Typically between 5-100. Default: 16
*/
private Integer m = 16;

/**
* EF_CONSTRUCTION parameter for HNSW algorithm. Size of the dynamic candidate
* list during index building. Higher values lead to better recall but slower
* indexing. Typically between 50-500. Default: 200
*/
private Integer efConstruction = 200;

/**
* EF_RUNTIME parameter for HNSW algorithm. Size of the dynamic candidate list
* during search. Higher values lead to more accurate but slower searches.
* Typically between 20-200. Default: 10
*/
private Integer efRuntime = 10;

public Integer getM() {
return this.m;
}

public void setM(Integer m) {
this.m = m;
}

public Integer getEfConstruction() {
return this.efConstruction;
}

public void setEfConstruction(Integer efConstruction) {
this.efConstruction = efConstruction;
}

public Integer getEfRuntime() {
return this.efRuntime;
}

public void setEfRuntime(Integer efRuntime) {
this.efRuntime = efRuntime;
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 the original author or authors.
* Copyright 2023-2025 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.
Expand Down Expand Up @@ -49,6 +49,7 @@
* @author Soby Chacko
* @author Christian Tzolov
* @author Thomas Vitale
* @author Brian Sam-Bodden
*/
@Testcontainers
class RedisVectorStoreAutoConfigurationIT {
Expand All @@ -57,11 +58,13 @@ class RedisVectorStoreAutoConfigurationIT {
static RedisStackContainer redisContainer = new RedisStackContainer(
RedisStackContainer.DEFAULT_IMAGE_NAME.withTag(RedisStackContainer.DEFAULT_TAG));

// Use host and port explicitly since getRedisURI() might not be consistent
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(DataRedisAutoConfiguration.class, RedisVectorStoreAutoConfiguration.class))
.withUserConfiguration(Config.class)
.withPropertyValues("spring.data.redis.url=" + redisContainer.getRedisURI())
.withPropertyValues("spring.data.redis.host=" + redisContainer.getHost(),
"spring.data.redis.port=" + redisContainer.getFirstMappedPort())
.withPropertyValues("spring.ai.vectorstore.redis.initialize-schema=true")
.withPropertyValues("spring.ai.vectorstore.redis.index=myIdx")
.withPropertyValues("spring.ai.vectorstore.redis.prefix=doc:")
Expand Down Expand Up @@ -151,4 +154,4 @@ public EmbeddingModel embeddingModel() {

}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
/**
* @author Julien Ruaux
* @author Eddú Meléndez
* @author Brian Sam-Bodden
*/
class RedisVectorStorePropertiesTests {

Expand All @@ -31,6 +32,11 @@ void defaultValues() {
var props = new RedisVectorStoreProperties();
assertThat(props.getIndexName()).isEqualTo("default-index");
assertThat(props.getPrefix()).isEqualTo("default:");

// Verify default HNSW parameters
assertThat(props.getHnsw().getM()).isEqualTo(16);
assertThat(props.getHnsw().getEfConstruction()).isEqualTo(200);
assertThat(props.getHnsw().getEfRuntime()).isEqualTo(10);
}

@Test
Expand All @@ -43,4 +49,18 @@ void customValues() {
assertThat(props.getPrefix()).isEqualTo("doc:");
}

@Test
void customHnswValues() {
var props = new RedisVectorStoreProperties();
RedisVectorStoreProperties.HnswProperties hnsw = props.getHnsw();

hnsw.setM(32);
hnsw.setEfConstruction(100);
hnsw.setEfRuntime(50);

assertThat(props.getHnsw().getM()).isEqualTo(32);
assertThat(props.getHnsw().getEfConstruction()).isEqualTo(100);
assertThat(props.getHnsw().getEfRuntime()).isEqualTo(50);
}

}
Loading
Loading