Skip to content
Closed
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
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<module>spring-ai-spring-boot-starters/spring-ai-starter-pinecone-store</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-azure-store</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-weaviate-store</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-redis</module>
<module>spring-ai-docs</module>
<module>vector-stores/spring-ai-pgvector-store</module>
<module>vector-stores/spring-ai-milvus-store</module>
Expand All @@ -42,6 +43,7 @@
<module>vector-stores/spring-ai-chroma</module>
<module>vector-stores/spring-ai-azure</module>
<module>vector-stores/spring-ai-weaviate</module>
<module>vector-stores/spring-ai-redis</module>
<module>spring-ai-vertex-ai</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-vertex-ai</module>

Expand Down
1 change: 1 addition & 0 deletions spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*** xref:api/vectordbs/neo4j.adoc[]
*** xref:api/vectordbs/pgvector.adoc[]
*** xref:api/vectordbs/weaviate.adoc[]
*** xref:api/vectordbs/redis.adoc[]
** xref:api/testing.adoc[]
* Appendices
** xref:glossary.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ public interface DocumentWriter extends Consumer<List<Document>> {
*Neo4jVectorStore*::
+ Leverages the Neo4j graph database for vector storage.

*RedisVectorStore*::
+ Provides vector storage capabilities using Redis.


== Using PDF Reader


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ These are the available implementations of the `VectorStore` interface:
* Neo4j [`Neo4jVectorStore`]: The https://neo4j.com/[Neo4j] vector store
* Weaviate [`WeaviateVectorStore`] The https://weaviate.io/[Weaviate] vector store
* Azure Vector Search [`AzureVectorStore`] the https://learn.microsoft.com/en-us/azure/search/vector-search-overview[Azure] vector store
* Redisj [`RedisVectorStore`]: The https://redis.io/[Redis] vector store

More implementations may be supported in future releases.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
= Redis

This section walks you through setting up `RedisVectorStore` to store document embeddings and perform similarity searches.

== What is Redis?

link:https://redis.io[Redis] is an open source (BSD licensed), in-memory data structure store used as a database, cache, message broker, and streaming engine. Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams.

== What is Redis Vector Search?

link:https://redis.io/docs/interact/search-and-query/[Redis Search and Query] extends the core features of Redis OSS and allows you to use Redis as a vector database:

* Store vectors and the associated metadata within hashes or JSON documents
* Retrieve vectors
* Perform vector searches

== Prerequisites

1. `EmbeddingClient` instance to compute the document embeddings. Several options are available:

- `Transformers Embedding` - computes the embedding in your local environment. Follow the ONNX Transformers Embedding instructions.
- `OpenAI Embedding` - uses the OpenAI embedding endpoint. You need to create an account at link:https://platform.openai.com/signup[OpenAI Signup] and generate the api-key token at link:https://platform.openai.com/account/api-keys[API Keys].
- You can also use the `Azure OpenAI Embedding`.

2. A Redis Stack instance
a. https://app.redislabs.com/#/[Redis Cloud] (recommended)
b. link:https://hub.docker.com/r/redis/redis-stack[Docker] image _redis/redis-stack:latest_


== Dependencies

Add these dependencies to your project:

* Embedding Client boot starter, required for calculating embeddings.

* Transformers Embedding (Local) and follow the ONNX Transformers Embedding instructions.

[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-transformers-embedding-spring-boot-starter</artifactId>
<version>0.8.0-SNAPSHOT</version>
</dependency>
----

or use OpenAI (Cloud)

[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>0.8.0-SNAPSHOT</version>
</dependency>
----

You'll need to provide your OpenAI API Key. Set it as an environment variable like so:

[source,bash]
----
export SPRING_AI_OPENAI_API_KEY='Your_OpenAI_API_Key'
----

* Add the Redis Vector Store and Jedis dependencies

[source,xml]
----
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store</artifactId>
<version>0.8.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.1.0</version>
</dependency>
----

== Usage

Create a RedisVectorStore instance connected to your Redis database:

[source,java]
----
@Bean
public VectorStore vectorStore(EmbeddingClient embeddingClient) {
RedisVectorStoreConfig config = RedisVectorStoreConfig.builder()
.withURI("redis://localhost:6379")
// Define the metadata fields to be used
// in the similarity search filters.
.withMetadataFields(
MetadataField.tag("country"),
MetadataField.numeric("year"))
.build();

return new RedisVectorStore(config, embeddingClient);
}
----

> [NOTE]
> You must list explicitly all metadata field names and types (`TAG`, `TEXT`, or `NUMERIC`) for any metadata field used in filter expression.
> The `withMetadataFields` above registers filterable metadata fields: `country` of type `TAG`, `year` of type `NUMERIC`.
>

Then in your main code, create some documents:

[source,java]
----
List<Document> documents = List.of(
new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("country", "UK", "year", 2020)),
new Document("The World is Big and Salvation Lurks Around the Corner", Map.of()),
new Document("You walk forward facing the past and you turn back toward the future.", Map.of("country", "NL", "year", 2023)));
----

Now add the documents to your vector store:


[source,java]
----
vectorStore.add(List.of(document));
----

And finally, retrieve documents similar to a query:

[source,java]
----
List<Document> results = vectorStore.similaritySearch(
SearchRequest
.query("Spring")
.withTopK(5));
----

If all goes well, you should retrieve the document containing the text "Spring AI rocks!!".

=== Metadata filtering

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.

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

[source,java]
----
vectorStore.similaritySearch(
SearchRequest
.query("The World")
.withTopK(TOP_K)
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
.withFilterExpression("country in ['UK', 'NL'] && year >= 2020"));
----

or programmatically using the expression DSL:

[source,java]
----
FilterExpressionBuilder b = Filter.builder();

vectorStore.similaritySearch(
SearchRequest
.query("The World")
.withTopK(TOP_K)
.withSimilarityThreshold(SIMILARITY_THRESHOLD)
.withFilterExpression(b.and(
b.in("country", "UK", "NL"),
b.gte("year", 2020)).build()));
----

The portable filter expressions get automatically converted into link:https://redis.io/docs/interact/search-and-query/query/[Redis search queries].
For example, the following portable filter expression:

[source,sql]
----
country in ['UK', 'NL'] && year >= 2020
----

is converted into Redis query:

[source]
----
@country:{UK | NL} @year:[2020 inf]
----
22 changes: 22 additions & 0 deletions spring-ai-spring-boot-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@
<version>${project.parent.version}</version>
<optional>true</optional>
</dependency>

<!-- Redis Vector Store-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis</artifactId>
<version>${project.parent.version}</version>
<optional>true</optional>
</dependency>

<!-- Override Jedis version -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.1.0</version>
</dependency>

<!-- Vertex AI LLM -->
<dependency>
Expand Down Expand Up @@ -186,6 +201,13 @@
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>

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

<dependency>
<groupId>org.awaitility</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2023-2023 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.autoconfigure.vectorstore.redis;

import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.ai.vectorstore.RedisVectorStore;
import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

/**
* @author Christian Tzolov
*/
@AutoConfiguration
@ConditionalOnClass({ RedisVectorStore.class, EmbeddingClient.class })
@EnableConfigurationProperties(RedisVectorStoreProperties.class)
public class RedisVectorStoreAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public VectorStore vectorStore(EmbeddingClient embeddingClient, RedisVectorStoreProperties properties) {

var config = RedisVectorStoreConfig.builder()
.withURI(properties.getUri())
.withIndexName(properties.getIndex())
.withPrefix(properties.getPrefix())
.build();

return new RedisVectorStore(config, embeddingClient);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2023-2023 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.autoconfigure.vectorstore.redis;

import org.springframework.boot.context.properties.ConfigurationProperties;

import static org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties.CONFIG_PREFIX;

/**
* @author Julien Ruaux
*/
@ConfigurationProperties(CONFIG_PREFIX)
public class RedisVectorStoreProperties {

public static final String CONFIG_PREFIX = "spring.ai.vectorstore.redis";

private String uri = "redis://localhost:6379";

private String index;

private String prefix;

public String getUri() {
return uri;
}

public void setUri(String uri) {
this.uri = uri;
}

public String getIndex() {
return index;
}

public void setIndex(String name) {
this.index = name;
}

public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.pgvector.PgVectorStoreAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.pinecone.PineconeVectorStoreAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreAutoConfiguration
org.springframework.ai.autoconfigure.embedding.transformer.TransformersEmbeddingClientAutoConfiguration
org.springframework.ai.autoconfigure.huggingface.HuggingfaceAutoConfiguration
org.springframework.ai.autoconfigure.vectorstore.chroma.ChromaVectorStoreAutoConfiguration
Expand Down
Loading