Index and query vectors

Overview

[Redis Search]()
lets you index vector fields in [hash]()
or [JSON]() objects (see the
[Vectors]()
reference page for more information).
Among other things, vector fields can store *text embeddings*, which are AI-generated vector
representations of the semantic information in pieces of text. The
[vector distance]()
between two embeddings indicates how similar they are semantically. By comparing the
similarity of an embedding generated from some query text with embeddings stored in hash
or JSON fields, Redis can retrieve documents that closely match the query in terms
of their meaning.

In the example below, we use [Microsoft.ML](https://dotnet.microsoft.com/en-us/apps/ai/ml-dotnet)
to generate the vector embeddings to store and index with Redis Search.
We also show how to adapt the code to use
[Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/embeddings?tabs=csharp)
for the embeddings. The code is first demonstrated for hash documents with a
separate section to explain the
[differences with JSON documents](#differences-with-json-documents).

From [v1.0.0](https://github.com/redis/NRedisStack/releases/tag/v1.0.0)
onwards, `NRedisStack` uses query dialect 2 by default.
Redis Search methods such as [`FT().Search()`]()
will explicitly request this dialect, overriding the default set for the server.
See
[Query dialects]()
for more information.

Initialize

The example is probably easiest to follow if you start with a new
console app, which you can create using the following command:

[code example]

In the app's project folder, add
[`NRedisStack`](`):

[code example]

Then, add the `Microsoft.ML` package.

[code example]

If you want to try the optional
[Azure embedding](#generate-an-embedding-from-azure-openai)
described below, you should also add `Azure.AI.OpenAI`:

[code example]

Import dependencies

Add the following imports to your source file:

[code example]

If you are using the Azure embeddings, also add:

[code example]

Define a function to obtain the embedding model

Ignore this step if you are using an Azure OpenAI
embedding model.

A few steps are involved in initializing the embedding model
(known as a `PredictionEngine`, in Microsoft terminology), so
we declare a function to contain those steps together.
(See the Microsoft.ML docs for more information about the
[`ApplyWordEmbedding`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.ml.textcatalog.applywordembedding?view=ml-dotnet)
method, including example code.)

Note that we use two classes, `TextData` and `TransformedTextData`, to
specify the `PredictionEngine` model. C# syntax requires us to place these
classes after the main code in a console app source file. The section
[Declare `TextData` and `TransformedTextData`](#declare-textdata-and-transformedtextdata)
below shows how to declare them.

[code example]

Ignore this step if you are using an Azure OpenAI
embedding model.

Our embedding model represents the vectors as an array of `float` values,
but when you store vectors in a Redis hash object, you must encode the vector
array as a `byte` string. To simplify this, we declare a
`GetEmbedding()` function that applies the `PredictionEngine` model described
[above](#define-a-function-to-obtain-the-embedding-model), and
then encodes the returned `float` array as a `byte` string. If you are
storing your documents as JSON objects instead of hashes, then you should
use the `float` array for the embedding directly, without first converting
it to a `byte` string (see [Differences with JSON documents](#differences-with-json-documents)
below).

[code example]

Generate an embedding from Azure OpenAI

Ignore this step if you are using a Microsoft.ML
embedding model.

Azure OpenAI can be a convenient way to access an embedding model, because
you don't need to manage and scale the server infrastructure yourself.

You can create an Azure OpenAI service and deployment to serve embeddings of
whatever type you need. Select your region, note the service endpoint and key,
and add them where you see placeholders in the function below.
See
[Learn how to generate embeddings with Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/embeddings?tabs=csharp)
for more information.

[code example]

Create the index

Connect to Redis and delete any index previously created with the
name `vector_idx`. (The `DropIndex()` call throws an exception if
the index doesn't already exist, which is why you need the
`try...catch` block.)

[code example]

Next, create the index.
The schema in the example below includes three fields: the text content to index, a
[tag]()
field to represent the "genre" of the text, and the embedding vector generated from
the original text content. The `embedding` field specifies
[HNSW]()
indexing, the
[L2]()
vector distance metric, `Float32` values to represent the vector's components,
and 150 dimensions, as required by our embedding model.

The `FTCreateParams` object specifies hash objects for storage and a
prefix `doc:` that identifies the hash objects we want to index.

[code example]

Add data

You can now supply the data objects, which will be indexed automatically
when you add them with [`HashSet()`](), as long as
you use the `doc:` prefix specified in the index definition.

Firstly, create an instance of the `PredictionEngine` model using our
`GetPredictionEngine()` function.
You can then pass this to the `GetEmbedding()` function
to create the embedding that represents the `content` field, as shown below .

(If you are using an Azure OpenAI model for the embeddings, then
use `GetEmbeddingFromAzure()` instead of `GetEmbedding()`, and note that
the `PredictionModel` is managed by the server, so you don't need to create
an instance yourself.)

[code example]

Run a query

After you have created the index and added the data, you are ready to run a query.
To do this, you must create another embedding vector from your chosen query
text. Redis calculates the vector distance between the query vector and each
embedding vector in the index as it runs the query. We can request the results to be
sorted to rank them in order of ascending distance.

The code below creates the query embedding using the `GetEmbedding()` method, as with
the indexing, and passes it as a parameter when the query executes (see
[Vector search]()
for more information about using query parameters with embeddings).
The query is a
[K nearest neighbors (KNN)]()
search that sorts the results in order of vector distance from the query vector.

(As before, replace `GetEmbedding()` with `GetEmbeddingFromAzure()` if you are using
Azure OpenAI.)

[code example]

Ignore this step if you are using an Azure OpenAI
embedding model.

As we noted in the section above about the
[embedding model](#define-a-function-to-obtain-the-embedding-model),
we must declare two very simple classes at the end of the source
file. These are required because the API that generates the model
expects classes with named fields for the input `string` and output
`float` array.

[code example]

Run the code

Assuming you have added the code from the steps above to your source file,
it is now ready to run, but note that it may take a while to complete when
you run it for the first time (which happens because the tokenizer must download the
embedding model data before it can generate the embeddings). When you run the code,
it outputs the following result text:

[code example]

The results are ordered according to the value of the `score`
field, which represents the vector distance here. The lowest distance indicates
the greatest similarity to the query.
As you would expect, the result for `doc:1` with the content text
*"That is a very happy person"*
is the result that is most similar in meaning to the query text
*"That is a happy person"*.

Differences with JSON documents

Indexing JSON documents is similar to hash indexing, but there are some
important differences. JSON allows much richer data modeling with nested fields, so
you must supply a [path]() in the schema
to identify each field you want to index. However, you can declare a short alias for each
of these paths to avoid typing it in full for
every query. Also, you must specify `IndexType.JSON` with the `On()` option when you
create the index.

The code below shows these differences, but the index is otherwise very similar to
the one created previously for hashes:

[code example]

An important difference with JSON indexing is that the vectors are
specified using arrays of `float` instead of binary strings. This requires a modification
to the `GetEmbedding()` function declared in
[Define a function to generate an embedding](#define-a-function-to-generate-an-embedding)
above:

[code example]

You should make a similar modification to the `GetEmbeddingFromAzure()` function
if you are using Azure OpenAI with JSON.

Use [`JSON().set()`]() to add the data
instead of [`HashSet()`]():

[code example]

The query is almost identical to the one for the hash documents. This
demonstrates how the right choice of aliases for the JSON paths can
save you having to write complex queries. The only significant difference is
that the `FieldName` objects created for the `ReturnFields()` option must
include the JSON path for the field.

An important thing to notice
is that the vector parameter for the query is still specified as a
binary string (using the `GetEmbedding()` method), even though the data for
the `embedding` field of the JSON was specified as a `float` array.

[code example]

Apart from the `jdoc:` prefixes for the keys, the result from the JSON
query is the same as for hash:

[code example]

Learn more

See
[Vector search]()
for more information about the indexing options, distance metrics, and query format
for vectors.

dotnet add package Azure.AI.OpenAI --prerelease

// Redis connection and Redis Search.
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;
using NRedisStack.Search;
using static NRedisStack.Search.Schema;
using NRedisStack.Search.Literals.Enums;

// Text embeddings.
using Microsoft.ML;
using Microsoft.ML.Transforms.Text;

// Azure embeddings.
using Azure;
using Azure.AI.OpenAI;

static PredictionEngine<TextData, TransformedTextData> GetPredictionEngine(){
    // Create a new ML context, for ML.NET operations. It can be used for
    // exception tracking and logging, as well as the source of randomness.
    var mlContext = new MLContext();

    // Create an empty list as the dataset
    var emptySamples = new List<TextData>();

    // Convert sample list to an empty IDataView.
    var emptyDataView = mlContext.Data.LoadFromEnumerable(emptySamples);

    // A pipeline for converting text into a 150-dimension embedding vector
    var textPipeline = mlContext.Transforms.Text.NormalizeText("Text")
        .Append(mlContext.Transforms.Text.TokenizeIntoWords("Tokens",
            "Text"))
        .Append(mlContext.Transforms.Text.ApplyWordEmbedding("Features",
            "Tokens", WordEmbeddingEstimator.PretrainedModelKind
            .SentimentSpecificWordEmbedding));

    // Fit to data.
    var textTransformer = textPipeline.Fit(emptyDataView);

    // Create the prediction engine to get the embedding vector from the input text/string.
    var predictionEngine = mlContext.Model.CreatePredictionEngine<TextData,
        TransformedTextData>(textTransformer);

    return predictionEngine;
}

static byte[] GetEmbedding(
    PredictionEngine<TextData, TransformedTextData> model, string sentence
)
{
    // Call the prediction API to convert the text into embedding vector.
    var data = new TextData()
    {
        Text = sentence
    };

    var prediction = model.Predict(data);

    // Convert prediction.Features to a binary blob
    float[] floatArray = Array.ConvertAll(prediction.Features, x => (float)x);
    byte[] byteArray = new byte[floatArray.Length * sizeof(float)];
    Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);

    return byteArray;
}

private static byte[] GetEmbeddingFromAzure(string sentence){
	Uri oaiEndpoint = new ("your-azure-openai-endpoint");
	string oaiKey = "your-openai-key";

	AzureKeyCredential credentials = new (oaiKey);
	OpenAIClient openAIClient = new (oaiEndpoint, credentials);

	EmbeddingsOptions embeddingOptions = new() {
    	     DeploymentName = "your-deployment-name",
    	     Input = { sentence },
	};

	// Generate the vector embedding
	var returnValue = openAIClient.GetEmbeddings(embeddingOptions);

	// Convert the array of floats to binary blob
	float[] floatArray = Array.ConvertAll(returnValue.Value.Data[0].Embedding.ToArray(), x => (float)x);
	byte[] byteArray = new byte[floatArray.Length * sizeof(float)];
	Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);
	return byteArray;
}

var muxer = ConnectionMultiplexer.Connect("localhost:6379");
var db = muxer.GetDatabase();

try { db.FT().DropIndex("vector_idx");} catch {}

var schema = new Schema()
    .AddTextField(new FieldName("content", "content"))
    .AddTagField(new FieldName("genre", "genre"))
    .AddVectorField("embedding", VectorField.VectorAlgo.HNSW,
        new Dictionary<string, object>()
        {
            ["TYPE"] = "FLOAT32",
            ["DIM"] = "150",
            ["DISTANCE_METRIC"] = "L2"
        }
    );

db.FT().Create(
    "vector_idx",
    new FTCreateParams()
        .On(IndexDataType.HASH)
        .Prefix("doc:"),
    schema
);

var predEngine = GetPredictionEngine();

var sentence1 = "That is a very happy person";

HashEntry[] doc1 = {
    new("content", sentence1),
    new("genre", "persons"),
    new("embedding", GetEmbedding(predEngine, sentence1))
};

db.HashSet("doc:1", doc1);

var sentence2 = "That is a happy dog";

HashEntry[] doc2 = {
    new("content", sentence2),
    new("genre", "pets"),
    new("embedding", GetEmbedding(predEngine, sentence2))
};

db.HashSet("doc:2", doc2);

var sentence3 = "Today is a sunny day";

HashEntry[] doc3 = {
    new("content", sentence3),
    new("genre", "weather"),
    new("embedding", GetEmbedding(predEngine, sentence3))
};

db.HashSet("doc:3", doc3);

var res = db.FT().Search("vector_idx",
    new Query("*=>[KNN 3 @embedding $query_vec AS score]")
    .AddParam("query_vec", GetEmbedding(predEngine, "That is a happy person"))
    .ReturnFields(
        new FieldName("content", "content"),
        new FieldName("score", "score")
    )
    .SetSortBy("score")
    .Dialect(2));

foreach (var doc in res.Documents) {
    var props = doc.GetProperties();
    var propText = string.Join(
        ", ",
        props.Select(p => $"{p.Key}: '{p.Value}'")
    );

    Console.WriteLine(
        $"ID: {doc.Id}, Properties: [\n  {propText}\n]"
    );
}

class TextData
{
    public string Text { get; set; }
}

class TransformedTextData : TextData
{
    public float[] Features { get; set; }
}

ID: doc:1, Properties: [
  score: '4.30777168274', content: 'That is a very happy person'
]
ID: doc:2, Properties: [
  score: '25.9752807617', content: 'That is a happy dog'
]
ID: doc:3, Properties: [
  score: '68.8638000488', content: 'Today is a sunny day'
]

var jsonSchema = new Schema()
    .AddTextField(new FieldName("$.content", "content"))
    .AddTagField(new FieldName("$.genre", "genre"))
    .AddVectorField(
        new FieldName("$.embedding", "embedding"),
        VectorField.VectorAlgo.HNSW,
        new Dictionary<string, object>()
        {
            ["TYPE"] = "FLOAT32",
            ["DIM"] = "150",
            ["DISTANCE_METRIC"] = "L2"
        }
    );

db.FT().Create(
    "vector_json_idx",
    new FTCreateParams()
        .On(IndexDataType.JSON)
        .Prefix("jdoc:"),
    jsonSchema
);

static float[] GetFloatEmbedding(
    PredictionEngine<TextData, TransformedTextData> model, string sentence
)
{
    // Call the prediction API to convert the text into embedding vector.
    var data = new TextData()
    {
        Text = sentence
    };

    var prediction = model.Predict(data);

    float[] floatArray = Array.ConvertAll(prediction.Features, x => (float)x);
    return floatArray;
}

var jSentence1 = "That is a very happy person";

var jdoc1 = new {
    content = jSentence1,
    genre = "persons",
    embedding = GetFloatEmbedding(predEngine, jSentence1),
};

db.JSON().Set("jdoc:1", "$", jdoc1);

var jSentence2 = "That is a happy dog";

var jdoc2 = new {
    content = jSentence2,
    genre = "pets",
    embedding = GetFloatEmbedding(predEngine, jSentence2),
};

db.JSON().Set("jdoc:2", "$", jdoc2);

var jSentence3 = "Today is a sunny day";

var jdoc3 = new {
    content = jSentence3,
    genre = "weather",
    embedding = GetFloatEmbedding(predEngine, jSentence3),
};

db.JSON().Set("jdoc:3", "$", jdoc3);

var jRes = db.FT().Search("vector_json_idx",
    new Query("*=>[KNN 3 @embedding $query_vec AS score]")
    .AddParam("query_vec", GetEmbedding(predEngine, "That is a happy person"))
    .ReturnFields(
        new FieldName("$.content", "content"),
        new FieldName("$.score", "score")
    )
    .SetSortBy("score")
    .Dialect(2));

foreach (var doc in jRes.Documents) {
    var props = doc.GetProperties();
    var propText = string.Join(
        ", ",
        props.Select(p => $"{p.Key}: '{p.Value}'")
    );

    Console.WriteLine(
        $"ID: {doc.Id}, Properties: [\n  {propText}\n]"
    );
}

ID: jdoc:1, Properties: [
  score: '4.30777168274', content: 'That is a very happy person'
]
ID: jdoc:2, Properties: [
  score: '25.9752807617', content: 'That is a happy dog'
]
ID: jdoc:3, Properties: [
  score: '68.8638000488', content: 'Today is a sunny day'
]


Vector set embeddings

Summary: Index and query embeddings with Redis vector sets

Initialize

Start by installing `StackExchange.Redis` with the following
command (version 2.9.17 or later is required for vector sets):

[code example]

Also, install `Microsoft.ML`:

[code example]

In a new C# file, import the required classes. Note that the `#pragma`
directive suppresses warnings about the experimental status of the vector set API:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

Access the model

Use the `GetPredictionEngine()` helper function declared in the example below to load the model that creates the embeddings:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

The `GetPredictionEngine()` function uses two classes, `TextData` and `TransformedTextData`,
to specify the `PredictionEngine` model. These have a very simple definition
and are required because the model expects the input and output to be
passed in named object fields:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

Note that you must declare these classes at the end of the source file
if you are using a console app without a main class.

The `GetEmbedding()` function declared below can then use this model to
generate an embedding from a section of text and return it as a `float[]` array,
which is the format required by the vector set API:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

Create the data

The example data is contained a `Dictionary` object with some brief
descriptions of famous people:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

Add the data to a vector set

The next step is to connect to Redis and add the data to a new vector set.

The code below iterates through `peopleData` and adds corresponding
elements to a vector set called `famousPeople`.

Use the `GetEmbedding()` function declared above to generate the
embedding as a `byte` array that you can pass to the
[`VectorSetAdd()`]() command to set the embedding.

The call to `VectorSetAdd()` also adds the `born` and `died` values from the
original dictionary as attribute data. You can access this during a query
or by using the [`VectorSetGetAttributesJson()`]() method.

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

Query the vector set

You can now query the data in the set. The basic approach is to use the
`GetEmbedding()` function to generate another embedding vector for the query text.
(This is the same method used to add the elements to the set.) Then, pass
the query vector to [`VectorSetSimilaritySearch()`]() to
return elements of the set, ranked in order of similarity to the query.

Start with a simple query for "actors":

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

This returns the following list of elements (formatted slightly for clarity):

[code example]

The first two people in the list are the two actors, as expected, but none of the
people from Linus Pauling onward was especially well-known for acting (and there certainly
isn't any information about that in the short description text).
As it stands, the search attempts to rank all the elements in the set, based
on the information contained in the embedding model.
You can use the `Count` property of `VectorSetSimilaritySearchRequest` to limit the
list of elements to just the most relevant few items:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

The reason for using text embeddings rather than simple text search
is that the embeddings represent semantic information. This allows a query
to find elements with a similar meaning even if the text is
different. For example, the word "entertainer" doesn't appear in any of the
descriptions but if you use it as a query, the actors and musicians are ranked
highest in the results list:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

Similarly, if you use "science" as a query, you get the following results:

[code example]

The scientists are ranked highest but they are then followed by the
mathematicians. This seems reasonable given the connection between mathematics
and science.

You can also use
[filter expressions]()
with `VectorSetSimilaritySearch()` to restrict the search further. For example,
repeat the "science" query, but this time limit the results to people
who died before the year 2000:

#### Code Examples

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), Java (Synchronous - Jedis), JavaScript (Node.js), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Asynchronous - Lettuce):**

[code example]

**Java (Reactive - Lettuce):**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**JavaScript (Node.js):**

[code example]

**Python:**

[code example]

Note that the boolean filter expression is applied to items in the list
before the vector distance calculation is performed. Items that don't
pass the filter test are removed from the results completely, rather
than just reduced in rank. This can help to improve the performance of the
search because there is no need to calculate the vector distance for
elements that have already been filtered out of the search.

More information

See the [vector sets]()
docs for more information and code examples. See the
[Redis for AI]() section for more details
about text embeddings and other AI techniques you can use with Redis.

You may also be interested in
[vector search]().
This is a feature of
[Redis Search]()
that lets you retrieve
[JSON]() and
[hash]() documents based on
vector data stored in their fields.

dotnet add package StackExchange.Redis --version 2.9.17

// Suppress experimental API warnings for VectorSet
#pragma warning disable SER001

using StackExchange.Redis;

using Microsoft.ML;
using Microsoft.ML.Transforms.Text;

package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/knights-analytics/hugot"
	"github.com/redis/go-redis/v9"
)

import * as transformers from '@xenova/transformers';
import { createClient } from 'redis';

from sentence_transformers import SentenceTransformer

import redis
import numpy as np

static PredictionEngine<TextData, TransformedTextData> GetPredictionEngine()
{
    // Create a new ML context, for ML.NET operations. It can be used for
    // exception tracking and logging, as well as the source of randomness.
    var mlContext = new MLContext();

    // Create an empty list as the dataset
    var emptySamples = new List<TextData>();

    // Convert sample list to an empty IDataView.
    var emptyDataView = mlContext.Data.LoadFromEnumerable(emptySamples);

    // A pipeline for converting text into a 150-dimension embedding vector
    var textPipeline = mlContext.Transforms.Text.NormalizeText("Text")
        .Append(mlContext.Transforms.Text.TokenizeIntoWords("Tokens",
            "Text"))
        .Append(mlContext.Transforms.Text.ApplyWordEmbedding("Features",
            "Tokens", WordEmbeddingEstimator.PretrainedModelKind
            .SentimentSpecificWordEmbedding));

    // Fit to data.
    var textTransformer = textPipeline.Fit(emptyDataView);

    // Create the prediction engine to get the embedding vector from the input text/string.
    var predictionEngine = mlContext.Model.CreatePredictionEngine<TextData,
        TransformedTextData>(textTransformer);

    return predictionEngine;
}

class TextData
{
    public string Text { get; set; } = string.Empty;
}

class TransformedTextData : TextData
{
    public float[] Features { get; set; } = Array.Empty<float>();
}

package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/knights-analytics/hugot"
	"github.com/redis/go-redis/v9"
)

type PersonData struct {
	Born        int
	Died        int
	Description string
}

var peopleData = map[string]PersonData{
	"Marie Curie": {
		Born: 1867, Died: 1934,
		Description: `Polish-French chemist and physicist. The only person ever to win
		two Nobel prizes for two different sciences.
		`,
	},
	"Linus Pauling": {
		Born: 1901, Died: 1994,
		Description: `American chemist and peace activist. One of only two people to win two
		Nobel prizes in different fields (chemistry and peace).
		`,
	},
	"Freddie Mercury": {
		Born: 1946, Died: 1991,
		Description: `British musician, best known as the lead singer of the rock band
		Queen.
		`,
	},
	"Marie Fredriksson": {
		Born: 1958, Died: 2019,
		Description: `Swedish multi-instrumentalist, mainly known as the lead singer and
		keyboardist of the band Roxette.
		`,
	},
	"Paul Erdos": {
		Born: 1913, Died: 1996,
		Description: `Hungarian mathematician, known for his eccentric personality almost
		as much as his contributions to many different fields of mathematics.
		`,
	},
	"Maryam Mirzakhani": {
		Born: 1977, Died: 2017,
		Description: `Iranian mathematician. The first woman ever to win the Fields medal
		for her contributions to mathematics.
		`,
	},
	"Masako Natsume": {
		Born: 1957, Died: 1985,
		Description: `Japanese actress. She was very famous in Japan but was primarily
		known elsewhere in the world for her portrayal of Tripitaka in the
		TV series Monkey.
		`,
	},
	"Chaim Topol": {
		Born: 1935, Died: 2023,
		Description: `Israeli actor and singer, usually credited simply as 'Topol'. He was
		best known for his many appearances as Tevye in the musical Fiddler
		on the Roof.
		`,
	},
}

func main() {
	ctx := context.Background()

	// Create a new Hugot session
	session, err := hugot.NewGoSession()
	if err != nil {
		panic(err)
	}
	defer func() {
		err := session.Destroy()
		if err != nil {
			panic(err)
		}
	}()

	// Download the model.
	downloadOptions := hugot.NewDownloadOptions()
	downloadOptions.OnnxFilePath = "onnx/model.onnx"
	modelPath, err := hugot.DownloadModel(
		"sentence-transformers/all-MiniLM-L6-v2",
		"./models/",
		downloadOptions,
	)
	if err != nil {
		panic(err)
	}

	// Create feature extraction pipeline configuration
	config := hugot.FeatureExtractionConfig{
		ModelPath: modelPath,
		Name:      "embeddingPipeline",
	}

	// Create the feature extraction pipeline
	embeddingPipeline, err := hugot.NewPipeline(session, config)
	if err != nil {
		panic(err)
	}

	rdb := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password
		DB:       0,  // use default DB
	})

	for name, details := range peopleData {
		// Generate embeddings using Hugot
		result, err := embeddingPipeline.RunPipeline([]string{details.Description})
		if err != nil {
			panic(err)
		}

		// Convert embedding to float64 slice
		embFloat32 := result.Embeddings[0]
		embFloat64 := make([]float64, len(embFloat32))
		for i, v := range embFloat32 {
			embFloat64[i] = float64(v)
		}

		// Add vector to vector set
		_, err = rdb.VAdd(ctx, "famousPeople", name, &redis.VectorValues{Val: embFloat64}).Result()
		if err != nil {
			panic(err)
		}

		// Set attributes for the element
		_, err = rdb.VSetAttr(ctx, "famousPeople", name, map[string]interface{}{
			"born": details.Born,
			"died": details.Died,
		}).Result()
		if err != nil {
			panic(err)
		}
	}

	queryValue := "actors"

	queryResult, err := embeddingPipeline.RunPipeline([]string{queryValue})
	if err != nil {
		panic(err)
	}

	// Convert embedding to float64 slice
	queryFloat32 := queryResult.Embeddings[0]
	queryFloat64 := make([]float64, len(queryFloat32))
	for i, v := range queryFloat32 {
		queryFloat64[i] = float64(v)
	}

	actorsResults, err := rdb.VSim(ctx, "famousPeople", &redis.VectorValues{Val: queryFloat64}).Result()
	if err != nil {
		panic(err)
	}

	fmt.Printf("'actors': %v\n", strings.Join(actorsResults, ", "))
	// >>> 'actors': Masako Natsume, Chaim Topol, Linus Pauling,
	// Marie Fredriksson, Maryam Mirzakhani, Marie Curie, Freddie Mercury,
	// Paul Erdos

	queryValue = "actors"

	queryResult, err = embeddingPipeline.RunPipeline([]string{queryValue})
	if err != nil {
		panic(err)
	}

	// Convert embedding to float64 slice
	queryFloat32 = queryResult.Embeddings[0]
	queryFloat64 = make([]float64, len(queryFloat32))
	for i, v := range queryFloat32 {
		queryFloat64[i] = float64(v)
	}

	twoActorsResults, err := rdb.VSimWithArgs(ctx, "famousPeople",
		&redis.VectorValues{Val: queryFloat64},
		&redis.VSimArgs{Count: 2}).Result()
	if err != nil {
		panic(err)
	}

	fmt.Printf("'actors (2)': %v\n", strings.Join(twoActorsResults, ", "))
	// >>> 'actors (2)': Masako Natsume, Chaim Topol

	queryValue = "entertainer"

	queryResult, err = embeddingPipeline.RunPipeline([]string{queryValue})
	if err != nil {
		panic(err)
	}

	// Convert embedding to float64 slice
	queryFloat32 = queryResult.Embeddings[0]
	queryFloat64 = make([]float64, len(queryFloat32))
	for i, v := range queryFloat32 {
		queryFloat64[i] = float64(v)
	}

	entertainerResults, err := rdb.VSim(ctx, "famousPeople", &redis.VectorValues{Val: queryFloat64}).Result()
	if err != nil {
		panic(err)
	}

	fmt.Printf("'entertainer': %v\n", strings.Join(entertainerResults, ", "))
	// >>> 'entertainer': Chaim Topol, Freddie Mercury, Marie Fredriksson,
	// Linus Pauling, Masako Natsume, Paul Erdos,
	// Maryam Mirzakhani, Marie Curie

	queryValue = "science"

	queryResult, err = embeddingPipeline.RunPipeline([]string{queryValue})
	if err != nil {
		panic(err)
	}

	// Convert embedding to float64 slice
	queryFloat32 = queryResult.Embeddings[0]
	queryFloat64 = make([]float64, len(queryFloat32))
	for i, v := range queryFloat32 {
		queryFloat64[i] = float64(v)
	}

	scienceResults, err := rdb.VSim(ctx, "famousPeople", &redis.VectorValues{Val: queryFloat64}).Result()
	if err != nil {
		panic(err)
	}

	fmt.Printf("'science': %v\n", strings.Join(scienceResults, ", "))
	// >>> 'science': Marie Curie, Linus Pauling, Maryam Mirzakhani, Paul Erdos,
	// Marie Fredriksson, Freddie Mercury, Masako Natsume, Chaim Topol
	queryValue = "science"

	queryResult, err = embeddingPipeline.RunPipeline([]string{queryValue})
	if err != nil {
		panic(err)
	}

	// Convert embedding to float64 slice
	queryFloat32 = queryResult.Embeddings[0]
	queryFloat64 = make([]float64, len(queryFloat32))
	for i, v := range queryFloat32 {
		queryFloat64[i] = float64(v)
	}

	science2000Results, err := rdb.VSimWithArgs(ctx, "famousPeople",
		&redis.VectorValues{Val: queryFloat64},
		&redis.VSimArgs{Filter: ".died < 2000"}).Result()
	if err != nil {
		panic(err)
	}

	fmt.Printf("'science2000': %v\n", strings.Join(science2000Results, ", "))
	// >>> 'science2000': Marie Curie, Linus Pauling, Paul Erdos, Freddie Mercury,
	// Masako Natsume
}

