Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
* [Hash Operations](usage/README.md)
* [Hash Field Expiry](hash-field-expiry.md) (Redis 7.4+)
* [GeoSpatial Indexes](geospatial.md)
* [VectorSet — AI/ML Similarity Search](vectorset.md) (Redis 8.0+)
* [Redis Streams](streams.md)
* [Pub/Sub Messaging](pubsub.md)
* [Custom Serializer](usage/custom-serializer.md)
Expand Down
143 changes: 143 additions & 0 deletions doc/vectorset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# VectorSet (AI/ML Similarity Search)

Redis 8.0 introduced VectorSet — a native data structure for storing and searching high-dimensional vectors. This is ideal for AI/ML applications like RAG, recommendations, and semantic search.

> **Requires Redis 8.0+**

## Overview

```mermaid
graph LR
A[AI Model] -->|Generate Embedding| V[float array]
V -->|VectorSetAddAsync| R[("Redis VectorSet")]
Q[Query Text] -->|Generate Embedding| QV[float array]
QV -->|VectorSetSimilaritySearchAsync| R
R -->|Top K Results| Results[Similar Items]
```

## Adding Vectors

```csharp
// Add a vector with a member name
var embedding = await aiModel.GetEmbeddingAsync("Red running shoes, size 42");

await redis.VectorSetAddAsync("products",
VectorSetAddRequest.Member("shoe-123", embedding));

// Add with JSON attributes (metadata)
await redis.VectorSetAddAsync("products",
VectorSetAddRequest.Member("shoe-456", embedding,
attributes: """{"category":"shoes","price":79.99,"brand":"Nike"}"""));
```

## Similarity Search

The search returns a `Lease<T>` which **must be disposed** after use to return pooled memory.

```csharp
// Find the 5 most similar items to a query vector
var queryEmbedding = await aiModel.GetEmbeddingAsync("comfortable sneakers for running");

using var results = await redis.VectorSetSimilaritySearchAsync("products",
VectorSetSimilaritySearchRequest.ByVector(queryEmbedding) with { Count = 5 });

if (results is not null)
{
foreach (var result in results.Span)
{
Console.WriteLine($"{result.Member}: score={result.Score:F4}");

// Get attributes for each result
var attrs = await redis.VectorSetGetAttributesJsonAsync("products", result.Member!);
Console.WriteLine($" Attributes: {attrs}");
}
}
```

## Managing Vectors

```csharp
// Check if a member exists
var exists = await redis.VectorSetContainsAsync("products", "shoe-123");

// Get cardinality
var count = await redis.VectorSetLengthAsync("products");

// Get vector dimensions
var dims = await redis.VectorSetDimensionAsync("products");

// Get a random member
var random = await redis.VectorSetRandomMemberAsync("products");

// Get multiple random members
var randoms = await redis.VectorSetRandomMembersAsync("products", 5);

// Get info about the VectorSet
var info = await redis.VectorSetInfoAsync("products");

// Get the approximate vector for a member
using var vector = await redis.VectorSetGetApproximateVectorAsync("products", "shoe-123");

// Get HNSW graph neighbors
var links = await redis.VectorSetGetLinksAsync("products", "shoe-123");
var linksWithScores = await redis.VectorSetGetLinksWithScoresAsync("products", "shoe-123");

// Remove a member
await redis.VectorSetRemoveAsync("products", "shoe-123");
```

## Attributes (Metadata)

```csharp
// Set JSON attributes on a member
await redis.VectorSetSetAttributesJsonAsync("products", "shoe-123",
"""{"category":"shoes","price":99.99,"sizes":[40,41,42]}""");

// Get JSON attributes
var json = await redis.VectorSetGetAttributesJsonAsync("products", "shoe-123");
```

## Use Cases

