Skip to content

Commit fba0aba

Browse files
authored
.Net: Use AsynEnumerable search interface for azure ai search. (#9090)
### Motivation and Context AzureAISearch has two ways of accessing search results. ### Description - Switching to IAsyncEnumerable interface to improve performance. - Switch to AzureOpenAI for integration test embeddings to remove need for key management. - Fix small bug in powershell module to create azure ai search test infra #9077 ### 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 cc8fe45 commit fba0aba

File tree

3 files changed

+49
-21
lines changed

3 files changed

+49
-21
lines changed

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

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ public Task<VectorSearchResults<TRecord>> VectorizableTextSearchAsync(string sea
446446
/// <param name="searchOptions">The options controlling the behavior of the search operation.</param>
447447
/// <param name="includeVectors">A value indicating whether to include vectors in the result or not.</param>
448448
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
449-
/// <returns></returns>
449+
/// <returns>The mapped search results.</returns>
450450
private async Task<VectorSearchResults<TRecord>> SearchAndMapToDataModelAsync(
451451
string? searchText,
452452
SearchOptions searchOptions,
@@ -462,23 +462,14 @@ private async Task<VectorSearchResults<TRecord>> SearchAndMapToDataModelAsync(
462462
OperationName,
463463
() => this._searchClient.SearchAsync<JsonObject>(searchText, searchOptions, cancellationToken)).ConfigureAwait(false);
464464

465-
var mappedJsonObjectResults = jsonObjectResults.Value.GetResults().Select(result =>
466-
{
467-
var document = VectorStoreErrorHandler.RunModelConversion(
468-
DatabaseName,
469-
this._collectionName,
470-
OperationName,
471-
() => this._options.JsonObjectCustomMapper!.MapFromStorageToDataModel(result.Document, new() { IncludeVectors = includeVectors }));
472-
return new VectorSearchResult<TRecord>(document, result.Score);
473-
});
474-
475-
return new VectorSearchResults<TRecord>(mappedJsonObjectResults.ToAsyncEnumerable()) { TotalCount = jsonObjectResults.Value.TotalCount };
465+
var mappedJsonObjectResults = this.MapSearchResultsAsync(jsonObjectResults.Value.GetResultsAsync(), OperationName, includeVectors);
466+
return new VectorSearchResults<TRecord>(mappedJsonObjectResults) { TotalCount = jsonObjectResults.Value.TotalCount };
476467
}
477468

478469
// Execute search and map using the built in Azure AI Search mapper.
479470
Response<SearchResults<TRecord>> results = await this.RunOperationAsync(OperationName, () => this._searchClient.SearchAsync<TRecord>(searchText, searchOptions, cancellationToken)).ConfigureAwait(false);
480-
var mappedResults = results.Value.GetResults().Select(result => new VectorSearchResult<TRecord>(result.Document, result.Score));
481-
return new VectorSearchResults<TRecord>(mappedResults.ToAsyncEnumerable()) { TotalCount = results.Value.TotalCount };
471+
var mappedResults = this.MapSearchResultsAsync(results.Value.GetResultsAsync());
472+
return new VectorSearchResults<TRecord>(mappedResults) { TotalCount = results.Value.TotalCount };
482473
}
483474

484475
/// <summary>
@@ -515,6 +506,39 @@ private Task<Response<IndexDocumentsResult>> MapToStorageModelAndUploadDocumentA
515506
() => this._searchClient.UploadDocumentsAsync<TRecord>(records, innerOptions, cancellationToken));
516507
}
517508