package io.redis.examples.async;

// Lettuce client and Redis Search classes.
import io.lettuce.core.*;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;

// Standard library classes for data manipulation and
// asynchronous programming.
import java.util.*;
import java.util.concurrent.CompletableFuture;

// DJL classes for model loading and inference.
import ai.djl.huggingface.translator.TextEmbeddingTranslatorFactory;
import ai.djl.inference.Predictor;
import ai.djl.repository.zoo.Criteria;
import ai.djl.training.util.ProgressBar;

public class HomeVecSetsExample {

    public static Double[] convertFloatsToDoubles(float[] input) {
        if (input == null) {
            return null;
        }

        Double[] output = new Double[input.length];

        for (int i = 0; i < input.length; i++) {
            output[i] = Double.valueOf(input[i]);
        }

        return output;
    }

    public void run() {
        Predictor<String, float[]> predictor = null;

        try {
            Criteria<String, float[]> criteria = Criteria.builder().setTypes(String.class, float[].class)
                    .optModelUrls("djl://ai.djl.huggingface.pytorch/sentence-transformers/all-MiniLM-L6-v2")
                    .optEngine("PyTorch").optTranslatorFactory(new TextEmbeddingTranslatorFactory())
                    .optProgress(new ProgressBar()).build();

            predictor = criteria.loadModel().newPredictor();
        } catch (Exception e) {
            // ...
        }

        final class Person {
            final String name;
            final int born;
            final int died;
            final String description;
            Person(String name, int born, int died, String description) {
                this.name = name; this.born = born; this.died = died; this.description = description;
            }
        }

        List<Person> people = Arrays.asList(
            new Person(
                "Marie Curie",
                1867, 1934,
                "Polish-French chemist and physicist. The only person ever to win" +
                " two Nobel prizes for two different sciences."
            ),
            new Person(
                "Linus Pauling",
                1901, 1994,
                "American chemist and peace activist. One of only two people to" +
                " win two Nobel prizes in different fields (chemistry and peace)."
            ),
            new Person(
                "Freddie Mercury",
                1946, 1991,
                "British musician, best known as the lead singer of the rock band Queen."
            ),
            new Person(
                "Marie Fredriksson",
                1958, 2019,
                "Swedish multi-instrumentalist, mainly known as the lead singer and" +
                " keyboardist of the band Roxette."
            ),
            new Person(
                "Paul Erdos",
                1913, 1996,
                "Hungarian mathematician, known for his eccentric personality almost" +
                " as much as his contributions to many different fields of mathematics."
            ),
            new Person(
                "Maryam Mirzakhani",
                1977, 2017,
                "Iranian mathematician. The first woman ever to win the Fields medal" +
                " for her contributions to mathematics."
            ),
            new Person(
                "Masako Natsume",
                1957, 1985,
                "Japanese actress. She was very famous in Japan but was primarily" +
                " known elsewhere in the world for her portrayal of Tripitaka in the" +
                " TV series Monkey."
            ),
            new Person(
                "Chaim Topol",
                1935, 2023,
                "Israeli actor and singer, usually credited simply as 'Topol'. He was" +
                " best known for his many appearances as Tevye in the musical Fiddler" +
                " on the Roof."
            )
        );

        RedisClient redisClient = RedisClient.create("redis://localhost:6379");

        try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
            RedisAsyncCommands<String, String> asyncCommands = connection.async();

            CompletableFuture<?>[] vaddFutures = new CompletableFuture[people.size()];

            for (int i = 0; i < people.size(); i++) {
                Person person = people.get(i);
                Double[] embedding = null;

                try {
                    embedding = convertFloatsToDoubles(predictor.predict(person.description));
                } catch (Exception e) {
                    // ...
                }

                VAddArgs personArgs = VAddArgs.Builder.attributes(String.format("{\"born\": %d, \"died\": %d}", person.born, person.died));

                vaddFutures[i] = asyncCommands.vadd("famousPeople", person.name, personArgs, embedding)
                        .thenApply(result -> {
                            System.out.println(result); // >>> true
                            return result;
                        }).toCompletableFuture();
            }

            CompletableFuture.allOf(vaddFutures).join();

            Double[] actorsEmbedding = null;

            try {
                actorsEmbedding = convertFloatsToDoubles(predictor.predict("actors"));
            } catch (Exception e) {
                // ...
            }

            CompletableFuture<List<String>> basicQuery = asyncCommands.vsim("famousPeople", actorsEmbedding)
                    .thenApply(result -> {
                        System.out.println(result);
                        // >>> [Masako Natsume, Chaim Topol, Linus Pauling, Marie Fredriksson, Maryam Mirzakhani, Marie Curie, Freddie Mercury, Paul Erdos]
                        return result;
                    }).toCompletableFuture();

            VSimArgs limitArgs = VSimArgs.Builder.count(2L);

            CompletableFuture<List<String>> limitedQuery = asyncCommands.vsim("famousPeople", limitArgs, actorsEmbedding)
                    .thenApply(result -> {
                        System.out.println(result);
                        // >>> [Masako Natsume, Chaim Topol]
                        return result;
                    }).toCompletableFuture();

            Double[] entertainerEmbedding = null;

            try {
                entertainerEmbedding = convertFloatsToDoubles(predictor.predict("entertainers"));
            } catch (Exception e) {
                // ...
            }

            CompletableFuture<List<String>> entertainerQuery = asyncCommands.vsim("famousPeople", entertainerEmbedding)
                    .thenApply(result -> {
                        System.out.println(result);
                        // >>> [Freddie Mercury, Chaim Topol, Linus Pauling, Marie Fredriksson, Masako Natsume, Paul Erdos, Maryam Mirzakhani, Marie Curie]
                        return result;
                    }).toCompletableFuture();

            Double[] scienceEmbedding = null;

            try {
                scienceEmbedding = convertFloatsToDoubles(predictor.predict("science"));
            } catch (Exception e) {
                // ...
            }

            CompletableFuture<List<String>> scienceQuery = asyncCommands.vsim("famousPeople", scienceEmbedding)
                    .thenApply(result -> {
                        System.out.println(result);
                        // >>> [Marie Curie, Linus Pauling, Maryam Mirzakhani, Paul Erdos, Marie Fredriksson, Freddie Mercury, Masako Natsume, Chaim Topol]
                        return result;
                    }).toCompletableFuture();

            Double[] science2000Embedding = null;

            try {
                science2000Embedding = convertFloatsToDoubles(predictor.predict("science"));
            } catch (Exception e) {
                // ...
            }

            VSimArgs filterArgs = VSimArgs.Builder.filter(".died < 2000");

            CompletableFuture<List<String>> filteredQuery = asyncCommands.vsim("famousPeople", filterArgs, science2000Embedding)
                    .thenApply(result -> {
                        System.out.println(result);
                        // >>> [Marie Curie, Linus Pauling, Paul Erdos, Freddie Mercury, Masako Natsume]
                        return result;
                    }).toCompletableFuture();

            CompletableFuture.allOf(basicQuery, limitedQuery, entertainerQuery, scienceQuery, filteredQuery).join();
        } finally {
            redisClient.shutdown();
        }
    }
}