### RAG (Retrieval-Augmented Generation)
```csharp
// Index documents
foreach (var doc in documents)
{
var embedding = await aiModel.GetEmbeddingAsync(doc.Content);
await redis.VectorSetAddAsync("docs",
VectorSetAddRequest.Member(doc.Id, embedding,
attributes: $"""{{ "title": "{doc.Title}" }}"""));
}

// Query: find relevant context for a prompt
using var context = await redis.VectorSetSimilaritySearchAsync("docs",
VectorSetSimilaritySearchRequest.ByVector(queryEmb) with { Count = 3 });
```

### Recommendations
```csharp
// Find products similar to what the user just viewed
using var vector = await redis.VectorSetGetApproximateVectorAsync("products", viewedProductId);
if (vector is not null)
{
using var similar = await redis.VectorSetSimilaritySearchAsync("products",
VectorSetSimilaritySearchRequest.ByVector(vector.Span.ToArray()) with { Count = 10 });
}
```

### Semantic Search
```csharp
// Search by meaning, not keywords
var searchEmb = await aiModel.GetEmbeddingAsync("something warm for winter");
using var results = await redis.VectorSetSimilaritySearchAsync("clothing",
VectorSetSimilaritySearchRequest.ByVector(searchEmb) with { Count = 20 });
```

## Performance Notes

- VectorSet uses HNSW (Hierarchical Navigable Small World) algorithm internally
- Approximate nearest neighbor search — extremely fast even with millions of vectors
- Memory efficient compared to external vector databases
- Vectors are stored directly in Redis — no external index to maintain
- `Lease<T>` return types use pooled memory — always dispose after use
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright (c) Ugo Lattanzi. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Threading.Tasks;

namespace StackExchange.Redis.Extensions.Core.Abstractions;

