Skip to content

Commit f69d879

Browse files
sobychackomarkpollack
authored andcommitted
Add builder pattern to RedisVectorStore and refactor package name
Refactors RedisVectorStore to use the builder pattern for improved configuration and usability. The changes include: * Move classes to org.springframework.ai.vectorstore.redis package * Add RedisBuilder with comprehensive configuration options * Deprecate RedisVectorStoreConfig in favor of builder pattern * Enhance documentation with detailed usage examples * Improve error handling and parameter validation This change makes RedisVectorStore configuration more intuitive and consistent with other vector stores in the project.
1 parent cde8f74 commit f69d879

File tree

7 files changed

+455
-220
lines changed

7 files changed

+455
-220
lines changed

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/redis.adoc

Lines changed: 87 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -45,41 +45,15 @@ TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Man
4545

4646
TIP: Refer to the xref:getting-started.adoc#repositories[Repositories] section to add Milestone and/or Snapshot Repositories to your build file.
4747

48-
4948
The vector store implementation can initialize the requisite schema for you, but you must opt-in by specifying the `initializeSchema` boolean in the appropriate constructor or by setting `...initialize-schema=true` in the `application.properties` file.
5049

5150
NOTE: this is a breaking change! In earlier versions of Spring AI, this schema initialization happened by default.
5251

52+
Please have a look at the list of <<redisvector-properties,configuration parameters>> for the vector store to learn about the default values and configuration options.
5353

5454
Additionally, you will need a configured `EmbeddingModel` bean. Refer to the xref:api/embeddings.adoc#available-implementations[EmbeddingModel] section for more information.
5555

56-
Here is an example of the needed bean:
57-
58-
[source,java]
59-
----
60-
@Bean
61-
public EmbeddingModel embeddingModel() {
62-
// Can be any other EmbeddingModel implementation.
63-
return new OpenAiEmbeddingModel(new OpenAiApi(System.getenv("SPRING_AI_OPENAI_API_KEY")));
64-
}
65-
----
66-
67-
To connect to Redis you need to provide access details for your instance.
68-
A simple configuration can either be provided via Spring Boot's _application.properties_,
69-
70-
[source,properties]
71-
----
72-
spring.ai.vectorstore.redis.uri=<your redis instance uri>
73-
spring.ai.vectorstore.redis.index=<your index name>
74-
spring.ai.vectorstore.redis.prefix=<your prefix>
75-
76-
# API key if needed, e.g. OpenAI
77-
spring.ai.openai.api.key=<api-key>
78-
----
79-
80-
Please have a look at the list of xref:#_configuration_properties[configuration parameters] for the vector store to learn about the default values and configuration options.
81-
82-
Now you can Auto-wire the Redis Vector Store in your application and use it
56+
Now you can auto-wire the `RedisVectorStore` as a vector store in your application.
8357

8458
[source,java]
8559
----
@@ -99,146 +73,144 @@ vectorStore.add(documents);
9973
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(5));
10074
----
10175

102-
=== Configuration properties
76+
[[redisvector-properties]]
77+
=== Configuration Properties
10378

104-
You can use the following properties in your Spring Boot configuration to customize the Redis vector store.
79+
To connect to Redis and use the `RedisVectorStore`, you need to provide access details for your instance.
80+
A simple configuration can be provided via Spring Boot's `application.yml`,
10581

106-
[stripes=even]
107-
|===
108-
|Property| Description | Default value
82+
[source,yaml]
83+
----
84+
spring:
85+
data:
86+
redis:
87+
uri: <redis instance uri>
88+
ai:
89+
vectorstore:
90+
redis:
91+
initialize-schema: true
92+
index-name: custom-index
93+
prefix: custom-prefix
94+
batching-strategy: TOKEN_COUNT # Optional: Controls how documents are batched for embedding
95+
----
10996

110-
|`spring.ai.vectorstore.redis.uri`| Server connection URI | `redis://localhost:6379`
111-
|`spring.ai.vectorstore.redis.index`| Index name | `default-index`
112-
|`spring.ai.vectorstore.redis.initialize-schema`| Whether to initialize the required schema | `false`
113-
|`spring.ai.vectorstore.redis.prefix`| Prefix | `default:`
97+
Properties starting with `spring.ai.vectorstore.redis.*` are used to configure the `RedisVectorStore`:
11498

99+
[cols="2,5,1",stripes=even]
115100
|===
101+
|Property | Description | Default Value
116102

117-
== Metadata filtering
103+
|`spring.ai.vectorstore.redis.initialize-schema`| Whether to initialize the required schema | `false`
104+
|`spring.ai.vectorstore.redis.index-name` | The name of the index to store the vectors | `spring-ai-index`
105+
|`spring.ai.vectorstore.redis.prefix` | The prefix for Redis keys | `embedding:`
106+
|`spring.ai.vectorstore.redis.batching-strategy` | Strategy for batching documents when calculating embeddings. Options are `TOKEN_COUNT` or `FIXED_SIZE` | `TOKEN_COUNT`
107+
|===
108+
109+
== Metadata Filtering
118110