package io.redis.examples.reactive;

// Lettuce client and Redis Search classes.
import io.lettuce.core.*;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.reactive.RedisReactiveCommands;

// Standard library classes for data manipulation and
// reactive programming.
import java.util.*;
import reactor.core.publisher.Mono;

// DJL classes for model loading and inference.
import ai.djl.huggingface.translator.TextEmbeddingTranslatorFactory;
import ai.djl.inference.Predictor;
import ai.djl.repository.zoo.Criteria;
import ai.djl.training.util.ProgressBar;

public class HomeVecSetsExample {

    public static Double[] convertFloatsToDoubles(float[] input) {
        if (input == null) {
            return null;
        }

        Double[] output = new Double[input.length];

        for (int i = 0; i < input.length; i++) {
            output[i] = Double.valueOf(input[i]);
        }

        return output;
    }

    public void run() {
        Predictor<String, float[]> predictor = null;

        try {
            Criteria<String, float[]> criteria = Criteria.builder().setTypes(String.class, float[].class)
                    .optModelUrls("djl://ai.djl.huggingface.pytorch/sentence-transformers/all-MiniLM-L6-v2")
                    .optEngine("PyTorch").optTranslatorFactory(new TextEmbeddingTranslatorFactory())
                    .optProgress(new ProgressBar()).build();

            predictor = criteria.loadModel().newPredictor();
        } catch (Exception e) {
            // ...
        }

        final class Person {
            final String name;
            final int born;
            final int died;
            final String description;
            Person(String name, int born, int died, String description) {
                this.name = name; this.born = born; this.died = died; this.description = description;
            }
        }

        List<Person> people = Arrays.asList(
            new Person(
                "Marie Curie",
                1867, 1934,
                "Polish-French chemist and physicist. The only person ever to win" +
                " two Nobel prizes for two different sciences."
            ),
            new Person(
                "Linus Pauling",
                1901, 1994,
                "American chemist and peace activist. One of only two people to" +
                " win two Nobel prizes in different fields (chemistry and peace)."
            ),
            new Person(
                "Freddie Mercury",
                1946, 1991,
                "British musician, best known as the lead singer of the rock band Queen."
            ),
            new Person(
                "Marie Fredriksson",
                1958, 2019,
                "Swedish multi-instrumentalist, mainly known as the lead singer and" +
                " keyboardist of the band Roxette."
            ),
            new Person(
                "Paul Erdos",
                1913, 1996,
                "Hungarian mathematician, known for his eccentric personality almost" +
                " as much as his contributions to many different fields of mathematics."
            ),
            new Person(
                "Maryam Mirzakhani",
                1977, 2017,
                "Iranian mathematician. The first woman ever to win the Fields medal" +
                " for her contributions to mathematics."
            ),
            new Person(
                "Masako Natsume",
                1957, 1985,
                "Japanese actress. She was very famous in Japan but was primarily" +
                " known elsewhere in the world for her portrayal of Tripitaka in the" +
                " TV series Monkey."
            ),
            new Person(
                "Chaim Topol",
                1935, 2023,
                "Israeli actor and singer, usually credited simply as 'Topol'. He was" +
                " best known for his many appearances as Tevye in the musical Fiddler" +
                " on the Roof."
            )
        );

        RedisClient redisClient = RedisClient.create("redis://localhost:6379");

        try (StatefulRedisConnection<String, String> connection = redisClient.connect()) {
            RedisReactiveCommands<String, String> reactiveCommands = connection.reactive();

            Mono<?>[] vaddFutures = new Mono[people.size()];

            for (int i = 0; i < people.size(); i++) {
                Person person = people.get(i);
                Double[] embedding = null;

                try {
                    embedding = convertFloatsToDoubles(predictor.predict(person.description));
                } catch (Exception e) {
                    // ...
                }

                VAddArgs personArgs = VAddArgs.Builder.attributes(String.format("{\"born\": %d, \"died\": %d}", person.born, person.died));

                vaddFutures[i] = reactiveCommands.vadd("famousPeople", person.name, personArgs, embedding)
                        .doOnNext(result -> {
                            System.out.println(result); // >>> true
                        });
            }

            Mono.when(vaddFutures).block();

            Double[] actorsEmbedding = null;

            try {
                actorsEmbedding = convertFloatsToDoubles(predictor.predict("actors"));
            } catch (Exception e) {
                // ...
            }

            Mono<java.util.List<String>> basicQuery = reactiveCommands.vsim("famousPeople", actorsEmbedding)
                    .collectList().doOnNext(result -> {
                        System.out.println(result);
                        // >>> [Masako Natsume, Chaim Topol, Linus Pauling, Marie Fredriksson, Maryam Mirzakhani, Marie Curie, Freddie Mercury, Paul Erdos]
                    });

            VSimArgs limitArgs = VSimArgs.Builder.count(2L);

            Mono<java.util.List<String>> limitedQuery = reactiveCommands.vsim("famousPeople", limitArgs, actorsEmbedding)
                    .collectList().doOnNext(result -> {
                        System.out.println(result);
                        // >>> [Masako Natsume, Chaim Topol]
                    });

            Double[] entertainerEmbedding = null;

            try {
                entertainerEmbedding = convertFloatsToDoubles(predictor.predict("entertainers"));
            } catch (Exception e) {
                // ...
            }

            Mono<java.util.List<String>> entertainerQuery = reactiveCommands.vsim("famousPeople", entertainerEmbedding)
                    .collectList().doOnNext(result -> {
                        System.out.println(result);
                        // >>> [Freddie Mercury, Chaim Topol, Linus Pauling, Marie Fredriksson, Masako Natsume, Paul Erdos, Maryam Mirzakhani, Marie Curie]
                    });

            Double[] scienceEmbedding = null;

            try {
                scienceEmbedding = convertFloatsToDoubles(predictor.predict("science"));
            } catch (Exception e) {
                // ...
            }

            Mono<java.util.List<String>> scienceQuery = reactiveCommands.vsim("famousPeople", scienceEmbedding)
                    .collectList().doOnNext(result -> {
                        System.out.println(result);
                        // >>> [Marie Curie, Linus Pauling, Maryam Mirzakhani, Paul Erdos, Marie Fredriksson, Freddie Mercury, Masako Natsume, Chaim Topol]
                    });

            Double[] science2000Embedding = null;

            try {
                science2000Embedding = convertFloatsToDoubles(predictor.predict("science"));
            } catch (Exception e) {
                // ...
            }

            VSimArgs filterArgs = VSimArgs.Builder.filter(".died < 2000");

            Mono<java.util.List<String>> filteredQuery = reactiveCommands.vsim("famousPeople", filterArgs, science2000Embedding)
                    .collectList().doOnNext(result -> {
                        System.out.println(result);
                        // >>> [Marie Curie, Linus Pauling, Paul Erdos, Freddie Mercury, Masako Natsume]
                    });

            Mono.when(basicQuery, limitedQuery, entertainerQuery, scienceQuery, filteredQuery).block();
        }
    }
}

