diff --git a/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs b/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs index 585c29cb0258..efcd2533b08f 100644 --- a/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs +++ b/dotnet/samples/Concepts/RAG/Bing_RagWithTextSearch.cs @@ -29,7 +29,7 @@ public async Task RagWithBingTextSearchAsync() var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey)); // Build a text search plugin with Bing search and add to the kernel - var searchPlugin = textSearch.CreateWithSearch("SearchPlugin"); + var searchPlugin = textSearch.CreateWithSearch(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -56,7 +56,7 @@ public async Task RagWithBingTextSearchIncludingCitationsAsync() var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey)); // Build a text search plugin with Bing search and add to the kernel - var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetTextSearchResults(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -103,7 +103,7 @@ public async Task RagWithBingTextSearchIncludingTimeStampedCitationsAsync() var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey)); // Build a text search plugin with Bing search and add to the kernel - var searchPlugin = textSearch.CreateWithGetSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetSearchResults(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -155,7 +155,7 @@ public async Task RagWithBingTextSearchUsingDevBlogsSiteAsync() var searchOptions = new TextSearchOptions() { Filter = filter }; var searchPlugin = KernelPluginFactory.CreateFromFunctions( "SearchPlugin", "Search Microsoft Developer Blogs site only", - [textSearch.CreateGetTextSearchResults(searchOptions: searchOptions)]); + [textSearch.CreateGetTextSearchResults(5, searchOptions: searchOptions)]); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information diff --git a/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs b/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs index 0245eb80757e..254af1182851 100644 --- a/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs +++ b/dotnet/samples/Concepts/Search/Bing_FunctionCallingWithTextSearch.cs @@ -30,7 +30,7 @@ public async Task FunctionCallingWithBingTextSearchAsync() var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey)); // Build a text search plugin with Bing search and add to the kernel - var searchPlugin = textSearch.CreateWithSearch("SearchPlugin"); + var searchPlugin = textSearch.CreateWithSearch(top: 5, pluginName: "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -57,7 +57,7 @@ public async Task FunctionCallingWithBingTextSearchIncludingCitationsAsync() var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey)); // Build a text search plugin with Bing search and add to the kernel - var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetTextSearchResults(top: 5, pluginName: "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -89,7 +89,7 @@ public async Task FunctionCallingWithBingTextSearchUsingDevBlogsSiteAsync() var searchOptions = new TextSearchOptions() { Filter = filter }; var searchPlugin = KernelPluginFactory.CreateFromFunctions( "SearchPlugin", "Search Microsoft Developer Blogs site only", - [textSearch.CreateGetTextSearchResults(searchOptions: searchOptions)]); + [textSearch.CreateGetTextSearchResults(top: 5, searchOptions: searchOptions)]); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -138,9 +138,9 @@ private static KernelFunction CreateSearchBySite(BingTextSearch textSearch, Text new KernelParameterMetadata("skip") { Description = "Number of results to skip", IsRequired = false, DefaultValue = 0 }, new KernelParameterMetadata("site") { Description = "Only return results from this domain", IsRequired = false }, ], - ReturnParameter = new() { ParameterType = typeof(KernelSearchResults<string>) }, + ReturnParameter = new() { ParameterType = typeof(List<string>) }, }; - return textSearch.CreateSearch(options); + return textSearch.CreateSearch(top: 5, options); } } diff --git a/dotnet/samples/Concepts/Search/Bing_TextSearch.cs b/dotnet/samples/Concepts/Search/Bing_TextSearch.cs index 78e0af672036..cab55e189ea1 100644 --- a/dotnet/samples/Concepts/Search/Bing_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/Bing_TextSearch.cs @@ -27,18 +27,18 @@ public async Task UsingBingTextSearchAsync() var query = "What is the Semantic Kernel?"; // Search and return results as a string items - KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 4, Skip = 0 }); + IAsyncEnumerable<string> stringResults = textSearch.SearchAsync(query, 4, new() { Skip = 0 }); Console.WriteLine("--- String Results ---\n"); - await foreach (string result in stringResults.Results) + await foreach (string result in stringResults) { Console.WriteLine(result); WriteHorizontalRule(); } // Search and return results as TextSearchResult items - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 4, Skip = 4 }); + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 4, new() { Skip = 4 }); Console.WriteLine("\n--- Text Search Results ---\n"); - await foreach (TextSearchResult result in textResults.Results) + await foreach (TextSearchResult result in textResults) { Console.WriteLine($"Name: {result.Name}"); Console.WriteLine($"Value: {result.Value}"); @@ -47,9 +47,9 @@ public async Task UsingBingTextSearchAsync() } // Search and return s results as BingWebPage items - KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 4, Skip = 8 }); + IAsyncEnumerable<object> fullResults = textSearch.GetSearchResultsAsync(query, 4, new() { Skip = 8 }); Console.WriteLine("\n--- Bing Web Page Results ---\n"); - await foreach (BingWebPage result in fullResults.Results) + await foreach (BingWebPage result in fullResults) { Console.WriteLine($"Name: {result.Name}"); Console.WriteLine($"Snippet: {result.Snippet}"); @@ -80,9 +80,9 @@ public async Task UsingBingTextSearchWithACustomMapperAsync() var query = "What is the Semantic Kernel?"; // Search with TextSearchResult textResult type - KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 2, Skip = 0 }); + IAsyncEnumerable<string> stringResults = textSearch.SearchAsync(query, 2, new() { Skip = 0 }); Console.WriteLine("--- Serialized JSON Results ---"); - await foreach (string result in stringResults.Results) + await foreach (string result in stringResults) { Console.WriteLine(result); WriteHorizontalRule(); @@ -109,10 +109,10 @@ public async Task UsingBingTextSearchWithASiteFilterAsync() var query = "What is the Semantic Kernel?"; // Search with TextSearchResult textResult type - TextSearchOptions searchOptions = new() { Top = 4, Skip = 0, Filter = new TextSearchFilter().Equality("site", "devblogs.microsoft.com") }; - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, searchOptions); + TextSearchOptions searchOptions = new() { Skip = 0, Filter = new TextSearchFilter().Equality("site", "devblogs.microsoft.com") }; + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 4, searchOptions); Console.WriteLine("--- Microsoft Developer Blogs Results ---"); - await foreach (TextSearchResult result in textResults.Results) + await foreach (TextSearchResult result in textResults) { Console.WriteLine(result.Link); WriteHorizontalRule(); diff --git a/dotnet/samples/Concepts/Search/Google_TextSearch.cs b/dotnet/samples/Concepts/Search/Google_TextSearch.cs index a77f65bcfbc3..21c87f71404b 100644 --- a/dotnet/samples/Concepts/Search/Google_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/Google_TextSearch.cs @@ -26,18 +26,18 @@ public async Task UsingGoogleTextSearchAsync() var query = "What is the Semantic Kernel?"; // Search and return results as string items - KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 4, Skip = 0 }); + IAsyncEnumerable<string> stringResults = textSearch.SearchAsync(query, 4, new() { Skip = 0 }); Console.WriteLine("——— String Results ———\n"); - await foreach (string result in stringResults.Results) + await foreach (string result in stringResults) { Console.WriteLine(result); Console.WriteLine(new string('—', HorizontalRuleLength)); } // Search and return results as TextSearchResult items - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 4, Skip = 4 }); + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 4, new() { Skip = 4 }); Console.WriteLine("\n——— Text Search Results ———\n"); - await foreach (TextSearchResult result in textResults.Results) + await foreach (TextSearchResult result in textResults) { Console.WriteLine($"Name: {result.Name}"); Console.WriteLine($"Value: {result.Value}"); @@ -46,9 +46,9 @@ public async Task UsingGoogleTextSearchAsync() } // Search and return results as Google.Apis.CustomSearchAPI.v1.Data.Result items - KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 4, Skip = 8 }); + IAsyncEnumerable<object> fullResults = textSearch.GetSearchResultsAsync(query, 4, new() { Skip = 8 }); Console.WriteLine("\n——— Google Web Page Results ———\n"); - await foreach (Google.Apis.CustomSearchAPI.v1.Data.Result result in fullResults.Results) + await foreach (Google.Apis.CustomSearchAPI.v1.Data.Result result in fullResults) { Console.WriteLine($"Title: {result.Title}"); Console.WriteLine($"Snippet: {result.Snippet}"); @@ -74,9 +74,9 @@ public async Task UsingGoogleTextSearchWithACustomMapperAsync() var query = "What is the Semantic Kernel?"; // Search with TextSearchResult textResult type - KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 2, Skip = 0 }); + IAsyncEnumerable<string> stringResults = textSearch.SearchAsync(query, 2, new() { Skip = 0 }); Console.WriteLine("--- Serialized JSON Results ---"); - await foreach (string result in stringResults.Results) + await foreach (string result in stringResults) { Console.WriteLine(result); Console.WriteLine(new string('-', HorizontalRuleLength)); @@ -97,10 +97,10 @@ public async Task UsingGoogleTextSearchWithASiteSearchFilterAsync() var query = "What is the Semantic Kernel?"; // Search with TextSearchResult textResult type - TextSearchOptions searchOptions = new() { Top = 4, Skip = 0, Filter = new TextSearchFilter().Equality("siteSearch", "devblogs.microsoft.com") }; - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, searchOptions); + TextSearchOptions searchOptions = new() { Skip = 0, Filter = new TextSearchFilter().Equality("siteSearch", "devblogs.microsoft.com") }; + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 4, searchOptions); Console.WriteLine("--- Microsoft Developer Blogs Results ---"); - await foreach (TextSearchResult result in textResults.Results) + await foreach (TextSearchResult result in textResults) { Console.WriteLine(result.Link); Console.WriteLine(new string('-', HorizontalRuleLength)); diff --git a/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs b/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs index 18078eaef238..d137a90147fb 100644 --- a/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/Tavily_TextSearch.cs @@ -27,18 +27,18 @@ public async Task UsingTavilyTextSearch() var query = "What is the Semantic Kernel?"; // Search and return results as a string items - KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 4 }); + IAsyncEnumerable<string> stringResults = textSearch.SearchAsync(query, 4); Console.WriteLine("--- String Results ---\n"); - await foreach (string result in stringResults.Results) + await foreach (string result in stringResults) { Console.WriteLine(result); WriteHorizontalRule(); } // Search and return results as TextSearchResult items - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 4 }); + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 4); Console.WriteLine("\n--- Text Search Results ---\n"); - await foreach (TextSearchResult result in textResults.Results) + await foreach (TextSearchResult result in textResults) { Console.WriteLine($"Name: {result.Name}"); Console.WriteLine($"Value: {result.Value}"); @@ -47,9 +47,9 @@ public async Task UsingTavilyTextSearch() } // Search and return s results as TavilySearchResult items - KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 4 }); + IAsyncEnumerable<object> fullResults = textSearch.GetSearchResultsAsync(query, 4); Console.WriteLine("\n--- Tavily Web Page Results ---\n"); - await foreach (TavilySearchResult result in fullResults.Results) + await foreach (TavilySearchResult result in fullResults) { Console.WriteLine($"Name: {result.Title}"); Console.WriteLine($"Content: {result.Content}"); @@ -76,9 +76,9 @@ public async Task UsingTavilyTextSearchToGetAnAnswer() var query = "What is the Semantic Kernel?"; // Search and return results as a string items - KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 1 }); + IAsyncEnumerable<string> stringResults = textSearch.SearchAsync(query, 1); Console.WriteLine("--- String Results ---\n"); - await foreach (string result in stringResults.Results) + await foreach (string result in stringResults) { Console.WriteLine(result); WriteHorizontalRule(); @@ -110,9 +110,9 @@ public async Task UsingTavilyTextSearchAndIncludeEverything() var query = "What is the Semantic Kernel?"; // Search and return s results as TavilySearchResult items - KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 4, Skip = 0 }); + IAsyncEnumerable<object> fullResults = textSearch.GetSearchResultsAsync(query, 4, new() { Skip = 0 }); Console.WriteLine("\n--- Tavily Web Page Results ---\n"); - await foreach (TavilySearchResult result in fullResults.Results) + await foreach (TavilySearchResult result in fullResults) { Console.WriteLine($"Name: {result.Title}"); Console.WriteLine($"Content: {result.Content}"); @@ -143,9 +143,9 @@ public async Task UsingTavilyTextSearchWithACustomMapperAsync() var query = "What is the Semantic Kernel?"; // Search with TextSearchResult textResult type - KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 2 }); + IAsyncEnumerable<string> stringResults = textSearch.SearchAsync(query, 2); Console.WriteLine("--- Serialized JSON Results ---"); - await foreach (string result in stringResults.Results) + await foreach (string result in stringResults) { Console.WriteLine(result); WriteHorizontalRule(); @@ -172,10 +172,10 @@ public async Task UsingTavilyTextSearchWithAnIncludeDomainFilterAsync() var query = "What is the Semantic Kernel?"; // Search with TextSearchResult textResult type - TextSearchOptions searchOptions = new() { Top = 4, Filter = new TextSearchFilter().Equality("include_domain", "devblogs.microsoft.com") }; - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, searchOptions); + TextSearchOptions searchOptions = new() { Filter = new TextSearchFilter().Equality("include_domain", "devblogs.microsoft.com") }; + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 4, searchOptions); Console.WriteLine("--- Microsoft Developer Blogs Results ---"); - await foreach (TextSearchResult result in textResults.Results) + await foreach (TextSearchResult result in textResults) { Console.WriteLine(result.Link); WriteHorizontalRule(); diff --git a/dotnet/samples/Concepts/Search/VectorStore_TextSearch.cs b/dotnet/samples/Concepts/Search/VectorStore_TextSearch.cs index f6a3d4ab6356..aa0561411009 100644 --- a/dotnet/samples/Concepts/Search/VectorStore_TextSearch.cs +++ b/dotnet/samples/Concepts/Search/VectorStore_TextSearch.cs @@ -65,18 +65,18 @@ private async Task ExecuteSearchesAsync(VectorStoreTextSearch<DataModel> textSea var query = "What is the Semantic Kernel?"; // Search and return results as a string items - KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 2, Skip = 0 }); + IAsyncEnumerable<string> stringResults = textSearch.SearchAsync(query, 2, new() { Skip = 0 }); Console.WriteLine("--- String Results ---\n"); - await foreach (string result in stringResults.Results) + await foreach (string result in stringResults) { Console.WriteLine(result); WriteHorizontalRule(); } // Search and return results as TextSearchResult items - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 2, Skip = 0 }); + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 2, new() { Skip = 0 }); Console.WriteLine("\n--- Text Search Results ---\n"); - await foreach (TextSearchResult result in textResults.Results) + await foreach (TextSearchResult result in textResults) { Console.WriteLine($"Name: {result.Name}"); Console.WriteLine($"Value: {result.Value}"); @@ -85,9 +85,9 @@ private async Task ExecuteSearchesAsync(VectorStoreTextSearch<DataModel> textSea } // Search and returns results as DataModel items - KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 2, Skip = 0 }); + IAsyncEnumerable<object> fullResults = textSearch.GetSearchResultsAsync(query, 2, new() { Skip = 0 }); Console.WriteLine("\n--- DataModel Results ---\n"); - await foreach (DataModel result in fullResults.Results) + await foreach (DataModel result in fullResults) { Console.WriteLine($"Key: {result.Key}"); Console.WriteLine($"Text: {result.Text}"); diff --git a/dotnet/samples/Demos/AgentFrameworkWithAspire/ChatWithAgent.ApiService/Program.cs b/dotnet/samples/Demos/AgentFrameworkWithAspire/ChatWithAgent.ApiService/Program.cs index 89c83cb3a48e..5306ce156272 100644 --- a/dotnet/samples/Demos/AgentFrameworkWithAspire/ChatWithAgent.ApiService/Program.cs +++ b/dotnet/samples/Demos/AgentFrameworkWithAspire/ChatWithAgent.ApiService/Program.cs @@ -230,7 +230,7 @@ static void AddAgentWithRag<TKey>(WebApplicationBuilder builder, PromptTemplateC // Add a search plugin to the kernel which we will use in the agent template // to do a vector search for related information to the user query. - kernel.Plugins.Add(vectorStoreTextSearch.CreateWithGetTextSearchResults("SearchPlugin")); + kernel.Plugins.Add(vectorStoreTextSearch.CreateWithGetTextSearchResults(top: 5, "SearchPlugin")); return new ChatCompletionAgent(templateConfig, new HandlebarsPromptTemplateFactory()) { diff --git a/dotnet/samples/Demos/OnnxSimpleRAG/Program.cs b/dotnet/samples/Demos/OnnxSimpleRAG/Program.cs index 0a8b76360850..7637ec976ea7 100644 --- a/dotnet/samples/Demos/OnnxSimpleRAG/Program.cs +++ b/dotnet/samples/Demos/OnnxSimpleRAG/Program.cs @@ -65,7 +65,7 @@ await collection.UpsertAsync(new() // Add a plugin to search the database with. var vectorStoreTextSearch = new VectorStoreTextSearch<InformationItem>(collection, embeddingService); -kernel.Plugins.Add(vectorStoreTextSearch.CreateWithSearch("SearchPlugin")); +kernel.Plugins.Add(vectorStoreTextSearch.CreateWithSearch(top: 5, "SearchPlugin")); // Start the conversation while (true) diff --git a/dotnet/samples/Demos/VectorStoreRAG/RAGChatService.cs b/dotnet/samples/Demos/VectorStoreRAG/RAGChatService.cs index 6b3af97d8824..056b8b2bd100 100644 --- a/dotnet/samples/Demos/VectorStoreRAG/RAGChatService.cs +++ b/dotnet/samples/Demos/VectorStoreRAG/RAGChatService.cs @@ -91,7 +91,7 @@ private async Task ChatLoopAsync(CancellationToken cancellationToken) // Add a search plugin to the kernel which we will use in the template below // to do a vector search for related information to the user query. - kernel.Plugins.Add(vectorStoreTextSearch.CreateWithGetTextSearchResults("SearchPlugin")); + kernel.Plugins.Add(vectorStoreTextSearch.CreateWithGetTextSearchResults(top: 5, "SearchPlugin")); // Start the chat loop. while (!cancellationToken.IsCancellationRequested) diff --git a/dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs b/dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs index a5676b9f1c5d..ecc0415a95a7 100644 --- a/dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs +++ b/dotnet/samples/GettingStartedWithTextSearch/Step1_Web_Search.cs @@ -23,8 +23,8 @@ public async Task BingSearchAsync() var query = "What is the Semantic Kernel?"; // Search and return results - KernelSearchResults<string> searchResults = await textSearch.SearchAsync(query, new() { Top = 4 }); - await foreach (string result in searchResults.Results) + IAsyncEnumerable<string> searchResults = textSearch.SearchAsync(query, 4); + await foreach (string result in searchResults) { Console.WriteLine(result); } @@ -44,8 +44,8 @@ public async Task GoogleSearchAsync() var query = "What is the Semantic Kernel?"; // Search and return results - KernelSearchResults<string> searchResults = await textSearch.SearchAsync(query, new() { Top = 4 }); - await foreach (string result in searchResults.Results) + IAsyncEnumerable<string> searchResults = textSearch.SearchAsync(query, 4); + await foreach (string result in searchResults) { Console.WriteLine(result); } @@ -69,11 +69,11 @@ public async Task SearchForWebPagesAsync() var query = "What is the Semantic Kernel?"; // Search and return results using the implementation specific data model - KernelSearchResults<object> objectResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 4 }); + IAsyncEnumerable<object> objectResults = textSearch.GetSearchResultsAsync(query, 4); if (this.UseBingSearch) { Console.WriteLine("\n--- Bing Web Page Results ---\n"); - await foreach (BingWebPage webPage in objectResults.Results) + await foreach (BingWebPage webPage in objectResults) { Console.WriteLine($"Name: {webPage.Name}"); Console.WriteLine($"Snippet: {webPage.Snippet}"); @@ -85,7 +85,7 @@ public async Task SearchForWebPagesAsync() else { Console.WriteLine("\n——— Google Web Page Results ———\n"); - await foreach (Google.Apis.CustomSearchAPI.v1.Data.Result result in objectResults.Results) + await foreach (Google.Apis.CustomSearchAPI.v1.Data.Result result in objectResults) { Console.WriteLine($"Title: {result.Title}"); Console.WriteLine($"Snippet: {result.Snippet}"); @@ -118,9 +118,9 @@ public async Task SearchForTextSearchResultsAsync() var query = "What is the Semantic Kernel?"; // Search and return results as TextSearchResult items - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 4 }); + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 4); Console.WriteLine("\n--- Text Search Results ---\n"); - await foreach (TextSearchResult result in textResults.Results) + await foreach (TextSearchResult result in textResults) { Console.WriteLine($"Name: {result.Name}"); Console.WriteLine($"Value: {result.Value}"); diff --git a/dotnet/samples/GettingStartedWithTextSearch/Step2_Search_For_RAG.cs b/dotnet/samples/GettingStartedWithTextSearch/Step2_Search_For_RAG.cs index cb21cccc66b4..c032a39772ef 100644 --- a/dotnet/samples/GettingStartedWithTextSearch/Step2_Search_For_RAG.cs +++ b/dotnet/samples/GettingStartedWithTextSearch/Step2_Search_For_RAG.cs @@ -1,4 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using HtmlAgilityPack; using Microsoft.SemanticKernel; @@ -37,7 +38,7 @@ public async Task RagWithTextSearchAsync() apiKey: TestConfiguration.Google.ApiKey); // Build a text search plugin with web search and add to the kernel - var searchPlugin = textSearch.CreateWithSearch("SearchPlugin"); + var searchPlugin = textSearch.CreateWithSearch(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -65,7 +66,7 @@ public async Task RagWithBingTextSearchIncludingCitationsAsync() var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey)); // Build a text search plugin with Bing search and add to the kernel - var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetTextSearchResults(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -112,7 +113,7 @@ public async Task RagWithBingTextSearchIncludingTimeStampedCitationsAsync() var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey)); // Build a text search plugin with Bing search and add to the kernel - var searchPlugin = textSearch.CreateWithGetSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetSearchResults(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -166,7 +167,7 @@ public async Task RagWithBingTextSearchUsingDevBlogsSiteAsync() // Build a text search plugin with Bing search and add to the kernel var searchPlugin = KernelPluginFactory.CreateFromFunctions( "SearchPlugin", "Search Microsoft Developer Blogs site only", - [textSearch.CreateGetTextSearchResults(searchOptions: searchOptions)]); + [textSearch.CreateGetTextSearchResults(5, searchOptions: searchOptions)]); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -224,9 +225,9 @@ public async Task RagWithBingTextSearchUsingCustomSiteAsync() new KernelParameterMetadata("skip") { Description = "Number of results to skip", IsRequired = false, DefaultValue = 0 }, new KernelParameterMetadata("site") { Description = "Only return results from this domain", IsRequired = false }, ], - ReturnParameter = new() { ParameterType = typeof(KernelSearchResults<string>) }, + ReturnParameter = new() { ParameterType = typeof(List<string>) }, }; - var searchPlugin = KernelPluginFactory.CreateFromFunctions("SearchPlugin", "Search specified site", [textSearch.CreateGetTextSearchResults(options)]); + var searchPlugin = KernelPluginFactory.CreateFromFunctions("SearchPlugin", "Search specified site", [textSearch.CreateGetTextSearchResults(5, options)]); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -280,7 +281,7 @@ public async Task RagWithBingTextSearchUsingFullPagesAsync() // Build a text search plugin with Bing search and add to the kernel var searchPlugin = KernelPluginFactory.CreateFromFunctions( "SearchPlugin", "Search Microsoft Developer Blogs site only", - [textSearch.CreateGetTextSearchResults(searchOptions: searchOptions)]); + [textSearch.CreateGetTextSearchResults(5, searchOptions: searchOptions)]); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -316,12 +317,52 @@ Include citations to the relevant information where it is referenced in the resp public partial class TextSearchWithFullValues(ITextSearch searchDelegate) : ITextSearch { /// <inheritdoc/> + public IAsyncEnumerable<object> GetSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) + { + return searchDelegate.GetSearchResultsAsync(query, top, searchOptions, cancellationToken); + } + + /// <inheritdoc/> + public async IAsyncEnumerable<TextSearchResult> GetTextSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var results = searchDelegate.GetTextSearchResultsAsync(query, top, searchOptions, cancellationToken); + + using HttpClient client = new(); + await foreach (var item in results.WithCancellation(cancellationToken).ConfigureAwait(false)) + { + string? value = item.Value; + try + { + if (item.Link is not null) + { + value = await client.GetStringAsync(new Uri(item.Link), cancellationToken); + value = ConvertHtmlToPlainText(value); + } + } + catch (HttpRequestException) + { + } + + yield return new TextSearchResult(value) { Name = item.Name, Link = item.Link }; + } + } + + /// <inheritdoc/> + public IAsyncEnumerable<string> SearchAsync(string query, int top, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) + { + return searchDelegate.SearchAsync(query, top, searchOptions, cancellationToken); + } + + #region obsolete + /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public Task<KernelSearchResults<object>> GetSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { return searchDelegate.GetSearchResultsAsync(query, searchOptions, cancellationToken); } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { var results = await searchDelegate.GetTextSearchResultsAsync(query, searchOptions, cancellationToken); @@ -351,10 +392,12 @@ public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsy } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public Task<KernelSearchResults<string>> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { return searchDelegate.SearchAsync(query, searchOptions, cancellationToken); } + #endregion /// <summary> /// Convert HTML to plain text. diff --git a/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs b/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs index 5b7766b589d3..55090c5ae454 100644 --- a/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs +++ b/dotnet/samples/GettingStartedWithTextSearch/Step3_Search_With_FunctionCalling.cs @@ -33,7 +33,7 @@ public async Task FunctionCallingWithBingTextSearchAsync() var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey)); // Build a text search plugin with Bing search and add to the kernel - var searchPlugin = textSearch.CreateWithSearch("SearchPlugin"); + var searchPlugin = textSearch.CreateWithSearch(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -60,7 +60,7 @@ public async Task FunctionCallingWithBingTextSearchIncludingCitationsAsync() var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey)); // Build a text search plugin with Bing search and add to the kernel - var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetTextSearchResults(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -92,7 +92,7 @@ public async Task FunctionCallingWithBingTextSearchUsingDevBlogsSiteAsync() var searchOptions = new TextSearchOptions() { Filter = filter }; var searchPlugin = KernelPluginFactory.CreateFromFunctions( "SearchPlugin", "Search Microsoft Developer Blogs site only", - [textSearch.CreateGetTextSearchResults(searchOptions: searchOptions)]); + [textSearch.CreateGetTextSearchResults(5, searchOptions: searchOptions)]); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -154,10 +154,10 @@ private static KernelFunction CreateSearchBySite(BingTextSearch textSearch, Text new KernelParameterMetadata("skip") { Description = "Number of results to skip", IsRequired = false, DefaultValue = 0 }, new KernelParameterMetadata("site") { Description = "Only return results from this domain", IsRequired = false, DefaultValue = 2 }, ], - ReturnParameter = new() { ParameterType = typeof(KernelSearchResults<string>) }, + ReturnParameter = new() { ParameterType = typeof(List<string>) }, }; - return textSearch.CreateSearch(options); + return textSearch.CreateSearch(5, options); } #endregion } diff --git a/dotnet/samples/GettingStartedWithTextSearch/Step4_Search_With_VectorStore.cs b/dotnet/samples/GettingStartedWithTextSearch/Step4_Search_With_VectorStore.cs index 9c48c3a6880b..c5a3753d7475 100644 --- a/dotnet/samples/GettingStartedWithTextSearch/Step4_Search_With_VectorStore.cs +++ b/dotnet/samples/GettingStartedWithTextSearch/Step4_Search_With_VectorStore.cs @@ -31,9 +31,9 @@ public async Task UsingInMemoryVectorStoreRecordTextSearchAsync() // Search and return results as TextSearchResult items var query = "What is the Semantic Kernel?"; - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 2, Skip = 0 }); + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 2, new() { Skip = 0 }); Console.WriteLine("\n--- Text Search Results ---\n"); - await foreach (TextSearchResult result in textResults.Results) + await foreach (TextSearchResult result in textResults) { Console.WriteLine($"Name: {result.Name}"); Console.WriteLine($"Value: {result.Value}"); @@ -63,7 +63,7 @@ public async Task RagWithInMemoryVectorStoreTextSearchAsync() var textSearch = new VectorStoreTextSearch<DataModel>(vectorizedSearch, textEmbeddingGeneration); // Build a text search plugin with vector store search and add to the kernel - var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetTextSearchResults(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information @@ -114,7 +114,7 @@ public async Task FunctionCallingWithInMemoryVectorStoreTextSearchAsync() var textSearch = new VectorStoreTextSearch<DataModel>(vectorizedSearch, textEmbeddingGeneration); // Build a text search plugin with vector store search and add to the kernel - var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetTextSearchResults(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Invoke prompt and use text search plugin to provide grounding information diff --git a/dotnet/src/IntegrationTests/Data/BaseTextSearchTests.cs b/dotnet/src/IntegrationTests/Data/BaseTextSearchTests.cs index bd2821a532cf..cb9bdfa2e85b 100644 --- a/dotnet/src/IntegrationTests/Data/BaseTextSearchTests.cs +++ b/dotnet/src/IntegrationTests/Data/BaseTextSearchTests.cs @@ -30,11 +30,11 @@ public virtual async Task CanSearchAsync() var query = this.GetQuery(); // Act - KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 4 }); + IAsyncEnumerable<string> stringResults = textSearch.SearchAsync(query, 4); // Assert Assert.NotNull(stringResults); - var results = await stringResults.Results.ToArrayAsync<string>(); + var results = await stringResults.ToArrayAsync<string>(); Assert.Equal(4, results.Length); foreach (var result in results) { @@ -54,11 +54,11 @@ public virtual async Task CanGetTextSearchResultsAsync() var query = this.GetQuery(); // Act - KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 4 }); + IAsyncEnumerable<TextSearchResult> textResults = textSearch.GetTextSearchResultsAsync(query, 4); // Assert Assert.NotNull(textResults); - var results = await textResults.Results.ToArrayAsync<TextSearchResult>(); + var results = await textResults.ToArrayAsync<TextSearchResult>(); Assert.Equal(4, results.Length); foreach (var result in results) { @@ -84,11 +84,11 @@ public virtual async Task CanGetSearchResultsAsync() var query = this.GetQuery(); // Act - KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 4 }); + IAsyncEnumerable<object> fullResults = textSearch.GetSearchResultsAsync(query, 4); // Assert Assert.NotNull(fullResults); - var results = await fullResults.Results.ToArrayAsync<object>(); + var results = await fullResults.ToArrayAsync<object>(); Assert.True(this.VerifySearchResults(results, query)); } @@ -105,11 +105,11 @@ public virtual async Task UsingTextSearchWithAFilterAsync() var filter = this.GetTextSearchFilter(); // Act - KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 4, Filter = filter }); + IAsyncEnumerable<object> fullResults = textSearch.GetSearchResultsAsync(query, 4, new() { Filter = filter }); // Assert Assert.NotNull(fullResults); - var results = await fullResults.Results.ToArrayAsync<object>(); + var results = await fullResults.ToArrayAsync<object>(); Assert.True(this.VerifySearchResults(results, query, filter)); } @@ -126,7 +126,7 @@ public virtual async Task FunctionCallingUsingCreateWithSearchAsync() var kernel = this.CreateKernelWithOpenAI(); kernel.AutoFunctionInvocationFilters.Add(filter); - var searchPlugin = textSearch.CreateWithSearch("SearchPlugin"); + var searchPlugin = textSearch.CreateWithSearch(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Act @@ -155,7 +155,7 @@ public virtual async Task FunctionCallingUsingCreateWithGetSearchResultsAsync() var kernel = this.CreateKernelWithOpenAI(); kernel.AutoFunctionInvocationFilters.Add(filter); - var searchPlugin = textSearch.CreateWithGetSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetSearchResults(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Act @@ -184,7 +184,7 @@ public virtual async Task FunctionCallingUsingGetTextSearchResultsAsync() var kernel = this.CreateKernelWithOpenAI(); kernel.AutoFunctionInvocationFilters.Add(filter); - var searchPlugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin"); + var searchPlugin = textSearch.CreateWithGetTextSearchResults(5, "SearchPlugin"); kernel.Plugins.Add(searchPlugin); // Act diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Web/Bing/BingTextSearchTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Web/Bing/BingTextSearchTests.cs index a6172e334314..f62d67fa04ff 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Web/Bing/BingTextSearchTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Web/Bing/BingTextSearchTests.cs @@ -50,12 +50,11 @@ public async Task SearchReturnsSuccessfullyAsync() var textSearch = new BingTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act - KernelSearchResults<string> result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 10, Skip = 0 }); + var results = textSearch.SearchAsync("What is the Semantic Kernel?", 10, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(10, resultList.Count); foreach (var stringResult in resultList) @@ -74,12 +73,11 @@ public async Task GetTextSearchResultsReturnsSuccessfullyAsync() var textSearch = new BingTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act - KernelSearchResults<TextSearchResult> result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 10, Skip = 0 }); + var results = textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", 10, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(10, resultList.Count); foreach (var textSearchResult in resultList) @@ -100,12 +98,11 @@ public async Task GetSearchResultsReturnsSuccessfullyAsync() var textSearch = new BingTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act - KernelSearchResults<object> result = await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 10, Skip = 0 }); + var results = textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", 10, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(10, resultList.Count); foreach (BingWebPage webPage in resultList) @@ -128,12 +125,11 @@ public async Task SearchWithCustomStringMapperReturnsSuccessfullyAsync() var textSearch = new BingTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, StringMapper = new TestTextSearchStringMapper() }); // Act - KernelSearchResults<string> result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 10, Skip = 0 }); + var results = textSearch.SearchAsync("What is the Semantic Kernel?", 10, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(10, resultList.Count); foreach (var stringResult in resultList) @@ -154,12 +150,11 @@ public async Task GetTextSearchResultsWithCustomResultMapperReturnsSuccessfullyA var textSearch = new BingTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, ResultMapper = new TestTextSearchResultMapper() }); // Act - KernelSearchResults<TextSearchResult> result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 10, Skip = 0 }); + var results = textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", 10, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(10, resultList.Count); foreach (var textSearchResult in resultList) @@ -206,8 +201,8 @@ public async Task BuildsCorrectUriForEqualityFilterAsync(string paramName, objec var textSearch = new BingTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act - TextSearchOptions searchOptions = new() { Top = 4, Skip = 0, Filter = new TextSearchFilter().Equality(paramName, paramValue) }; - KernelSearchResults<object> result = await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", searchOptions); + TextSearchOptions searchOptions = new() { Skip = 0, Filter = new TextSearchFilter().Equality(paramName, paramValue) }; + var results = await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", 4, searchOptions).ToListAsync(); // Assert var requestUris = this._messageHandlerStub.RequestUris; @@ -221,13 +216,13 @@ public async Task DoesNotBuildsUriForInvalidQueryParameterAsync() { // Arrange this._messageHandlerStub.AddJsonResponse(File.ReadAllText(SiteFilterDevBlogsResponseJson)); - TextSearchOptions searchOptions = new() { Top = 4, Skip = 0, Filter = new TextSearchFilter().Equality("fooBar", "Baz") }; + TextSearchOptions searchOptions = new() { Skip = 0, Filter = new TextSearchFilter().Equality("fooBar", "Baz") }; // Create an ITextSearch instance using Bing search var textSearch = new BingTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act && Assert - var e = await Assert.ThrowsAsync<ArgumentException>(async () => await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", searchOptions)); + var e = await Assert.ThrowsAsync<ArgumentException>(async () => await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", 4, searchOptions).ToListAsync()); Assert.Equal("Unknown equality filter clause field name 'fooBar', must be one of answerCount,cc,freshness,mkt,promote,responseFilter,safeSearch,setLang,textDecorations,textFormat,contains,ext,filetype,inanchor,inbody,intitle,ip,language,loc,location,prefer,site,feed,hasfeed,url (Parameter 'searchOptions')", e.Message); } diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Web/Google/GoogleTextSearchTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Web/Google/GoogleTextSearchTests.cs index 1d97ae8ec26b..85673208d6ff 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Web/Google/GoogleTextSearchTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Web/Google/GoogleTextSearchTests.cs @@ -53,12 +53,11 @@ public async Task SearchReturnsSuccessfullyAsync() searchEngineId: "SearchEngineId"); // Act - KernelSearchResults<string> result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.SearchAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (var stringResult in resultList) @@ -79,12 +78,11 @@ public async Task GetTextSearchResultsReturnsSuccessfullyAsync() searchEngineId: "SearchEngineId"); // Act - KernelSearchResults<TextSearchResult> result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 10, Skip = 0 }); + var results = textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", 10, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (var textSearchResult in resultList) @@ -107,12 +105,11 @@ public async Task GetSearchResultsReturnsSuccessfullyAsync() searchEngineId: "SearchEngineId"); // Act - KernelSearchResults<object> results = await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 10, Skip = 0 }); + var results = textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", 10, new() { Skip = 0 }); // Assert Assert.NotNull(results); - Assert.NotNull(results.Results); - var resultList = await results.Results.ToListAsync(); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (Result result in resultList.Cast<Result>()) @@ -138,12 +135,11 @@ public async Task SearchWithCustomStringMapperReturnsSuccessfullyAsync() options: new() { StringMapper = new TestTextSearchStringMapper() }); // Act - KernelSearchResults<string> result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.SearchAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (var stringResult in resultList) @@ -167,12 +163,11 @@ public async Task GetTextSearchResultsWithCustomResultMapperReturnsSuccessfullyA options: new() { ResultMapper = new TestTextSearchResultMapper() }); // Act - KernelSearchResults<TextSearchResult> result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (var textSearchResult in resultList) @@ -208,8 +203,8 @@ public async Task BuildsCorrectUriForEqualityFilterAsync(string paramName, objec searchEngineId: "SearchEngineId"); // Act - TextSearchOptions searchOptions = new() { Top = 4, Skip = 0, Filter = new TextSearchFilter().Equality(paramName, paramValue) }; - KernelSearchResults<object> result = await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", searchOptions); + TextSearchOptions searchOptions = new() { Skip = 0, Filter = new TextSearchFilter().Equality(paramName, paramValue) }; + var results = await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", 4, searchOptions).ToListAsync(); // Assert var requestUris = this._messageHandlerStub.RequestUris; @@ -224,14 +219,14 @@ public async Task DoesNotBuildsUriForInvalidQueryParameterAsync() { // Arrange this._messageHandlerStub.AddJsonResponse(File.ReadAllText(SiteFilterDevBlogsResponseJson)); - TextSearchOptions searchOptions = new() { Top = 4, Skip = 0, Filter = new TextSearchFilter().Equality("fooBar", "Baz") }; + TextSearchOptions searchOptions = new() { Skip = 0, Filter = new TextSearchFilter().Equality("fooBar", "Baz") }; using var textSearch = new GoogleTextSearch( initializer: new() { ApiKey = "ApiKey", HttpClientFactory = this._clientFactory }, searchEngineId: "SearchEngineId"); // Act && Assert - var e = await Assert.ThrowsAsync<ArgumentException>(async () => await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", searchOptions)); + var e = await Assert.ThrowsAsync<ArgumentException>(async () => await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", 4, searchOptions).ToListAsync()); Assert.Equal("Unknown equality filter clause field name 'fooBar', must be one of cr,dateRestrict,exactTerms,excludeTerms,filter,gl,hl,linkSite,lr,orTerms,rights,siteSearch (Parameter 'searchOptions')", e.Message); } diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Web/Tavily/TavilyTextSearchTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Web/Tavily/TavilyTextSearchTests.cs index 553290a4287d..462c4ef07671 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Web/Tavily/TavilyTextSearchTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Web/Tavily/TavilyTextSearchTests.cs @@ -51,12 +51,11 @@ public async Task SearchReturnsSuccessfullyAsync() var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act - KernelSearchResults<string> result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.SearchAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (var stringResult in resultList) @@ -75,12 +74,11 @@ public async Task GetTextSearchResultsReturnsSuccessfullyAsync() var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act - KernelSearchResults<TextSearchResult> result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (var textSearchResult in resultList) @@ -101,12 +99,11 @@ public async Task GetSearchResultsReturnsSuccessfullyAsync() var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, IncludeRawContent = true }); // Act - KernelSearchResults<object> result = await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (TavilySearchResult searchResult in resultList) @@ -129,12 +126,11 @@ public async Task SearchWithCustomStringMapperReturnsSuccessfullyAsync() var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, StringMapper = new TestTextSearchStringMapper() }); // Act - KernelSearchResults<string> result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.SearchAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (var stringResult in resultList) @@ -155,12 +151,11 @@ public async Task GetTextSearchResultsWithCustomResultMapperReturnsSuccessfullyA var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, ResultMapper = new TestTextSearchResultMapper() }); // Act - KernelSearchResults<TextSearchResult> result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (var textSearchResult in resultList) @@ -182,12 +177,11 @@ public async Task SearchWithAnswerReturnsSuccessfullyAsync() var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, IncludeAnswer = true }); // Act - KernelSearchResults<string> result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.SearchAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(5, resultList.Count); foreach (var stringResult in resultList) @@ -206,12 +200,11 @@ public async Task SearchWithImagesReturnsSuccessfullyAsync() var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, IncludeImages = true }); // Act - KernelSearchResults<string> result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.SearchAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(9, resultList.Count); foreach (var stringResult in resultList) @@ -230,12 +223,11 @@ public async Task GetTextSearchResultsWithAnswerReturnsSuccessfullyAsync() var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, IncludeAnswer = true }); // Act - KernelSearchResults<TextSearchResult> result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(4, resultList.Count); foreach (var textSearchResult in resultList) @@ -256,12 +248,11 @@ public async Task GetTextSearchResultsWithImagesReturnsSuccessfullyAsync() var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, IncludeImages = true, IncludeImageDescriptions = true }); // Act - KernelSearchResults<TextSearchResult> result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 }); + var results = textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", 4, new() { Skip = 0 }); // Assert - Assert.NotNull(result); - Assert.NotNull(result.Results); - var resultList = await result.Results.ToListAsync(); + Assert.NotNull(results); + var resultList = await results.ToListAsync(); Assert.NotNull(resultList); Assert.Equal(9, resultList.Count); foreach (var textSearchResult in resultList) @@ -295,8 +286,8 @@ public async Task BuildsCorrectRequestForEqualityFilterAsync(string paramName, o var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act - TextSearchOptions searchOptions = new() { Top = 4, Skip = 0, Filter = new TextSearchFilter().Equality(paramName, paramValue) }; - KernelSearchResults<object> result = await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", searchOptions); + TextSearchOptions searchOptions = new() { Skip = 0, Filter = new TextSearchFilter().Equality(paramName, paramValue) }; + var results = await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", 4, searchOptions).ToListAsync(); // Assert var requestContents = this._messageHandlerStub.RequestContents; @@ -312,13 +303,13 @@ public async Task DoesNotBuildRequestForInvalidQueryParameterAsync(string paramN { // Arrange this._messageHandlerStub.AddJsonResponse(File.ReadAllText(SiteFilterDevBlogsResponseJson)); - TextSearchOptions searchOptions = new() { Top = 4, Skip = 0, Filter = new TextSearchFilter().Equality(paramName, paramValue) }; + TextSearchOptions searchOptions = new() { Skip = 0, Filter = new TextSearchFilter().Equality(paramName, paramValue) }; // Create an ITextSearch instance using Tavily search var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act && Assert - var e = await Assert.ThrowsAsync<ArgumentException>(async () => await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", searchOptions)); + var e = await Assert.ThrowsAsync<ArgumentException>(async () => await textSearch.GetSearchResultsAsync("What is the Semantic Kernel?", 4, searchOptions).ToListAsync()); Assert.Equal("Unknown equality filter clause field name 'fooBar', must be one of topic,time_range,days,include_domain,exclude_domain (Parameter 'searchOptions')", e.Message); } @@ -332,7 +323,7 @@ public async Task DoesNotBuildRequestForInvalidQueryAsync() var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient }); // Act && Assert - var e = await Assert.ThrowsAsync<ArgumentNullException>(async () => await textSearch.GetSearchResultsAsync(null!)); + var e = await Assert.ThrowsAsync<ArgumentNullException>(async () => await textSearch.GetSearchResultsAsync(null!, 4).ToListAsync()); Assert.Equal("Value cannot be null. (Parameter 'query')", e.Message); } diff --git a/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs index 556e04f148d3..9a7bc608afce 100644 --- a/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Bing/BingTextSearch.cs @@ -42,10 +42,48 @@ public BingTextSearch(string apiKey, BingTextSearchOptions? options = null) } /// <inheritdoc/> + public async IAsyncEnumerable<string> SearchAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + searchOptions ??= new TextSearchOptions(); + BingSearchResponse<BingWebPage>? searchResponse = await this.ExecuteSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsStringAsync(searchResponse, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + /// <inheritdoc/> + public async IAsyncEnumerable<TextSearchResult> GetTextSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + searchOptions ??= new TextSearchOptions(); + BingSearchResponse<BingWebPage>? searchResponse = await this.ExecuteSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsTextSearchResultAsync(searchResponse, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + /// <inheritdoc/> + public async IAsyncEnumerable<object> GetSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + searchOptions ??= new TextSearchOptions(); + BingSearchResponse<BingWebPage>? searchResponse = await this.ExecuteSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsWebPageAsync(searchResponse, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + #region obsolete + /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<string>> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { searchOptions ??= new TextSearchOptions(); - BingSearchResponse<BingWebPage>? searchResponse = await this.ExecuteSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + BingSearchResponse<BingWebPage>? searchResponse = await this.ExecuteSearchAsync(query, searchOptions.Top, searchOptions, cancellationToken).ConfigureAwait(false); long? totalCount = searchOptions.IncludeTotalCount ? searchResponse?.WebPages?.TotalEstimatedMatches : null; @@ -53,10 +91,11 @@ public async Task<KernelSearchResults<string>> SearchAsync(string query, TextSea } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { searchOptions ??= new TextSearchOptions(); - BingSearchResponse<BingWebPage>? searchResponse = await this.ExecuteSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + BingSearchResponse<BingWebPage>? searchResponse = await this.ExecuteSearchAsync(query, searchOptions.Top, searchOptions, cancellationToken).ConfigureAwait(false); long? totalCount = searchOptions.IncludeTotalCount ? searchResponse?.WebPages?.TotalEstimatedMatches : null; @@ -64,18 +103,20 @@ public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsy } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { searchOptions ??= new TextSearchOptions(); - BingSearchResponse<BingWebPage>? searchResponse = await this.ExecuteSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + BingSearchResponse<BingWebPage>? searchResponse = await this.ExecuteSearchAsync(query, searchOptions.Top, searchOptions, cancellationToken).ConfigureAwait(false); long? totalCount = searchOptions.IncludeTotalCount ? searchResponse?.WebPages?.TotalEstimatedMatches : null; return new KernelSearchResults<object>(this.GetResultsAsWebPageAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse)); } - #region private + #endregion + #region private private readonly ILogger _logger; private readonly HttpClient _httpClient; private readonly string? _apiKey; @@ -96,11 +137,12 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer /// Execute a Bing search query and return the results. /// </summary> /// <param name="query">What to search for.</param> + /// <param name="top">Index of the first result to return.</param> /// <param name="searchOptions">Search options.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param> - private async Task<BingSearchResponse<BingWebPage>?> ExecuteSearchAsync(string query, TextSearchOptions searchOptions, CancellationToken cancellationToken = default) + private async Task<BingSearchResponse<BingWebPage>?> ExecuteSearchAsync(string query, int top, TextSearchOptions searchOptions, CancellationToken cancellationToken = default) { - using HttpResponseMessage response = await this.SendGetRequestAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + using HttpResponseMessage response = await this.SendGetRequestAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); this._logger.LogDebug("Response received: {StatusCode}", response.StatusCode); @@ -116,17 +158,18 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer /// Sends a GET request to the specified URI. /// </summary> /// <param name="query">The query string.</param> + /// <param name="top">Index of the first result to return.</param> /// <param name="searchOptions">The search options.</param> /// <param name="cancellationToken">A cancellation token to cancel the request.</param> /// <returns>A <see cref="HttpResponseMessage"/> representing the response from the request.</returns> - private async Task<HttpResponseMessage> SendGetRequestAsync(string query, TextSearchOptions searchOptions, CancellationToken cancellationToken = default) + private async Task<HttpResponseMessage> SendGetRequestAsync(string query, int top, TextSearchOptions searchOptions, CancellationToken cancellationToken = default) { - if (searchOptions.Top is <= 0 or > 50) + if (top is <= 0 or > 50) { throw new ArgumentOutOfRangeException(nameof(searchOptions), searchOptions, $"{nameof(searchOptions)} count value must be greater than 0 and have a maximum value of 50."); } - Uri uri = new($"{this._uri}?q={BuildQuery(query, searchOptions)}"); + Uri uri = new($"{this._uri}?q={BuildQuery(query, top, searchOptions)}"); using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri); @@ -246,8 +289,9 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) /// Build a query string from the <see cref="TextSearchOptions"/> /// </summary> /// <param name="query">The query.</param> + /// <param name="top">The maximum number of results to return.</param> /// <param name="searchOptions">The search options.</param> - private static string BuildQuery(string query, TextSearchOptions searchOptions) + private static string BuildQuery(string query, int top, TextSearchOptions searchOptions) { StringBuilder fullQuery = new(); fullQuery.Append(Uri.EscapeDataString(query.Trim())); @@ -277,7 +321,7 @@ private static string BuildQuery(string query, TextSearchOptions searchOptions) } } - fullQuery.Append($"&count={searchOptions.Top}&offset={searchOptions.Skip}{queryParams}"); + fullQuery.Append($"&count={top}&offset={searchOptions.Skip}{queryParams}"); return fullQuery.ToString(); } diff --git a/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs index c4165a2edadc..3f6a590b839f 100644 --- a/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Google/GoogleTextSearch.cs @@ -55,10 +55,54 @@ public GoogleTextSearch( } /// <inheritdoc/> + public async IAsyncEnumerable<object> GetSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + searchOptions ??= new TextSearchOptions(); + var searchResponse = await this.ExecuteSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsResultAsync(searchResponse, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + /// <inheritdoc/> + public async IAsyncEnumerable<TextSearchResult> GetTextSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + searchOptions ??= new TextSearchOptions(); + var searchResponse = await this.ExecuteSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsTextSearchResultAsync(searchResponse, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + /// <inheritdoc/> + public async IAsyncEnumerable<string> SearchAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + searchOptions ??= new TextSearchOptions(); + var searchResponse = await this.ExecuteSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsStringAsync(searchResponse, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + /// <inheritdoc/> + public void Dispose() + { + this._search.Dispose(); + } + + #region obsolete + /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { searchOptions ??= new TextSearchOptions(); - var searchResponse = await this.ExecuteSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + var searchResponse = await this.ExecuteSearchAsync(query, searchOptions.Top, searchOptions, cancellationToken).ConfigureAwait(false); long? totalCount = searchOptions.IncludeTotalCount ? long.Parse(searchResponse.SearchInformation.TotalResults) : null; @@ -66,10 +110,11 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { searchOptions ??= new TextSearchOptions(); - var searchResponse = await this.ExecuteSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + var searchResponse = await this.ExecuteSearchAsync(query, searchOptions.Top, searchOptions, cancellationToken).ConfigureAwait(false); long? totalCount = searchOptions.IncludeTotalCount ? long.Parse(searchResponse.SearchInformation.TotalResults) : null; @@ -77,21 +122,17 @@ public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsy } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<string>> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { searchOptions ??= new TextSearchOptions(); - var searchResponse = await this.ExecuteSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + var searchResponse = await this.ExecuteSearchAsync(query, searchOptions.Top, searchOptions, cancellationToken).ConfigureAwait(false); long? totalCount = searchOptions.IncludeTotalCount ? long.Parse(searchResponse.SearchInformation.TotalResults) : null; return new KernelSearchResults<string>(this.GetResultsAsStringAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse)); } - - /// <inheritdoc/> - public void Dispose() - { - this._search.Dispose(); - } + #endregion #region private @@ -130,13 +171,14 @@ public void Dispose() /// Execute a Google search /// </summary> /// <param name="query">The query string.</param> + /// <param name="top">Index of the first result to return.</param> /// <param name="searchOptions">Search options.</param> /// <param name="cancellationToken">A cancellation token to cancel the request.</param> /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="NotSupportedException"></exception> - private async Task<global::Google.Apis.CustomSearchAPI.v1.Data.Search> ExecuteSearchAsync(string query, TextSearchOptions searchOptions, CancellationToken cancellationToken) + private async Task<global::Google.Apis.CustomSearchAPI.v1.Data.Search> ExecuteSearchAsync(string query, int top, TextSearchOptions searchOptions, CancellationToken cancellationToken) { - var count = searchOptions.Top; + var count = top; var offset = searchOptions.Skip; if (count is <= 0 or > MaxCount) diff --git a/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs index 4e01d0ffb88b..a95a28baae90 100644 --- a/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs +++ b/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs @@ -43,10 +43,48 @@ public TavilyTextSearch(string apiKey, TavilyTextSearchOptions? options = null) } /// <inheritdoc/> + public async IAsyncEnumerable<string> SearchAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + searchOptions ??= new TextSearchOptions(); + TavilySearchResponse? searchResponse = await this.ExecuteSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsStringAsync(searchResponse, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + /// <inheritdoc/> + public async IAsyncEnumerable<TextSearchResult> GetTextSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + searchOptions ??= new TextSearchOptions(); + TavilySearchResponse? searchResponse = await this.ExecuteSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsTextSearchResultAsync(searchResponse, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + /// <inheritdoc/> + public async IAsyncEnumerable<object> GetSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + searchOptions ??= new TextSearchOptions(); + TavilySearchResponse? searchResponse = await this.ExecuteSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetSearchResultsAsync(searchResponse, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + #region obsolete + /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<string>> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { searchOptions ??= new TextSearchOptions(); - TavilySearchResponse? searchResponse = await this.ExecuteSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + TavilySearchResponse? searchResponse = await this.ExecuteSearchAsync(query, searchOptions.Top, searchOptions, cancellationToken).ConfigureAwait(false); long? totalCount = null; @@ -54,10 +92,11 @@ public async Task<KernelSearchResults<string>> SearchAsync(string query, TextSea } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { searchOptions ??= new TextSearchOptions(); - TavilySearchResponse? searchResponse = await this.ExecuteSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + TavilySearchResponse? searchResponse = await this.ExecuteSearchAsync(query, searchOptions.Top, searchOptions, cancellationToken).ConfigureAwait(false); long? totalCount = null; @@ -65,18 +104,19 @@ public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsy } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { searchOptions ??= new TextSearchOptions(); - TavilySearchResponse? searchResponse = await this.ExecuteSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + TavilySearchResponse? searchResponse = await this.ExecuteSearchAsync(query, searchOptions.Top, searchOptions, cancellationToken).ConfigureAwait(false); long? totalCount = null; return new KernelSearchResults<object>(this.GetSearchResultsAsync(searchResponse, cancellationToken), totalCount, GetResultsMetadata(searchResponse)); } + #endregion #region private - private readonly ILogger _logger; private readonly HttpClient _httpClient; private readonly string? _apiKey; @@ -102,11 +142,12 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer /// Execute a Tavily search query and return the results. /// </summary> /// <param name="query">What to search for.</param> + /// <param name="top">Index of the first result to return.</param> /// <param name="searchOptions">Search options.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param> - private async Task<TavilySearchResponse?> ExecuteSearchAsync(string query, TextSearchOptions searchOptions, CancellationToken cancellationToken) + private async Task<TavilySearchResponse?> ExecuteSearchAsync(string query, int top, TextSearchOptions searchOptions, CancellationToken cancellationToken) { - using HttpResponseMessage response = await this.SendGetRequestAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + using HttpResponseMessage response = await this.SendGetRequestAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); this._logger.LogDebug("Response received: {StatusCode}", response.StatusCode); @@ -122,17 +163,18 @@ public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string quer /// Sends a POST request to the specified URI. /// </summary> /// <param name="query">The query string.</param> + /// <param name="top">Index of the first result to return.</param> /// <param name="searchOptions">The search options.</param> /// <param name="cancellationToken">A cancellation token to cancel the request.</param> /// <returns>A <see cref="HttpResponseMessage"/> representing the response from the request.</returns> - private async Task<HttpResponseMessage> SendGetRequestAsync(string query, TextSearchOptions searchOptions, CancellationToken cancellationToken) + private async Task<HttpResponseMessage> SendGetRequestAsync(string query, int top, TextSearchOptions searchOptions, CancellationToken cancellationToken) { - if (searchOptions.Top is <= 0 or > 50) + if (top is <= 0 or > 50) { throw new ArgumentOutOfRangeException(nameof(searchOptions), searchOptions, $"{nameof(searchOptions)} count value must be greater than 0 and have a maximum value of 50."); } - var requestContent = this.BuildRequestContent(query, searchOptions); + var requestContent = this.BuildRequestContent(query, top, searchOptions); using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, this._uri) { @@ -296,13 +338,14 @@ public TextSearchResult MapFromResultToTextSearchResult(object result) /// Build a query string from the <see cref="TextSearchOptions"/> /// </summary> /// <param name="query">The query.</param> + /// <param name="top">The maximum number of results to return.</param> /// <param name="searchOptions">The search options.</param> - private TavilySearchRequest BuildRequestContent(string query, TextSearchOptions searchOptions) + private TavilySearchRequest BuildRequestContent(string query, int top, TextSearchOptions searchOptions) { string? topic = null; string? timeRange = null; int? days = null; - int? maxResults = searchOptions.Top - searchOptions.Skip; + int? maxResults = top - searchOptions.Skip; IList<string>? includeDomains = null; IList<string>? excludeDomains = null; diff --git a/dotnet/src/SemanticKernel.Abstractions/CompatibilitySuppressions.xml b/dotnet/src/SemanticKernel.Abstractions/CompatibilitySuppressions.xml new file mode 100644 index 000000000000..53e8f709f49e --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/CompatibilitySuppressions.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids --> +<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <Suppression> + <DiagnosticId>CP0006</DiagnosticId> + <Target>M:Microsoft.SemanticKernel.Data.ITextSearch.GetSearchResultsAsync(System.String,System.Int32,Microsoft.SemanticKernel.Data.TextSearchOptions,System.Threading.CancellationToken)</Target> + <Left>lib/net8.0/Microsoft.SemanticKernel.Abstractions.dll</Left> + <Right>lib/net8.0/Microsoft.SemanticKernel.Abstractions.dll</Right> + <IsBaselineSuppression>true</IsBaselineSuppression> + </Suppression> + <Suppression> + <DiagnosticId>CP0006</DiagnosticId> + <Target>M:Microsoft.SemanticKernel.Data.ITextSearch.GetTextSearchResultsAsync(System.String,System.Int32,Microsoft.SemanticKernel.Data.TextSearchOptions,System.Threading.CancellationToken)</Target> + <Left>lib/net8.0/Microsoft.SemanticKernel.Abstractions.dll</Left> + <Right>lib/net8.0/Microsoft.SemanticKernel.Abstractions.dll</Right> + <IsBaselineSuppression>true</IsBaselineSuppression> + </Suppression> + <Suppression> + <DiagnosticId>CP0006</DiagnosticId> + <Target>M:Microsoft.SemanticKernel.Data.ITextSearch.SearchAsync(System.String,System.Int32,Microsoft.SemanticKernel.Data.TextSearchOptions,System.Threading.CancellationToken)</Target> + <Left>lib/net8.0/Microsoft.SemanticKernel.Abstractions.dll</Left> + <Right>lib/net8.0/Microsoft.SemanticKernel.Abstractions.dll</Right> + <IsBaselineSuppression>true</IsBaselineSuppression> + </Suppression> + <Suppression> + <DiagnosticId>CP0006</DiagnosticId> + <Target>M:Microsoft.SemanticKernel.Data.ITextSearch.GetSearchResultsAsync(System.String,System.Int32,Microsoft.SemanticKernel.Data.TextSearchOptions,System.Threading.CancellationToken)</Target> + <Left>lib/netstandard2.0/Microsoft.SemanticKernel.Abstractions.dll</Left> + <Right>lib/netstandard2.0/Microsoft.SemanticKernel.Abstractions.dll</Right> + <IsBaselineSuppression>true</IsBaselineSuppression> + </Suppression> + <Suppression> + <DiagnosticId>CP0006</DiagnosticId> + <Target>M:Microsoft.SemanticKernel.Data.ITextSearch.GetTextSearchResultsAsync(System.String,System.Int32,Microsoft.SemanticKernel.Data.TextSearchOptions,System.Threading.CancellationToken)</Target> + <Left>lib/netstandard2.0/Microsoft.SemanticKernel.Abstractions.dll</Left> + <Right>lib/netstandard2.0/Microsoft.SemanticKernel.Abstractions.dll</Right> + <IsBaselineSuppression>true</IsBaselineSuppression> + </Suppression> + <Suppression> + <DiagnosticId>CP0006</DiagnosticId> + <Target>M:Microsoft.SemanticKernel.Data.ITextSearch.SearchAsync(System.String,System.Int32,Microsoft.SemanticKernel.Data.TextSearchOptions,System.Threading.CancellationToken)</Target> + <Left>lib/netstandard2.0/Microsoft.SemanticKernel.Abstractions.dll</Left> + <Right>lib/netstandard2.0/Microsoft.SemanticKernel.Abstractions.dll</Right> + <IsBaselineSuppression>true</IsBaselineSuppression> + </Suppression> +</Suppressions> \ No newline at end of file diff --git a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/ITextSearch.cs b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/ITextSearch.cs index bb348a158c79..3f3451180240 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/ITextSearch.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/ITextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -14,8 +16,49 @@ public interface ITextSearch /// Perform a search for content related to the specified query and return <see cref="string"/> values representing the search results. /// </summary> /// <param name="query">What to search for.</param> + /// <param name="top">Maximum number of results to return.</param> /// <param name="searchOptions">Options used when executing a text search.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param> + IAsyncEnumerable<string> SearchAsync( + string query, + int top, + TextSearchOptions? searchOptions = null, + CancellationToken cancellationToken = default); + + /// <summary> + /// Perform a search for content related to the specified query and return <see cref="TextSearchResult"/> values representing the search results. + /// </summary> + /// <param name="query">What to search for.</param> + /// <param name="top">Maximum number of results to return.</param> + /// <param name="searchOptions">Options used when executing a text search.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param> + IAsyncEnumerable<TextSearchResult> GetTextSearchResultsAsync( + string query, + int top, + TextSearchOptions? searchOptions = null, + CancellationToken cancellationToken = default); + + /// <summary> + /// Perform a search for content related to the specified query and return <see cref="object"/> values representing the search results. + /// </summary> + /// <param name="query">What to search for.</param> + /// <param name="top">Maximum number of results to return.</param> + /// <param name="searchOptions">Options used when executing a text search.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param> + IAsyncEnumerable<object> GetSearchResultsAsync( + string query, + int top, + TextSearchOptions? searchOptions = null, + CancellationToken cancellationToken = default); + + #region obsolete + /// <summary> + /// Perform a search for content related to the specified query and return <see cref="string"/> values representing the search results. + /// </summary> + /// <param name="query">What to search for.</param> + /// <param name="searchOptions">Options used when executing a text search.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] Task<KernelSearchResults<string>> SearchAsync( string query, TextSearchOptions? searchOptions = null, @@ -27,6 +70,7 @@ Task<KernelSearchResults<string>> SearchAsync( /// <param name="query">What to search for.</param> /// <param name="searchOptions">Options used when executing a text search.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param> + [Obsolete("This method is deprecated and will be removed in future versions. Use GetTextSearchResultsAsync that returns IAsyncEnumerable<T> instead.", false)] Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync( string query, TextSearchOptions? searchOptions = null, @@ -38,8 +82,10 @@ Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync( /// <param name="query">What to search for.</param> /// <param name="searchOptions">Options used when executing a text search.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param> + [Obsolete("This method is deprecated and will be removed in future versions. Use GetSearchResultsAsync that returns IAsyncEnumerable<T> instead.", false)] Task<KernelSearchResults<object>> GetSearchResultsAsync( string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default); + #endregion } diff --git a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/KernelSearchResults.cs b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/KernelSearchResults.cs index f1a2ed341352..13d40be5dae7 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/KernelSearchResults.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/KernelSearchResults.cs @@ -1,4 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Collections.Generic; namespace Microsoft.SemanticKernel.Data; @@ -12,6 +13,7 @@ namespace Microsoft.SemanticKernel.Data; /// <param name="results">The search results.</param> /// <param name="totalCount">The total count of results found by the search operation, or null if the count was not requested.</param> /// <param name="metadata">Metadata associated with the search results.</param> +[Obsolete("This class is deprecated and will be removed in future versions. Methods will return IAsyncEnumerable<T> instead.", false)] public sealed class KernelSearchResults<T>(IAsyncEnumerable<T> results, long? totalCount = null, IReadOnlyDictionary<string, object?>? metadata = null) { /// <summary> diff --git a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchOptions.cs b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchOptions.cs index cc995af02e8d..0b12e065d0eb 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchOptions.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Data/TextSearch/TextSearchOptions.cs @@ -1,4 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System; + namespace Microsoft.SemanticKernel.Data; /// <summary> @@ -18,6 +20,7 @@ public sealed class TextSearchOptions /// Default value is false. /// Not all text search implementations will support this option. /// </remarks> + [Obsolete("This property is deprecated and will be removed in future versions. Total count will be returned if available via the last TextSearchResult.", false)] public bool IncludeTotalCount { get; init; } = false; /// <summary> @@ -28,6 +31,7 @@ public sealed class TextSearchOptions /// <summary> /// Number of search results to return. /// </summary> + [Obsolete("This property is deprecated and will be removed in future versions. Use the required top parameter instead.", false)] public int Top { get; init; } = DefaultTop; /// <summary> diff --git a/dotnet/src/SemanticKernel.AotTests/JsonSerializerContexts/CustomResultJsonSerializerContext.cs b/dotnet/src/SemanticKernel.AotTests/JsonSerializerContexts/CustomResultJsonSerializerContext.cs index c5a0d599864c..6a61f354aea7 100644 --- a/dotnet/src/SemanticKernel.AotTests/JsonSerializerContexts/CustomResultJsonSerializerContext.cs +++ b/dotnet/src/SemanticKernel.AotTests/JsonSerializerContexts/CustomResultJsonSerializerContext.cs @@ -8,9 +8,11 @@ namespace SemanticKernel.AotTests.JsonSerializerContexts; [JsonSerializable(typeof(CustomResult))] [JsonSerializable(typeof(int))] -[JsonSerializable(typeof(KernelSearchResults<string>))] -[JsonSerializable(typeof(KernelSearchResults<TextSearchResult>))] -[JsonSerializable(typeof(KernelSearchResults<object>))] +[JsonSerializable(typeof(List<string>))] +[JsonSerializable(typeof(List<TextSearchResult>))] +[JsonSerializable(typeof(IAsyncEnumerable<string>))] +[JsonSerializable(typeof(IAsyncEnumerable<TextSearchResult>))] +[JsonSerializable(typeof(IAsyncEnumerable<object>))] internal sealed partial class CustomResultJsonSerializerContext : JsonSerializerContext { } diff --git a/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/MockTextSearch.cs b/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/MockTextSearch.cs index 72aa218239f9..1c414d3dc9f1 100644 --- a/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/MockTextSearch.cs +++ b/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/MockTextSearch.cs @@ -6,37 +6,55 @@ namespace SemanticKernel.AotTests.UnitTests.Search; internal sealed class MockTextSearch : ITextSearch { - private readonly KernelSearchResults<object>? _objectResults; - private readonly KernelSearchResults<TextSearchResult>? _textSearchResults; - private readonly KernelSearchResults<string>? _stringResults; + private readonly IAsyncEnumerable<object>? _objectResults; + private readonly IAsyncEnumerable<TextSearchResult>? _textSearchResults; + private readonly IAsyncEnumerable<string>? _stringResults; - public MockTextSearch(KernelSearchResults<object>? objectResults) + public MockTextSearch(IAsyncEnumerable<object>? objectResults) { this._objectResults = objectResults; } - public MockTextSearch(KernelSearchResults<TextSearchResult>? textSearchResults) + public MockTextSearch(IAsyncEnumerable<TextSearchResult>? textSearchResults) { this._textSearchResults = textSearchResults; } - public MockTextSearch(KernelSearchResults<string>? stringResults) + public MockTextSearch(IAsyncEnumerable<string>? stringResults) { this._stringResults = stringResults; } + public IAsyncEnumerable<object> GetSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) + { + return this._objectResults!; + } + + public IAsyncEnumerable<TextSearchResult> GetTextSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) + { + return this._textSearchResults!; + } + + public IAsyncEnumerable<string> SearchAsync(string query, int top, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) + { + return this._stringResults!; + } + + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public Task<KernelSearchResults<object>> GetSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { - return Task.FromResult(this._objectResults!); + return Task.FromResult(new KernelSearchResults<object>(this._objectResults!)); } + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { - return Task.FromResult(this._textSearchResults!); + return Task.FromResult(new KernelSearchResults<TextSearchResult>(this._textSearchResults!)); } + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public Task<KernelSearchResults<string>> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { - return Task.FromResult(this._stringResults!); + return Task.FromResult(new KernelSearchResults<string>(this._stringResults!)); } } diff --git a/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/TextSearchExtensionsTests.cs b/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/TextSearchExtensionsTests.cs index 8aff74675ecf..7cbb9420f002 100644 --- a/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/TextSearchExtensionsTests.cs +++ b/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/TextSearchExtensionsTests.cs @@ -20,11 +20,11 @@ public static async Task CreateWithSearch() { // Arrange var testData = new List<string> { "test-value" }; - KernelSearchResults<string> results = new(testData.ToAsyncEnumerable()); + IAsyncEnumerable<string> results = testData.ToAsyncEnumerable(); ITextSearch textSearch = new MockTextSearch(results); // Act - var plugin = textSearch.CreateWithSearch("SearchPlugin", s_jsonSerializerOptions); + var plugin = textSearch.CreateWithSearch(2, "SearchPlugin", s_jsonSerializerOptions); // Assert await AssertSearchFunctionSchemaAndInvocationResult<string>(plugin["Search"], testData[0]); @@ -34,11 +34,11 @@ public static async Task CreateWithGetTextSearchResults() { // Arrange var testData = new List<TextSearchResult> { new("test-value") }; - KernelSearchResults<TextSearchResult> results = new(testData.ToAsyncEnumerable()); + IAsyncEnumerable<TextSearchResult> results = testData.ToAsyncEnumerable(); ITextSearch textSearch = new MockTextSearch(results); // Act - var plugin = textSearch.CreateWithGetTextSearchResults("SearchPlugin", s_jsonSerializerOptions); + var plugin = textSearch.CreateWithGetTextSearchResults(2, "SearchPlugin", s_jsonSerializerOptions); // Assert await AssertSearchFunctionSchemaAndInvocationResult<TextSearchResult>(plugin["GetTextSearchResults"], testData[0]); @@ -48,11 +48,11 @@ public static async Task CreateWithGetSearchResults() { // Arrange var testData = new List<CustomResult> { new("test-value") }; - KernelSearchResults<object> results = new(testData.ToAsyncEnumerable()); + IAsyncEnumerable<object> results = testData.ToAsyncEnumerable(); ITextSearch textSearch = new MockTextSearch(results); // Act - var plugin = textSearch.CreateWithGetSearchResults("SearchPlugin", s_jsonSerializerOptions); + var plugin = textSearch.CreateWithGetSearchResults(2, "SearchPlugin", s_jsonSerializerOptions); // Assert await AssertSearchFunctionSchemaAndInvocationResult<object>(plugin["GetSearchResults"], testData[0]); @@ -84,9 +84,9 @@ internal static void AssertSearchFunctionMetadata<T>(KernelFunctionMetadata meta var type = typeof(T).Name; var expectedSchema = type switch { - "String" => """{"type":"object","properties":{"TotalCount":{"type":["integer","null"],"default":null},"Metadata":{"type":["object","null"],"default":null},"Results":{"type":"array","items":{"type":"string"}}},"required":["Results"]}""", - "TextSearchResult" => """{"type":"object","properties":{"TotalCount":{"type":["integer","null"],"default":null},"Metadata":{"type":["object","null"],"default":null},"Results":{"type":"array","items":{"type":"object","properties":{"Name":{"type":["string","null"]},"Link":{"type":["string","null"]},"Value":{"type":"string"}},"required":["Value"]}}},"required":["Results"]}""", - _ => """{"type":"object","properties":{"TotalCount":{"type":["integer","null"],"default":null},"Metadata":{"type":["object","null"],"default":null},"Results":{"type":"array","items":{"type":"object","properties":{"Name":{"type":["string","null"]},"Link":{"type":["string","null"]},"Value":{"type":"string"}},"required":["Value"]}}},"required":["Results"]}""" + "String" => """{"type":"array","items":{"type":"string"}}""", + "TextSearchResult" => """{"type":"array","items":{"type":"object","properties":{"Name":{"type":["string","null"]},"Link":{"type":["string","null"]},"Value":{"type":"string"}},"required":["Value"]}}""", + _ => """{"type":"array","items":{"type":"object","properties":{"Name":{"type":["string","null"]},"Link":{"type":["string","null"]},"Value":{"type":"string"}},"required":["Value"]}}""" }; Assert.AreEqual(expectedSchema, metadata.ReturnParameter.Schema!.ToString()); } diff --git a/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/VectorStoreTextSearchTests.cs b/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/VectorStoreTextSearchTests.cs index eee8ae4db55e..17697590ec7f 100644 --- a/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/VectorStoreTextSearchTests.cs +++ b/dotnet/src/SemanticKernel.AotTests/UnitTests/Search/VectorStoreTextSearchTests.cs @@ -21,11 +21,11 @@ public static async Task GetTextSearchResultsAsync() VectorStoreTextSearch<DataModel> textSearch = new(new MockVectorizableTextSearch<DataModel>(testData)); // Act - KernelSearchResults<TextSearchResult> searchResults = await textSearch.GetTextSearchResultsAsync("query"); + IAsyncEnumerable<TextSearchResult> searchResults = textSearch.GetTextSearchResultsAsync("query", 5); List<TextSearchResult> results = []; - await foreach (TextSearchResult result in searchResults.Results) + await foreach (TextSearchResult result in searchResults) { results.Add(result); } @@ -54,11 +54,11 @@ public static async Task AddVectorStoreTextSearch() Assert.IsNotNull(textSearch); // Assert - KernelSearchResults<TextSearchResult> searchResults = await textSearch.GetTextSearchResultsAsync("query"); + IAsyncEnumerable<TextSearchResult> searchResults = textSearch.GetTextSearchResultsAsync("query", 5); List<TextSearchResult> results = []; - await foreach (TextSearchResult result in searchResults.Results) + await foreach (TextSearchResult result in searchResults) { results.Add(result); } diff --git a/dotnet/src/SemanticKernel.Core/Data/TextSearch/TextSearchExtensions.cs b/dotnet/src/SemanticKernel.Core/Data/TextSearch/TextSearchExtensions.cs index bfb829c44759..be63a6db42f2 100644 --- a/dotnet/src/SemanticKernel.Core/Data/TextSearch/TextSearchExtensions.cs +++ b/dotnet/src/SemanticKernel.Core/Data/TextSearch/TextSearchExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -29,12 +30,31 @@ public static class TextSearchExtensions /// <returns>A <see cref="KernelPlugin"/> instance with a Search operation that calls the provided <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] - public static KernelPlugin CreateWithSearch(this ITextSearch textSearch, string pluginName, string? description = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateWithSearch which takes the top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelPlugin CreateWithSearch(this ITextSearch textSearch, string pluginName, string? description = null) => + textSearch.CreateWithSearch(TextSearchOptions.DefaultTop, pluginName, description); + + /// <summary> + /// Creates a plugin from an ITextSearch implementation. + /// </summary> + /// <remarks> + /// The plugin will have a single function called `Search` which + /// will return a <see cref="IEnumerable{String}"/> + /// </remarks> + /// <param name="textSearch">The instance of ITextSearch to be used by the plugin.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="pluginName">The name for the plugin.</param> + /// <param name="description">A description of the plugin.</param> + /// <returns>A <see cref="KernelPlugin"/> instance with a Search operation that calls the provided <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + public static KernelPlugin CreateWithSearch(this ITextSearch textSearch, int top, string pluginName, string? description = null) { Verify.NotNull(textSearch); Verify.NotNull(pluginName); - return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateSearch()]); + return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateSearch(top)]); } /// <summary> @@ -49,12 +69,30 @@ public static KernelPlugin CreateWithSearch(this ITextSearch textSearch, string /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization of various aspects of the function.</param> /// <param name="description">A description of the plugin.</param> /// <returns>A <see cref="KernelPlugin"/> instance with a Search operation that calls the provided <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> - public static KernelPlugin CreateWithSearch(this ITextSearch textSearch, string pluginName, JsonSerializerOptions jsonSerializerOptions, string? description = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateWithSearch which takes the top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelPlugin CreateWithSearch(this ITextSearch textSearch, string pluginName, JsonSerializerOptions jsonSerializerOptions, string? description = null) => + textSearch.CreateWithSearch(TextSearchOptions.DefaultTop, pluginName, jsonSerializerOptions, description); + + /// <summary> + /// Creates a plugin from an ITextSearch implementation. + /// </summary> + /// <remarks> + /// The plugin will have a single function called `Search` which + /// will return a <see cref="IEnumerable{String}"/> + /// </remarks> + /// <param name="textSearch">The instance of ITextSearch to be used by the plugin.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="pluginName">The name for the plugin.</param> + /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization of various aspects of the function.</param> + /// <param name="description">A description of the plugin.</param> + /// <returns>A <see cref="KernelPlugin"/> instance with a Search operation that calls the provided <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + public static KernelPlugin CreateWithSearch(this ITextSearch textSearch, int top, string pluginName, JsonSerializerOptions jsonSerializerOptions, string? description = null) { Verify.NotNull(textSearch); Verify.NotNull(pluginName); - return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateSearch(jsonSerializerOptions)]); + return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateSearch(top, jsonSerializerOptions)]); } /// <summary> @@ -70,12 +108,31 @@ public static KernelPlugin CreateWithSearch(this ITextSearch textSearch, string /// <returns>A <see cref="KernelPlugin"/> instance with a GetTextSearchResults operation that calls the provided <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] - public static KernelPlugin CreateWithGetTextSearchResults(this ITextSearch textSearch, string pluginName, string? description = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateWithGetTextSearchResults which takes the top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelPlugin CreateWithGetTextSearchResults(this ITextSearch textSearch, string pluginName, string? description = null) => + textSearch.CreateWithGetTextSearchResults(TextSearchOptions.DefaultTop, pluginName, description); + + /// <summary> + /// Creates a plugin from an ITextSearch implementation. + /// </summary> + /// <remarks> + /// The plugin will have a single function called `GetSearchResults` which + /// will return a <see cref="IEnumerable{TextSearchResult}"/> + /// </remarks> + /// <param name="textSearch">The instance of ITextSearch to be used by the plugin.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="pluginName">The name for the plugin.</param> + /// <param name="description">A description of the plugin.</param> + /// <returns>A <see cref="KernelPlugin"/> instance with a GetTextSearchResults operation that calls the provided <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + public static KernelPlugin CreateWithGetTextSearchResults(this ITextSearch textSearch, int top, string pluginName, string? description = null) { Verify.NotNull(textSearch); Verify.NotNull(pluginName); - return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateGetTextSearchResults()]); + return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateGetTextSearchResults(top)]); } /// <summary> @@ -90,12 +147,30 @@ public static KernelPlugin CreateWithGetTextSearchResults(this ITextSearch textS /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization of various aspects of the function.</param> /// <param name="description">A description of the plugin.</param> /// <returns>A <see cref="KernelPlugin"/> instance with a GetTextSearchResults operation that calls the provided <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> - public static KernelPlugin CreateWithGetTextSearchResults(this ITextSearch textSearch, string pluginName, JsonSerializerOptions jsonSerializerOptions, string? description = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateWithGetTextSearchResults which takes the top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelPlugin CreateWithGetTextSearchResults(this ITextSearch textSearch, string pluginName, JsonSerializerOptions jsonSerializerOptions, string? description = null) => + textSearch.CreateWithGetTextSearchResults(TextSearchOptions.DefaultTop, pluginName, jsonSerializerOptions, description); + + /// <summary> + /// Creates a plugin from an ITextSearch implementation. + /// </summary> + /// <remarks> + /// The plugin will have a single function called `GetSearchResults` which + /// will return a <see cref="IEnumerable{TextSearchResult}"/> + /// </remarks> + /// <param name="textSearch">The instance of ITextSearch to be used by the plugin.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="pluginName">The name for the plugin.</param> + /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization of various aspects of the function.</param> + /// <param name="description">A description of the plugin.</param> + /// <returns>A <see cref="KernelPlugin"/> instance with a GetTextSearchResults operation that calls the provided <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + public static KernelPlugin CreateWithGetTextSearchResults(this ITextSearch textSearch, int top, string pluginName, JsonSerializerOptions jsonSerializerOptions, string? description = null) { Verify.NotNull(textSearch); Verify.NotNull(pluginName); - return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateGetTextSearchResults(jsonSerializerOptions)]); + return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateGetTextSearchResults(top, jsonSerializerOptions)]); } /// <summary> @@ -111,12 +186,31 @@ public static KernelPlugin CreateWithGetTextSearchResults(this ITextSearch textS /// <returns>A <see cref="KernelPlugin"/> instance with a GetSearchResults operation that calls the provided <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] - public static KernelPlugin CreateWithGetSearchResults(this ITextSearch textSearch, string pluginName, string? description = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateWithGetSearchResults which takes the top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelPlugin CreateWithGetSearchResults(this ITextSearch textSearch, string pluginName, string? description = null) => + textSearch.CreateWithGetSearchResults(TextSearchOptions.DefaultTop, pluginName, description); + + /// <summary> + /// Creates a plugin from an ITextSearch implementation. + /// </summary> + /// <remarks> + /// The plugin will have a single function called `GetSearchResults` which + /// will return a <see cref="IEnumerable{TextSearchResult}"/> + /// </remarks> + /// <param name="textSearch">The instance of ITextSearch to be used by the plugin.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="pluginName">The name for the plugin.</param> + /// <param name="description">A description of the plugin.</param> + /// <returns>A <see cref="KernelPlugin"/> instance with a GetSearchResults operation that calls the provided <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + public static KernelPlugin CreateWithGetSearchResults(this ITextSearch textSearch, int top, string pluginName, string? description = null) { Verify.NotNull(textSearch); Verify.NotNull(pluginName); - return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateGetSearchResults()]); + return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateGetSearchResults(top)]); } /// <summary> @@ -131,12 +225,30 @@ public static KernelPlugin CreateWithGetSearchResults(this ITextSearch textSearc /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization of various aspects of the function.</param> /// <param name="description">A description of the plugin.</param> /// <returns>A <see cref="KernelPlugin"/> instance with a GetSearchResults operation that calls the provided <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> - public static KernelPlugin CreateWithGetSearchResults(this ITextSearch textSearch, string pluginName, JsonSerializerOptions jsonSerializerOptions, string? description = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateWithGetSearchResults which takes the top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelPlugin CreateWithGetSearchResults(this ITextSearch textSearch, string pluginName, JsonSerializerOptions jsonSerializerOptions, string? description = null) => + textSearch.CreateWithGetSearchResults(TextSearchOptions.DefaultTop, pluginName, jsonSerializerOptions, description); + + /// <summary> + /// Creates a plugin from an ITextSearch implementation. + /// </summary> + /// <remarks> + /// The plugin will have a single function called `GetSearchResults` which + /// will return a <see cref="IEnumerable{TextSearchResult}"/> + /// </remarks> + /// <param name="textSearch">The instance of ITextSearch to be used by the plugin.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="pluginName">The name for the plugin.</param> + /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization of various aspects of the function.</param> + /// <param name="description">A description of the plugin.</param> + /// <returns>A <see cref="KernelPlugin"/> instance with a GetSearchResults operation that calls the provided <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + public static KernelPlugin CreateWithGetSearchResults(this ITextSearch textSearch, int top, string pluginName, JsonSerializerOptions jsonSerializerOptions, string? description = null) { Verify.NotNull(textSearch); Verify.NotNull(pluginName); - return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateGetSearchResults(jsonSerializerOptions)]); + return KernelPluginFactory.CreateFromFunctions(pluginName, description, [textSearch.CreateGetSearchResults(top, jsonSerializerOptions)]); } #endregion @@ -150,9 +262,24 @@ public static KernelPlugin CreateWithGetSearchResults(this ITextSearch textSearc /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] - public static KernelFunction CreateSearch(this ITextSearch textSearch, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateSearch with top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelFunction CreateSearch(this ITextSearch textSearch, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) => + textSearch.CreateSearch(searchOptions?.Top ?? TextSearchOptions.DefaultTop, options, searchOptions); + + /// <summary> + /// Create a <see cref="KernelFunction"/> which invokes <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>. + /// </summary> + /// <param name="textSearch">The ITextSearch instance to use.</param> + /// <param name="top">Maximum number of search results to return.</param> + /// <param name="options">Optional KernelFunctionFromMethodOptions which allow the KernelFunction metadata to be specified.</param> + /// <param name="searchOptions">Optional TextSearchOptions which override the options provided when the function is invoked.</param> + /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + public static KernelFunction CreateSearch(this ITextSearch textSearch, int top, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) { - async Task<IEnumerable<string>> SearchAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int count = 2, int skip = 0) + async Task<IEnumerable<string>> SearchAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int? count = null, int? skip = null) { arguments.TryGetValue("query", out var query); if (string.IsNullOrEmpty(query?.ToString())) @@ -164,17 +291,15 @@ async Task<IEnumerable<string>> SearchAsync(Kernel kernel, KernelFunction functi searchOptions ??= new() { - Top = count, - Skip = skip, + Skip = skip ?? 0, Filter = CreateBasicFilter(options, arguments) }; - var result = await textSearch.SearchAsync(query?.ToString()!, searchOptions, cancellationToken).ConfigureAwait(false); - var resultList = await result.Results.ToListAsync(cancellationToken).ConfigureAwait(false); - return resultList; + var results = textSearch.SearchAsync(query?.ToString()!, count ?? top, searchOptions, cancellationToken); + return await results.ToListAsync(cancellationToken).ConfigureAwait(false); } - options ??= DefaultSearchMethodOptions(); + options ??= DefaultSearchMethodOptions(top); return KernelFunctionFactory.CreateFromMethod( SearchAsync, options); @@ -188,9 +313,25 @@ async Task<IEnumerable<string>> SearchAsync(Kernel kernel, KernelFunction functi /// <param name="options">Optional KernelFunctionFromMethodOptions which allow the KernelFunction metadata to be specified.</param> /// <param name="searchOptions">Optional TextSearchOptions which override the options provided when the function is invoked.</param> /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> - public static KernelFunction CreateSearch(this ITextSearch textSearch, JsonSerializerOptions jsonSerializerOptions, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) + [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateSearch with top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelFunction CreateSearch(this ITextSearch textSearch, JsonSerializerOptions jsonSerializerOptions, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) => + textSearch.CreateSearch(searchOptions?.Top ?? TextSearchOptions.DefaultTop, jsonSerializerOptions, options, searchOptions); + + /// <summary> + /// Create a <see cref="KernelFunction"/> which invokes <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>. + /// </summary> + /// <param name="textSearch">The ITextSearch instance to use.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization of various aspects of the function.</param> + /// <param name="options">Optional KernelFunctionFromMethodOptions which allow the KernelFunction metadata to be specified.</param> + /// <param name="searchOptions">Optional TextSearchOptions which override the options provided when the function is invoked.</param> + /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + public static KernelFunction CreateSearch(this ITextSearch textSearch, int top, JsonSerializerOptions jsonSerializerOptions, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) { - async Task<IEnumerable<string>> SearchAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int count = 2, int skip = 0) + async Task<IEnumerable<string>> SearchAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int? count = null, int? skip = null) { arguments.TryGetValue("query", out var query); if (string.IsNullOrEmpty(query?.ToString())) @@ -202,17 +343,15 @@ async Task<IEnumerable<string>> SearchAsync(Kernel kernel, KernelFunction functi searchOptions ??= new() { - Top = count, - Skip = skip, + Skip = skip ?? 0, Filter = CreateBasicFilter(options, arguments) }; - var result = await textSearch.SearchAsync(query?.ToString()!, searchOptions, cancellationToken).ConfigureAwait(false); - var resultList = await result.Results.ToListAsync(cancellationToken).ConfigureAwait(false); - return resultList; + var results = textSearch.SearchAsync(query?.ToString()!, count ?? top, searchOptions, cancellationToken); + return await results.ToListAsync(cancellationToken).ConfigureAwait(false); } - options ??= DefaultSearchMethodOptions(jsonSerializerOptions); + options ??= DefaultSearchMethodOptions(top, jsonSerializerOptions); return KernelFunctionFactory.CreateFromMethod( SearchAsync, jsonSerializerOptions, @@ -228,9 +367,24 @@ async Task<IEnumerable<string>> SearchAsync(Kernel kernel, KernelFunction functi /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] - public static KernelFunction CreateGetTextSearchResults(this ITextSearch textSearch, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateGetTextSearchResults with top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelFunction CreateGetTextSearchResults(this ITextSearch textSearch, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) => + textSearch.CreateGetTextSearchResults(searchOptions?.Top ?? TextSearchOptions.DefaultTop, options, searchOptions); + + /// <summary> + /// Create a <see cref="KernelFunction"/> which invokes <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>. + /// </summary> + /// <param name="textSearch">The ITextSearch instance to use.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="options">Optional KernelFunctionFromMethodOptions which allow the KernelFunction metadata to be specified.</param> + /// <param name="searchOptions">Optional TextSearchOptions which override the options provided when the function is invoked.</param> + /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + public static KernelFunction CreateGetTextSearchResults(this ITextSearch textSearch, int top, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) { - async Task<IEnumerable<TextSearchResult>> GetTextSearchResultAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int count = 2, int skip = 0) + async Task<IEnumerable<TextSearchResult>> GetTextSearchResultAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int? count = null, int? skip = null) { arguments.TryGetValue("query", out var query); if (string.IsNullOrEmpty(query?.ToString())) @@ -242,16 +396,15 @@ async Task<IEnumerable<TextSearchResult>> GetTextSearchResultAsync(Kernel kernel searchOptions ??= new() { - Top = count, - Skip = skip, + Skip = skip ?? 0, Filter = CreateBasicFilter(options, arguments) }; - var result = await textSearch.GetTextSearchResultsAsync(query?.ToString()!, searchOptions, cancellationToken).ConfigureAwait(false); - return await result.Results.ToListAsync(cancellationToken).ConfigureAwait(false); + var results = textSearch.GetTextSearchResultsAsync(query?.ToString()!, count ?? top, searchOptions, cancellationToken); + return await results.ToListAsync(cancellationToken).ConfigureAwait(false); } - options ??= DefaultGetTextSearchResultsMethodOptions(); + options ??= DefaultGetTextSearchResultsMethodOptions(top); return KernelFunctionFactory.CreateFromMethod( GetTextSearchResultAsync, options); @@ -265,9 +418,23 @@ async Task<IEnumerable<TextSearchResult>> GetTextSearchResultAsync(Kernel kernel /// <param name="options">Optional KernelFunctionFromMethodOptions which allow the KernelFunction metadata to be specified.</param> /// <param name="searchOptions">Optional TextSearchOptions which override the options provided when the function is invoked.</param> /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> - public static KernelFunction CreateGetTextSearchResults(this ITextSearch textSearch, JsonSerializerOptions jsonSerializerOptions, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateGetTextSearchResults which takes the top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelFunction CreateGetTextSearchResults(this ITextSearch textSearch, JsonSerializerOptions jsonSerializerOptions, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) => + textSearch.CreateGetTextSearchResults(searchOptions?.Top ?? TextSearchOptions.DefaultTop, jsonSerializerOptions, options, searchOptions); + + /// <summary> + /// Create a <see cref="KernelFunction"/> which invokes <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>. + /// </summary> + /// <param name="textSearch">The ITextSearch instance to use.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization of various aspects of the function.</param> + /// <param name="options">Optional KernelFunctionFromMethodOptions which allow the KernelFunction metadata to be specified.</param> + /// <param name="searchOptions">Optional TextSearchOptions which override the options provided when the function is invoked.</param> + /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + public static KernelFunction CreateGetTextSearchResults(this ITextSearch textSearch, int top, JsonSerializerOptions jsonSerializerOptions, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) { - async Task<IEnumerable<TextSearchResult>> GetTextSearchResultAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int count = 2, int skip = 0) + async Task<IEnumerable<TextSearchResult>> GetTextSearchResultAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int? count = null, int? skip = null) { arguments.TryGetValue("query", out var query); if (string.IsNullOrEmpty(query?.ToString())) @@ -279,16 +446,15 @@ async Task<IEnumerable<TextSearchResult>> GetTextSearchResultAsync(Kernel kernel searchOptions ??= new() { - Top = count, - Skip = skip, + Skip = skip ?? 0, Filter = CreateBasicFilter(options, arguments) }; - var result = await textSearch.GetTextSearchResultsAsync(query?.ToString()!, searchOptions, cancellationToken).ConfigureAwait(false); - return await result.Results.ToListAsync(cancellationToken).ConfigureAwait(false); + var results = textSearch.GetTextSearchResultsAsync(query?.ToString()!, count ?? top, searchOptions, cancellationToken); + return await results.ToListAsync(cancellationToken).ConfigureAwait(false); } - options ??= DefaultGetTextSearchResultsMethodOptions(jsonSerializerOptions); + options ??= DefaultGetTextSearchResultsMethodOptions(top, jsonSerializerOptions); return KernelFunctionFactory.CreateFromMethod( GetTextSearchResultAsync, jsonSerializerOptions, @@ -304,9 +470,24 @@ async Task<IEnumerable<TextSearchResult>> GetTextSearchResultAsync(Kernel kernel /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] - public static KernelFunction CreateGetSearchResults(this ITextSearch textSearch, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateGetSearchResults which takes the top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelFunction CreateGetSearchResults(this ITextSearch textSearch, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) => + textSearch.CreateGetSearchResults(searchOptions?.Top ?? TextSearchOptions.DefaultTop, options, searchOptions); + + /// <summary> + /// Create a <see cref="KernelFunction"/> which invokes <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>. + /// </summary> + /// <param name="textSearch">The ITextSearch instance to use.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="options">Optional KernelFunctionFromMethodOptions which allow the KernelFunction metadata to be specified.</param> + /// <param name="searchOptions">Optional TextSearchOptions which override the options provided when the function is invoked.</param> + /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + [RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + [RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")] + public static KernelFunction CreateGetSearchResults(this ITextSearch textSearch, int top, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) { - async Task<IEnumerable<object>> GetSearchResultAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int count = 2, int skip = 0) + async Task<IEnumerable<object>> GetSearchResultAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int? count = null, int? skip = null) { arguments.TryGetValue("query", out var query); if (string.IsNullOrEmpty(query?.ToString())) @@ -318,16 +499,15 @@ async Task<IEnumerable<object>> GetSearchResultAsync(Kernel kernel, KernelFuncti searchOptions ??= new() { - Top = count, - Skip = skip, + Skip = skip ?? 0, Filter = CreateBasicFilter(options, arguments) }; - var result = await textSearch.GetSearchResultsAsync(query?.ToString()!, searchOptions, cancellationToken).ConfigureAwait(false); - return await result.Results.ToListAsync(cancellationToken).ConfigureAwait(false); + var results = textSearch.GetSearchResultsAsync(query?.ToString()!, count ?? top, searchOptions, cancellationToken); + return await results.ToListAsync(cancellationToken).ConfigureAwait(false); } - options ??= DefaultGetSearchResultsMethodOptions(); + options ??= DefaultGetSearchResultsMethodOptions(top); return KernelFunctionFactory.CreateFromMethod( GetSearchResultAsync, options); @@ -341,9 +521,23 @@ async Task<IEnumerable<object>> GetSearchResultAsync(Kernel kernel, KernelFuncti /// <param name="options">Optional KernelFunctionFromMethodOptions which allow the KernelFunction metadata to be specified.</param> /// <param name="searchOptions">Optional TextSearchOptions which override the options provided when the function is invoked.</param> /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> - public static KernelFunction CreateGetSearchResults(this ITextSearch textSearch, JsonSerializerOptions jsonSerializerOptions, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) + [Obsolete("This property is deprecated and will be removed in future versions. Use CreateGetSearchResults which takes the top parameter instead.", false)] + [ExcludeFromCodeCoverage] + public static KernelFunction CreateGetSearchResults(this ITextSearch textSearch, JsonSerializerOptions jsonSerializerOptions, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) => + textSearch.CreateGetSearchResults(searchOptions?.Top ?? TextSearchOptions.DefaultTop, jsonSerializerOptions, options, searchOptions); + + /// <summary> + /// Create a <see cref="KernelFunction"/> which invokes <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>. + /// </summary> + /// <param name="textSearch">The ITextSearch instance to use.</param> + /// <param name="top">The maximum number of search results to return.</param> + /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization and deserialization of various aspects of the function.</param> + /// <param name="options">Optional KernelFunctionFromMethodOptions which allow the KernelFunction metadata to be specified.</param> + /// <param name="searchOptions">Optional TextSearchOptions which override the options provided when the function is invoked.</param> + /// <returns>A <see cref="KernelFunction"/> instance with a Search operation that calls the provided <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>.</returns> + public static KernelFunction CreateGetSearchResults(this ITextSearch textSearch, int top, JsonSerializerOptions jsonSerializerOptions, KernelFunctionFromMethodOptions? options = null, TextSearchOptions? searchOptions = null) { - async Task<IEnumerable<object>> GetSearchResultAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int count = 2, int skip = 0) + async Task<IEnumerable<object>> GetSearchResultAsync(Kernel kernel, KernelFunction function, KernelArguments arguments, CancellationToken cancellationToken, int? count = null, int? skip = null) { arguments.TryGetValue("query", out var query); if (string.IsNullOrEmpty(query?.ToString())) @@ -355,22 +549,20 @@ async Task<IEnumerable<object>> GetSearchResultAsync(Kernel kernel, KernelFuncti searchOptions ??= new() { - Top = count, - Skip = skip, + Skip = skip ?? 0, Filter = CreateBasicFilter(options, arguments) }; - var result = await textSearch.GetSearchResultsAsync(query?.ToString()!, searchOptions, cancellationToken).ConfigureAwait(false); - return await result.Results.ToListAsync(cancellationToken).ConfigureAwait(false); + var results = textSearch.GetSearchResultsAsync(query?.ToString()!, count ?? top, searchOptions, cancellationToken); + return await results.ToListAsync(cancellationToken).ConfigureAwait(false); } - options ??= DefaultGetSearchResultsMethodOptions(jsonSerializerOptions); + options ??= DefaultGetSearchResultsMethodOptions(top, jsonSerializerOptions); return KernelFunctionFactory.CreateFromMethod( GetSearchResultAsync, jsonSerializerOptions, options); } - #endregion #region private @@ -379,26 +571,25 @@ async Task<IEnumerable<object>> GetSearchResultAsync(Kernel kernel, KernelFuncti /// </summary> [RequiresUnreferencedCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] - private static KernelFunctionFromMethodOptions DefaultSearchMethodOptions() => + private static KernelFunctionFromMethodOptions DefaultSearchMethodOptions(int defaultCount) => new() { FunctionName = "Search", Description = "Perform a search for content related to the specified query and return string results", - Parameters = GetDefaultKernelParameterMetadata(), - ReturnParameter = new() { ParameterType = typeof(KernelSearchResults<string>) }, + Parameters = GetDefaultKernelParameterMetadata(defaultCount), + ReturnParameter = new() { ParameterType = typeof(List<string>) }, }; /// <summary> /// Create the default <see cref="KernelFunctionFromMethodOptions"/> for <see cref="ITextSearch.SearchAsync(string, TextSearchOptions?, CancellationToken)"/>. - /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> used for generating JSON schema for method parameters and return type.</param> /// </summary> - private static KernelFunctionFromMethodOptions DefaultSearchMethodOptions(JsonSerializerOptions jsonSerializerOptions) => + private static KernelFunctionFromMethodOptions DefaultSearchMethodOptions(int defaultCount, JsonSerializerOptions jsonSerializerOptions) => new() { FunctionName = "Search", Description = "Perform a search for content related to the specified query and return string results", - Parameters = CreateDefaultKernelParameterMetadata(jsonSerializerOptions), - ReturnParameter = new(jsonSerializerOptions) { ParameterType = typeof(KernelSearchResults<string>) }, + Parameters = CreateDefaultKernelParameterMetadata(defaultCount, jsonSerializerOptions), + ReturnParameter = new(jsonSerializerOptions) { ParameterType = typeof(List<string>) }, }; /// <summary> @@ -406,26 +597,25 @@ private static KernelFunctionFromMethodOptions DefaultSearchMethodOptions(JsonSe /// </summary> [RequiresUnreferencedCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] - private static KernelFunctionFromMethodOptions DefaultGetTextSearchResultsMethodOptions() => + private static KernelFunctionFromMethodOptions DefaultGetTextSearchResultsMethodOptions(int defaultCount) => new() { FunctionName = "GetTextSearchResults", Description = "Perform a search for content related to the specified query. The search will return the name, value and link for the related content.", - Parameters = GetDefaultKernelParameterMetadata(), - ReturnParameter = new() { ParameterType = typeof(KernelSearchResults<TextSearchResult>) }, + Parameters = GetDefaultKernelParameterMetadata(defaultCount), + ReturnParameter = new() { ParameterType = typeof(List<TextSearchResult>) }, }; /// <summary> /// Create the default <see cref="KernelFunctionFromMethodOptions"/> for <see cref="ITextSearch.GetTextSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>. - /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> used for generating JSON schema for method parameters and return type.</param> /// </summary> - private static KernelFunctionFromMethodOptions DefaultGetTextSearchResultsMethodOptions(JsonSerializerOptions jsonSerializerOptions) => + private static KernelFunctionFromMethodOptions DefaultGetTextSearchResultsMethodOptions(int defaultCount, JsonSerializerOptions jsonSerializerOptions) => new() { FunctionName = "GetTextSearchResults", Description = "Perform a search for content related to the specified query. The search will return the name, value and link for the related content.", - Parameters = CreateDefaultKernelParameterMetadata(jsonSerializerOptions), - ReturnParameter = new(jsonSerializerOptions) { ParameterType = typeof(KernelSearchResults<TextSearchResult>) }, + Parameters = CreateDefaultKernelParameterMetadata(defaultCount, jsonSerializerOptions), + ReturnParameter = new(jsonSerializerOptions) { ParameterType = typeof(List<TextSearchResult>) }, }; /// <summary> @@ -433,26 +623,25 @@ private static KernelFunctionFromMethodOptions DefaultGetTextSearchResultsMethod /// </summary> [RequiresUnreferencedCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] - private static KernelFunctionFromMethodOptions DefaultGetSearchResultsMethodOptions() => + private static KernelFunctionFromMethodOptions DefaultGetSearchResultsMethodOptions(int defaultCount) => new() { FunctionName = "GetSearchResults", Description = "Perform a search for content related to the specified query.", - Parameters = GetDefaultKernelParameterMetadata(), - ReturnParameter = new() { ParameterType = typeof(KernelSearchResults<TextSearchResult>) }, + Parameters = GetDefaultKernelParameterMetadata(defaultCount), + ReturnParameter = new() { ParameterType = typeof(List<TextSearchResult>) }, }; /// <summary> /// Create the default <see cref="KernelFunctionFromMethodOptions"/> for <see cref="ITextSearch.GetSearchResultsAsync(string, TextSearchOptions?, CancellationToken)"/>. /// </summary> - /// <param name="jsonSerializerOptions">The <see cref="JsonSerializerOptions"/> used for generating JSON schema for method parameters and return type.</param> - private static KernelFunctionFromMethodOptions DefaultGetSearchResultsMethodOptions(JsonSerializerOptions jsonSerializerOptions) => + private static KernelFunctionFromMethodOptions DefaultGetSearchResultsMethodOptions(int defaultCount, JsonSerializerOptions jsonSerializerOptions) => new() { FunctionName = "GetSearchResults", Description = "Perform a search for content related to the specified query.", - Parameters = CreateDefaultKernelParameterMetadata(jsonSerializerOptions), - ReturnParameter = new(jsonSerializerOptions) { ParameterType = typeof(KernelSearchResults<TextSearchResult>) }, + Parameters = CreateDefaultKernelParameterMetadata(defaultCount, jsonSerializerOptions), + ReturnParameter = new(jsonSerializerOptions) { ParameterType = typeof(List<TextSearchResult>) }, }; /// <summary> @@ -486,22 +675,22 @@ private static KernelFunctionFromMethodOptions DefaultGetSearchResultsMethodOpti return filter; } - private static IEnumerable<KernelParameterMetadata> CreateDefaultKernelParameterMetadata(JsonSerializerOptions jsonSerializerOptions) + private static IEnumerable<KernelParameterMetadata> CreateDefaultKernelParameterMetadata(int defaultCount, JsonSerializerOptions jsonSerializerOptions) { return [ new KernelParameterMetadata("query", jsonSerializerOptions) { Description = "What to search for", ParameterType = typeof(string), IsRequired = true }, - new KernelParameterMetadata("count", jsonSerializerOptions) { Description = "Number of results", ParameterType = typeof(int), IsRequired = false, DefaultValue = 2 }, + new KernelParameterMetadata("count", jsonSerializerOptions) { Description = "Number of results", ParameterType = typeof(int), IsRequired = false, DefaultValue = defaultCount }, new KernelParameterMetadata("skip", jsonSerializerOptions) { Description = "Number of results to skip", ParameterType = typeof(int), IsRequired = false, DefaultValue = 0 }, ]; } [RequiresUnreferencedCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] [RequiresDynamicCode("Uses reflection for generating JSON schema for method parameters and return type, making it incompatible with AOT scenarios.")] - private static IEnumerable<KernelParameterMetadata> GetDefaultKernelParameterMetadata() + private static IEnumerable<KernelParameterMetadata> GetDefaultKernelParameterMetadata(int defaultCount) { return s_kernelParameterMetadata ??= [ new KernelParameterMetadata("query") { Description = "What to search for", ParameterType = typeof(string), IsRequired = true }, - new KernelParameterMetadata("count") { Description = "Number of results", ParameterType = typeof(int), IsRequired = false, DefaultValue = 2 }, + new KernelParameterMetadata("count") { Description = "Number of results", ParameterType = typeof(int), IsRequired = false, DefaultValue = defaultCount }, new KernelParameterMetadata("skip") { Description = "Number of results to skip", ParameterType = typeof(int), IsRequired = false, DefaultValue = 0 }, ]; } diff --git a/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs b/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs index 68ae09c883d5..06b989ecc1b8 100644 --- a/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs +++ b/dotnet/src/SemanticKernel.Core/Data/TextSearch/VectorStoreTextSearch.cs @@ -116,28 +116,66 @@ public VectorStoreTextSearch( } /// <inheritdoc/> + public async IAsyncEnumerable<string> SearchAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + VectorSearchResults<TRecord> searchResponse = await this.ExecuteVectorSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsStringAsync(searchResponse.Results, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + /// <inheritdoc/> + public async IAsyncEnumerable<TextSearchResult> GetTextSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + VectorSearchResults<TRecord> searchResponse = await this.ExecuteVectorSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsTextSearchResultAsync(searchResponse.Results, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + /// <inheritdoc/> + public async IAsyncEnumerable<object> GetSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + VectorSearchResults<TRecord> searchResponse = await this.ExecuteVectorSearchAsync(query, top, searchOptions, cancellationToken).ConfigureAwait(false); + + await foreach (var result in this.GetResultsAsRecordAsync(searchResponse.Results, cancellationToken).ConfigureAwait(false)) + { + yield return result; + } + } + + #region obsolete + /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<string>> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { - VectorSearchResults<TRecord> searchResponse = await this.ExecuteVectorSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + VectorSearchResults<TRecord> searchResponse = await this.ExecuteVectorSearchAsync(query, searchOptions?.Top ?? TextSearchOptions.DefaultTop, searchOptions, cancellationToken).ConfigureAwait(false); return new KernelSearchResults<string>(this.GetResultsAsStringAsync(searchResponse.Results, cancellationToken), searchResponse.TotalCount, searchResponse.Metadata); } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { - VectorSearchResults<TRecord> searchResponse = await this.ExecuteVectorSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + VectorSearchResults<TRecord> searchResponse = await this.ExecuteVectorSearchAsync(query, searchOptions?.Top ?? TextSearchOptions.DefaultTop, searchOptions, cancellationToken).ConfigureAwait(false); return new KernelSearchResults<TextSearchResult>(this.GetResultsAsTextSearchResultAsync(searchResponse.Results, cancellationToken), searchResponse.TotalCount, searchResponse.Metadata); } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public async Task<KernelSearchResults<object>> GetSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { - VectorSearchResults<TRecord> searchResponse = await this.ExecuteVectorSearchAsync(query, searchOptions, cancellationToken).ConfigureAwait(false); + VectorSearchResults<TRecord> searchResponse = await this.ExecuteVectorSearchAsync(query, searchOptions?.Top ?? TextSearchOptions.DefaultTop, searchOptions, cancellationToken).ConfigureAwait(false); return new KernelSearchResults<object>(this.GetResultsAsRecordAsync(searchResponse.Results, cancellationToken), searchResponse.TotalCount, searchResponse.Metadata); } + #endregion #region private private readonly IVectorizedSearch<TRecord>? _vectorizedSearch; @@ -192,9 +230,10 @@ private TextSearchStringMapper CreateTextSearchStringMapper() /// Execute a vector search and return the results. /// </summary> /// <param name="query">What to search for.</param> + /// <param name="top">Maximum number of search results to return.</param> /// <param name="searchOptions">Search options.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param> - private async Task<VectorSearchResults<TRecord>> ExecuteVectorSearchAsync(string query, TextSearchOptions? searchOptions, CancellationToken cancellationToken) + private async Task<VectorSearchResults<TRecord>> ExecuteVectorSearchAsync(string query, int top, TextSearchOptions? searchOptions, CancellationToken cancellationToken) { searchOptions ??= new TextSearchOptions(); var vectorSearchOptions = new VectorSearchOptions<TRecord> @@ -202,8 +241,8 @@ private async Task<VectorSearchResults<TRecord>> ExecuteVectorSearchAsync(string #pragma warning disable CS0618 // VectorSearchFilter is obsolete OldFilter = searchOptions.Filter?.FilterClauses is not null ? new VectorSearchFilter(searchOptions.Filter.FilterClauses) : null, #pragma warning restore CS0618 // VectorSearchFilter is obsolete + Top = top, Skip = searchOptions.Skip, - Top = searchOptions.Top, }; if (this._vectorizedSearch is not null) diff --git a/dotnet/src/SemanticKernel.UnitTests/Data/MockTextSearch.cs b/dotnet/src/SemanticKernel.UnitTests/Data/MockTextSearch.cs index 916b158fc770..6a2f00b1d136 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Data/MockTextSearch.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Data/MockTextSearch.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -13,6 +15,31 @@ namespace SemanticKernel.UnitTests.Data; internal sealed class MockTextSearch(int count = 3, long totalCount = 30) : ITextSearch { /// <inheritdoc/> + public IAsyncEnumerable<object> GetSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) + { + var results = Enumerable.Range(1, top).Select(i => new MySearchResult($"Name {i}", $"Result {i}", $"http://example.com/page{i}")).ToList(); + return results.ToAsyncEnumerable<object>(); + } + + /// <inheritdoc/> + public IAsyncEnumerable<TextSearchResult> GetTextSearchResultsAsync(string query, int top, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) + { + var results = Enumerable.Range(1, top).Select( + i => new TextSearchResult($"Result {i}") { Name = $"Name {i}", Link = $"http://example.com/page{i}" }) + .ToList(); + return results.ToAsyncEnumerable(); + } + + /// <inheritdoc/> + public IAsyncEnumerable<string> SearchAsync(string query, int top, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) + { + var results = Enumerable.Range(1, top).Select(i => $"Result {i}").ToList(); + return results.ToAsyncEnumerable(); + } + + #region obsolete + /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public Task<KernelSearchResults<object>> GetSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { int count = searchOptions?.Top ?? this._count; @@ -22,6 +49,7 @@ public Task<KernelSearchResults<object>> GetSearchResultsAsync(string query, Tex } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { int count = searchOptions?.Top ?? this._count; @@ -33,6 +61,7 @@ public Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsync(str } /// <inheritdoc/> + [Obsolete("This method is deprecated and will be removed in future versions. Use SearchAsync that returns IAsyncEnumerable<T> instead.", false)] public Task<KernelSearchResults<string>> SearchAsync(string query, TextSearchOptions? searchOptions = null, CancellationToken cancellationToken = default) { int count = searchOptions?.Top ?? this._count; @@ -40,6 +69,7 @@ public Task<KernelSearchResults<string>> SearchAsync(string query, TextSearchOpt long? totalCount = searchOptions?.IncludeTotalCount ?? false ? this._totalCount : null; return Task.FromResult(new KernelSearchResults<string>(results.ToAsyncEnumerable(), totalCount)); } + #endregion #region private private readonly int _count = count; diff --git a/dotnet/src/SemanticKernel.UnitTests/Data/TextSearchExtensionsTests.cs b/dotnet/src/SemanticKernel.UnitTests/Data/TextSearchExtensionsTests.cs index 6c339579befc..60427474607a 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Data/TextSearchExtensionsTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Data/TextSearchExtensionsTests.cs @@ -20,40 +20,40 @@ public class TextSearchExtensionsTests public static TheoryData<KernelPlugin> StandardPlugins => new() { - { TextSearch.CreateWithSearch("SearchPlugin") }, - { TextSearch.CreateWithSearch("SearchPlugin", JsonSerializerOptions) }, - { TextSearch.CreateWithGetTextSearchResults("SearchPlugin") }, - { TextSearch.CreateWithGetTextSearchResults("SearchPlugin", JsonSerializerOptions) }, - { TextSearch.CreateWithGetSearchResults("SearchPlugin") }, - { TextSearch.CreateWithGetSearchResults("SearchPlugin", JsonSerializerOptions) }, + { TextSearch.CreateWithSearch(5, "SearchPlugin") }, + { TextSearch.CreateWithSearch(5, "SearchPlugin", JsonSerializerOptions) }, + { TextSearch.CreateWithGetTextSearchResults(5, "SearchPlugin") }, + { TextSearch.CreateWithGetTextSearchResults(5, "SearchPlugin", JsonSerializerOptions) }, + { TextSearch.CreateWithGetSearchResults(5, "SearchPlugin") }, + { TextSearch.CreateWithGetSearchResults(5, "SearchPlugin", JsonSerializerOptions) }, }; public static TheoryData<KernelFunction, string> StandardFunctions => new() { - { TextSearch.CreateSearch(), "Search" }, - { TextSearch.CreateSearch(JsonSerializerOptions), "Search" }, - { TextSearch.CreateGetTextSearchResults(), "GetTextSearchResults" }, - { TextSearch.CreateGetTextSearchResults(JsonSerializerOptions), "GetTextSearchResults" }, - { TextSearch.CreateGetSearchResults(), "GetSearchResults" }, - { TextSearch.CreateGetSearchResults(JsonSerializerOptions), "GetSearchResults" }, + { TextSearch.CreateSearch(5), "Search" }, + { TextSearch.CreateSearch(5, JsonSerializerOptions), "Search" }, + { TextSearch.CreateGetTextSearchResults(5), "GetTextSearchResults" }, + { TextSearch.CreateGetTextSearchResults(5, JsonSerializerOptions), "GetTextSearchResults" }, + { TextSearch.CreateGetSearchResults(5), "GetSearchResults" }, + { TextSearch.CreateGetSearchResults(5, JsonSerializerOptions), "GetSearchResults" }, }; public static TheoryData<KernelFunction> CustomFunctions => new() { - { TextSearch.CreateSearch(CustomSearchMethodOptions()) }, - { TextSearch.CreateSearch(JsonSerializerOptions, CustomSearchMethodOptions()) }, - { TextSearch.CreateGetTextSearchResults(CustomSearchMethodOptions()) }, - { TextSearch.CreateGetTextSearchResults(JsonSerializerOptions, CustomSearchMethodOptions()) }, - { TextSearch.CreateGetSearchResults(CustomSearchMethodOptions()) }, - { TextSearch.CreateGetSearchResults(JsonSerializerOptions, CustomSearchMethodOptions()) }, + { TextSearch.CreateSearch(5, CustomSearchMethodOptions()) }, + { TextSearch.CreateSearch(5, JsonSerializerOptions, CustomSearchMethodOptions()) }, + { TextSearch.CreateGetTextSearchResults(5, CustomSearchMethodOptions()) }, + { TextSearch.CreateGetTextSearchResults(5, JsonSerializerOptions, CustomSearchMethodOptions()) }, + { TextSearch.CreateGetSearchResults(5, CustomSearchMethodOptions()) }, + { TextSearch.CreateGetSearchResults(5, JsonSerializerOptions, CustomSearchMethodOptions()) }, }; public static TheoryData<KernelFunction, int> FunctionsWithCount => new() { - { TextSearch.CreateSearch(searchOptions: new() { Top = 10 }), 10 }, - { TextSearch.CreateSearch(JsonSerializerOptions, searchOptions: new() { Top = 10 }), 10 }, - { TextSearch.CreateGetTextSearchResults(searchOptions: new() { Top = 10 }), 10 }, - { TextSearch.CreateGetTextSearchResults(JsonSerializerOptions, searchOptions: new() { Top = 10 }), 10 }, - { TextSearch.CreateGetSearchResults(searchOptions: new() { Top = 10 }), 10 }, - { TextSearch.CreateGetSearchResults(JsonSerializerOptions, searchOptions: new() { Top = 10 }), 10 }, + { TextSearch.CreateSearch(top: 10), 10 }, + { TextSearch.CreateSearch(10, JsonSerializerOptions), 10 }, + { TextSearch.CreateGetTextSearchResults(10), 10 }, + { TextSearch.CreateGetTextSearchResults(10, JsonSerializerOptions), 10 }, + { TextSearch.CreateGetSearchResults(10), 10 }, + { TextSearch.CreateGetSearchResults(10, JsonSerializerOptions), 10 }, }; [Theory] @@ -163,7 +163,7 @@ private static KernelFunctionFromMethodOptions CustomSearchMethodOptions() => new KernelParameterMetadata("query") { Description = "What to search for", IsRequired = true }, new KernelParameterMetadata("custom") { Description = "Some custom parameter", IsRequired = true }, ], - ReturnParameter = new() { ParameterType = typeof(KernelSearchResults<string>) }, + ReturnParameter = new() { ParameterType = typeof(List<string>) }, }; #endregion } diff --git a/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs b/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs index dcb2d310eda7..7c91bdc2beba 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Data/VectorStoreTextSearchTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.SemanticKernel.Connectors.InMemory; @@ -49,8 +50,8 @@ public async Task CanSearchWithVectorizedSearchAsync() var sut = await CreateVectorStoreTextSearchFromVectorizedSearchAsync(); // Act. - KernelSearchResults<string> searchResults = await sut.SearchAsync("What is the Semantic Kernel?", new() { Top = 2, Skip = 0 }); - var results = await searchResults.Results.ToListAsync(); + IAsyncEnumerable<string> searchResults = sut.SearchAsync("What is the Semantic Kernel?", 2, new() { Skip = 0 }); + var results = await searchResults.ToListAsync(); Assert.Equal(2, results.Count); } @@ -62,8 +63,8 @@ public async Task CanGetTextSearchResultsWithVectorizedSearchAsync() var sut = await CreateVectorStoreTextSearchFromVectorizedSearchAsync(); // Act. - KernelSearchResults<TextSearchResult> searchResults = await sut.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 2, Skip = 0 }); - var results = await searchResults.Results.ToListAsync(); + IAsyncEnumerable<TextSearchResult> searchResults = sut.GetTextSearchResultsAsync("What is the Semantic Kernel?", 2, new() { Skip = 0 }); + var results = await searchResults.ToListAsync(); Assert.Equal(2, results.Count); } @@ -75,8 +76,8 @@ public async Task CanGetSearchResultsWithVectorizedSearchAsync() var sut = await CreateVectorStoreTextSearchFromVectorizedSearchAsync(); // Act. - KernelSearchResults<object> searchResults = await sut.GetSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 2, Skip = 0 }); - var results = await searchResults.Results.ToListAsync(); + IAsyncEnumerable<object> searchResults = sut.GetSearchResultsAsync("What is the Semantic Kernel?", 2, new() { Skip = 0 }); + var results = await searchResults.ToListAsync(); Assert.Equal(2, results.Count); } @@ -88,8 +89,8 @@ public async Task CanSearchWithVectorizableTextSearchAsync() var sut = await CreateVectorStoreTextSearchFromVectorizableTextSearchAsync(); // Act. - KernelSearchResults<string> searchResults = await sut.SearchAsync("What is the Semantic Kernel?", new() { Top = 2, Skip = 0 }); - var results = await searchResults.Results.ToListAsync(); + IAsyncEnumerable<string> searchResults = sut.SearchAsync("What is the Semantic Kernel?", 2, new() { Skip = 0 }); + var results = await searchResults.ToListAsync(); Assert.Equal(2, results.Count); } @@ -101,8 +102,8 @@ public async Task CanGetTextSearchResultsWithVectorizableTextSearchAsync() var sut = await CreateVectorStoreTextSearchFromVectorizableTextSearchAsync(); // Act. - KernelSearchResults<TextSearchResult> searchResults = await sut.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 2, Skip = 0 }); - var results = await searchResults.Results.ToListAsync(); + IAsyncEnumerable<TextSearchResult> searchResults = sut.GetTextSearchResultsAsync("What is the Semantic Kernel?", 2, new() { Skip = 0 }); + var results = await searchResults.ToListAsync(); Assert.Equal(2, results.Count); } @@ -114,8 +115,8 @@ public async Task CanGetSearchResultsWithVectorizableTextSearchAsync() var sut = await CreateVectorStoreTextSearchFromVectorizableTextSearchAsync(); // Act. - KernelSearchResults<object> searchResults = await sut.GetSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 2, Skip = 0 }); - var results = await searchResults.Results.ToListAsync(); + IAsyncEnumerable<object> searchResults = sut.GetSearchResultsAsync("What is the Semantic Kernel?", 2, new() { Skip = 0 }); + var results = await searchResults.ToListAsync(); Assert.Equal(2, results.Count); } @@ -131,20 +132,18 @@ public async Task CanFilterGetSearchResultsWithVectorizedSearchAsync() oddFilter.Equality("Tag", "Odd"); // Act. - KernelSearchResults<object> evenSearchResults = await sut.GetSearchResultsAsync("What is the Semantic Kernel?", new() + IAsyncEnumerable<object> evenSearchResults = sut.GetSearchResultsAsync("What is the Semantic Kernel?", 2, new() { - Top = 2, Skip = 0, Filter = evenFilter }); - var evenResults = await evenSearchResults.Results.ToListAsync(); - KernelSearchResults<object> oddSearchResults = await sut.GetSearchResultsAsync("What is the Semantic Kernel?", new() + var evenResults = await evenSearchResults.ToListAsync(); + IAsyncEnumerable<object> oddSearchResults = sut.GetSearchResultsAsync("What is the Semantic Kernel?", 2, new() { - Top = 2, Skip = 0, Filter = oddFilter }); - var oddResults = await oddSearchResults.Results.ToListAsync(); + var oddResults = await oddSearchResults.ToListAsync(); Assert.Equal(2, evenResults.Count); var result1 = evenResults[0] as DataModel;