Skip to content

.Net MEVD Replace VectorSearchResults with IAsyncEnumerable #11486

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
merged 9 commits into from
Apr 12, 2025
4 changes: 2 additions & 2 deletions dotnet/samples/Concepts/Caching/SemanticCachingWithFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRe
await collection.CreateCollectionIfNotExistsAsync();

// Search for similar prompts in cache.
var searchResults = await collection.VectorizedSearchAsync(promptEmbedding, top: 1, cancellationToken: context.CancellationToken);
var searchResult = (await searchResults.Results.FirstOrDefaultAsync())?.Record;
var searchResults = collection.VectorizedSearchAsync(promptEmbedding, top: 1, cancellationToken: context.CancellationToken);
var searchResult = (await searchResults.FirstOrDefaultAsync())?.Record;

// If result exists, return it.
if (searchResult is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Embeddings;

Expand Down Expand Up @@ -117,7 +118,7 @@ public async Task<IReadOnlyList<TKey>> UpsertAsync(IEnumerable<TRecord> records,
}

/// <inheritdoc />
public Task<VectorSearchResults<TRecord>> VectorizedSearchAsync<TVector>(TVector vector, int top, VectorSearchOptions<TRecord>? options = null, CancellationToken cancellationToken = default)
public IAsyncEnumerable<VectorSearchResult<TRecord>> VectorizedSearchAsync<TVector>(TVector vector, int top, VectorSearchOptions<TRecord>? options = null, CancellationToken cancellationToken = default)
{
return this._decoratedVectorStoreRecordCollection.VectorizedSearchAsync(vector, top, options, cancellationToken);
}
Expand All @@ -127,10 +128,13 @@ public IAsyncEnumerable<TRecord> GetAsync(Expression<Func<TRecord, bool>> filter
=> this._decoratedVectorStoreRecordCollection.GetAsync(filter, top, options, cancellationToken);

/// <inheritdoc />
public async Task<VectorSearchResults<TRecord>> VectorizableTextSearchAsync(string searchText, int top, VectorSearchOptions<TRecord>? options = null, CancellationToken cancellationToken = default)
public async IAsyncEnumerable<VectorSearchResult<TRecord>> VectorizableTextSearchAsync(string searchText, int top, VectorSearchOptions<TRecord>? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var embeddingValue = await this._textEmbeddingGenerationService.GenerateEmbeddingAsync(searchText, cancellationToken: cancellationToken).ConfigureAwait(false);
return await this.VectorizedSearchAsync(embeddingValue, top, options, cancellationToken).ConfigureAwait(false);
await foreach (var result in this.VectorizedSearchAsync(embeddingValue, top, options, cancellationToken))
{
yield return result;
}
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ public async Task UseEmbeddingGenerationViaDecoratorAsync()
// Search the collection using a vectorizable text search.
var search = collection as IVectorizableTextSearch<Glossary>;
var searchString = "What is an Application Programming Interface";
var searchResult = await search!.VectorizableTextSearchAsync(searchString, top: 1);
var resultRecords = await searchResult.Results.ToListAsync();
var resultRecords = await search!.VectorizableTextSearchAsync(searchString, top: 1).ToListAsync();

Console.WriteLine("Search string: " + searchString);
Console.WriteLine("Result: " + resultRecords.First().Record.Definition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ public async Task IngestDataAndUseHybridSearch()
// Search the collection using a vector search.
var searchString = "What is an Application Programming Interface";
var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
var searchResult = await hybridSearchCollection.HybridSearchAsync(searchVector, ["Application", "Programming", "Interface"], top: 1);
var resultRecords = await searchResult.Results.ToListAsync();
var searchResult = hybridSearchCollection.HybridSearchAsync(searchVector, ["Application", "Programming", "Interface"], top: 1);
var resultRecords = await searchResult.ToListAsync();

Console.WriteLine("Search string: " + searchString);
Console.WriteLine("Result: " + resultRecords.First().Record.Definition);
Expand All @@ -66,8 +66,8 @@ public async Task IngestDataAndUseHybridSearch()
// Search the collection using a vector search.
searchString = "What is Retrieval Augmented Generation";
searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
searchResult = await hybridSearchCollection.HybridSearchAsync(searchVector, ["Retrieval", "Augmented", "Generation"], top: 1);
resultRecords = await searchResult.Results.ToListAsync();
searchResult = hybridSearchCollection.HybridSearchAsync(searchVector, ["Retrieval", "Augmented", "Generation"], top: 1);
resultRecords = await searchResult.ToListAsync();

Console.WriteLine("Search string: " + searchString);
Console.WriteLine("Result: " + resultRecords.First().Record.Definition);
Expand All @@ -76,8 +76,8 @@ public async Task IngestDataAndUseHybridSearch()
// Search the collection using a vector search with pre-filtering.
searchString = "What is Retrieval Augmented Generation";
searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
searchResult = await hybridSearchCollection.HybridSearchAsync(searchVector, ["Retrieval", "Augmented", "Generation"], top: 3, new() { Filter = g => g.Category == "External Definitions" });
resultRecords = await searchResult.Results.ToListAsync();
searchResult = hybridSearchCollection.HybridSearchAsync(searchVector, ["Retrieval", "Augmented", "Generation"], top: 3, new() { Filter = g => g.Category == "External Definitions" });
resultRecords = await searchResult.ToListAsync();

Console.WriteLine("Search string: " + searchString);
Console.WriteLine("Number of results: " + resultRecords.Count);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ private async Task ReadDataFromCollectionAsync(IVectorStore vectorStore, string
// Search the data set.
var searchString = "I'm looking for an animal that is loyal and will make a great companion";
var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
var searchResult = await collection.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.Results.ToListAsync();
var searchResult = collection.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.ToListAsync();

this.Output.WriteLine("Search string: " + searchString);
this.Output.WriteLine("Source: " + resultRecords.First().Record.Source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ public async Task IngestDataAndSearchAsync<TKey>(string collectionName, Func<TKe
// Search the collection using a vector search.
var searchString = "What is an Application Programming Interface";
var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
var searchResult = await collection.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.Results.ToListAsync();
var searchResult = collection.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.ToListAsync();

output.WriteLine("Search string: " + searchString);
output.WriteLine("Result: " + resultRecords.First().Record.Definition);
Expand All @@ -61,8 +61,8 @@ public async Task IngestDataAndSearchAsync<TKey>(string collectionName, Func<TKe
// Search the collection using a vector search.
searchString = "What is Retrieval Augmented Generation";
searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
searchResult = await collection.VectorizedSearchAsync(searchVector, top: 1);
resultRecords = await searchResult.Results.ToListAsync();
searchResult = collection.VectorizedSearchAsync(searchVector, top: 1);
resultRecords = await searchResult.ToListAsync();

output.WriteLine("Search string: " + searchString);
output.WriteLine("Result: " + resultRecords.First().Record.Definition);
Expand All @@ -71,8 +71,8 @@ public async Task IngestDataAndSearchAsync<TKey>(string collectionName, Func<TKe
// Search the collection using a vector search with pre-filtering.
searchString = "What is Retrieval Augmented Generation";
searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
searchResult = await collection.VectorizedSearchAsync(searchVector, top: 3, new() { Filter = g => g.Category == "External Definitions" });
resultRecords = await searchResult.Results.ToListAsync();
searchResult = collection.VectorizedSearchAsync(searchVector, top: 3, new() { Filter = g => g.Category == "External Definitions" });
resultRecords = await searchResult.ToListAsync();

output.WriteLine("Search string: " + searchString);
output.WriteLine("Number of results: " + resultRecords.Count);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ public async Task VectorSearchWithMultiVectorRecordAsync()
// Search the store using the description embedding.
var searchString = "I am looking for a reasonably priced coffee maker";
var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
var searchResult = await collection.VectorizedSearchAsync(
var searchResult = collection.VectorizedSearchAsync(
searchVector, top: 1, new()
{
VectorProperty = r => r.DescriptionEmbedding
});
var resultRecords = await searchResult.Results.ToListAsync();
var resultRecords = await searchResult.ToListAsync();

WriteLine("Search string: " + searchString);
WriteLine("Result: " + resultRecords.First().Record.Description);
Expand All @@ -69,14 +69,14 @@ public async Task VectorSearchWithMultiVectorRecordAsync()
// Search the store using the feature list embedding.
searchString = "I am looking for a handheld vacuum cleaner that will remove pet hair";
searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
searchResult = await collection.VectorizedSearchAsync(
searchResult = collection.VectorizedSearchAsync(
searchVector,
top: 1,
new()
{
VectorProperty = r => r.FeatureListEmbedding
});
resultRecords = await searchResult.Results.ToListAsync();
resultRecords = await searchResult.ToListAsync();

WriteLine("Search string: " + searchString);
WriteLine("Result: " + resultRecords.First().Record.Description);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public async Task VectorSearchWithPagingAsync()
while (moreResults)
{
// Get the next page of results by asking for 10 results, and using 'Skip' to skip the results from the previous pages.
var currentPageResults = await collection.VectorizedSearchAsync(
var currentPageResults = collection.VectorizedSearchAsync(
searchVector,
top: 10,
new()
Expand All @@ -57,7 +57,7 @@ public async Task VectorSearchWithPagingAsync()

// Print the results.
var pageCount = 0;
await foreach (var result in currentPageResults.Results)
await foreach (var result in currentPageResults)
{
Console.WriteLine($"Key: {result.Record.Key}, Text: {result.Record.Text}");
pageCount++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public async Task ExampleAsync()
// Search the collection using a vector search.
var searchString = "What is an Application Programming Interface";
var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
var searchResult = await collection.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.Results.ToListAsync();
var searchResult = collection.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.ToListAsync();

Console.WriteLine("Search string: " + searchString);
Console.WriteLine("Result: " + resultRecords.First().Record.Definition);
Expand All @@ -60,8 +60,8 @@ public async Task ExampleAsync()
// Search the collection using a vector search.
searchString = "What is Retrieval Augmented Generation";
searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
searchResult = await collection.VectorizedSearchAsync(searchVector, top: 1);
resultRecords = await searchResult.Results.ToListAsync();
searchResult = collection.VectorizedSearchAsync(searchVector, top: 1);
resultRecords = await searchResult.ToListAsync();

Console.WriteLine("Search string: " + searchString);
Console.WriteLine("Result: " + resultRecords.First().Record.Definition);
Expand All @@ -70,8 +70,8 @@ public async Task ExampleAsync()
// Search the collection using a vector search with pre-filtering.
searchString = "What is Retrieval Augmented Generation";
searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(searchString);
searchResult = await collection.VectorizedSearchAsync(searchVector, top: 3, new() { Filter = g => g.Category == "External Definitions" });
resultRecords = await searchResult.Results.ToListAsync();
searchResult = collection.VectorizedSearchAsync(searchVector, top: 3, new() { Filter = g => g.Category == "External Definitions" });
resultRecords = await searchResult.ToListAsync();

Console.WriteLine("Search string: " + searchString);
Console.WriteLine("Number of results: " + resultRecords.Count);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ static DataModel CreateRecord(string text, ReadOnlyMemory<float> embedding)
// Search the collection using a vector search.
var searchString = "What is the Semantic Kernel?";
var searchVector = await embeddingGenerationService.GenerateEmbeddingAsync(searchString);
var searchResult = await vectorSearch!.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.Results.ToListAsync();
var searchResult = vectorSearch!.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.ToListAsync();

Console.WriteLine("Search string: " + searchString);
Console.WriteLine("Result: " + resultRecords.First().Record.Text);
Expand Down Expand Up @@ -116,8 +116,8 @@ static DataModel CreateRecord(TextSearchResult searchResult, ReadOnlyMemory<floa
// Search the collection using a vector search.
var searchString = "What is the Semantic Kernel?";
var searchVector = await embeddingGenerationService.GenerateEmbeddingAsync(searchString);
var searchResult = await vectorSearch!.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.Results.ToListAsync();
var searchResult = vectorSearch!.VectorizedSearchAsync(searchVector, top: 1);
var resultRecords = await searchResult.ToListAsync();

Console.WriteLine("Search string: " + searchString);
Console.WriteLine("Result: " + resultRecords.First().Record.Text);
Expand Down
4 changes: 2 additions & 2 deletions dotnet/samples/Concepts/Optimization/FrugalGPTWithFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRe
var requestEmbedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(request, cancellationToken: context.CancellationToken);

// Find top N examples which are similar to original request.
var searchResults = await collection.VectorizedSearchAsync(requestEmbedding, top: TopN, cancellationToken: context.CancellationToken);
var topNExamples = (await searchResults.Results.ToListAsync(context.CancellationToken)).Select(l => l.Record).ToList();
var searchResults = collection.VectorizedSearchAsync(requestEmbedding, top: TopN, cancellationToken: context.CancellationToken);
var topNExamples = (await searchResults.ToListAsync(context.CancellationToken)).Select(l => l.Record).ToList();

// Override arguments to use only top N examples, which will be sent to LLM.
context.Arguments["Examples"] = topNExamples.Select(l => l.Example);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,8 @@ public async Task<List<KernelFunction>> GetBestFunctionsAsync(
await collection.CreateCollectionIfNotExistsAsync(cancellationToken);

// Find best functions to call for original request.
var searchResults = await collection.VectorizedSearchAsync(requestEmbedding, top: numberOfBestFunctions, cancellationToken: cancellationToken);
var recordKeys = (await searchResults.Results.ToListAsync(cancellationToken)).Select(l => l.Record.Id);
var searchResults = collection.VectorizedSearchAsync(requestEmbedding, top: numberOfBestFunctions, cancellationToken: cancellationToken);
var recordKeys = (await searchResults.ToListAsync(cancellationToken)).Select(l => l.Record.Id);

return plugins
.SelectMany(plugin => plugin)
Expand Down
8 changes: 6 additions & 2 deletions dotnet/samples/Concepts/Search/VectorStore_TextSearch.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Runtime.CompilerServices;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.InMemory;
using Microsoft.SemanticKernel.Connectors.OpenAI;
Expand Down Expand Up @@ -145,11 +146,14 @@ internal static async Task<IVectorStoreRecordCollection<TKey, TRecord>> CreateCo
private sealed class VectorizedSearchWrapper<TRecord>(IVectorizedSearch<TRecord> vectorizedSearch, ITextEmbeddingGenerationService textEmbeddingGeneration) : IVectorizableTextSearch<TRecord>
{
/// <inheritdoc/>
public async Task<VectorSearchResults<TRecord>> VectorizableTextSearchAsync(string searchText, int top, VectorSearchOptions<TRecord>? options = null, CancellationToken cancellationToken = default)
public async IAsyncEnumerable<VectorSearchResult<TRecord>> VectorizableTextSearchAsync(string searchText, int top, VectorSearchOptions<TRecord>? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var vectorizedQuery = await textEmbeddingGeneration!.GenerateEmbeddingAsync(searchText, cancellationToken: cancellationToken).ConfigureAwait(false);

return await vectorizedSearch.VectorizedSearchAsync(vectorizedQuery, top, options, cancellationToken);
await foreach (var result in vectorizedSearch.VectorizedSearchAsync(vectorizedQuery, top, options, cancellationToken))
{
yield return result;
}
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ static TextDataModel CreateRecord(string text, ReadOnlyMemory<float> embedding)
ReadOnlyMemory<float> promptEmbedding = await embeddingGenerationService.GenerateEmbeddingAsync(prompt, cancellationToken: cancellationToken);

// Retrieve top three matching records from the vector store
VectorSearchResults<TextDataModel> result = await vsCollection.VectorizedSearchAsync(promptEmbedding, top: 3, cancellationToken: cancellationToken);
var result = vsCollection.VectorizedSearchAsync(promptEmbedding, top: 3, cancellationToken: cancellationToken);

// Return the records as resource contents
List<ResourceContents> contents = [];

await foreach (var record in result.Results)
await foreach (var record in result)
{
contents.Add(new TextResourceContents()
{
Expand Down
Loading
Loading