import redis.clients.jedis.RedisClient;
import redis.clients.jedis.params.VAddParams;
import redis.clients.jedis.params.VSimParams;

import java.util.*;

// DJL classes for model loading and inference.
import ai.djl.huggingface.translator.TextEmbeddingTranslatorFactory;
import ai.djl.inference.Predictor;
import ai.djl.repository.zoo.Criteria;
import ai.djl.training.util.ProgressBar;

public class HomeVecSets {

        Predictor<String, float[]> predictor = null;

        try {
            Criteria<String, float[]> criteria = Criteria.builder().setTypes(String.class, float[].class)
                    .optModelUrls("djl://ai.djl.huggingface.pytorch/sentence-transformers/all-MiniLM-L6-v2")
                    .optEngine("PyTorch").optTranslatorFactory(new TextEmbeddingTranslatorFactory())
                    .optProgress(new ProgressBar()).build();

            predictor = criteria.loadModel().newPredictor();
        } catch (Exception e) {
            // ...
        }

        final class Person {
            final String name;
            final int born;
            final int died;
            final String description;
            Person(String name, int born, int died, String description) {
                this.name = name; this.born = born; this.died = died; this.description = description;
            }
        }

        List<Person> people = Arrays.asList(
            new Person(
                "Marie Curie",
                1867, 1934,
                "Polish-French chemist and physicist. The only person ever to win" +
                " two Nobel prizes for two different sciences."
            ),
            new Person(
                "Linus Pauling",
                1901, 1994,
                "American chemist and peace activist. One of only two people to" +
                " win two Nobel prizes in different fields (chemistry and peace)."
            ),
            new Person(
                "Freddie Mercury",
                1946, 1991,
                "British musician, best known as the lead singer of the rock band Queen."
            ),
            new Person(
                "Marie Fredriksson",
                1958, 2019,
                "Swedish multi-instrumentalist, mainly known as the lead singer and" +
                " keyboardist of the band Roxette."
            ),
            new Person(
                "Paul Erdos",
                1913, 1996,
                "Hungarian mathematician, known for his eccentric personality almost" +
                " as much as his contributions to many different fields of mathematics."
            ),
            new Person(
                "Maryam Mirzakhani",
                1977, 2017,
                "Iranian mathematician. The first woman ever to win the Fields medal" +
                " for her contributions to mathematics."
            ),
            new Person(
                "Masako Natsume",
                1957, 1985,
                "Japanese actress. She was very famous in Japan but was primarily" +
                " known elsewhere in the world for her portrayal of Tripitaka in the" +
                " TV series Monkey."
            ),
            new Person(
                "Chaim Topol",
                1935, 2023,
                "Israeli actor and singer, usually credited simply as 'Topol'. He was" +
                " best known for his many appearances as Tevye in the musical Fiddler" +
                " on the Roof."
            )
        );

