Skip to content

Commit 1d8243f

Browse files
committed
fix: GH-3690, enhance RedisVectorStore with RedisVectorStoreBuilderCustomizer, add generic VectorStore.Builder customizer interface and Redis-specific customizer interface
Signed-off-by: lanpf <[email protected]>
1 parent aa590e8 commit 1d8243f

File tree

6 files changed

+151
-5
lines changed

6 files changed

+151
-5
lines changed

auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.ai.vectorstore.SpringAIVectorStoreTypes;
2929
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
3030
import org.springframework.ai.vectorstore.redis.RedisVectorStore;
31+
import org.springframework.ai.vectorstore.redis.RedisVectorStoreBuilderCustomizer;
3132
import org.springframework.beans.factory.ObjectProvider;
3233
import org.springframework.boot.autoconfigure.AutoConfiguration;
3334
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -46,6 +47,7 @@
4647
* @author Eddú Meléndez
4748
* @author Soby Chacko
4849
* @author Jihoon Kim
50+
* @author Pengfei Lan
4951
*/
5052
@AutoConfiguration(after = RedisAutoConfiguration.class)
5153
@ConditionalOnClass({ JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class })
@@ -63,22 +65,29 @@ BatchingStrategy batchingStrategy() {
6365

6466
@Bean
6567
@ConditionalOnMissingBean
66-
public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties,
68+
public RedisVectorStore.Builder vectorStoreBuilder(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties,
6769
JedisConnectionFactory jedisConnectionFactory, ObjectProvider<ObservationRegistry> observationRegistry,
6870
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
69-
BatchingStrategy batchingStrategy) {
71+
BatchingStrategy batchingStrategy, ObjectProvider<RedisVectorStoreBuilderCustomizer> vectorStoreBuilderCustomizers) {
7072

7173
JedisPooled jedisPooled = this.jedisPooled(jedisConnectionFactory);
72-
return RedisVectorStore.builder(jedisPooled, embeddingModel)
74+
RedisVectorStore.Builder builder = RedisVectorStore.builder(jedisPooled, embeddingModel)
7375
.initializeSchema(properties.isInitializeSchema())
7476
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
7577
.customObservationConvention(customObservationConvention.getIfAvailable(() -> null))
7678
.batchingStrategy(batchingStrategy)
7779
.indexName(properties.getIndexName())
78-
.prefix(properties.getPrefix())
79-
.build();
80+
.prefix(properties.getPrefix());
81+
vectorStoreBuilderCustomizers.orderedStream().forEach(customizer -> customizer.customize(builder));
82+
return builder;
8083
}
8184

85+
@Bean
86+
@ConditionalOnMissingBean
87+
public RedisVectorStore vectorStore(RedisVectorStore.Builder vectorStoreBuilder) {
88+
return vectorStoreBuilder.build();
89+
}
90+
8291
private JedisPooled jedisPooled(JedisConnectionFactory jedisConnectionFactory) {
8392

8493
String host = jedisConnectionFactory.getHostName();

auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/test/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfigurationIT.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.ai.vectorstore.VectorStore;
3636
import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
3737
import org.springframework.ai.vectorstore.redis.RedisVectorStore;
38+
import org.springframework.ai.vectorstore.redis.RedisVectorStoreBuilderCustomizer;
3839
import org.springframework.boot.autoconfigure.AutoConfigurations;
3940
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
4041
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -49,6 +50,7 @@
4950
* @author Soby Chacko
5051
* @author Christian Tzolov
5152
* @author Thomas Vitale
53+
* @author Dongha Koo
5254
*/
5355
@Testcontainers
5456
class RedisVectorStoreAutoConfigurationIT {
@@ -134,6 +136,18 @@ public void autoConfigurationEnabledWhenTypeIsRedis() {
134136
});
135137
}
136138

139+
@Test
140+
void customizerShouldApplyMetadataField() {
141+
this.contextRunner
142+
.withBean(RedisVectorStoreBuilderCustomizer.class,
143+
() -> builder -> builder.metadataFields(RedisVectorStore.MetadataField.tag("customField")))
144+
.run(context -> {
145+
RedisVectorStore vectorStore = context.getBean(RedisVectorStore.class);
146+
List<RedisVectorStore.MetadataField> metadataFields = vectorStore.getMetadataFields();
147+
assertThat(metadataFields).extracting(RedisVectorStore.MetadataField::name).contains("customField");
148+
});
149+
}
150+
137151
@Configuration(proxyBeanMethods = false)
138152
static class Config {
139153

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.vectorstore;
18+
19+
/**
20+
* Callback interface for customizing {@link VectorStore.Builder} instances.
21+
* <p>
22+
* Implemented by Spring beans that wish to configure {@code VectorStore} builders
23+
* before they are constructed. Customizers are applied in declaration order
24+
* (or priority order if {@link org.springframework.core.Ordered} is implemented),
25+
* allowing incremental builder configuration while preserving auto-configuration.
26+
*
27+
* <h3>Typical Use Cases</h3>
28+
* <ul>
29+
* <li>Adding custom metadata fields to vector stores</li>
30+
* <li>Configuring observation behaviors</li>
31+
* </ul>
32+
*
33+
* @param <T> the specific type of {@link VectorStore.Builder} being customized
34+
* @author Pengfei Lan
35+
* @see VectorStore
36+
* @see VectorStore.Builder
37+
* @see org.springframework.core.Ordered
38+
*/
39+
@FunctionalInterface
40+
public interface VectorStoreBuilderCustomizer<T extends VectorStore.Builder<T>> {
41+
42+
/**
43+
* Customizes the given {@link VectorStore.Builder} instance.
44+
* @param builder the builder to configure (never {@code null})
45+
*/
46+
void customize(T builder);
47+
}

vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStore.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,21 @@
169169
* <li>NUMERIC: For range queries on numerical data</li>
170170
* </ul>
171171
*
172+
* <p>
173+
* Introspection:
174+
* </p>
175+
* <ul>
176+
* <li>{@link #getMetadataFields()} can be used to inspect which metadata fields are
177+
* registered in the store.</li>
178+
* </ul>
179+
*
172180
* @author Julien Ruaux
173181
* @author Christian Tzolov
174182
* @author Eddú Meléndez
175183
* @author Thomas Vitale
176184
* @author Soby Chacko
177185
* @author Jihoon Kim
186+
* @author Dongha Koo
178187
* @see VectorStore
179188
* @see EmbeddingModel
180189
* @since 1.0.0
@@ -471,6 +480,14 @@ public <T> Optional<T> getNativeClient() {
471480
return Optional.of(client);
472481
}
473482

483+
/**
484+
* Returns the metadata fields used by this RedisVectorStore.
485+
* @return list of configured metadata fields
486+
*/
487+
public List<MetadataField> getMetadataFields() {
488+
return this.metadataFields;
489+
}
490+
474491
public static Builder builder(JedisPooled jedis, EmbeddingModel embeddingModel) {
475492
return new Builder(jedis, embeddingModel);
476493
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.vectorstore.redis;
18+
19+
import org.springframework.ai.vectorstore.VectorStore;
20+
import org.springframework.ai.vectorstore.VectorStoreBuilderCustomizer;
21+
22+
/**
23+
* Customizer interface for {@link RedisVectorStore.Builder} configuration.
24+
* <p>
25+
* Allows customization of Redis-based {@link VectorStore} configuration while preserving
26+
* default auto-configuration. Implementations can add additional settings or modify
27+
* existing ones without overriding the entire configuration.
28+
*
29+
* <h3>Usage Example</h3>
30+
* The following example shows how to add custom metadata fields:
31+
* <pre>{@code
32+
* @Bean
33+
* public RedisVectorStoreBuilderCustomizer metadataCustomizer() {
34+
* return builder -> builder.metadataFields(
35+
* List.of(RedisVectorStore.MetadataField.tag("conversationId"))
36+
* );
37+
* }
38+
* }</pre>
39+
*
40+
* @author Pengfei Lan
41+
* @see RedisVectorStore
42+
* @see VectorStore
43+
*/
44+
public interface RedisVectorStoreBuilderCustomizer extends VectorStoreBuilderCustomizer<RedisVectorStore.Builder> {
45+
46+
}

vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreIT.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
* @author Eddú Meléndez
6060
* @author Thomas Vitale
6161
* @author Soby Chacko
62+
* @author Dongha Koo
6263
*/
6364
@Testcontainers
6465
class RedisVectorStoreIT extends BaseVectorStoreTests {
@@ -316,6 +317,18 @@ void getNativeClientTest() {
316317
});
317318
}
318319

320+
@Test
321+
void customizerShouldBeAppliedToBuilder() {
322+
this.contextRunner
323+
.withBean(RedisVectorStoreBuilderCustomizer.class,
324+
() -> builder -> builder.metadataFields(MetadataField.tag("customField")))
325+
.run(context -> {
326+
RedisVectorStore vectorStore = context.getBean(RedisVectorStore.class);
327+
List<MetadataField> metadataFields = vectorStore.getMetadataFields();
328+
assertThat(metadataFields).extracting(MetadataField::name).contains("customField");
329+
});
330+
}
331+
319332
@SpringBootConfiguration
320333
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
321334
public static class TestApplication {

0 commit comments

Comments
 (0)