Skip to content

Commit

Permalink
readme updates
Browse files Browse the repository at this point in the history
  • Loading branch information
slorello89 committed Nov 2, 2023
1 parent 16c694c commit 73c6c05
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 22 deletions.
98 changes: 98 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,104 @@ customers.Where(x => x.LastName == "Bond" && x.FirstName == "James");
customers.Where(x=>x.NickNames.Contains("Jim"));
```

### Vectors

Redis OM .NET also supports storing and querying Vectors stored in Redis.

A `Vector<T>` is a representation of an object that can be transformed into a vector by a Vectorizer.

A `VectorizerAttribute` is the abstract class you use to decorate your Vector fields, it is responsible for defining the logic to convert your Vectors into Embeddings. In the package `Redis.OM.Vectorizers` we provide vectorizers for HuggingFace, OpenAI, and AzureOpenAI to allow you to easily integrate them into your workflows.

#### Define a Vector in your Model.

To define a vector in your model, simply decorate a `Vector<T>` field with and `Indexed` and a `Vectorizer` attribute (in this case we'll use OpenAI):

```cs
[Document(StorageType = StorageType.Json)]
public class OpenAIQuery
{
[RedisIdField]
public string Id { get; set; }

[Indexed(DistanceMetric = DistanceMetric.COSINE)]
[OpenAIVectorizer]
public Vector<string> Prompt { get; set; }

public string Response { get; set; }

[Indexed]
public string Language { get; set; }

[Indexed]
public DateTime TimeStamp { get; set; }
}
```

#### Insert Vectors into Redis

With the vector defined in our model, all we need to do is create Vectors of the generic type, and insert them with our model. Using our `RedisCollection`, you can do this by simply using `Insert`:

```cs
var query = new OpenAIQuery
{
Language = "en_us",
Prompt = Vector.Of("What is the Capital of France?"),
Response = "Paris",
TimeStamp = DateTime.Now - TimeSpan.FromHours(3)
};
collection.Insert(query);
```

The Vectorizer will manage the embedding generation for you without you having to intervene.

#### Query Vectors in Redis

To query vector fields in Redis, all you need to do is use the `VectorRange` method on a vector within our normal LINQ queries, and/or use the `NearestNeighbors` with whatever other filters you want to use, here's some examples:

```cs
var queryPrompt = Vector.Of("What really is the Capital of France?");

// simple vector range, find first within .15
var result = collection.First(x => x.Prompt.VectorRange(queryPrompt, .15));

// simple nearest neighbors query, finds first nearest neighbor
result = collection.NearestNeighbors(x => x.Prompt, 1, queryPrompt).First();

// hybrid query, pre-filters result set for english responses, then runs a nearest neighbors search.
result = collection.Where(x=>x.Language == "en_us").NearestNeighbors(x => x.Prompt, 1, queryPrompt).First();