        RedisClient jedis = new RedisClient("redis://localhost:6379");

        for (Person person : people) {
            float[] embedding;
            try {
                embedding = predictor.predict(person.description);
            } catch (Exception e) {
                // This just allows the code to compile without errors.
                // In a real-world scenario, you would handle the exception properly.
                embedding = new float[384];
            }

            // Add element with attributes using VAddParams
            String attrs = String.format("{\"born\": %d, \"died\": %d}", person.born, person.died);
            boolean added = jedis.vadd("famousPeople", embedding, person.name, new VAddParams().setAttr(attrs));
            System.out.println(added); // >>> true
        }

        float[] actorsEmbedding;
        try {
            actorsEmbedding = predictor.predict("actors");
        } catch (Exception e) {
            actorsEmbedding = new float[384];
        }

        List<String> basicResults = jedis.vsim("famousPeople", actorsEmbedding);
        System.out.println(basicResults);
        // >>> [Masako Natsume, Chaim Topol, Linus Pauling, Marie Fredriksson,
        // >>> Maryam Mirzakhani, Marie Curie, Freddie Mercury, Paul Erdos]

        VSimParams limitParams = new VSimParams().count(2);
        List<String> limitedResults = jedis.vsim("famousPeople", actorsEmbedding, limitParams);
        System.out.println(limitedResults);
        // >>> [Masako Natsume, Chaim Topol]

        float[] entertainerEmbedding;
        try {
            entertainerEmbedding = predictor.predict("entertainers");
        } catch (Exception e) {
            entertainerEmbedding = new float[384];
        }

        List<String> entertainerResults = jedis.vsim("famousPeople", entertainerEmbedding);
        System.out.println(entertainerResults);
        // >>> [Freddie Mercury, Chaim Topol, Linus Pauling, Marie Fredriksson,
        // >>> Masako Natsume, Paul Erdos, Maryam Mirzakhani, Marie Curie]

        float[] scienceEmbedding;
        try {
            scienceEmbedding = predictor.predict("science");
        } catch (Exception e) {
            scienceEmbedding = new float[384];
        }

        List<String> scienceResults = jedis.vsim("famousPeople", scienceEmbedding);
        System.out.println(scienceResults);
        // >>> [Marie Curie, Linus Pauling, Maryam Mirzakhani, Paul Erdos, Marie Fredriksson, Freddie Mercury, Masako Natsume, Chaim Topol]

        float[] science2000Embedding;
        try {
            science2000Embedding = predictor.predict("science");
        } catch (Exception e) {
            science2000Embedding = new float[384];
        }

        VSimParams filterParams = new VSimParams().filter(".died < 2000");
        List<String> filteredResults = jedis.vsim("famousPeople", science2000Embedding, filterParams);
        System.out.println(filteredResults);
        // >>> [Marie Curie, Linus Pauling, Paul Erdos, Freddie Mercury, Masako Natsume]
    }
}

import * as transformers from '@xenova/transformers';
import { createClient } from 'redis';

const pipe = await transformers.pipeline(
    'feature-extraction', 'Xenova/all-MiniLM-L6-v2'
);

const pipeOptions = {
    pooling: 'mean',
    normalize: true,
};

const peopleData = {
    "Marie Curie": {
        "born": 1867, "died": 1934,
        "description": `
        Polish-French chemist and physicist. The only person ever to win
        two Nobel prizes for two different sciences.
        `
    },
    "Linus Pauling": {
        "born": 1901, "died": 1994,
        "description": `
        American chemist and peace activist. One of only two people to win two
        Nobel prizes in different fields (chemistry and peace).
        `
    },
    "Freddie Mercury": {
        "born": 1946, "died": 1991,
        "description": `
        British musician, best known as the lead singer of the rock band
        Queen.
        `
    },
    "Marie Fredriksson": {
        "born": 1958, "died": 2019,
        "description": `
        Swedish multi-instrumentalist, mainly known as the lead singer and
        keyboardist of the band Roxette.
        `
    },
    "Paul Erdos": {
        "born": 1913, "died": 1996,
        "description": `
        Hungarian mathematician, known for his eccentric personality almost
        as much as his contributions to many different fields of mathematics.
        `
    },
    "Maryam Mirzakhani": {
        "born": 1977, "died": 2017,
        "description": `
        Iranian mathematician. The first woman ever to win the Fields medal
        for her contributions to mathematics.
        `
    },
    "Masako Natsume": {
        "born": 1957, "died": 1985,
        "description": `
        Japanese actress. She was very famous in Japan but was primarily
        known elsewhere in the world for her portrayal of Tripitaka in the
        TV series Monkey.
        `
    },
    "Chaim Topol": {
        "born": 1935, "died": 2023,
        "description": `
        Israeli actor and singer, usually credited simply as 'Topol'. He was
        best known for his many appearances as Tevye in the musical Fiddler
        on the Roof.
        `
    }
};

const client = createClient({ url: 'redis://localhost:6379' });

client.on('error', err => console.log('Redis Client Error', err));
await client.connect();

for (const [name, details] of Object.entries(peopleData)) {
    const embedding = await pipe(details.description, pipeOptions);
    const embeddingArray = Array.from(embedding.data);

    await client.vAdd('famousPeople', embeddingArray, name);
    await client.vSetAttr('famousPeople', name, JSON.stringify({
        born: details.born,
        died: details.died
    }));
}

const queryValue = "actors";

const queryEmbedding = await pipe(queryValue, pipeOptions);
const queryArray = Array.from(queryEmbedding.data);

const actorsResults = await client.vSim('famousPeople', queryArray);

console.log(`'actors': ${JSON.stringify(actorsResults)}`);
// >>> 'actors': ["Masako Natsume","Chaim Topol","Linus Pauling",
// "Marie Fredriksson","Maryam Mirzakhani","Freddie Mercury",
// "Marie Curie","Paul Erdos"]

const queryValue2 = "actors";

const queryEmbedding2 = await pipe(queryValue2, pipeOptions);
const queryArray2 = Array.from(queryEmbedding2.data);

const twoActorsResults = await client.vSim('famousPeople', queryArray2, {
    COUNT: 2
});

console.log(`'actors (2)': ${JSON.stringify(twoActorsResults)}`);
// >>> 'actors (2)': ["Masako Natsume","Chaim Topol"]

const queryValue3 = "entertainer";

const queryEmbedding3 = await pipe(queryValue3, pipeOptions);
const queryArray3 = Array.from(queryEmbedding3.data);

const entertainerResults = await client.vSim('famousPeople', queryArray3);

console.log(`'entertainer': ${JSON.stringify(entertainerResults)}`);
// >>> 'actors': ["Masako Natsume","Chaim Topol","Linus Pauling",
// "Marie Fredriksson","Maryam Mirzakhani","Freddie Mercury",
// "Marie Curie","Paul Erdos"]

const queryValue4 = "science";

const queryEmbedding4 = await pipe(queryValue4, pipeOptions);
const queryArray4 = Array.from(queryEmbedding4.data);

const scienceResults = await client.vSim('famousPeople', queryArray4);

console.log(`'science': ${JSON.stringify(scienceResults)}`);
// >>> 'science': ["Linus Pauling","Marie Curie","Maryam Mirzakhani",
// "Paul Erdos","Marie Fredriksson","Masako Natsume","Freddie Mercury",
// "Chaim Topol"]

const queryValue5 = "science";

