Skip to content

Commit d4cd6ca

Browse files
authored
Merge pull request #647 from imperugo/feature/vectorset-api
Add VectorSet API for AI/ML similarity search
2 parents dc8073c + 935977d commit d4cd6ca

File tree

4 files changed

+348
-0
lines changed

4 files changed

+348
-0
lines changed

doc/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* [Hash Operations](usage/README.md)
3434
* [Hash Field Expiry](hash-field-expiry.md) (Redis 7.4+)
3535
* [GeoSpatial Indexes](geospatial.md)
36+
* [VectorSet — AI/ML Similarity Search](vectorset.md) (Redis 8.0+)
3637
* [Redis Streams](streams.md)
3738
* [Pub/Sub Messaging](pubsub.md)
3839
* [Custom Serializer](usage/custom-serializer.md)

doc/vectorset.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# VectorSet (AI/ML Similarity Search)
2+
3+
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.
4+
5+
> **Requires Redis 8.0+**
6+
7+
## Overview
8+
9+
```mermaid
10+
graph LR
11+
A[AI Model] -->|Generate Embedding| V[float array]
12+
V -->|VectorSetAddAsync| R[("Redis VectorSet")]
13+
Q[Query Text] -->|Generate Embedding| QV[float array]
14+
QV -->|VectorSetSimilaritySearchAsync| R
15+
R -->|Top K Results| Results[Similar Items]
16+
```
17+
18+
## Adding Vectors
19+
20+
```csharp
21+
// Add a vector with a member name
22+
var embedding = await aiModel.GetEmbeddingAsync("Red running shoes, size 42");
23+
24+
await redis.VectorSetAddAsync("products",
25+
VectorSetAddRequest.Member("shoe-123", embedding));
26+
27+
// Add with JSON attributes (metadata)
28+
await redis.VectorSetAddAsync("products",
29+
VectorSetAddRequest.Member("shoe-456", embedding,
30+
attributes: """{"category":"shoes","price":79.99,"brand":"Nike"}"""));
31+
```
32+
33+
## Similarity Search
34+
35+
The search returns a `Lease<T>` which **must be disposed** after use to return pooled memory.
36+
37+
```csharp
38+
// Find the 5 most similar items to a query vector
39+
var queryEmbedding = await aiModel.GetEmbeddingAsync("comfortable sneakers for running");
40+
41+
using var results = await redis.VectorSetSimilaritySearchAsync("products",
42+
VectorSetSimilaritySearchRequest.ByVector(queryEmbedding) with { Count = 5 });
43+
44+
if (results is not null)
45+
{
46+
foreach (var result in results.Span)
47+
{
48+
Console.WriteLine($"{result.Member}: score={result.Score:F4}");
49+
50+
// Get attributes for each result
51+
var attrs = await redis.VectorSetGetAttributesJsonAsync("products", result.Member!);
52+
Console.WriteLine($" Attributes: {attrs}");
53+
}
54+
}
55+
```
56+
57+
## Managing Vectors
58+
59+
```csharp
60+
// Check if a member exists
61+
var exists = await redis.VectorSetContainsAsync("products", "shoe-123");
62+
63+
// Get cardinality
64+
var count = await redis.VectorSetLengthAsync("products");
65+
66+
// Get vector dimensions
67+
var dims = await redis.VectorSetDimensionAsync("products");
68+
69+
// Get a random member
70+
var random = await redis.VectorSetRandomMemberAsync("products");
71+
72+
// Get multiple random members
73+
var randoms = await redis.VectorSetRandomMembersAsync("products", 5);
74+
75+
// Get info about the VectorSet
76+
var info = await redis.VectorSetInfoAsync("products");
77+
78+
// Get the approximate vector for a member
79+
using var vector = await redis.VectorSetGetApproximateVectorAsync("products", "shoe-123");
80+
81+
// Get HNSW graph neighbors
82+
var links = await redis.VectorSetGetLinksAsync("products", "shoe-123");
83+
var linksWithScores = await redis.VectorSetGetLinksWithScoresAsync("products", "shoe-123");
84+
85+
// Remove a member
86+
await redis.VectorSetRemoveAsync("products", "shoe-123");
87+
```
88+
89+
## Attributes (Metadata)
90+
91+
```csharp
92+
// Set JSON attributes on a member
93+
await redis.VectorSetSetAttributesJsonAsync("products", "shoe-123",
94+
"""{"category":"shoes","price":99.99,"sizes":[40,41,42]}""");
95+
96+
// Get JSON attributes
97+
var json = await redis.VectorSetGetAttributesJsonAsync("products", "shoe-123");
98+
```
99+
100+
## Use Cases
101+
102+
### RAG (Retrieval-Augmented Generation)
103+
```csharp
104+
// Index documents
105+
foreach (var doc in documents)
106+
{
107+
var embedding = await aiModel.GetEmbeddingAsync(doc.Content);
108+
await redis.VectorSetAddAsync("docs",
109+
VectorSetAddRequest.Member(doc.Id, embedding,
110+
attributes: $"""{{ "title": "{doc.Title}" }}"""));
111+
}
112+
113+
// Query: find relevant context for a prompt
114+
using var context = await redis.VectorSetSimilaritySearchAsync("docs",
115+
VectorSetSimilaritySearchRequest.ByVector(queryEmb) with { Count = 3 });
116+
```
117+
118+
### Recommendations
119+
```csharp
120+
// Find products similar to what the user just viewed
121+
using var vector = await redis.VectorSetGetApproximateVectorAsync("products", viewedProductId);
122+
if (vector is not null)
123+
{
124+
using var similar = await redis.VectorSetSimilaritySearchAsync("products",
125+
VectorSetSimilaritySearchRequest.ByVector(vector.Span.ToArray()) with { Count = 10 });
126+
}
127+
```
128+
129+
### Semantic Search
130+
```csharp
131+
// Search by meaning, not keywords
132+
var searchEmb = await aiModel.GetEmbeddingAsync("something warm for winter");
133+
using var results = await redis.VectorSetSimilaritySearchAsync("clothing",
134+
VectorSetSimilaritySearchRequest.ByVector(searchEmb) with { Count = 20 });
135+
```
136+
137+
## Performance Notes
138+
139+
- VectorSet uses HNSW (Hierarchical Navigable Small World) algorithm internally
140+
- Approximate nearest neighbor search — extremely fast even with millions of vectors
141+
- Memory efficient compared to external vector databases
142+
- Vectors are stored directly in Redis — no external index to maintain
143+
- `Lease<T>` return types use pooled memory — always dispose after use
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright (c) Ugo Lattanzi. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System.Threading.Tasks;
4+
5+
namespace StackExchange.Redis.Extensions.Core.Abstractions;
6+
7+
/// <summary>
8+
/// The Redis Database VectorSet extensions for AI/ML similarity search.
9+
/// Requires Redis 8.0+.
10+
/// </summary>
11+
public partial interface IRedisDatabase
12+
{
13+
/// <summary>
14+
/// Adds a vector to the VectorSet stored at key.
15+
/// </summary>
16+
/// <param name="key">The key of the VectorSet.</param>
17+
/// <param name="request">The add request containing the member, vector, and optional attributes.</param>
18+
/// <param name="flag">Behaviour markers associated with a given command.</param>
19+
/// <returns>True if the member was added, false if it already existed and was updated.</returns>
20+
Task<bool> VectorSetAddAsync(string key, VectorSetAddRequest request, CommandFlags flag = CommandFlags.None);
21+
22+
/// <summary>
23+
/// Performs a similarity search against the VectorSet stored at key.
24+
/// </summary>
25+
/// <param name="key">The key of the VectorSet.</param>
26+
/// <param name="query">The search request containing the query vector and parameters.</param>
27+
/// <param name="flag">Behaviour markers associated with a given command.</param>
28+
/// <returns>The matching results with scores. The returned Lease must be disposed after use.</returns>
29+
Task<Lease<VectorSetSimilaritySearchResult>?> VectorSetSimilaritySearchAsync(string key, VectorSetSimilaritySearchRequest query, CommandFlags flag = CommandFlags.None);
30+
31+
/// <summary>
32+
/// Removes a member from the VectorSet stored at key.
33+
/// </summary>
34+
/// <param name="key">The key of the VectorSet.</param>
35+
/// <param name="member">The member to remove.</param>
36+
/// <param name="flag">Behaviour markers associated with a given command.</param>
37+
/// <returns>True if the member was removed, false if it did not exist.</returns>
38+
Task<bool> VectorSetRemoveAsync(string key, string member, CommandFlags flag = CommandFlags.None);
39+
40+
/// <summary>
41+
/// Checks if a member exists in the VectorSet stored at key.
42+
/// </summary>
43+
/// <param name="key">The key of the VectorSet.</param>
44+
/// <param name="member">The member to check.</param>
45+
/// <param name="flag">Behaviour markers associated with a given command.</param>
46+
/// <returns>True if the member exists.</returns>
47+
Task<bool> VectorSetContainsAsync(string key, string member, CommandFlags flag = CommandFlags.None);
48+
49+
/// <summary>
50+
/// Returns the number of members in the VectorSet stored at key.
51+
/// </summary>
52+
/// <param name="key">The key of the VectorSet.</param>
53+
/// <param name="flag">Behaviour markers associated with a given command.</param>
54+
/// <returns>The cardinality of the VectorSet, or 0 if the key does not exist.</returns>
55+
Task<long> VectorSetLengthAsync(string key, CommandFlags flag = CommandFlags.None);
56+
57+
/// <summary>
58+
/// Returns the number of dimensions of vectors in the VectorSet stored at key.
59+
/// </summary>
60+
/// <param name="key">The key of the VectorSet.</param>
61+
/// <param name="flag">Behaviour markers associated with a given command.</param>
62+
/// <returns>The number of dimensions.</returns>
63+
Task<long> VectorSetDimensionAsync(string key, CommandFlags flag = CommandFlags.None);
64+
65+
/// <summary>
66+
/// Gets the JSON attributes associated with a member in the VectorSet.
67+
/// </summary>
68+
/// <param name="key">The key of the VectorSet.</param>
69+
/// <param name="member">The member to retrieve attributes for.</param>
70+
/// <param name="flag">Behaviour markers associated with a given command.</param>
71+
/// <returns>The JSON attributes string, or null if the member has no attributes.</returns>
72+
Task<string?> VectorSetGetAttributesJsonAsync(string key, string member, CommandFlags flag = CommandFlags.None);
73+
74+
/// <summary>
75+
/// Sets JSON attributes on a member in the VectorSet.
76+
/// </summary>
77+
/// <param name="key">The key of the VectorSet.</param>
78+
/// <param name="member">The member to set attributes on.</param>
79+
/// <param name="attributesJson">The JSON attributes string.</param>
80+
/// <param name="flag">Behaviour markers associated with a given command.</param>
81+
/// <returns>True if the attributes were set.</returns>
82+
Task<bool> VectorSetSetAttributesJsonAsync(string key, string member, string attributesJson, CommandFlags flag = CommandFlags.None);
83+
84+
/// <summary>
85+
/// Returns information about the VectorSet stored at key.
86+
/// </summary>
87+
/// <param name="key">The key of the VectorSet.</param>
88+
/// <param name="flag">Behaviour markers associated with a given command.</param>
89+
/// <returns>Information about the VectorSet.</returns>
90+
Task<VectorSetInfo?> VectorSetInfoAsync(string key, CommandFlags flag = CommandFlags.None);
91+
92+
/// <summary>
93+
/// Returns a random member from the VectorSet stored at key.
94+
/// </summary>
95+
/// <param name="key">The key of the VectorSet.</param>
96+
/// <param name="flag">Behaviour markers associated with a given command.</param>
97+
/// <returns>A random member, or null if the VectorSet is empty.</returns>
98+
Task<RedisValue> VectorSetRandomMemberAsync(string key, CommandFlags flag = CommandFlags.None);
99+
100+
/// <summary>
101+
/// Returns multiple random members from the VectorSet stored at key.
102+
/// </summary>
103+
/// <param name="key">The key of the VectorSet.</param>
104+
/// <param name="count">The number of random members to return.</param>
105+
/// <param name="flag">Behaviour markers associated with a given command.</param>
106+
/// <returns>An array of random members.</returns>
107+
Task<RedisValue[]> VectorSetRandomMembersAsync(string key, long count, CommandFlags flag = CommandFlags.None);
108+
109+
/// <summary>
110+
/// Returns the approximate vector for a member in the VectorSet.
111+
/// </summary>
112+
/// <param name="key">The key of the VectorSet.</param>
113+
/// <param name="member">The member to retrieve the vector for.</param>
114+
/// <param name="flag">Behaviour markers associated with a given command.</param>
115+
/// <returns>The approximate vector as a Lease of floats. Must be disposed after use. Null if member not found.</returns>
116+
Task<Lease<float>?> VectorSetGetApproximateVectorAsync(string key, string member, CommandFlags flag = CommandFlags.None);
117+
118+
/// <summary>
119+
/// Returns the links (neighbors) for a member in the VectorSet's HNSW graph.
120+
/// </summary>
121+
/// <param name="key">The key of the VectorSet.</param>
122+
/// <param name="member">The member to retrieve links for.</param>
123+
/// <param name="flag">Behaviour markers associated with a given command.</param>
124+
/// <returns>The linked member names. The returned Lease must be disposed after use.</returns>
125+
Task<Lease<RedisValue>?> VectorSetGetLinksAsync(string key, string member, CommandFlags flag = CommandFlags.None);
126+
127+
/// <summary>
128+
/// Returns the links (neighbors) with similarity scores for a member in the VectorSet's HNSW graph.
129+
/// </summary>
130+
/// <param name="key">The key of the VectorSet.</param>
131+
/// <param name="member">The member to retrieve links for.</param>
132+
/// <param name="flag">Behaviour markers associated with a given command.</param>
133+
/// <returns>The links with scores. The returned Lease must be disposed after use.</returns>
134+
Task<Lease<VectorSetLink>?> VectorSetGetLinksWithScoresAsync(string key, string member, CommandFlags flag = CommandFlags.None);
135+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) Ugo Lattanzi. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
2+
3+
using System.Threading.Tasks;
4+
5+
namespace StackExchange.Redis.Extensions.Core.Implementations;
6+
7+
/// <inheritdoc/>
8+
public partial class RedisDatabase
9+
{
10+
/// <inheritdoc/>
11+
public Task<bool> VectorSetAddAsync(string key, VectorSetAddRequest request, CommandFlags flag = CommandFlags.None) =>
12+
Database.VectorSetAddAsync(key, request, flag);
13+
14+
/// <inheritdoc/>
15+
public Task<Lease<VectorSetSimilaritySearchResult>?> VectorSetSimilaritySearchAsync(string key, VectorSetSimilaritySearchRequest query, CommandFlags flag = CommandFlags.None) =>
16+
Database.VectorSetSimilaritySearchAsync(key, query, flag);
17+
18+
/// <inheritdoc/>
19+
public Task<bool> VectorSetRemoveAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
20+
Database.VectorSetRemoveAsync(key, member, flag);
21+
22+
/// <inheritdoc/>
23+
public Task<bool> VectorSetContainsAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
24+
Database.VectorSetContainsAsync(key, member, flag);
25+
26+
/// <inheritdoc/>
27+
#pragma warning disable RCS1174 // async/await required for cross-TFM int→long cast
28+
public async Task<long> VectorSetLengthAsync(string key, CommandFlags flag = CommandFlags.None) =>
29+
await Database.VectorSetLengthAsync(key, flag).ConfigureAwait(false);
30+
#pragma warning restore RCS1174
31+
32+
/// <inheritdoc/>
33+
#pragma warning disable RCS1174
34+
public async Task<long> VectorSetDimensionAsync(string key, CommandFlags flag = CommandFlags.None) =>
35+
await Database.VectorSetDimensionAsync(key, flag).ConfigureAwait(false);
36+
#pragma warning restore RCS1174
37+
38+
/// <inheritdoc/>
39+
public Task<string?> VectorSetGetAttributesJsonAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
40+
Database.VectorSetGetAttributesJsonAsync(key, member, flag);
41+
42+
/// <inheritdoc/>
43+
public Task<bool> VectorSetSetAttributesJsonAsync(string key, string member, string attributesJson, CommandFlags flag = CommandFlags.None) =>
44+
Database.VectorSetSetAttributesJsonAsync(key, member, attributesJson, flag);
45+
46+
/// <inheritdoc/>
47+
public Task<VectorSetInfo?> VectorSetInfoAsync(string key, CommandFlags flag = CommandFlags.None) =>
48+
Database.VectorSetInfoAsync(key, flag);
49+
50+
/// <inheritdoc/>
51+
public Task<RedisValue> VectorSetRandomMemberAsync(string key, CommandFlags flag = CommandFlags.None) =>
52+
Database.VectorSetRandomMemberAsync(key, flag);
53+
54+
/// <inheritdoc/>
55+
public Task<RedisValue[]> VectorSetRandomMembersAsync(string key, long count, CommandFlags flag = CommandFlags.None) =>
56+
Database.VectorSetRandomMembersAsync(key, count, flag);
57+
58+
/// <inheritdoc/>
59+
public Task<Lease<float>?> VectorSetGetApproximateVectorAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
60+
Database.VectorSetGetApproximateVectorAsync(key, member, flag);
61+
62+
/// <inheritdoc/>
63+
public Task<Lease<RedisValue>?> VectorSetGetLinksAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
64+
Database.VectorSetGetLinksAsync(key, member, flag);
65+
66+
/// <inheritdoc/>
67+
public Task<Lease<VectorSetLink>?> VectorSetGetLinksWithScoresAsync(string key, string member, CommandFlags flag = CommandFlags.None) =>
68+
Database.VectorSetGetLinksWithScoresAsync(key, member, flag);
69+
}

0 commit comments

Comments
 (0)