/// <summary>
/// The Redis Database VectorSet extensions for AI/ML similarity search.
/// Requires Redis 8.0+.
/// </summary>
public partial interface IRedisDatabase
{
/// <summary>
/// Adds a vector to the VectorSet stored at key.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="request">The add request containing the member, vector, and optional attributes.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>True if the member was added, false if it already existed and was updated.</returns>
Task<bool> VectorSetAddAsync(string key, VectorSetAddRequest request, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Performs a similarity search against the VectorSet stored at key.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="query">The search request containing the query vector and parameters.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>The matching results with scores. The returned Lease must be disposed after use.</returns>
Task<Lease<VectorSetSimilaritySearchResult>?> VectorSetSimilaritySearchAsync(string key, VectorSetSimilaritySearchRequest query, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Removes a member from the VectorSet stored at key.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="member">The member to remove.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>True if the member was removed, false if it did not exist.</returns>
Task<bool> VectorSetRemoveAsync(string key, string member, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Checks if a member exists in the VectorSet stored at key.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="member">The member to check.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>True if the member exists.</returns>
Task<bool> VectorSetContainsAsync(string key, string member, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Returns the number of members in the VectorSet stored at key.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>The cardinality of the VectorSet, or 0 if the key does not exist.</returns>
Task<long> VectorSetLengthAsync(string key, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Returns the number of dimensions of vectors in the VectorSet stored at key.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>The number of dimensions.</returns>
Task<long> VectorSetDimensionAsync(string key, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Gets the JSON attributes associated with a member in the VectorSet.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="member">The member to retrieve attributes for.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>The JSON attributes string, or null if the member has no attributes.</returns>
Task<string?> VectorSetGetAttributesJsonAsync(string key, string member, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Sets JSON attributes on a member in the VectorSet.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="member">The member to set attributes on.</param>
/// <param name="attributesJson">The JSON attributes string.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>True if the attributes were set.</returns>
Task<bool> VectorSetSetAttributesJsonAsync(string key, string member, string attributesJson, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Returns information about the VectorSet stored at key.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>Information about the VectorSet.</returns>
Task<VectorSetInfo?> VectorSetInfoAsync(string key, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Returns a random member from the VectorSet stored at key.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>A random member, or null if the VectorSet is empty.</returns>
Task<RedisValue> VectorSetRandomMemberAsync(string key, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Returns multiple random members from the VectorSet stored at key.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="count">The number of random members to return.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>An array of random members.</returns>
Task<RedisValue[]> VectorSetRandomMembersAsync(string key, long count, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Returns the approximate vector for a member in the VectorSet.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="member">The member to retrieve the vector for.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>The approximate vector as a Lease of floats. Must be disposed after use. Null if member not found.</returns>
Task<Lease<float>?> VectorSetGetApproximateVectorAsync(string key, string member, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Returns the links (neighbors) for a member in the VectorSet's HNSW graph.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="member">The member to retrieve links for.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>The linked member names. The returned Lease must be disposed after use.</returns>
Task<Lease<RedisValue>?> VectorSetGetLinksAsync(string key, string member, CommandFlags flag = CommandFlags.None);

/// <summary>
/// Returns the links (neighbors) with similarity scores for a member in the VectorSet's HNSW graph.
/// </summary>
/// <param name="key">The key of the VectorSet.</param>
/// <param name="member">The member to retrieve links for.</param>
/// <param name="flag">Behaviour markers associated with a given command.</param>
/// <returns>The links with scores. The returned Lease must be disposed after use.</returns>
Task<Lease<VectorSetLink>?> VectorSetGetLinksWithScoresAsync(string key, string member, CommandFlags flag = CommandFlags.None);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Ugo Lattanzi. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Threading.Tasks;

namespace StackExchange.Redis.Extensions.Core.Implementations;

/// <inheritdoc/>
public partial class RedisDatabase
{
/// <inheritdoc/>
public Task<bool> VectorSetAddAsync(string key, VectorSetAddRequest request, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetAddAsync(key, request, flag);

/// <inheritdoc/>
public Task<Lease<VectorSetSimilaritySearchResult>?> VectorSetSimilaritySearchAsync(string key, VectorSetSimilaritySearchRequest query, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetSimilaritySearchAsync(key, query, flag);

/// <inheritdoc/>
public Task<bool> VectorSetRemoveAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetRemoveAsync(key, member, flag);

/// <inheritdoc/>
public Task<bool> VectorSetContainsAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetContainsAsync(key, member, flag);

/// <inheritdoc/>
#pragma warning disable RCS1174 // async/await required for cross-TFM int→long cast
public async Task<long> VectorSetLengthAsync(string key, CommandFlags flag = CommandFlags.None) =>
await Database.VectorSetLengthAsync(key, flag).ConfigureAwait(false);
#pragma warning restore RCS1174

/// <inheritdoc/>
#pragma warning disable RCS1174
public async Task<long> VectorSetDimensionAsync(string key, CommandFlags flag = CommandFlags.None) =>
await Database.VectorSetDimensionAsync(key, flag).ConfigureAwait(false);
#pragma warning restore RCS1174

/// <inheritdoc/>
public Task<string?> VectorSetGetAttributesJsonAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetGetAttributesJsonAsync(key, member, flag);

/// <inheritdoc/>
public Task<bool> VectorSetSetAttributesJsonAsync(string key, string member, string attributesJson, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetSetAttributesJsonAsync(key, member, attributesJson, flag);

/// <inheritdoc/>
public Task<VectorSetInfo?> VectorSetInfoAsync(string key, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetInfoAsync(key, flag);

/// <inheritdoc/>
public Task<RedisValue> VectorSetRandomMemberAsync(string key, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetRandomMemberAsync(key, flag);

/// <inheritdoc/>
public Task<RedisValue[]> VectorSetRandomMembersAsync(string key, long count, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetRandomMembersAsync(key, count, flag);

/// <inheritdoc/>
public Task<Lease<float>?> VectorSetGetApproximateVectorAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetGetApproximateVectorAsync(key, member, flag);

/// <inheritdoc/>
public Task<Lease<RedisValue>?> VectorSetGetLinksAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetGetLinksAsync(key, member, flag);

/// <inheritdoc/>
public Task<Lease<VectorSetLink>?> VectorSetGetLinksWithScoresAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
Database.VectorSetGetLinksWithScoresAsync(key, member, flag);
}
Loading