const queryEmbedding5 = await pipe(queryValue5, pipeOptions);
const queryArray5 = Array.from(queryEmbedding5.data);

const science2000Results = await client.vSim('famousPeople', queryArray5, {
    FILTER: '.died < 2000'
});

console.log(`'science2000': ${JSON.stringify(science2000Results)}`);
// >>> 'science2000': ["Linus Pauling","Marie Curie","Paul Erdos",
// "Masako Natsume","Freddie Mercury"]

await client.quit();

from sentence_transformers import SentenceTransformer

import redis
import numpy as np

model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")

peopleData = {
    "Marie Curie": {
        "born": 1867, "died": 1934,
        "description": """
        Polish-French chemist and physicist. The only person ever to win
        two Nobel prizes for two different sciences.
        """
    },
    "Linus Pauling": {
        "born": 1901, "died": 1994,
        "description": """
        American chemist and peace activist. One of only two people to win two
        Nobel prizes in different fields (chemistry and peace).
        """
    },
    "Freddie Mercury": {
        "born": 1946, "died": 1991,
        "description": """
        British musician, best known as the lead singer of the rock band
        Queen.
        """
    },
    "Marie Fredriksson": {
        "born": 1958, "died": 2019,
        "description": """
        Swedish multi-instrumentalist, mainly known as the lead singer and
        keyboardist of the band Roxette.
        """
    },
    "Paul Erdos": {
        "born": 1913, "died": 1996,
        "description": """
        Hungarian mathematician, known for his eccentric personality almost
        as much as his contributions to many different fields of mathematics.
        """
    },
    "Maryam Mirzakhani": {
        "born": 1977, "died": 2017,
        "description": """
        Iranian mathematician. The first woman ever to win the Fields medal
        for her contributions to mathematics.
        """
    },
    "Masako Natsume": {
        "born": 1957, "died": 1985,
        "description": """
        Japanese actress. She was very famous in Japan but was primarily
        known elsewhere in the world for her portrayal of Tripitaka in the
        TV series Monkey.
        """
    },
    "Chaim Topol": {
        "born": 1935, "died": 2023,
        "description": """
        Israeli actor and singer, usually credited simply as 'Topol'. He was
        best known for his many appearances as Tevye in the musical Fiddler
        on the Roof.
        """
    }
}

r = redis.Redis(decode_responses=True)

for name, details in peopleData.items():
    emb = model.encode(details["description"]).astype(np.float32).tobytes()

    r.vset().vadd(
        "famousPeople",
        emb,
        name,
        attributes={
            "born": details["born"],
            "died": details["died"]
        }
    )

query_value = "actors"

actors_results = r.vset().vsim(
    "famousPeople",
    model.encode(query_value).astype(np.float32).tobytes(),
)

print(f"'actors': {actors_results}")

query_value = "actors"

two_actors_results = r.vset().vsim(
    "famousPeople",
    model.encode(query_value).astype(np.float32).tobytes(),
    count=2
)

print(f"'actors (2)': {two_actors_results}")
# >>> 'actors (2)': ['Masako Natsume', 'Chaim Topol']

query_value = "entertainer"

entertainer_results = r.vset().vsim(
    "famousPeople",
    model.encode(query_value).astype(np.float32).tobytes()
)

print(f"'entertainer': {entertainer_results}")
# >>> 'entertainer': ['Chaim Topol', 'Freddie Mercury',
# 'Marie Fredriksson', 'Masako Natsume', 'Linus Pauling',
# 'Paul Erdos', 'Maryam Mirzakhani', 'Marie Curie']

query_value = "science"

science_results = r.vset().vsim(
    "famousPeople",
    model.encode(query_value).astype(np.float32).tobytes()
)

print(f"'science': {science_results}")
# >>> 'science': ['Marie Curie', 'Linus Pauling',
# 'Maryam Mirzakhani', 'Paul Erdos', 'Marie Fredriksson',
# 'Freddie Mercury', 'Masako Natsume', 'Chaim Topol']

query_value = "science"

science2000_results = r.vset().vsim(
    "famousPeople",
    model.encode(query_value).astype(np.float32).tobytes(),
    filter=".died < 2000"
)

print(f"'science2000': {science2000_results}")
# >>> 'science2000': ['Marie Curie', 'Linus Pauling',
# 'Paul Erdos', 'Freddie Mercury', 'Masako Natsume']

static float[] GetEmbedding(
    PredictionEngine<TextData, TransformedTextData> model, string sentence
)
{
    // Call the prediction API to convert the text into embedding vector.
    var data = new TextData()
    {
        Text = sentence
    };

    var prediction = model.Predict(data);

    float[] floatArray = Array.ConvertAll(prediction.Features, x => (float)x);

    return floatArray;
}

Dictionary<string, dynamic> peopleData = new Dictionary<string, dynamic>
{
    ["Marie Curie"] = new
    {
        born = 1867,
        died = 1934,
        description = @"
        Polish-French chemist and physicist. The only person ever to win
        two Nobel prizes for two different sciences.
        "
    },
    ["Linus Pauling"] = new
    {
        born = 1901,
        died = 1994,
        description = @"
        American chemist and peace activist. One of only two people to win two
        Nobel prizes in different fields (chemistry and peace).
        "
    },
    ["Freddie Mercury"] = new
    {
        born = 1946,
        died = 1991,
        description = @"
        British musician, best known as the lead singer of the rock band
        Queen.
        "
    },
    ["Marie Fredriksson"] = new
    {
        born = 1958,
        died = 2019,
        description = @"
        Swedish multi-instrumentalist, mainly known as the lead singer and
        keyboardist of the band Roxette.
        "
    },
    ["Paul Erdos"] = new
    {
        born = 1913,
        died = 1996,
        description = @"
        Hungarian mathematician, known for his eccentric personality almost
        as much as his contributions to many different fields of mathematics.
        "
    },
    ["Maryam Mirzakhani"] = new
    {
        born = 1977,
        died = 2017,
        description = @"
        Iranian mathematician. The first woman ever to win the Fields medal
        for her contributions to mathematics.
        "
    },
    ["Masako Natsume"] = new
    {
        born = 1957,
        died = 1985,
        description = @"
        Japanese actress. She was very famous in Japan but was primarily
        known elsewhere in the world for her portrayal of Tripitaka in the
        TV series Monkey.
        "
    },
    ["Chaim Topol"] = new
    {
        born = 1935,
        died = 2023,
        description = @"
        Israeli actor and singer, usually credited simply as 'Topol'. He was
        best known for his many appearances as Tevye in the musical Fiddler
        on the Roof.
        "
    }
};

ConnectionMultiplexer muxer = ConnectionMultiplexer.Connect("localhost:6379");
IDatabase db = muxer.GetDatabase();

PredictionEngine<TextData, TransformedTextData> model = GetPredictionEngine();

foreach (KeyValuePair<string, dynamic> person in peopleData)
{
    string name = person.Key;
    dynamic details = person.Value;
    float[] embedding = GetEmbedding(model, details.description);

    VectorSetAddRequest addRequest = VectorSetAddRequest.Member(name, embedding, null);
    db.VectorSetAdd("famousPeople", addRequest);

    // Set attributes separately
    string attributesJson = $"{{\"born\": {details.born}, \"died\": {details.died}}}";
    db.VectorSetSetAttributesJson("famousPeople", name, attributesJson);
}

string queryValue = "actors";

float[] queryEmbedding = GetEmbedding(model, queryValue);

VectorSetSimilaritySearchRequest basicQuery = VectorSetSimilaritySearchRequest.ByVector(queryEmbedding);
using (Lease<VectorSetSimilaritySearchResult>? actorsResults = db.VectorSetSimilaritySearch("famousPeople", basicQuery))
{
    IEnumerable<string> resultIds = actorsResults!.Span.ToArray()
        .Select(r => (string?)r.Member)
        .Where(id => id != null)
        .Select(id => $"'{id!}'");
    Console.WriteLine($"'actors': [{string.Join(", ", resultIds)}]");
}

'actors': ['Masako Natsume', 'Chaim Topol', 'Linus Pauling',
'Marie Fredriksson', 'Maryam Mirzakhani', 'Marie Curie',
'Freddie Mercury', 'Paul Erdos']

queryValue = "actors";

queryEmbedding = GetEmbedding(model, queryValue);

VectorSetSimilaritySearchRequest limitedQuery = VectorSetSimilaritySearchRequest.ByVector(queryEmbedding);
limitedQuery.Count = 2;
using (Lease<VectorSetSimilaritySearchResult>? twoActorsResults = db.VectorSetSimilaritySearch("famousPeople", limitedQuery))
{
    IEnumerable<string> resultIds = twoActorsResults!.Span.ToArray()
        .Select(r => (string?)r.Member)
        .Where(id => id != null)
        .Select(id => $"'{id!}'");
    Console.WriteLine($"'actors (2)': [{string.Join(", ", resultIds)}]");
    // >>> 'actors (2)': ['Masako Natsume', 'Chaim Topol']
}

queryValue = "entertainer";

queryEmbedding = GetEmbedding(model, queryValue);

VectorSetSimilaritySearchRequest entertainerQuery = VectorSetSimilaritySearchRequest.ByVector(queryEmbedding);
using (Lease<VectorSetSimilaritySearchResult>? entertainerResults = db.VectorSetSimilaritySearch("famousPeople", entertainerQuery))
{
    IEnumerable<string> resultIds = entertainerResults!.Span.ToArray()
        .Select(r => (string?)r.Member)
        .Where(id => id != null)
        .Select(id => $"'{id!}'");
    Console.WriteLine($"'entertainer': [{string.Join(", ", resultIds)}]");
    // >>> 'entertainer': ['Chaim Topol', 'Freddie Mercury',
    // 'Marie Fredriksson', 'Masako Natsume', 'Linus Pauling',
    // 'Paul Erdos', 'Maryam Mirzakhani', 'Marie Curie']
}

'science': ['Marie Curie', 'Linus Pauling', 'Maryam Mirzakhani',
'Paul Erdos', 'Marie Fredriksson', 'Freddie Mercury', 'Masako Natsume',
'Chaim Topol']

queryValue = "science";

queryEmbedding = GetEmbedding(model, queryValue);

VectorSetSimilaritySearchRequest filteredQuery = VectorSetSimilaritySearchRequest.ByVector(queryEmbedding);
filteredQuery.FilterExpression = ".died < 2000";
using (Lease<VectorSetSimilaritySearchResult>? science2000Results = db.VectorSetSimilaritySearch("famousPeople", filteredQuery))
{
    IEnumerable<string> resultIds = science2000Results!.Span.ToArray()
        .Select(r => (string?)r.Member)
        .Where(id => id != null)
        .Select(id => $"'{id!}'");
    Console.WriteLine($"'science2000': [{string.Join(", ", resultIds)}]");
    // >>> 'science2000': ['Marie Curie', 'Linus Pauling',
    // 'Paul Erdos', 'Freddie Mercury', 'Masako Natsume']
}


