Skip to content

Commit a964eea

Browse files
committed
.Net: Adding generic redis IVectorRecordStore implementation (#6741)
### Motivation and Context As part of the evolution of memory connectors, we need to support custom data models and remove opinionated behaviors, so adding a new record store implementation for redis. ### Description Adding an implementation for IVectorRecordStore for redis with support for: Custom mappers Generic data models Annotating data models via attributes or via definition objects. Also improving some styling in the AzureAISearch implementation. See #6525 ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
1 parent 239e80a commit a964eea

File tree

11 files changed

+1085
-47
lines changed

11 files changed

+1085
-47
lines changed

dotnet/src/Connectors/Connectors.Memory.AzureAISearch/AzureAISearchVectorRecordStore.cs

+22-22
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
namespace Microsoft.SemanticKernel.Connectors.AzureAISearch;
1919

2020
/// <summary>
21-
/// Service for storing and retrieving records, that uses Azure AI Search as the underlying storage.
21+
/// Service for storing and retrieving vector records, that uses Azure AI Search as the underlying storage.
2222
/// </summary>
2323
/// <typeparam name="TRecord">The data model to use for adding, updating and retrieving data from storage.</typeparam>
2424
public sealed class AzureAISearchVectorRecordStore<TRecord> : IVectorRecordStore<string, TRecord>
@@ -140,9 +140,9 @@ public Task DeleteAsync(string key, DeleteRecordOptions? options = default, Canc
140140
// Remove record.
141141
var searchClient = this.GetSearchClient(collectionName);
142142
return RunOperationAsync(
143-
() => searchClient.DeleteDocumentsAsync(this._keyPropertyName, [key], new IndexDocumentsOptions(), cancellationToken),
144143
collectionName,
145-
"DeleteDocuments");
144+
"DeleteDocuments",
145+
() => searchClient.DeleteDocumentsAsync(this._keyPropertyName, [key], new IndexDocumentsOptions(), cancellationToken));
146146
}
147147

148148
/// <inheritdoc />
@@ -156,9 +156,9 @@ public Task DeleteBatchAsync(IEnumerable<string> keys, DeleteRecordOptions? opti
156156
// Remove records.
157157
var searchClient = this.GetSearchClient(collectionName);
158158
return RunOperationAsync(
159-
() => searchClient.DeleteDocumentsAsync(this._keyPropertyName, keys, new IndexDocumentsOptions(), cancellationToken),
160159
collectionName,
161-
"DeleteDocuments");
160+
"DeleteDocuments",
161+
() => searchClient.DeleteDocumentsAsync(this._keyPropertyName, keys, new IndexDocumentsOptions(), cancellationToken));
162162
}
163163

164164
/// <inheritdoc />
@@ -214,21 +214,21 @@ private async Task<TRecord> GetDocumentAndMapToDataModelAsync(
214214
if (this._options.MapperType == AzureAISearchRecordMapperType.JsonObjectCustomMapper)
215215
{
216216
var jsonObject = await RunOperationAsync(
217-
() => searchClient.GetDocumentAsync<JsonObject>(key, innerOptions, cancellationToken),
218217
collectionName,
219-
"GetDocument").ConfigureAwait(false);
218+
"GetDocument",
219+
() => searchClient.GetDocumentAsync<JsonObject>(key, innerOptions, cancellationToken)).ConfigureAwait(false);
220220

221221
return RunModelConversion(
222-
() => this._options.JsonObjectCustomMapper!.MapFromStorageToDataModel(jsonObject),
223222
collectionName,
224-
"GetDocument");
223+
"GetDocument",
224+
() => this._options.JsonObjectCustomMapper!.MapFromStorageToDataModel(jsonObject));
225225
}
226226

227227
// Use the built in Azure AI Search mapper.
228228
return await RunOperationAsync(
229-
() => searchClient.GetDocumentAsync<TRecord>(key, innerOptions, cancellationToken),
230229
collectionName,
231-
"GetDocument").ConfigureAwait(false);
230+
"GetDocument",
231+
() => searchClient.GetDocumentAsync<TRecord>(key, innerOptions, cancellationToken)).ConfigureAwait(false);
232232
}
233233

