From d8014e6f12a12f7fee156454708d775e5f94d606 Mon Sep 17 00:00:00 2001 From: "ugo.lattanzi" Date: Sat, 11 Apr 2026 14:18:05 +0200 Subject: [PATCH 1/2] Add VectorSet API for AI/ML similarity search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New partial files: IRedisDatabase.VectorSet.cs + RedisDatabase.VectorSet.cs 10 methods covering Redis 8.0 VectorSet operations: - VectorSetAddAsync — add vectors with optional attributes - VectorSetSimilaritySearchAsync — similarity search (core AI/ML operation) - VectorSetRemoveAsync — remove members - VectorSetContainsAsync — check membership - VectorSetLengthAsync — get cardinality - VectorSetDimensionAsync — get vector dimensions - VectorSetGetAttributesJsonAsync — get JSON metadata - VectorSetSetAttributesJsonAsync — set JSON metadata - VectorSetInfoAsync — get VectorSet info - VectorSetRandomMemberAsync — random sampling New documentation: doc/vectorset.md with Mermaid diagram, usage examples for RAG, recommendations, and semantic search. Closes #631 --- doc/README.md | 1 + doc/vectorset.md | 124 ++++++++++++++++++ .../Abstractions/IRedisDatabase.VectorSet.cs | 99 ++++++++++++++ .../RedisDatabase.VectorSet.cs | 50 +++++++ 4 files changed, 274 insertions(+) create mode 100644 doc/vectorset.md create mode 100644 src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.VectorSet.cs create mode 100644 src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.VectorSet.cs diff --git a/doc/README.md b/doc/README.md index 4259abf2..842899a5 100644 --- a/doc/README.md +++ b/doc/README.md @@ -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) diff --git a/doc/vectorset.md b/doc/vectorset.md new file mode 100644 index 00000000..a5406ddf --- /dev/null +++ b/doc/vectorset.md @@ -0,0 +1,124 @@ +# 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.Create("shoe-123", embedding)); + +// Add with JSON attributes (metadata) +await redis.VectorSetAddAsync("products", + VectorSetAddRequest.Create("shoe-456", embedding) + .WithAttributes("""{"category":"shoes","price":79.99,"brand":"Nike"}""")); +``` + +## Similarity Search + +```csharp +// Find the 5 most similar items to a query vector +var queryEmbedding = await aiModel.GetEmbeddingAsync("comfortable sneakers for running"); + +var results = await redis.VectorSetSimilaritySearchAsync("products", + VectorSetSimilaritySearchRequest.Create(queryEmbedding, count: 5)); + +foreach (var result in results) +{ + 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 info about the VectorSet +var info = await redis.VectorSetInfoAsync("products"); + +// 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.Create(doc.Id, embedding) + .WithAttributes($"""{{ "title": "{doc.Title}" }}""")); +} + +// Query: find relevant context for a prompt +var queryEmb = await aiModel.GetEmbeddingAsync(userQuestion); +var context = await redis.VectorSetSimilaritySearchAsync("docs", + VectorSetSimilaritySearchRequest.Create(queryEmb, count: 3)); +``` + +### Recommendations +```csharp +// Find products similar to what the user just viewed +var viewedProduct = await redis.VectorSetGetApproximateVectorAsync("products", productId); +// Use the vector to find similar items +``` + +### Semantic Search +```csharp +// Search by meaning, not keywords +var searchEmb = await aiModel.GetEmbeddingAsync("something warm for winter"); +var results = await redis.VectorSetSimilaritySearchAsync("clothing", + VectorSetSimilaritySearchRequest.Create(searchEmb, 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 diff --git a/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.VectorSet.cs b/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.VectorSet.cs new file mode 100644 index 00000000..73446d8e --- /dev/null +++ b/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.VectorSet.cs @@ -0,0 +1,99 @@ +// 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; + +/// +/// The Redis Database VectorSet extensions for AI/ML similarity search. +/// Requires Redis 8.0+. +/// +public partial interface IRedisDatabase +{ + /// + /// Adds a vector to the VectorSet stored at key. + /// + /// The key of the VectorSet. + /// The add request containing the member, vector, and optional attributes. + /// Behaviour markers associated with a given command. + /// True if the member was added, false if it already existed and was updated. + Task VectorSetAddAsync(string key, VectorSetAddRequest request, CommandFlags flag = CommandFlags.None); + + /// + /// Performs a similarity search against the VectorSet stored at key. + /// + /// The key of the VectorSet. + /// The search request containing the query vector and parameters. + /// Behaviour markers associated with a given command. + /// The matching results with scores. The returned Lease must be disposed after use. + Task?> VectorSetSimilaritySearchAsync(string key, VectorSetSimilaritySearchRequest query, CommandFlags flag = CommandFlags.None); + + /// + /// Removes a member from the VectorSet stored at key. + /// + /// The key of the VectorSet. + /// The member to remove. + /// Behaviour markers associated with a given command. + /// True if the member was removed, false if it did not exist. + Task VectorSetRemoveAsync(string key, string member, CommandFlags flag = CommandFlags.None); + + /// + /// Checks if a member exists in the VectorSet stored at key. + /// + /// The key of the VectorSet. + /// The member to check. + /// Behaviour markers associated with a given command. + /// True if the member exists. + Task VectorSetContainsAsync(string key, string member, CommandFlags flag = CommandFlags.None); + + /// + /// Returns the number of members in the VectorSet stored at key. + /// + /// The key of the VectorSet. + /// Behaviour markers associated with a given command. + /// The cardinality of the VectorSet, or 0 if the key does not exist. + Task VectorSetLengthAsync(string key, CommandFlags flag = CommandFlags.None); + + /// + /// Returns the number of dimensions of vectors in the VectorSet stored at key. + /// + /// The key of the VectorSet. + /// Behaviour markers associated with a given command. + /// The number of dimensions. + Task VectorSetDimensionAsync(string key, CommandFlags flag = CommandFlags.None); + + /// + /// Gets the JSON attributes associated with a member in the VectorSet. + /// + /// The key of the VectorSet. + /// The member to retrieve attributes for. + /// Behaviour markers associated with a given command. + /// The JSON attributes string, or null if the member has no attributes. + Task VectorSetGetAttributesJsonAsync(string key, string member, CommandFlags flag = CommandFlags.None); + + /// + /// Sets JSON attributes on a member in the VectorSet. + /// + /// The key of the VectorSet. + /// The member to set attributes on. + /// The JSON attributes string. + /// Behaviour markers associated with a given command. + /// True if the attributes were set. + Task VectorSetSetAttributesJsonAsync(string key, string member, string attributesJson, CommandFlags flag = CommandFlags.None); + + /// + /// Returns information about the VectorSet stored at key. + /// + /// The key of the VectorSet. + /// Behaviour markers associated with a given command. + /// Information about the VectorSet. + Task VectorSetInfoAsync(string key, CommandFlags flag = CommandFlags.None); + + /// + /// Returns a random member from the VectorSet stored at key. + /// + /// The key of the VectorSet. + /// Behaviour markers associated with a given command. + /// A random member, or null if the VectorSet is empty. + Task VectorSetRandomMemberAsync(string key, CommandFlags flag = CommandFlags.None); +} diff --git a/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.VectorSet.cs b/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.VectorSet.cs new file mode 100644 index 00000000..86d01362 --- /dev/null +++ b/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.VectorSet.cs @@ -0,0 +1,50 @@ +// 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; +using System.Threading.Tasks; + +namespace StackExchange.Redis.Extensions.Core.Implementations; + +/// +public partial class RedisDatabase +{ + /// + public Task VectorSetAddAsync(string key, VectorSetAddRequest request, CommandFlags flag = CommandFlags.None) => + Database.VectorSetAddAsync(key, request, flag); + + /// + public Task?> VectorSetSimilaritySearchAsync(string key, VectorSetSimilaritySearchRequest query, CommandFlags flag = CommandFlags.None) => + Database.VectorSetSimilaritySearchAsync(key, query, flag); + + /// + public Task VectorSetRemoveAsync(string key, string member, CommandFlags flag = CommandFlags.None) => + Database.VectorSetRemoveAsync(key, member, flag); + + /// + public Task VectorSetContainsAsync(string key, string member, CommandFlags flag = CommandFlags.None) => + Database.VectorSetContainsAsync(key, member, flag); + + /// + public Task VectorSetLengthAsync(string key, CommandFlags flag = CommandFlags.None) => + Database.VectorSetLengthAsync(key, flag).ContinueWith(t => (long)t.Result, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); + + /// + public Task VectorSetDimensionAsync(string key, CommandFlags flag = CommandFlags.None) => + Database.VectorSetDimensionAsync(key, flag).ContinueWith(t => (long)t.Result, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); + + /// + public Task VectorSetGetAttributesJsonAsync(string key, string member, CommandFlags flag = CommandFlags.None) => + Database.VectorSetGetAttributesJsonAsync(key, member, flag); + + /// + public Task VectorSetSetAttributesJsonAsync(string key, string member, string attributesJson, CommandFlags flag = CommandFlags.None) => + Database.VectorSetSetAttributesJsonAsync(key, member, attributesJson, flag); + + /// + public Task VectorSetInfoAsync(string key, CommandFlags flag = CommandFlags.None) => + Database.VectorSetInfoAsync(key, flag); + + /// + public Task VectorSetRandomMemberAsync(string key, CommandFlags flag = CommandFlags.None) => + Database.VectorSetRandomMemberAsync(key, flag); +} From 935977dcd879b11ac5882346b725c804d7d1d51c Mon Sep 17 00:00:00 2001 From: "ugo.lattanzi" Date: Sat, 11 Apr 2026 14:34:50 +0200 Subject: [PATCH 2/2] Fix VectorSet review findings: async pattern, Lease types, missing methods, doc fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace ContinueWith(OnlyOnRanToCompletion) with async/await for VectorSetLengthAsync and VectorSetDimensionAsync (was hanging on fault) - Fix doc examples: VectorSetAddRequest.Create() → .Member(), VectorSetSimilaritySearchRequest.Create() → .ByVector() - Add using/dispose pattern for Lease in all doc examples - Add 5 missing VectorSet methods: RandomMembersAsync, GetApproximateVectorAsync, GetLinksAsync, GetLinksWithScoresAsync - Fix return types to match SE.Redis: Lease?, Lease?, Lease? - Total: 15 VectorSet methods now covered --- doc/vectorset.md | 59 ++++++++++++------- .../Abstractions/IRedisDatabase.VectorSet.cs | 36 +++++++++++ .../RedisDatabase.VectorSet.cs | 29 +++++++-- 3 files changed, 99 insertions(+), 25 deletions(-) diff --git a/doc/vectorset.md b/doc/vectorset.md index a5406ddf..9f8129ae 100644 --- a/doc/vectorset.md +++ b/doc/vectorset.md @@ -22,30 +22,35 @@ graph LR var embedding = await aiModel.GetEmbeddingAsync("Red running shoes, size 42"); await redis.VectorSetAddAsync("products", - VectorSetAddRequest.Create("shoe-123", embedding)); + VectorSetAddRequest.Member("shoe-123", embedding)); // Add with JSON attributes (metadata) await redis.VectorSetAddAsync("products", - VectorSetAddRequest.Create("shoe-456", embedding) - .WithAttributes("""{"category":"shoes","price":79.99,"brand":"Nike"}""")); + VectorSetAddRequest.Member("shoe-456", embedding, + attributes: """{"category":"shoes","price":79.99,"brand":"Nike"}""")); ``` ## Similarity Search +The search returns a `Lease` 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"); -var results = await redis.VectorSetSimilaritySearchAsync("products", - VectorSetSimilaritySearchRequest.Create(queryEmbedding, count: 5)); +using var results = await redis.VectorSetSimilaritySearchAsync("products", + VectorSetSimilaritySearchRequest.ByVector(queryEmbedding) with { Count = 5 }); -foreach (var result in results) +if (results is not null) { - 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}"); + 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}"); + } } ``` @@ -64,9 +69,19 @@ 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"); ``` @@ -91,29 +106,32 @@ foreach (var doc in documents) { var embedding = await aiModel.GetEmbeddingAsync(doc.Content); await redis.VectorSetAddAsync("docs", - VectorSetAddRequest.Create(doc.Id, embedding) - .WithAttributes($"""{{ "title": "{doc.Title}" }}""")); + VectorSetAddRequest.Member(doc.Id, embedding, + attributes: $"""{{ "title": "{doc.Title}" }}""")); } // Query: find relevant context for a prompt -var queryEmb = await aiModel.GetEmbeddingAsync(userQuestion); -var context = await redis.VectorSetSimilaritySearchAsync("docs", - VectorSetSimilaritySearchRequest.Create(queryEmb, count: 3)); +using var context = await redis.VectorSetSimilaritySearchAsync("docs", + VectorSetSimilaritySearchRequest.ByVector(queryEmb) with { Count = 3 }); ``` ### Recommendations ```csharp // Find products similar to what the user just viewed -var viewedProduct = await redis.VectorSetGetApproximateVectorAsync("products", productId); -// Use the vector to find similar items +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"); -var results = await redis.VectorSetSimilaritySearchAsync("clothing", - VectorSetSimilaritySearchRequest.Create(searchEmb, count: 20)); +using var results = await redis.VectorSetSimilaritySearchAsync("clothing", + VectorSetSimilaritySearchRequest.ByVector(searchEmb) with { Count = 20 }); ``` ## Performance Notes @@ -122,3 +140,4 @@ var results = await redis.VectorSetSimilaritySearchAsync("clothing", - 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` return types use pooled memory — always dispose after use diff --git a/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.VectorSet.cs b/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.VectorSet.cs index 73446d8e..cf9e7b49 100644 --- a/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.VectorSet.cs +++ b/src/core/StackExchange.Redis.Extensions.Core/Abstractions/IRedisDatabase.VectorSet.cs @@ -96,4 +96,40 @@ public partial interface IRedisDatabase /// Behaviour markers associated with a given command. /// A random member, or null if the VectorSet is empty. Task VectorSetRandomMemberAsync(string key, CommandFlags flag = CommandFlags.None); + + /// + /// Returns multiple random members from the VectorSet stored at key. + /// + /// The key of the VectorSet. + /// The number of random members to return. + /// Behaviour markers associated with a given command. + /// An array of random members. + Task VectorSetRandomMembersAsync(string key, long count, CommandFlags flag = CommandFlags.None); + + /// + /// Returns the approximate vector for a member in the VectorSet. + /// + /// The key of the VectorSet. + /// The member to retrieve the vector for. + /// Behaviour markers associated with a given command. + /// The approximate vector as a Lease of floats. Must be disposed after use. Null if member not found. + Task?> VectorSetGetApproximateVectorAsync(string key, string member, CommandFlags flag = CommandFlags.None); + + /// + /// Returns the links (neighbors) for a member in the VectorSet's HNSW graph. + /// + /// The key of the VectorSet. + /// The member to retrieve links for. + /// Behaviour markers associated with a given command. + /// The linked member names. The returned Lease must be disposed after use. + Task?> VectorSetGetLinksAsync(string key, string member, CommandFlags flag = CommandFlags.None); + + /// + /// Returns the links (neighbors) with similarity scores for a member in the VectorSet's HNSW graph. + /// + /// The key of the VectorSet. + /// The member to retrieve links for. + /// Behaviour markers associated with a given command. + /// The links with scores. The returned Lease must be disposed after use. + Task?> VectorSetGetLinksWithScoresAsync(string key, string member, CommandFlags flag = CommandFlags.None); } diff --git a/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.VectorSet.cs b/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.VectorSet.cs index 86d01362..3e073fb5 100644 --- a/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.VectorSet.cs +++ b/src/core/StackExchange.Redis.Extensions.Core/Implementations/RedisDatabase.VectorSet.cs @@ -1,6 +1,5 @@ // 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; using System.Threading.Tasks; namespace StackExchange.Redis.Extensions.Core.Implementations; @@ -25,12 +24,16 @@ public Task VectorSetContainsAsync(string key, string member, CommandFlags Database.VectorSetContainsAsync(key, member, flag); /// - public Task VectorSetLengthAsync(string key, CommandFlags flag = CommandFlags.None) => - Database.VectorSetLengthAsync(key, flag).ContinueWith(t => (long)t.Result, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); +#pragma warning disable RCS1174 // async/await required for cross-TFM int→long cast + public async Task VectorSetLengthAsync(string key, CommandFlags flag = CommandFlags.None) => + await Database.VectorSetLengthAsync(key, flag).ConfigureAwait(false); +#pragma warning restore RCS1174 /// - public Task VectorSetDimensionAsync(string key, CommandFlags flag = CommandFlags.None) => - Database.VectorSetDimensionAsync(key, flag).ContinueWith(t => (long)t.Result, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); +#pragma warning disable RCS1174 + public async Task VectorSetDimensionAsync(string key, CommandFlags flag = CommandFlags.None) => + await Database.VectorSetDimensionAsync(key, flag).ConfigureAwait(false); +#pragma warning restore RCS1174 /// public Task VectorSetGetAttributesJsonAsync(string key, string member, CommandFlags flag = CommandFlags.None) => @@ -47,4 +50,20 @@ public Task VectorSetSetAttributesJsonAsync(string key, string member, str /// public Task VectorSetRandomMemberAsync(string key, CommandFlags flag = CommandFlags.None) => Database.VectorSetRandomMemberAsync(key, flag); + + /// + public Task VectorSetRandomMembersAsync(string key, long count, CommandFlags flag = CommandFlags.None) => + Database.VectorSetRandomMembersAsync(key, count, flag); + + /// + public Task?> VectorSetGetApproximateVectorAsync(string key, string member, CommandFlags flag = CommandFlags.None) => + Database.VectorSetGetApproximateVectorAsync(key, member, flag); + + /// + public Task?> VectorSetGetLinksAsync(string key, string member, CommandFlags flag = CommandFlags.None) => + Database.VectorSetGetLinksAsync(key, member, flag); + + /// + public Task?> VectorSetGetLinksWithScoresAsync(string key, string member, CommandFlags flag = CommandFlags.None) => + Database.VectorSetGetLinksWithScoresAsync(key, member, flag); }