119-
You can leverage the generic, portable link:https://docs.spring.io/spring-ai/reference/api/vectordbs.html#_metadata_filters[metadata filters] with RedisVectorStore as well.
111+
You can leverage the generic, portable xref:api/vectordbs.adoc#metadata-filters[metadata filters] with Redis as well.
120112

121113
For example, you can use either the text expression language:
122114

123115
[source,java]
124116
----
125-
vectorStore.similaritySearch(
126-
SearchRequest
127-
.query("The World")
128-
.withTopK(TOP_K)
129-
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
130-
.withFilterExpression("country in ['UK', 'NL'] && year >= 2020"));
117+
vectorStore.similaritySearch(SearchRequest.defaults()
118+
.withQuery("The World")
119+
.withTopK(TOP_K)
120+
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
121+
.withFilterExpression("country in ['UK', 'NL'] && year >= 2020"));
131122
----
132123

133-
or programmatically using the expression DSL:
124+
or programmatically using the `Filter.Expression` DSL:
134125

135126
[source,java]
136127
----
137128
FilterExpressionBuilder b = new FilterExpressionBuilder();
138129
139-
vectorStore.similaritySearch(
140-
SearchRequest
141-
.query("The World")
142-
.withTopK(TOP_K)
143-
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
144-
.withFilterExpression(b.and(
145-
b.in("country", "UK", "NL"),
146-
b.gte("year", 2020)).build()));
130+
vectorStore.similaritySearch(SearchRequest.defaults()
131+
.withQuery("The World")
132+
.withTopK(TOP_K)
133+
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
134+
.withFilterExpression(b.and(
135+
b.in("country", "UK", "NL"),
136+
b.gte("year", 2020)).build()));
147137
----
148138

149-
The portable filter expressions get automatically converted into link:https://redis.io/docs/interact/search-and-query/query/[Redis search queries].
150-
For example, the following portable filter expression:
139+
NOTE: Those (portable) filter expressions get automatically converted into link:https://redis.io/docs/interact/search-and-query/query/[Redis search queries].
140+
141+
For example, this portable filter expression:
151142

152143
[source,sql]
153144
----
154145
country in ['UK', 'NL'] && year >= 2020
155146
----
156147

157-
is converted into Redis query:
148+
is converted into the proprietary Redis filter format:
158149

159-
[source]
150+
[source,text]
160151
----
161152
@country:{UK | NL} @year:[2020 inf]
162153
----
163154

164-
== Manual configuration
155+
== Manual Configuration
165156

166-
If you prefer not to use the auto-configuration, you can manually configure the Redis Vector Store.
167-
Add the Redis Vector Store and Jedis dependencies
157+
Instead of using the Spring Boot auto-configuration, you can manually configure the Redis vector store. For this you need to add the `spring-ai-redis-store` to your project:
168158

169159
[source,xml]
170160
----
171161
<dependency>
172-
<groupId>org.springframework.ai</groupId>
173-
<artifactId>spring-ai-redis-store</artifactId>
174-
</dependency>
175-
176-
<dependency>
177-
<groupId>redis.clients</groupId>
178-
<artifactId>jedis</artifactId>
179-
<version>5.1.0</version>
162+
<groupId>org.springframework.ai</groupId>
163+
<artifactId>spring-ai-redis-store</artifactId>
180164
</dependency>
181165
----
182166

183-
TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file.
184-
185-
Then, create a `RedisVectorStore` bean in your Spring configuration:
167+
or to your Gradle `build.gradle` build file.
186168

187-
[source,java]
169+
[source,groovy]
188170
----
189-
@Bean
190-
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
191-
RedisVectorStoreConfig config = RedisVectorStoreConfig.builder()
192-
.withURI("redis://localhost:6379")
193-
// Define the metadata fields to be used
194-
// in the similarity search filters.
195-
.withMetadataFields(
196-
MetadataField.tag("country"),
197-
MetadataField.numeric("year"))
198-
.build();
199-
200-
return new RedisVectorStore(config, embeddingModel);
171+
dependencies {
172+
implementation 'org.springframework.ai:spring-ai-redis-store'
201173
}
202174
----
203175

204-
[NOTE]
205-
====
206-
It is more convenient and preferred to create the `RedisVectorStore` as a Bean.
207-
But if you decide to create it manually, then you must call the `RedisVectorStore#afterPropertiesSet()` after setting the properties and before using the client.
208-
====
209-
210-
[NOTE]
211-
====
212-
You must list explicitly all metadata field names and types (`TAG`, `TEXT`, or `NUMERIC`) for any metadata field used in filter expression.
213-
The `withMetadataFields` above registers filterable metadata fields: `country` of type `TAG`, `year` of type `NUMERIC`.
214-
====
215-
216-
Then in your main code, create some documents:
176+
Create a `JedisPooled` bean:
217177