Error handling

Summary: Learn how to handle errors when using Redis client libraries.

Overview

When working with Redis, errors can occur for various reasons, including network issues, invalid commands, or resource constraints. This guide explains the types of errors you might encounter and how to handle them effectively.

Categories of errors

Redis errors fall into four main categories. The table below provides a quick overview of each type. Click on any error type to jump to its detailed section, which includes common causes, examples, handling strategies, and code examples.

| Error Type | Common Causes | When to Handle | Examples |
|---|---|---|---|
| [Connection errors](#connection-errors) | Network issues, server down, auth failure, timeouts, pool exhaustion | Almost always | `ConnectionError`, `TimeoutError`, `AuthenticationError` |
| [Command errors](#command-errors) | Typo in command, wrong arguments, invalid types, unsupported command | Rarely (usually indicates a bug) | `ResponseError`, `WRONGTYPE`, `ERR unknown command` |
| [Data errors](#data-errors) | Serialization failures, corrupted data, type mismatches | Sometimes (depends on data source) | `JSONDecodeError`, `SerializationError`, `WRONGTYPE` |
| [Resource errors](#resource-errors) | Memory limit, pool exhausted, too many connections, key eviction | Sometimes (some are temporary) | `OOM`, pool timeout, `LOADING` |

Connection errors

Connection errors occur when your application cannot communicate with Redis. These are typically temporary and often recoverable.

**Common causes:**
- Network connectivity issues
- Redis server is down or unreachable
- Authentication failure
- Connection timeout
- Connection pool exhaustion

**Examples:**
- `ConnectionError`: Network failure or server unreachable
- `TimeoutError`: Operation exceeded the configured timeout
- `AuthenticationError`: Invalid credentials

**When to handle:** Almost always. Connection errors are usually temporary, so implementing retry logic or fallback strategies is recommended.

**Example strategy:**

[code example]

Command errors

Command errors occur when Redis receives an invalid or malformed command. These typically indicate a bug in your code.

**Common causes:**
-   Typo in command name
-   Wrong number of arguments
-   Invalid argument types (for example, supplying a
    [string]() key to a
    [list]() command))
-   Using a command that doesn't exist in your Redis version

**Examples:**
- `ResponseError`: Invalid command or syntax error
- `WRONGTYPE Operation against a key holding the wrong kind of value`
- `ERR unknown command`

**When to handle:** Rarely. These usually indicate programming error and so you
should fix the errors in your code rather than attempt to handle them at runtime. However, some cases (like invalid user input) may be worth handling.

**Example:**

[code example]

Data errors

Data errors occur when there are problems with the data itself, such as
serialization failures, or data corruption.

**Common causes:**
- Object cannot be serialized to JSON
- Cached data is corrupted
- Attempting to deserialize invalid data

**Examples:**
- `JSONDecodeError`: Cannot deserialize JSON data
- `SerializationError`: Cannot serialize object

**When to handle:** Sometimes. If the error is due to user input or external data, handle it gracefully. If it's due to your code, fix the code.

**Example:**

[code example]

Resource errors

Resource errors occur when Redis runs out of resources or hits limits.

**Common causes:**
- Memory limit reached
- Connection pool exhausted
- Too many connections
- Key eviction due to memory pressure

**Examples:**
- `OOM command not allowed when used memory > 'maxmemory'`
- Connection pool timeout
- `LOADING Redis is loading the dataset in memory`

**When to handle:** Sometimes. Some resource errors are temporary (Redis loading), while others indicate a configuration problem.

**Example:**

[code example]

Error handling patterns

Pattern 1: Fail fast

Use this when the error is unrecoverable or indicates a bug in your code.

**When to use:**

- Command errors (invalid syntax)
- Authentication errors
- Programming errors

**Example:**

[code example]

Pattern 2: Graceful degradation

Use this when you have an alternative way to get the data you need, so you can
fall back to using the alternative instead of the preferred code.

**When to use:**

- Cache reads (fallback to database)
- Session reads (fallback to default values)
- Optional data (skip if unavailable)

**Example:**

[code example]

Pattern 3: Retry with backoff

Use this when the error could be due to network load or other temporary
conditions.

**When to use:**

- Connection timeouts
- Temporary network issues
- Redis loading data

**Example:**

[code example]

Note that client libraries often implement retry logic for you, so
you may just need to provide the right configuration rather than
implementing retries yourself. See [Client-specific error handling](#client-specific-error-handling) below for links to pages that
describe retry configuration for each client library.

Pattern 4: Log and continue

Use this when the operation is not critical to your application.

**When to use:**

- Cache writes (data loss is acceptable)
- Non-critical updates
- Metrics collection

**Example:**

[code example]

Decision tree: How to handle errors

[code example]

Logging and monitoring

In production, you may find it useful to log errors when they
occur and monitor the logs for patterns. This can help you identify
which errors are most common and whether your retry and fallback
strategies are effective. Note that some Redis client
libraries have built-in instrumentation that can provide this
information for you (see [Observability]()
for a full description).

What to log

- **Error type and message:** What went wrong?
- **Context:** Which key? Which operation?
- **Timestamp:** When did it happen?
- **Retry information:** Is this a retry? How many attempts?

**Example:**
[code example]

What to monitor

- **Error rate:** How many errors per minute?
- **Error types:** Which errors are most common?
- **Recovery success:** How many retries succeed?
- **Fallback usage:** How often do we use fallback strategies?

These metrics help you identify patterns and potential issues.

Common mistakes

Catching all exceptions

**Problem:** If you catch all exceptions, you might catch unexpected
errors and hide bugs.

**Example (wrong):**

[code example]

**Better approach:** Catch specific exception types.

**Example (correct):**

[code example]

Not distinguishing error types

**Problem:** Different errors need different handling. For example, retrying a syntax error won't help.

**Example (wrong):**

[code example]

**Better approach:** Handle each error type differently based on whether or not
it is recoverable.

**Example (correct):**

[code example]

Ignoring connection pool errors

**Problem:** Connection pool errors indicate a configuration or concurrency issue that needs to be addressed.

**Example (wrong):**

[code example]

**Better approach:** Monitor pool usage and increase size if needed.

Client-specific error handling

For detailed information about exceptions in your client library, see:

- [redis-py error handling]()
- [Node.js error handling]()
- [Java (Jedis) error handling]()
- [Go (go-redis) error handling]()
- [.NET (NRedisStack) error handling]()

graph TB
    A["Try to connect
to Redis"]
    A -->|Success| B(["Use the result"])
    A -->|Failure| C{Error type?}
    C -->|Timeout| D(["Retry with
exponential backoff"])
    C -->|Auth failure| E(["Check credentials
and fail"])
    C -->|Network error| F(["Fallback to
alternative data source"])

graph TB
    A["User provides
JSONPath expression"]
    A --> B["Try to execute it"]
    direction TB
    B -->|ResponseError| C["Log the error"]
    C --> D(["Return default value
or error message to user"])
    B -->|Success| E(["Use the result"])

graph TB
    A["Read cached data"]
    A --> B["Try to deserialize"]
    B -->|Success| C(["Use the data"])
    B -->|Deserialization fails| D["Log the error"]
    D --> E["Delete corrupted
cache entry"]
    E --> F(["Fetch fresh data
from source"])

graph TB
    A{Resource error
occurred?}
    A -->|Redis loading| B(["Retry after
a delay"])
    A -->|Memory full| C(["Check Redis
configuration and data"])
    A -->|Pool exhausted| D(["Increase pool size
or reduce concurrency"])

try:
    result = r.get(key)
except redis.ResponseError as e:
    # This indicates a bug in our code
    raise  # Re-raise the exception

try:
    cached_value = r.get(key)
    if cached_value:
        return cached_value
except redis.ConnectionError:
    logger.warning("Cache unavailable, using database")

# Fallback to database
return database.get(key)

import time

max_retries = 3
retry_delay = 0.1

for attempt in range(max_retries):
    try:
        return r.get(key)
    except redis.TimeoutError:
        if attempt < max_retries - 1:
            time.sleep(retry_delay)
            retry_delay *= 2  # Exponential backoff
        else:
            raise

try:
    r.setex(key, 3600, value)
except redis.ConnectionError:
    logger.warning(f"Failed to cache {key}, continuing without cache")
    # Application continues normally

graph LR
    Start{Error occurred?}

    Start -->|Connection error| C1{Operation type?}
    C1 -->|Read| C2["Graceful degradation
fallback"]
    C1 -->|Write| C3["Log and continue
or retry"]
    C1 -->|Critical| C4["Retry with backoff"]

    Start -->|Command error| Cmd1{Error source?}
    Cmd1 -->|User input| Cmd2["Log and return
error to user"]
    Cmd1 -->|Your code| Cmd3["Fail fast
fix the bug"]

    Start -->|Data error| D1{Operation type?}
    D1 -->|Read| D2["Log, invalidate,
fallback"]
    D1 -->|Write| D3["Log and fail
data is invalid"]

    Start -->|Resource error| R1{Error type?}
    R1 -->|Redis loading| R2["Retry with backoff"]
    R1 -->|Pool exhausted| R3["Increase pool size"]
    R1 -->|Memory full| R4["Check configuration"]

logger.error(
    "Redis operation failed",
    extra={
        "error_type": type(e).__name__,
        "operation": "get",
        "key": key,
        "attempt": attempt,
        "timestamp": datetime.now().isoformat(),
    }
)

try:
    result = r.get(key)
except Exception:  # Too broad - some errors indicate code problems.
    pass

try:
    result = r.get(key)
except redis.ConnectionError:
    # Handle connection error
    pass

try:
    result = r.get(key)
except redis.ResponseError:
    # Retry? This won't help if it's a syntax error.
    retry()

try:
    result = r.get(key)
except redis.TimeoutError:
    retry()  # Retry on timeout
except redis.ResponseError:
    raise   # Fail on syntax error

# Pool is exhausted, but we don't handle it
result = r.get(key)  # Might timeout waiting for connection


Client-side geographic failover

Summary: Improve reliability using the failover/failback features of client libraries.

Overview

Some Redis client libraries support
[Client-side geographic failover](https://en.wikipedia.org/wiki/Failover)
to improve the availability of connections to Redis databases. Use this page
to get a general overview of the concepts and then see the documentation for
your client library to learn how to configure it for failover and failback:

- [Jedis]()
- [redis-py]() (preview)
- [Lettuce]() (preview)

Concepts

You may have several [Active-Active databases]()
or independent Redis servers that are all suitable to serve your app.
Typically, you would prefer to use some database endpoints over others for a particular
instance of your app (perhaps the ones that are closest geographically to the app server
to reduce network latency). However, if the best endpoint is not available due
to a failure, it is generally better to switch to another, suboptimal endpoint
than to let the app fail completely.

*Failover* is the technique of actively checking for connection failures or
unacceptably slow connections and automatically switching to the best available endpoint
when they occur. This requires you to specify a list of endpoints to try, ordered by priority. The diagram below shows this process:

The complementary technique of *failback* then involves periodically checking the health
of all endpoints that have failed. If any endpoints recover, the failback mechanism
automatically switches the connection to the one with the highest priority.
This could potentially be repeated until the optimal endpoint is available again.

Detecting connection problems

Redis clients use a [circuit breaker design pattern](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) to detect connection problems.

The circuit breaker is a software component that tracks the sequence of recent
Redis connection attempts and commands, recording which ones have succeeded and
which have failed.
(Note that many command failures are caused by transient errors such as timeouts,
so before recording a failure, the first response should usually be just to retry
the command a few times.)

The status of the attempted command calls is kept in a "sliding window", which
is simply a buffer where the least recent item is dropped as each new
one is added. The buffer can be configured to have a fixed number of failures and/or a failure ratio (specified as a percentage), both based on a time window.

When the number of failures in the window exceeds a configured
threshold, the circuit breaker declares the server to be unhealthy and triggers
a failover.

Selecting a failover target

Since you may have multiple Redis servers available to fail over to, the client
lets you configure a list of endpoints to try, ordered by priority or
"weight". When a failover is triggered, the client selects the highest-weighted
endpoint that is still healthy and uses it for the temporary connection.

Health checks

Given that the original endpoint had some geographical or other advantage
over the failover target, you will generally want to fail back to it as soon
as it recovers. In the meantime, another server might recover that is
still better than the current failover target, so it might be worth
failing back to that server even if it is not optimal.

Clients periodically run a "health check" on each server to see if it has recovered.
Several health check strategies are implemented in all clients:

-   **Ping**: This is the default strategy, which just sends a
    [`PING`]() command and ensures that it gives the
    expected response.
-   **Lag aware** (Redis Software only): This strategy uses the
    [REST API]() to check the
    synchronization lag between a specific database and the others in the Active-Active
    setup. If the lag is within a specified tolerance, the server is considered healthy.
-   **Custom**: You can implement your own health check strategy to use information
    or take action that is specific to your application.

See the documentation for your client library for more information on how to
configure health checks.

You can also configure the client to run health checks on the current target
server during periods of inactivity, even if no failover has occurred. This can
help to detect problems even if your app is not actively using the server.


Connect to Azure Managed Redis

Summary: Learn how to authenticate to an Azure Managed Redis (AMR) database

Overview

The [`go-redis-entraid`](https://github.com/redis/go-redis-entraid) package
lets you authenticate your app to
[Azure Managed Redis (AMR)](https://azure.microsoft.com/en-us/products/managed-redis)
using [Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/).
You can authenticate using a system-assigned or user-assigned
[managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview)
or a [service principal](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals),
letting `go-redis-entraid` fetch and renew the authentication tokens for you automatically.

Install

Install [`go-redis`]() if you
have not already done so. Note that `go-redis-entraid`
requires `go-redis` v9.9.0 or above, so you should upgrade if you
are using an earlier version.

Install `go-redis-entraid` with the following command:

[code example]

Create a `StreamingCredentialsProvider` instance

The `StreamingCredentialsProvider` interface defines methods
to provide credentials for authentication. Use an object that
implements this interface to obtain the authentication credentials you
need when you connect to Redis. See the sections below to learn how
to create the `StreamingCredentialsProvider` instances for AMR
using the factory functions that `go-redis-entraid` provides.

`StreamingCredentialsProvider` for a service principal

Use the `NewConfidentialCredentialsProvider()` factory function to create a
`StreamingCredentialsProvider` that authenticates to AMR using a
service principal (see the
[Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals) to learn more about service principals).

You will need the following details of your service principal to make the connection:

- Client ID
- Client secret
- Tenant ID

Use an `AuthorityConfiguration` instance to pass the tenant ID.
This type has the following fields:

-   `AuthorityType`: This should have one of the values
    - `identity.AuthorityTypeDefault` ("default")
    - `identity.AuthorityTypeMultiTenant` ("multi-tenant")
    - `identity.AuthorityTypeCustom` ("custom")
-   `TenantID`: Pass your tenant ID string here, or use "common" for
    a multi-tentant application.
-   `Authority`: Custom authority URL. This is only required if you
    specified `AuthorityTypeCustom` in the `AuthorityType` field.

The example below shows how to import the required modules and call
`NewConfidentialCredentialsProvider()`:

[code example]

`StreamingCredentialsProvider` for a managed identity

Use the `NewManagedIdentityCredentialsProvider()` function to create a
`StreamingCredentialsProvider` that authenticates to AMR using a
managed identity (see the
[Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) to learn more about managed identities).

The example below shows how to import the required modules and call
`NewManagedIdentityCredentialsProvider()`.
Pass `identity.SystemAssignedIdentity` or `identity.UserAssignedIdentity`
as the `ManagedIdentityType` parameter.

[code example]

Custom configuration

The examples above use a default configuration but you can also provide a custom
configuration using the `TokenManagerOptions` field of `CredentialsProviderOptions`:

[code example]

You can then pass this configuration when you create the
`StreamingCredentialsProvider`. The example below shows how to do this
with the `NewManagedIdentityCredentialsProvider()` method:

[code example]

The fields of `TokenManagerOptions` are explained below:

-   `ExpirationRefreshRatio`: A `float` value representing the fraction
    of a token's lifetime that should elapse before attempting to
    refresh it. For example, a value of 0.75 means that you want to
    refresh the token after 75% of its lifetime has passed.
-   `LowerRefreshBounds`: The minimum amount of the token's lifetime
    (in milliseconds) remaining before attempting to refresh, regardless
    of the `expirationRefreshRatio` value. Set this to zero if you want
    the refresh time to depend only on `expirationRefreshRatio`.
-   `RetryOptions`: This object specifies how to retry a token request
    after failure. The available options are:
    -   `MaxAttempts`: The maximum number of times to retry a token request
        The default value is 3.
    -   `InitialDelay`: The initial delay between retries in milliseconds. This
        will be modified during successive attempts by the `BackoffMultiplier`
        value (see below). The default is 1000ms.
    -   `BackoffMultiplier`: The factor by which the `InitialDelay` is multiplied
        between attempts, following an
        [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff)
        strategy. The default multiplier is 2.0.
    -   `IsRetryable`: A function that receives an `error` parameter and returns
        a boolean `true` result if an attempt that failed with that error is
        retryable and `false` otherwise. Use this to implement your own custom
        logic to decide which errors should be retried.

Connect

When you have created your `StreamingCredentialsProvider` instance, you are ready to
connect to AMR.
The example below shows how to pass the instance as a parameter to the standard
`NewClusterClient()` connection method. It also illustrates how to use
[`os.Getenv()`](https://pkg.go.dev/os#Getenv) to get the connection details
from environment variables rather than include their values in the code.

[code example]

More information

See the [`go-redis-entraid`](https://github.com/redis/go-redis-entraid)
GitHub repository for full source code and more examples and details.

import (
    "github.com/redis-developer/go-redis-entraid/entraid"
    "github.com/redis-developer/go-redis-entraid/identity"
        ...
)
    .
    .
provider, err := entraid.NewConfidentialCredentialsProvider(
    entraid.ConfidentialCredentialsProviderOptions{
	    ConfidentialIdentityProviderOptions: identity.ConfidentialIdentityProviderOptions{
		    ClientID:        "<your-azure-client-id>",
		    ClientSecret:    "<your-azure-client-secret>",
		    CredentialsType: identity.ClientSecretCredentialType,
		    Authority: identity.AuthorityConfiguration{
			    AuthorityType: identity.AuthorityTypeDefault,
			    TenantID:      "<your-azure-tenant-id>",
		    },
	    },
    },
)

import (
    "github.com/redis-developer/go-redis-entraid/entraid"
    "github.com/redis-developer/go-redis-entraid/identity"
        ...
)
    .
    .
provider, err := entraid.NewManagedIdentityCredentialsProvider(
	entraid.ManagedIdentityCredentialsProviderOptions{
		ManagedIdentityProviderOptions: identity.ManagedIdentityProviderOptions{
			ManagedIdentityType:  identity.UserAssignedObjectID,
			UserAssignedObjectID: "<your-user-assigned-client-id>",
		},
	},
)

options := entraid.CredentialsProviderOptions{
    TokenManagerOptions: manager.TokenManagerOptions{
        ExpirationRefreshRatio: 0.7,
        LowerRefreshBounds: 10000,
        RetryOptions: manager.RetryOptions{
            MaxAttempts: 3,
            InitialDelay: 1000 * time.Millisecond,
            MaxDelay: 30000 * time.Millisecond,
            BackoffMultiplier: 2.0,
            IsRetryable: func(err error) bool {
                return strings.Contains(err.Error(), "network error") ||
                    strings.Contains(err.Error(), "timeout")
            },
        },
    },
}

provider, err := entraid.NewManagedIdentityCredentialsProvider(
	entraid.ManagedIdentityCredentialsProviderOptions{
	    CredentialsProviderOptions: options,
		ManagedIdentityProviderOptions: identity.ManagedIdentityProviderOptions{
			ManagedIdentityType:  identity.UserAssignedObjectID,
			UserAssignedObjectID: "<your-user-assigned-client-id>",
		},
	},
)

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "strings"

    "github.com/redis-developer/go-redis-entraid/entraid"
    "github.com/redis/go-redis/v9"
)

func main() {
    // Get required environment variables
    redisEndpoint := os.Getenv("REDIS_ENDPOINT")
    if redisEndpoint == "" {
        log.Fatal(
            "REDIS_ENDPOINT environment variable is required"
        )
    }

    // Create credentials provider for system assigned identity
    provider, err := entraid.NewManagedIdentityCredentialsProvider(
        entraid.ManagedIdentityCredentialsProviderOptions{
		    ManagedIdentityProviderOptions: identity.ManagedIdentityProviderOptions{
			    ManagedIdentityType: identity.SystemAssignedIdentity,
		    },
        }
    )
    if err != nil {
        log.Fatalf("Failed to create credentials provider: %v", err)
    }

    // Create Redis client
    // Note: AMR databases have clustering enabled by default.
    client := redis.NewClusterClient(&redis.Options{
        Addr: redisEndpoint,
        StreamingCredentialsProvider: provider,
    })
    defer client.Close()

    // Test connection
    ctx := context.Background()
    if err := client.Ping(ctx).Err(); err != nil {
        log.Fatalf("Failed to connect to Redis: %v", err)
    }
    log.Println("Connected to Redis!")
}