// hybrid query, pre-filters responses newer than 4 hours, and finds first result within .15
var ts = DateTimeOffset.Now - TimeSpan.FromHours(4);
result = collection.First(x=>x.TimeStamp > ts && x.Prompt.VectorRange(queryPrompt, .15));
```

#### What Happens to the Embeddings?

With Redis OM, the embeddings can be completely transparent to you, they are generated and bound to the `Vector<T>` when you query/insert your vectors. If however you needed your embedding after the insertion/Query, they are available at `Vector<T>.Embedding`, and be queried either as the raw bytes, as an array of doubles or as an array of floats (depending on your vectorizer).

#### Configuration

The Vectorizers provided by the `Redis.OM.Vectorizers` package have some configuration parameters that it will pull in either from your `appsettings.json` file, or your environment variables (with your appsettings taking precedence).

| Configuration Parameter | Description |
|-------------------------------- |-----------------------------------------------|
| REDIS_OM_HF_TOKEN | HuggingFace Authorization token. |
| REDIS_OM_OAI_TOKEN | OpenAI Authorization token |
| REDIS_OM_OAI_API_URL | OpenAI URL |
| REDIS_OM_AZURE_OAI_TOKEN | Azure OpenAI api key |
| REDIS_OM_AZURE_OAI_RESOURCE_NAME | Azure resource name |
| REDIS_OM_AZURE_OAI_DEPLOYMENT_NAME | Azure deployment |

### Semantic Caching

Redis OM also provides the ability to use Semantic Caching, as well as providers for OpenAI, HuggingFace, and Azure OpenAI to perform semantic caching. To use a Semantic Cache, simply pull one out of the RedisConnectionProvider and use `Store` to insert items, and `GetSimilar` to retrieve items. For example:

```cs
var cache = _provider.OpenAISemanticCache(token);
cache.Store("What is the capital of France?", "Paris");
var res = cache.GetSimilar("What really is the capital of France?").First();
```

### 🖩 Aggregations

We can also run aggregations on the customer object, again using expressions in LINQ:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

namespace Redis.OM.Vectorizers;

public class AzureOpenAISentenceVectorizer : IVectorizer<string>
public class AzureOpenAIVectorizer : IVectorizer<string>
{
private readonly string _apiKey;
private readonly string _resourceName;
private readonly string _deploymentName;

public AzureOpenAISentenceVectorizer(string apiKey, string resourceName, string deploymentName, int dim)
public AzureOpenAIVectorizer(string apiKey, string resourceName, string deploymentName, int dim)
{
_apiKey = apiKey;
_resourceName = resourceName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
namespace Redis.OM.Vectorizers;

/// <inheritdoc />
public class AzureOpenAISentenceVectorizerAttribute : VectorizerAttribute<string>
public class AzureOpenAIVectorizerAttribute : VectorizerAttribute<string>
{
/// <inheritdoc />
public AzureOpenAISentenceVectorizerAttribute(string deploymentName, string resourceName, int dim)
public AzureOpenAIVectorizerAttribute(string deploymentName, string resourceName, int dim)
{
DeploymentName = deploymentName;
ResourceName = resourceName;
Dim = dim;
Vectorizer = new AzureOpenAISentenceVectorizer(Configuration.Instance.AzureOpenAIApiKey, ResourceName, DeploymentName, Dim);
Vectorizer = new AzureOpenAIVectorizer(Configuration.Instance.AzureOpenAIApiKey, ResourceName, DeploymentName, Dim);
}

/// <summary>
Expand Down Expand Up @@ -42,7 +42,7 @@ public override byte[] Vectorize(object obj)
throw new ArgumentException("Object must be a string to be embedded", nameof(obj));
}

var floats = AzureOpenAISentenceVectorizer.GetFloats(s, ResourceName, DeploymentName, Configuration.Instance.AzureOpenAIApiKey);
var floats = AzureOpenAIVectorizer.GetFloats(s, ResourceName, DeploymentName, Configuration.Instance.AzureOpenAIApiKey);
return floats.SelectMany(BitConverter.GetBytes).ToArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

namespace Redis.OM.Vectorizers;

public class HuggingFaceApiSentenceVectorizer : IVectorizer<string>
public class HuggingFaceVectorizer : IVectorizer<string>
{
public HuggingFaceApiSentenceVectorizer(string authToken, string modelId, int dim)
public HuggingFaceVectorizer(string authToken, string modelId, int dim)
{
_huggingFaceAuthToken = authToken;
ModelId = modelId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Redis.OM.Vectorizers;
/// <summary>
/// An attribute that provides a Hugging Face API Sentence Vectorizer.
/// </summary>
public class HuggingFaceApiSentenceVectorizerAttribute : VectorizerAttribute<string>
public class HuggingFaceVectorizerAttribute : VectorizerAttribute<string>
{
public string? ModelId { get; set; }

Expand All @@ -26,7 +26,7 @@ public override IVectorizer<string> Vectorizer
throw new InvalidOperationException("Need a Model ID in order to process vector");
}

_vectorizer = new HuggingFaceApiSentenceVectorizer(Configuration.Instance.HuggingFaceAuthorizationToken, ModelId, Dim);
_vectorizer = new HuggingFaceVectorizer(Configuration.Instance.HuggingFaceAuthorizationToken, ModelId, Dim);
}

return _vectorizer;
Expand Down Expand Up @@ -73,6 +73,6 @@ public float[] GetFloats(string s)
{
var modelId = ModelId ?? Configuration.Instance["REDIS_OM_HF_MODEL_ID"];
if (modelId is null) throw new InvalidOperationException("Model Id Required to use Hugging Face API.");
return HuggingFaceApiSentenceVectorizer.GetFloats(s, modelId, Configuration.Instance.HuggingFaceAuthorizationToken);
return HuggingFaceVectorizer.GetFloats(s, modelId, Configuration.Instance.HuggingFaceAuthorizationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

namespace Redis.OM.Vectorizers;

public class OpenAISentenceVectorizer : IVectorizer<string>
public class OpenAIVectorizer : IVectorizer<string>
{
private readonly string _openAIAuthToken;
private readonly string _model;

public OpenAISentenceVectorizer(string openAIAuthToken, string model = "text-embedding-ada-002", int dim = 1536)
public OpenAIVectorizer(string openAIAuthToken, string model = "text-embedding-ada-002", int dim = 1536)
{
_openAIAuthToken = openAIAuthToken;
_model = model;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Redis.OM.Vectorizers;
/// <summary>
/// An OpenAI Sentence Vectorizer.
/// </summary>
public class OpenAISentenceVectorizerAttribute : VectorizerAttribute<string>
public class OpenAIVectorizerAttribute : VectorizerAttribute<string>
{
private const string DefaultModel = "text-embedding-ada-002";

Expand All @@ -28,7 +28,7 @@ public override IVectorizer<string> Vectorizer
{
get
{
return _vectorizer ??= new OpenAISentenceVectorizer(Configuration.Instance.OpenAiAuthorizationToken, ModelId, Dim);
return _vectorizer ??= new OpenAIVectorizer(Configuration.Instance.OpenAiAuthorizationToken, ModelId, Dim);
}
}

Expand All @@ -42,6 +42,6 @@ public override byte[] Vectorize(object obj)

internal float[] GetFloats(string s)
{
return OpenAISentenceVectorizer.GetFloats(s, ModelId, Configuration.Instance.OpenAiAuthorizationToken);
return OpenAIVectorizer.GetFloats(s, ModelId, Configuration.Instance.OpenAiAuthorizationToken);
}
}
6 changes: 3 additions & 3 deletions src/Redis.OM.Vectorizers/RedisConnectionProviderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public static class RedisConnectionProviderExtensions
{
public static ISemanticCache HuggingFaceSemanticCache(this IRedisConnectionProvider provider, string huggingFaceAuthToken, double threshold = .15, string modelId = "sentence-transformers/all-mpnet-base-v2", int dim = 768, string indexName = "HuggingFaceSemanticCache", string? prefix = null, long? ttl = null)
{
var vectorizer = new HuggingFaceApiSentenceVectorizer(huggingFaceAuthToken, modelId, dim);
var vectorizer = new HuggingFaceVectorizer(huggingFaceAuthToken, modelId, dim);
var connection = provider.Connection;
var info = connection.GetIndexInfo(indexName);
var cache = new SemanticCache(indexName, prefix ?? indexName, threshold, ttl, vectorizer, connection);
Expand All @@ -20,7 +20,7 @@ public static ISemanticCache HuggingFaceSemanticCache(this IRedisConnectionProvi

public static ISemanticCache OpenAISemanticCache(this IRedisConnectionProvider provider, string openAIAuthToken, double threshold = .15, string indexName = "OpenAISemanticCache", string? prefix = null, long? ttl = null)
{
var vectorizer = new OpenAISentenceVectorizer(openAIAuthToken);
var vectorizer = new OpenAIVectorizer(openAIAuthToken);
var connection = provider.Connection;
var info = connection.GetIndexInfo(indexName);
var cache = new SemanticCache(indexName, prefix ?? indexName, threshold, ttl, vectorizer, connection);
Expand All @@ -34,7 +34,7 @@ public static ISemanticCache OpenAISemanticCache(this IRedisConnectionProvider p

public static ISemanticCache AzureOpenAISemanticCache(this IRedisConnectionProvider provider, string apiKey, string resourceName, string deploymentId, int dim, double threshold = .15, string indexName = "AzureOpenAISemanticCache", string? prefix = null, long? ttl = null)
{
var vectorizer = new AzureOpenAISentenceVectorizer(apiKey, resourceName, deploymentId, dim);
var vectorizer = new AzureOpenAIVectorizer(apiKey, resourceName, deploymentId, dim);
var connection = provider.Connection;
var cache = new SemanticCache(indexName, prefix ?? indexName, threshold, ttl, vectorizer, connection);
var info = connection.GetIndexInfo(indexName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class HuggingFaceVectors
public string Id { get; set; }

[Indexed]
[HuggingFaceApiSentenceVectorizer(ModelId = "sentence-transformers/all-MiniLM-L6-v2")]
[HuggingFaceVectorizer(ModelId = "sentence-transformers/all-MiniLM-L6-v2")]
public Vector<string> Sentence { get; set; }

[Indexed]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using Redis.OM.Modeling;
using Redis.OM.Vectorizers;

namespace Redis.OM.Unit.Tests;

[Document(StorageType = StorageType.Json)]
public class OpenAIQuery
{
[RedisIdField]
public string Id { get; set; }

[Indexed(DistanceMetric = DistanceMetric.COSINE)]
[OpenAIVectorizer]
public Vector<string> Prompt { get; set; }

public string Response { get; set; }

[Indexed]
public string Language { get; set; }

[Indexed]
public DateTime TimeStamp { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class OpenAIVectors
public string Id { get; set; }

[Indexed]
[OpenAISentenceVectorizer]
[OpenAIVectorizer]
public Vector<string> Sentence { get; set; }

[Indexed]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void HuggingFaceSemanticCache()
{
var token = Environment.GetEnvironmentVariable("REDIS_OM_HF_TOKEN");
Assert.NotNull(token);
var cache = _provider.HuggingFaceSemanticCache(token);
var cache = _provider.HuggingFaceSemanticCache(token, threshold: .15);
cache.Store("What is the capital of France?", "Paris");
var res = cache.GetSimilar("What really is the capital of France?").First();
Assert.Equal("Paris",res.Response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,4 +308,43 @@ public void Insert()
Assert.Equal(simpleVectorizedVector.Value, res.SimpleVectorizedVector.Value);
Assert.Equal(simpleVectorizedVector.Embedding, res.SimpleVectorizedVector.Embedding);
}

[Fact]
public void OpenAIQueryTest()
{
_connection.DropIndexAndAssociatedRecords(typeof(OpenAIQuery));
_connection.CreateIndex(typeof(OpenAIQuery));

var collection = new RedisCollection<OpenAIQuery>(_connection);
var query = new OpenAIQuery
{
Language = "en_us",
Prompt = Vector.Of("What is the Capital of France?"),
Response = "Paris",
TimeStamp = DateTime.Now - TimeSpan.FromHours(3)
};
collection.Insert(query);
var queryPrompt = Vector.Of("What really is the Capital of France?");
var result = collection.First(x => x.Prompt.VectorRange(queryPrompt, .15));

Assert.Equal("Paris", result.Response);
Assert.NotNull(queryPrompt.Embedding);

result = collection.NearestNeighbors(x => x.Prompt, 1, queryPrompt).First();
Assert.Equal("Paris", result.Response);
Assert.NotNull(queryPrompt.Embedding);

result = collection.Where(x=>x.Language == "en_us").NearestNeighbors(x => x.Prompt, 1, queryPrompt).First();
Assert.Equal("Paris", result.Response);
Assert.NotNull(queryPrompt.Embedding);

result = collection.First(x=>x.Language == "en_us" && x.Prompt.VectorRange(queryPrompt, .15));
Assert.Equal("Paris", result.Response);
Assert.NotNull(queryPrompt.Embedding);

var ts = DateTimeOffset.Now - TimeSpan.FromHours(4);
result = collection.First(x=>x.TimeStamp > ts && x.Prompt.VectorRange(queryPrompt, .15));
Assert.Equal("Paris", result.Response);
Assert.NotNull(queryPrompt.Embedding);
}
}

0 comments on commit 73c6c05

Please sign in to comment.