234234
/// <summary>
@@ -251,21 +251,21 @@ private Task<Response<IndexDocumentsResult>> MapToStorageModelAndUploadDocumentA
251251
if (this._options.MapperType == AzureAISearchRecordMapperType.JsonObjectCustomMapper)
252252
{
253253
var jsonObjects = RunModelConversion(
254-
() => records.Select(this._options.JsonObjectCustomMapper!.MapFromDataToStorageModel),
255254
collectionName,
256-
"UploadDocuments");
255+
"UploadDocuments",
256+
() => records.Select(this._options.JsonObjectCustomMapper!.MapFromDataToStorageModel));
257257

258258
return RunOperationAsync(
259-
() => searchClient.UploadDocumentsAsync<JsonObject>(jsonObjects, innerOptions, cancellationToken),
260259
collectionName,
261-
"UploadDocuments");
260+
"UploadDocuments",
261+
() => searchClient.UploadDocumentsAsync<JsonObject>(jsonObjects, innerOptions, cancellationToken));
262262
}
263263

264264
// Use the built in Azure AI Search mapper.
265265
return RunOperationAsync(
266-
() => searchClient.UploadDocumentsAsync<TRecord>(records, innerOptions, cancellationToken),
267266
collectionName,
268-
"UploadDocuments");
267+
"UploadDocuments",
268+
() => searchClient.UploadDocumentsAsync<TRecord>(records, innerOptions, cancellationToken));
269269
}
270270

271271
/// <summary>
@@ -322,14 +322,14 @@ private GetDocumentOptions ConvertGetDocumentOptions(GetRecordOptions? options)
322322
}
323323

324324
/// <summary>
325-
/// Run the given operation and wrap any <see cref="RequestFailedException"/> with <see cref="HttpOperationException"/>."/>
325+
/// Run the given operation and wrap any <see cref="RequestFailedException"/> with <see cref="VectorStoreOperationException"/>."/>
326326
/// </summary>
327327
/// <typeparam name="T">The response type of the operation.</typeparam>
328-
/// <param name="operation">The operation to run.</param>
329328
/// <param name="collectionName">The name of the collection the operation is being run on.</param>
330329
/// <param name="operationName">The type of database operation being run.</param>
330+
/// <param name="operation">The operation to run.</param>
331331
/// <returns>The result of the operation.</returns>
332-
private static async Task<T> RunOperationAsync<T>(Func<Task<T>> operation, string collectionName, string operationName)
332+
private static async Task<T> RunOperationAsync<T>(string collectionName, string operationName, Func<Task<T>> operation)
333333
{
334334
try
335335
{
@@ -365,11 +365,11 @@ private static async Task<T> RunOperationAsync<T>(Func<Task<T>> operation, strin
365365
/// Run the given model conversion and wrap any exceptions with <see cref="VectorStoreRecordMappingException"/>.
366366
/// </summary>
367367
/// <typeparam name="T">The response type of the operation.</typeparam>
368-
/// <param name="operation">The operation to run.</param>
369368
/// <param name="collectionName">The name of the collection the operation is being run on.</param>
370369
/// <param name="operationName">The type of database operation being run.</param>
370+
/// <param name="operation">The operation to run.</param>
371371
/// <returns>The result of the operation.</returns>
372-
private static T RunModelConversion<T>(Func<T> operation, string collectionName, string operationName)
372+
private static T RunModelConversion<T>(string collectionName, string operationName, Func<T> operation)
373373
{
374374
try
375375
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Text.Json.Nodes;
4+
5+
namespace Microsoft.SemanticKernel.Connectors.Redis;
6+
7+
/// <summary>
8+
/// The types of mapper supported by <see cref="RedisVectorRecordStore{TRecord}"/>.
9+
/// </summary>
10+
public enum RedisRecordMapperType
11+
{
12+
/// <summary>
13+
/// Use the default semantic kernel mapper that uses property attributes to determine how to map fields.
14+
/// </summary>
15+
Default,
16+
17+
/// <summary>
18+
/// Use a custom mapper between <see cref="JsonNode"/> and the data model.
19+
/// </summary>
20+
JsonNodeCustomMapper
21+
}

0 commit comments

Comments
 (0)