Skip to content

.Net: [MEVD] Added GetService and Metadata for vector store abstractions #11055

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
762adb7
Added metadata and GetService for IVectorStore
dmytrostruk Mar 18, 2025
a718acd
Added metadata and GetService for IVectorStoreRecordCollection
dmytrostruk Mar 18, 2025
b5bc68c
Small updates in tests
dmytrostruk Mar 19, 2025
c35ac3e
Updated baseline version
dmytrostruk Mar 19, 2025
5844a65
Small updates
dmytrostruk Mar 19, 2025
211f149
Merge branch 'main' into vector-data-metadata
dmytrostruk Mar 19, 2025
cdbeeb5
Updated suppression files
dmytrostruk Mar 19, 2025
dc1cba2
Fixed Azure AI Search unit tests
dmytrostruk Mar 19, 2025
f79ee7b
Fixed unit tests for Sqlite
dmytrostruk Mar 19, 2025
648278f
Fixed more unit tests
dmytrostruk Mar 19, 2025
f631bb2
Merge branch 'main' into vector-data-metadata
dmytrostruk Mar 19, 2025
2cade4f
Fixed SQL Server tests
dmytrostruk Mar 19, 2025
5ab0890
Merge branch 'main' into vector-data-metadata
dmytrostruk Mar 19, 2025
dd20b69
Addressed PR feedback
dmytrostruk Mar 20, 2025
8791b69
Merge branch 'vector-data-metadata' of https://github.com/dmytrostruk…
dmytrostruk Mar 20, 2025
42bad91
Merge branch 'feature-vector-data-preb2' into vector-data-metadata
dmytrostruk Mar 20, 2025
8ea1c84
More updates after merge
dmytrostruk Mar 20, 2025
df076b1
Small fix
dmytrostruk Mar 20, 2025
fd907a4
Removed some of the metadata classes
dmytrostruk Mar 20, 2025
75cffa1
Merge branch 'feature-vector-data-preb2' into vector-data-metadata
dmytrostruk Apr 1, 2025
fb715b0
Merge branch 'feature-vector-data-preb2' into vector-data-metadata
dmytrostruk Apr 2, 2025
8c761d0
Merge branch 'feature-vector-data-preb2' into vector-data-metadata
dmytrostruk Apr 4, 2025
cdb336f
Addressed PR feedback
dmytrostruk Apr 4, 2025
2692768
Removed obsolete flag
dmytrostruk Apr 7, 2025
3dceb88
Small fix
dmytrostruk Apr 7, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ public IVectorStoreRecordCollection<TKey, TRecord> GetCollection<TKey, TRecord>(
return embeddingStore;
}

/// <inheritdoc />
public object? GetService(Type serviceType, object? serviceKey = null)
{
ArgumentNullException.ThrowIfNull(serviceType);

return
serviceKey is null && serviceType.IsInstanceOfType(this) ? this :
this._decoratedVectorStore.GetService(serviceType, serviceKey);
}

/// <inheritdoc />
public IAsyncEnumerable<string> ListCollectionNamesAsync(CancellationToken cancellationToken = default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public TextEmbeddingVectorStoreRecordCollection(IVectorStoreRecordCollection<TKe
}

/// <inheritdoc />
[Obsolete("Use GetService(typeof(VectorStoreRecordCollectionMetadata)) to get an information about vector store record collection.")]
public string CollectionName => this._decoratedVectorStoreRecordCollection.CollectionName;

/// <inheritdoc />
Expand Down Expand Up @@ -132,6 +133,16 @@ public async Task<VectorSearchResults<TRecord>> VectorizableTextSearchAsync(stri
return await this.VectorizedSearchAsync(embeddingValue, options, cancellationToken).ConfigureAwait(false);
}

/// <inheritdoc />
public object? GetService(Type serviceType, object? serviceKey = null)
{
ArgumentNullException.ThrowIfNull(serviceType);

return
serviceKey is null && serviceType.IsInstanceOfType(this) ? this :
this._decoratedVectorStoreRecordCollection.GetService(serviceType, serviceKey);
}

/// <summary>
/// Generate and add embeddings for each embedding field that has a <see cref="GenerateTextEmbeddingAttribute"/> on the provided record.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions dotnet/samples/Concepts/Search/VectorStore_TextSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,16 @@ public async Task<VectorSearchResults<TRecord>> VectorizableTextSearchAsync(stri

return await vectorizedSearch.VectorizedSearchAsync(vectorizedQuery, options, cancellationToken);
}