218178
[source,java]
219179
----
220-
List<Document> documents = List.of(
221-
new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("country", "UK", "year", 2020)),
222-
new Document("The World is Big and Salvation Lurks Around the Corner", Map.of()),
223-
new Document("You walk forward facing the past and you turn back toward the future.", Map.of("country", "NL", "year", 2023)));
180+
@Bean
181+
public JedisPooled jedisPooled() {
182+
return new JedisPooled("<host>", 6379);
183+
}
224184
----
225185

226-
Now add the documents to your vector store:
227-
186+
Then create the `RedisVectorStore` bean using the builder pattern:
228187

229188
[source,java]
230189
----
231-
vectorStore.add(documents);
232-
----
233-
234-
And finally, retrieve documents similar to a query:
190+
@Bean
191+
public VectorStore vectorStore(JedisPooled jedisPooled, EmbeddingModel embeddingModel) {
192+
return RedisVectorStore.builder()
193+
.jedis(jedisPooled)
194+
.embeddingModel(embeddingModel)
195+
.indexName("custom-index") // Optional: defaults to "spring-ai-index"
196+
.prefix("custom-prefix") // Optional: defaults to "embedding:"
197+
.metadataFields( // Optional: define metadata fields for filtering
198+
MetadataField.tag("country"),
199+
MetadataField.numeric("year"))
200+
.initializeSchema(true) // Optional: defaults to false
201+
.batchingStrategy(new TokenCountBatchingStrategy()) // Optional: defaults to TokenCountBatchingStrategy
202+
.build();
203+
}
235204
236-
[source,java]
237-
----
238-
List<Document> results = vectorStore.similaritySearch(
239-
SearchRequest
240-
.query("Spring")
241-
.withTopK(5));
205+
// This can be any EmbeddingModel implementation
206+
@Bean
207+
public EmbeddingModel embeddingModel() {
208+
return new OpenAiEmbeddingModel(new OpenAiApi(System.getenv("OPENAI_API_KEY")));
209+
}
242210
----
243211

244-
If all goes well, you should retrieve the document containing the text "Spring AI rocks!!".
212+
[NOTE]
213+
====
214+
You must list explicitly all metadata field names and types (`TAG`, `TEXT`, or `NUMERIC`) for any metadata field used in filter expressions.
215+
The `metadataFields` above registers filterable metadata fields: `country` of type `TAG`, `year` of type `NUMERIC`.
216+
====

spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
import org.springframework.ai.embedding.BatchingStrategy;
2323
import org.springframework.ai.embedding.EmbeddingModel;
2424
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
25-
import org.springframework.ai.vectorstore.RedisVectorStore;
26-
import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
25+
import org.springframework.ai.vectorstore.redis.RedisVectorStore;
2726
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
2827
import org.springframework.beans.factory.ObjectProvider;
2928
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -61,15 +60,16 @@ public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorSt
6160
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
6261
BatchingStrategy batchingStrategy) {
6362

64-
var config = RedisVectorStoreConfig.builder()
65-
.withIndexName(properties.getIndex())
66-
.withPrefix(properties.getPrefix())
63+
return RedisVectorStore.builder()
64+
.jedis(new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()))
65+
.embeddingModel(embeddingModel)
66+
.initializeSchema(properties.isInitializeSchema())
67+
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
68+
.customObservationConvention(customObservationConvention.getIfAvailable(() -> null))
69+
.batchingStrategy(batchingStrategy)
70+
.indexName(properties.getIndex())
71+
.prefix(properties.getPrefix())
6772
.build();
68-
69-
return new RedisVectorStore(config, embeddingModel,
70-
new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()),
71-
properties.isInitializeSchema(), observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
72-
customObservationConvention.getIfAvailable(() -> null), batchingStrategy);
7373
}
7474

7575
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.ai.vectorstore;
17+
package org.springframework.ai.vectorstore.redis;
1818

1919
import java.text.MessageFormat;
2020
import java.util.List;
2121
import java.util.Map;
2222
import java.util.function.Function;
2323
import java.util.stream.Collectors;
2424

25-
import org.springframework.ai.vectorstore.RedisVectorStore.MetadataField;
25+
import org.springframework.ai.vectorstore.redis.RedisVectorStore.MetadataField;
2626
import org.springframework.ai.vectorstore.filter.Filter.Expression;
2727
import org.springframework.ai.vectorstore.filter.Filter.ExpressionType;
2828
import org.springframework.ai.vectorstore.filter.Filter.Group;

0 commit comments

Comments
 (0)