From 1d8243fd04bf581057c81cfb9db002611f892923 Mon Sep 17 00:00:00 2001 From: lanpf Date: Mon, 14 Jul 2025 05:09:11 +0800 Subject: [PATCH 1/2] fix: GH-3690, enhance RedisVectorStore with RedisVectorStoreBuilderCustomizer, add generic VectorStore.Builder customizer interface and Redis-specific customizer interface Signed-off-by: lanpf --- .../RedisVectorStoreAutoConfiguration.java | 19 ++++++-- .../RedisVectorStoreAutoConfigurationIT.java | 14 ++++++ .../VectorStoreBuilderCustomizer.java | 47 +++++++++++++++++++ .../vectorstore/redis/RedisVectorStore.java | 17 +++++++ .../RedisVectorStoreBuilderCustomizer.java | 46 ++++++++++++++++++ .../vectorstore/redis/RedisVectorStoreIT.java | 13 +++++ 6 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStoreBuilderCustomizer.java create mode 100644 vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreBuilderCustomizer.java diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java index f332752faa1..18816ad891d 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.ai.vectorstore.SpringAIVectorStoreTypes; import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; import org.springframework.ai.vectorstore.redis.RedisVectorStore; +import org.springframework.ai.vectorstore.redis.RedisVectorStoreBuilderCustomizer; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -46,6 +47,7 @@ * @author Eddú Meléndez * @author Soby Chacko * @author Jihoon Kim + * @author Pengfei Lan */ @AutoConfiguration(after = RedisAutoConfiguration.class) @ConditionalOnClass({ JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class }) @@ -63,22 +65,29 @@ BatchingStrategy batchingStrategy() { @Bean @ConditionalOnMissingBean - public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties, + public RedisVectorStore.Builder vectorStoreBuilder(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties, JedisConnectionFactory jedisConnectionFactory, ObjectProvider observationRegistry, ObjectProvider customObservationConvention, - BatchingStrategy batchingStrategy) { + BatchingStrategy batchingStrategy, ObjectProvider vectorStoreBuilderCustomizers) { 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()); + vectorStoreBuilderCustomizers.orderedStream().forEach(customizer -> customizer.customize(builder)); + return builder; } + @Bean + @ConditionalOnMissingBean + public RedisVectorStore vectorStore(RedisVectorStore.Builder vectorStoreBuilder) { + return vectorStoreBuilder.build(); + } + private JedisPooled jedisPooled(JedisConnectionFactory jedisConnectionFactory) { String host = jedisConnectionFactory.getHostName(); diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/test/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/test/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfigurationIT.java index 40d3bce6e93..04912a30b04 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/test/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/test/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfigurationIT.java @@ -35,6 +35,7 @@ import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.ai.vectorstore.redis.RedisVectorStore; +import org.springframework.ai.vectorstore.redis.RedisVectorStoreBuilderCustomizer; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -49,6 +50,7 @@ * @author Soby Chacko * @author Christian Tzolov * @author Thomas Vitale + * @author Dongha Koo */ @Testcontainers class RedisVectorStoreAutoConfigurationIT { @@ -134,6 +136,18 @@ public void autoConfigurationEnabledWhenTypeIsRedis() { }); } + @Test + void customizerShouldApplyMetadataField() { + this.contextRunner + .withBean(RedisVectorStoreBuilderCustomizer.class, + () -> builder -> builder.metadataFields(RedisVectorStore.MetadataField.tag("customField"))) + .run(context -> { + RedisVectorStore vectorStore = context.getBean(RedisVectorStore.class); + List metadataFields = vectorStore.getMetadataFields(); + assertThat(metadataFields).extracting(RedisVectorStore.MetadataField::name).contains("customField"); + }); + } + @Configuration(proxyBeanMethods = false) static class Config { diff --git a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStoreBuilderCustomizer.java b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStoreBuilderCustomizer.java new file mode 100644 index 00000000000..c75c012a796 --- /dev/null +++ b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStoreBuilderCustomizer.java @@ -0,0 +1,47 @@ +/* + * Copyright 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. + * 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.vectorstore; + +/** + * Callback interface for customizing {@link VectorStore.Builder} instances. + *

+ * Implemented by Spring beans that wish to configure {@code VectorStore} builders + * before they are constructed. Customizers are applied in declaration order + * (or priority order if {@link org.springframework.core.Ordered} is implemented), + * allowing incremental builder configuration while preserving auto-configuration. + * + *

Typical Use Cases

+ *
    + *
  • Adding custom metadata fields to vector stores
  • + *
  • Configuring observation behaviors
  • + *
+ * + * @param the specific type of {@link VectorStore.Builder} being customized + * @author Pengfei Lan + * @see VectorStore + * @see VectorStore.Builder + * @see org.springframework.core.Ordered + */ +@FunctionalInterface +public interface VectorStoreBuilderCustomizer> { + + /** + * Customizes the given {@link VectorStore.Builder} instance. + * @param builder the builder to configure (never {@code null}) + */ + void customize(T builder); +} diff --git a/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStore.java b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStore.java index 67d033fb2cf..1c442652d36 100644 --- a/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStore.java +++ b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStore.java @@ -169,12 +169,21 @@ *
  • NUMERIC: For range queries on numerical data
  • * * + *

    + * Introspection: + *

    + *
      + *
    • {@link #getMetadataFields()} can be used to inspect which metadata fields are + * registered in the store.
    • + *
    + * * @author Julien Ruaux * @author Christian Tzolov * @author Eddú Meléndez * @author Thomas Vitale * @author Soby Chacko * @author Jihoon Kim + * @author Dongha Koo * @see VectorStore * @see EmbeddingModel * @since 1.0.0 @@ -471,6 +480,14 @@ public Optional getNativeClient() { return Optional.of(client); } + /** + * Returns the metadata fields used by this RedisVectorStore. + * @return list of configured metadata fields + */ + public List getMetadataFields() { + return this.metadataFields; + } + public static Builder builder(JedisPooled jedis, EmbeddingModel embeddingModel) { return new Builder(jedis, embeddingModel); } diff --git a/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreBuilderCustomizer.java b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreBuilderCustomizer.java new file mode 100644 index 00000000000..16d2bc5c258 --- /dev/null +++ b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreBuilderCustomizer.java @@ -0,0 +1,46 @@ +/* + * Copyright 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. + * 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.vectorstore.redis; + +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.ai.vectorstore.VectorStoreBuilderCustomizer; + +/** + * Customizer interface for {@link RedisVectorStore.Builder} configuration. + *

    + * Allows customization of Redis-based {@link VectorStore} configuration while preserving + * default auto-configuration. Implementations can add additional settings or modify + * existing ones without overriding the entire configuration. + * + *

    Usage Example

    + * The following example shows how to add custom metadata fields: + *
    {@code
    + * @Bean
    + * public RedisVectorStoreBuilderCustomizer metadataCustomizer() {
    + *     return builder -> builder.metadataFields(
    + *         List.of(RedisVectorStore.MetadataField.tag("conversationId"))
    + *     );
    + * }
    + * }
    + * + * @author Pengfei Lan + * @see RedisVectorStore + * @see VectorStore + */ +public interface RedisVectorStoreBuilderCustomizer extends VectorStoreBuilderCustomizer { + +} diff --git a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreIT.java b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreIT.java index 80b2b304614..d602941826e 100644 --- a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreIT.java +++ b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreIT.java @@ -59,6 +59,7 @@ * @author Eddú Meléndez * @author Thomas Vitale * @author Soby Chacko + * @author Dongha Koo */ @Testcontainers class RedisVectorStoreIT extends BaseVectorStoreTests { @@ -316,6 +317,18 @@ void getNativeClientTest() { }); } + @Test + void customizerShouldBeAppliedToBuilder() { + this.contextRunner + .withBean(RedisVectorStoreBuilderCustomizer.class, + () -> builder -> builder.metadataFields(MetadataField.tag("customField"))) + .run(context -> { + RedisVectorStore vectorStore = context.getBean(RedisVectorStore.class); + List metadataFields = vectorStore.getMetadataFields(); + assertThat(metadataFields).extracting(MetadataField::name).contains("customField"); + }); + } + @SpringBootConfiguration @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class }) public static class TestApplication { From 37085c5e2abd56b562d02490d04770cca3787eb0 Mon Sep 17 00:00:00 2001 From: lanpf Date: Sun, 20 Jul 2025 09:38:33 +0800 Subject: [PATCH 2/2] fix: apply spring-javaformat to comply with code style Signed-off-by: lanpf --- .../RedisVectorStoreAutoConfiguration.java | 20 ++++++++++--------- .../VectorStoreBuilderCustomizer.java | 13 ++++++------ .../RedisVectorStoreBuilderCustomizer.java | 5 ++--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java index 18816ad891d..21b77187a78 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java @@ -65,10 +65,12 @@ BatchingStrategy batchingStrategy() { @Bean @ConditionalOnMissingBean - public RedisVectorStore.Builder vectorStoreBuilder(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties, - JedisConnectionFactory jedisConnectionFactory, ObjectProvider observationRegistry, + public RedisVectorStore.Builder vectorStoreBuilder(EmbeddingModel embeddingModel, + RedisVectorStoreProperties properties, JedisConnectionFactory jedisConnectionFactory, + ObjectProvider observationRegistry, ObjectProvider customObservationConvention, - BatchingStrategy batchingStrategy, ObjectProvider vectorStoreBuilderCustomizers) { + BatchingStrategy batchingStrategy, + ObjectProvider vectorStoreBuilderCustomizers) { JedisPooled jedisPooled = this.jedisPooled(jedisConnectionFactory); RedisVectorStore.Builder builder = RedisVectorStore.builder(jedisPooled, embeddingModel) @@ -79,14 +81,14 @@ public RedisVectorStore.Builder vectorStoreBuilder(EmbeddingModel embeddingModel .indexName(properties.getIndexName()) .prefix(properties.getPrefix()); vectorStoreBuilderCustomizers.orderedStream().forEach(customizer -> customizer.customize(builder)); - return builder; + return builder; } - @Bean - @ConditionalOnMissingBean - public RedisVectorStore vectorStore(RedisVectorStore.Builder vectorStoreBuilder) { - return vectorStoreBuilder.build(); - } + @Bean + @ConditionalOnMissingBean + public RedisVectorStore vectorStore(RedisVectorStore.Builder vectorStoreBuilder) { + return vectorStoreBuilder.build(); + } private JedisPooled jedisPooled(JedisConnectionFactory jedisConnectionFactory) { diff --git a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStoreBuilderCustomizer.java b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStoreBuilderCustomizer.java index c75c012a796..959e8939fd6 100644 --- a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStoreBuilderCustomizer.java +++ b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStoreBuilderCustomizer.java @@ -19,15 +19,15 @@ /** * Callback interface for customizing {@link VectorStore.Builder} instances. *

    - * Implemented by Spring beans that wish to configure {@code VectorStore} builders - * before they are constructed. Customizers are applied in declaration order - * (or priority order if {@link org.springframework.core.Ordered} is implemented), - * allowing incremental builder configuration while preserving auto-configuration. + * Implemented by Spring beans that wish to configure {@code VectorStore} builders before + * they are constructed. Customizers are applied in declaration order (or priority order + * if {@link org.springframework.core.Ordered} is implemented), allowing incremental + * builder configuration while preserving auto-configuration. * *

    Typical Use Cases

    *
      - *
    • Adding custom metadata fields to vector stores
    • - *
    • Configuring observation behaviors
    • + *
    • Adding custom metadata fields to vector stores
    • + *
    • Configuring observation behaviors
    • *
    * * @param the specific type of {@link VectorStore.Builder} being customized @@ -44,4 +44,5 @@ public interface VectorStoreBuilderCustomizer> * @param builder the builder to configure (never {@code null}) */ void customize(T builder); + } diff --git a/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreBuilderCustomizer.java b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreBuilderCustomizer.java index 16d2bc5c258..c1594a6fb6a 100644 --- a/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreBuilderCustomizer.java +++ b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreBuilderCustomizer.java @@ -26,10 +26,9 @@ * default auto-configuration. Implementations can add additional settings or modify * existing ones without overriding the entire configuration. * - *

    Usage Example

    - * The following example shows how to add custom metadata fields: + *

    Usage Example

    The following example shows how to add custom metadata fields: *
    {@code
    - * @Bean
    + * @Bean
      * public RedisVectorStoreBuilderCustomizer metadataCustomizer() {
      *     return builder -> builder.metadataFields(
      *         List.of(RedisVectorStore.MetadataField.tag("conversationId"))