/// <inheritdoc />
public object? GetService(Type serviceType, object? serviceKey = null)
{
ArgumentNullException.ThrowIfNull(serviceType);

return
serviceKey is null && serviceType.IsInstanceOfType(this) ? this :
vectorizedSearch.GetService(serviceType, serviceKey);
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public AzureAISearchVectorStoreRecordCollectionTests()
this._searchClientMock = new Mock<SearchClient>(MockBehavior.Strict);
this._searchIndexClientMock = new Mock<SearchIndexClient>(MockBehavior.Strict);
this._searchIndexClientMock.Setup(x => x.GetSearchClient(TestCollectionName)).Returns(this._searchClientMock.Object);
this._searchIndexClientMock.Setup(x => x.ServiceName).Returns("TestService");
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public AzureAISearchVectorStoreTests()
this._searchClientMock = new Mock<SearchClient>(MockBehavior.Strict);
this._searchIndexClientMock = new Mock<SearchIndexClient>(MockBehavior.Strict);
this._searchIndexClientMock.Setup(x => x.GetSearchClient(TestCollectionName)).Returns(this._searchClientMock.Object);
this._searchIndexClientMock.Setup(x => x.ServiceName).Returns("TestService");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Microsoft.SemanticKernel.Connectors.AzureAISearch;

#pragma warning disable SKEXP0020 // VectorStoreMetadata is experimental

/// <summary>
/// Class for accessing the list of collections in a Azure AI Search vector store.
/// </summary>
Expand All @@ -19,8 +21,8 @@ namespace Microsoft.SemanticKernel.Connectors.AzureAISearch;
/// </remarks>
public class AzureAISearchVectorStore : IVectorStore
{
/// <summary>The name of this database for telemetry purposes.</summary>
private const string DatabaseName = "AzureAISearch";
/// <summary>Metadata about vector store.</summary>
private readonly VectorStoreMetadata _metadata;

/// <summary>Azure AI Search client that can be used to manage the list of indices in an Azure AI Search Service.</summary>
private readonly SearchIndexClient _searchIndexClient;
Expand All @@ -39,6 +41,12 @@ public AzureAISearchVectorStore(SearchIndexClient searchIndexClient, AzureAISear

this._searchIndexClient = searchIndexClient;
this._options = options ?? new AzureAISearchVectorStoreOptions();

this._metadata = new()
{
VectorStoreSystemName = "azure.aisearch",
DatabaseName = searchIndexClient.ServiceName
};
}

/// <inheritdoc />
Expand Down Expand Up @@ -75,22 +83,38 @@ public virtual async IAsyncEnumerable<string> ListCollectionNamesAsync([Enumerat
var indexNamesEnumerable = this._searchIndexClient.GetIndexNamesAsync(cancellationToken).ConfigureAwait(false);
var indexNamesEnumerator = indexNamesEnumerable.GetAsyncEnumerator();

var nextResult = await GetNextIndexNameAsync(indexNamesEnumerator).ConfigureAwait(false);
var nextResult = await GetNextIndexNameAsync(indexNamesEnumerator, this._metadata.VectorStoreSystemName).ConfigureAwait(false);
while (nextResult.more)
{
yield return nextResult.name;
nextResult = await GetNextIndexNameAsync(indexNamesEnumerator).ConfigureAwait(false);
nextResult = await GetNextIndexNameAsync(indexNamesEnumerator, this._metadata.VectorStoreSystemName).ConfigureAwait(false);
}
}

/// <inheritdoc />
public object? GetService(Type serviceType, object? serviceKey = null)
{
Verify.NotNull(serviceType);

return
serviceKey is not null ? null :
serviceType == typeof(VectorStoreMetadata) ? this._metadata :
serviceType == typeof(SearchIndexClient) ? this._searchIndexClient :
serviceType.IsInstanceOfType(this) ? this :
null;
}

/// <summary>
/// Helper method to get the next index name from the enumerator with a try catch around the move next call to convert
/// any <see cref="RequestFailedException"/> to <see cref="VectorStoreOperationException"/>, since try catch is not supported
/// around a yield return.
/// </summary>
/// <param name="enumerator">The enumerator to get the next result from.</param>
/// <param name="vectorStoreSystemName">The vector store system name.</param>
/// <returns>A value indicating whether there are more results and the current string if true.</returns>
private static async Task<(string name, bool more)> GetNextIndexNameAsync(ConfiguredCancelableAsyncEnumerable<string>.Enumerator enumerator)
private static async Task<(string name, bool more)> GetNextIndexNameAsync(
ConfiguredCancelableAsyncEnumerable<string>.Enumerator enumerator,
string? vectorStoreSystemName)
{
const string OperationName = "GetIndexNames";

Expand All @@ -103,15 +127,15 @@ public virtual async IAsyncEnumerable<string> ListCollectionNamesAsync([Enumerat
{
throw new VectorStoreOperationException("Call to vector store failed.", ex)
{
VectorStoreType = DatabaseName,
VectorStoreType = vectorStoreSystemName,
OperationName = OperationName
};
}
catch (RequestFailedException ex)
{
throw new VectorStoreOperationException("Call to vector store failed.", ex)
{
VectorStoreType = DatabaseName,
VectorStoreType = vectorStoreSystemName,
OperationName = OperationName
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

namespace Microsoft.SemanticKernel.Connectors.AzureAISearch;

#pragma warning disable SKEXP0020 // Metadata classes are experimental

/// <summary>
/// Service for storing and retrieving vector records, that uses Azure AI Search as the underlying storage.
/// </summary>
Expand All @@ -29,8 +31,8 @@ public class AzureAISearchVectorStoreRecordCollection<TRecord> :
IKeywordHybridSearch<TRecord>
#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
{
/// <summary>The name of this database for telemetry purposes.</summary>
private const string DatabaseName = "AzureAISearch";
/// <summary>Metadata about vector store record collection.</summary>
private readonly VectorStoreRecordCollectionMetadata _collectionMetadata;

/// <summary>A set of types that a key on the provided model may have.</summary>
private static readonly HashSet<Type> s_supportedKeyTypes =
Expand Down Expand Up @@ -141,6 +143,13 @@ public AzureAISearchVectorStoreRecordCollection(SearchIndexClient searchIndexCli
{
this._mapper = new AzureAISearchGenericDataModelMapper(this._propertyReader.RecordDefinition) as IVectorStoreRecordMapper<TRecord, JsonObject>;
}

this._collectionMetadata = new()
{
VectorStoreSystemName = "azure.aisearch",
DatabaseName = searchIndexClient.ServiceName,
CollectionName = collectionName
};
}

/// <inheritdoc />
Expand All @@ -162,8 +171,8 @@ public virtual async Task<bool> CollectionExistsAsync(CancellationToken cancella
{
throw new VectorStoreOperationException("Call to vector store failed.", ex)
{
VectorStoreType = DatabaseName,
CollectionName = this._collectionName,
VectorStoreType = this._collectionMetadata.VectorStoreSystemName,
CollectionName = this._collectionMetadata.CollectionName,
OperationName = "GetIndex"
};
}
Expand Down Expand Up @@ -496,6 +505,20 @@ public Task<VectorSearchResults<TRecord>> HybridSearchAsync<TVector>(TVector vec
return this.SearchAndMapToDataModelAsync(keywordsCombined, searchOptions, internalOptions.IncludeVectors, cancellationToken);
}

/// <inheritdoc />
public object? GetService(Type serviceType, object? serviceKey = null)
{
Verify.NotNull(serviceType);

return
serviceKey is not null ? null :
serviceType == typeof(VectorStoreRecordCollectionMetadata) ? this._collectionMetadata :
serviceType == typeof(SearchIndexClient) ? this._searchIndexClient :
serviceType == typeof(SearchClient) ? this._searchClient :
serviceType.IsInstanceOfType(this) ? this :
null;
}

/// <summary>
/// Get the document with the given key and map it to the data model using the configured mapper type.
/// </summary>
Expand Down Expand Up @@ -525,7 +548,7 @@ public Task<VectorSearchResults<TRecord>> HybridSearchAsync<TVector>(TVector vec
}

return VectorStoreErrorHandler.RunModelConversion(
DatabaseName,
this._collectionMetadata.VectorStoreSystemName!,
this._collectionName,
OperationName,
() => this._mapper!.MapFromStorageToDataModel(jsonObject, new() { IncludeVectors = includeVectors }));
Expand Down Expand Up @@ -588,7 +611,7 @@ private Task<Response<IndexDocumentsResult>> MapToStorageModelAndUploadDocumentA
if (this._mapper is not null)
{
var jsonObjects = VectorStoreErrorHandler.RunModelConversion(
DatabaseName,
this._collectionMetadata.VectorStoreSystemName!,
this._collectionName,
OperationName,
() => records.Select(this._mapper!.MapFromDataToStorageModel));
Expand Down Expand Up @@ -616,7 +639,7 @@ private async IAsyncEnumerable<VectorSearchResult<TRecord>> MapSearchResultsAsyn
await foreach (var result in results.ConfigureAwait(false))
{
var document = VectorStoreErrorHandler.RunModelConversion(
DatabaseName,
this._collectionMetadata.VectorStoreSystemName!,
this._collectionName,
operationName,
() => this._options.JsonObjectCustomMapper!.MapFromStorageToDataModel(result.Document, new() { IncludeVectors = includeVectors }));
Expand Down Expand Up @@ -696,17 +719,17 @@ private async Task<T> RunOperationAsync<T>(string operationName, Func<Task<T>> o
{
throw new VectorStoreOperationException("Call to vector store failed.", ex)
{
VectorStoreType = DatabaseName,
CollectionName = this._collectionName,
VectorStoreType = this._collectionMetadata.VectorStoreSystemName,
CollectionName = this._collectionMetadata.CollectionName,
OperationName = operationName
};
}
catch (RequestFailedException ex)
{
throw new VectorStoreOperationException("Call to vector store failed.", ex)
{
VectorStoreType = DatabaseName,
CollectionName = this._collectionName,
VectorStoreType = this._collectionMetadata.VectorStoreSystemName,
CollectionName = this._collectionMetadata.CollectionName,
OperationName = operationName
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

namespace Microsoft.SemanticKernel.Connectors.AzureCosmosDBMongoDB;

#pragma warning disable SKEXP0020 // VectorStoreMetadata is experimental

/// <summary>
/// Class for accessing the list of collections in a Azure CosmosDB MongoDB vector store.
/// </summary>
Expand All @@ -17,6 +19,9 @@ namespace Microsoft.SemanticKernel.Connectors.AzureCosmosDBMongoDB;
/// </remarks>
public class AzureCosmosDBMongoDBVectorStore : IVectorStore
{
/// <summary>Metadata about vector store.</summary>
private readonly VectorStoreMetadata _metadata;

/// <summary><see cref="IMongoDatabase"/> that can be used to manage the collections in Azure CosmosDB MongoDB.</summary>
private readonly IMongoDatabase _mongoDatabase;

Expand All @@ -34,6 +39,12 @@ public AzureCosmosDBMongoDBVectorStore(IMongoDatabase mongoDatabase, AzureCosmos

this._mongoDatabase = mongoDatabase;
this._options = options ?? new();

this._metadata = new()
{
VectorStoreSystemName = "azure.cosmosdbmongodb",
DatabaseName = mongoDatabase.DatabaseNamespace?.DatabaseName
};
}

/// <inheritdoc />
Expand Down Expand Up @@ -75,4 +86,17 @@ public virtual async IAsyncEnumerable<string> ListCollectionNamesAsync([Enumerat
}
}
}

/// <inheritdoc />
public object? GetService(Type serviceType, object? serviceKey = null)
{
Verify.NotNull(serviceType);

return
serviceKey is not null ? null :
serviceType == typeof(VectorStoreMetadata) ? this._metadata :
serviceType == typeof(IMongoDatabase) ? this._mongoDatabase :
serviceType.IsInstanceOfType(this) ? this :
null;
}
}
Loading
Loading