509+
/// <summary>
510+
/// Map the search results from <see cref="SearchResult{JsonObject}"/> to <see cref="VectorSearchResults{TRecord}"/> objects using the configured mapper type.
511+
/// </summary>
512+
/// <param name="results">The search results to map.</param>
513+
/// <param name="operationName">The name of the current operation for telemetry purposes.</param>
514+
/// <param name="includeVectors">A value indicating whether to include vectors in the resultset or not.</param>
515+
/// <returns>The mapped results.</returns>
516+
private async IAsyncEnumerable<VectorSearchResult<TRecord>> MapSearchResultsAsync(IAsyncEnumerable<SearchResult<JsonObject>> results, string operationName, bool includeVectors)
517+
{
518+
await foreach (var result in results.ConfigureAwait(false))
519+
{
520+
var document = VectorStoreErrorHandler.RunModelConversion(
521+
DatabaseName,
522+
this._collectionName,
523+
operationName,
524+
() => this._options.JsonObjectCustomMapper!.MapFromStorageToDataModel(result.Document, new() { IncludeVectors = includeVectors }));
525+
yield return new VectorSearchResult<TRecord>(document, result.Score);
526+
}
527+
}
528+
529+
/// <summary>
530+
/// Map the search results from <see cref="SearchResult{TRecord}"/> to <see cref="VectorSearchResults{TRecord}"/> objects.
531+
/// </summary>
532+
/// <param name="results">The search results to map.</param>
533+
/// <returns>The mapped results.</returns>
534+
private async IAsyncEnumerable<VectorSearchResult<TRecord>> MapSearchResultsAsync(IAsyncEnumerable<SearchResult<TRecord>> results)
535+
{
536+
await foreach (var result in results.ConfigureAwait(false))
537+
{
538+
yield return new VectorSearchResult<TRecord>(result.Document, result.Score);
539+
}
540+
}
541+
518542
/// <summary>
519543
/// Convert the public <see cref="GetRecordOptions"/> options model to the Azure AI Search <see cref="GetDocumentOptions"/> options model.
520544
/// </summary>

dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchTextSearchTests.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
using System;
44
using System.Threading.Tasks;
5+
using Azure.Identity;
56
using Microsoft.Extensions.Configuration;
67
using Microsoft.SemanticKernel.Connectors.AzureAISearch;
7-
using Microsoft.SemanticKernel.Connectors.OpenAI;
8+
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
89
using Microsoft.SemanticKernel.Data;
910
using SemanticKernel.IntegrationTests.Data;
1011
using SemanticKernel.IntegrationTests.TestSettings;
@@ -69,11 +70,14 @@ public override Task<ITextSearch> CreateTextSearchAsync()
6970
{
7071
if (this.VectorStore is null)
7172
{
72-
OpenAIConfiguration? openAIConfiguration = this.Configuration.GetSection("OpenAIEmbeddings").Get<OpenAIConfiguration>();
73-
Assert.NotNull(openAIConfiguration);
74-
Assert.NotEmpty(openAIConfiguration.ModelId);
75-
Assert.NotEmpty(openAIConfiguration.ApiKey);
76-
this.EmbeddingGenerator = new OpenAITextEmbeddingGenerationService(openAIConfiguration.ModelId, openAIConfiguration.ApiKey);
73+
AzureOpenAIConfiguration? azureOpenAIConfiguration = this.Configuration.GetSection("AzureOpenAIEmbeddings").Get<AzureOpenAIConfiguration>();
74+
Assert.NotNull(azureOpenAIConfiguration);
75+
Assert.NotEmpty(azureOpenAIConfiguration.DeploymentName);
76+
Assert.NotEmpty(azureOpenAIConfiguration.Endpoint);
77+
this.EmbeddingGenerator = new AzureOpenAITextEmbeddingGenerationService(
78+
azureOpenAIConfiguration.DeploymentName,
79+
azureOpenAIConfiguration.Endpoint,
80+
new AzureCliCredential());
7781

7882
this.VectorStore = new AzureAISearchVectorStore(fixture.SearchIndexClient);
7983
}

dotnet/src/IntegrationTests/TestSettings/Memory/AzureAISearchSetup.psm1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function New-AzureAISearchIntegrationInfra($overrideResourceGroup = $resourceGro
3232
}
3333

3434
# Create the ai search service if it doesn't exist.
35-
$service = Get-AzSearchService -ResourceGroupName $resourceGroup -Name $aiSearchResourceName
35+
$service = Get-AzSearchService -ResourceGroupName $overrideResourceGroup -Name $overrideAISearchResourceName
3636
if (-not $service) {
3737
Write-Host "Service does not exist, creating '$overrideAISearchResourceName' ..."
3838
New-AzSearchService -ResourceGroupName $overrideResourceGroup -Name $overrideAISearchResourceName -Sku "Basic" -Location "North Europe" -PartitionCount 1 -ReplicaCount 1 -HostingMode Default

0 commit comments

Comments
 (0)