chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel.Plugins.Web;
|
||||
using Microsoft.SemanticKernel.Plugins.Web.Bing;
|
||||
using Microsoft.SemanticKernel.Plugins.Web.Google;
|
||||
|
||||
namespace Search;
|
||||
|
||||
/// <summary>
|
||||
/// The example shows how to use Bing and Google to search for current data
|
||||
/// you might want to import into your system, e.g. providing AI prompts with
|
||||
/// recent information, or for AI to generate recent information to display to users.
|
||||
/// </summary>
|
||||
public class BingAndGooglePlugins(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact(Skip = "Setup Credentials")]
|
||||
public async Task RunAsync()
|
||||
{
|
||||
string openAIModelId = TestConfiguration.OpenAI.ChatModelId;
|
||||
string openAIApiKey = TestConfiguration.OpenAI.ApiKey;
|
||||
|
||||
if (openAIModelId is null || openAIApiKey is null)
|
||||
{
|
||||
Console.WriteLine("OpenAI credentials not found. Skipping example.");
|
||||
return;
|
||||
}
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddOpenAIChatCompletion(
|
||||
modelId: openAIModelId,
|
||||
apiKey: openAIApiKey)
|
||||
.Build();
|
||||
|
||||
// Load Bing plugin
|
||||
string bingApiKey = TestConfiguration.Bing.ApiKey;
|
||||
if (bingApiKey is null)
|
||||
{
|
||||
Console.WriteLine("Bing credentials not found. Skipping example.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var bingConnector = new BingConnector(bingApiKey);
|
||||
var bing = new WebSearchEnginePlugin(bingConnector);
|
||||
kernel.ImportPluginFromObject(bing, "bing");
|
||||
await Example1Async(kernel, "bing");
|
||||
await Example2Async(kernel);
|
||||
}
|
||||
|
||||
// Load Google plugin
|
||||
string googleApiKey = TestConfiguration.Google.ApiKey;
|
||||
string googleSearchEngineId = TestConfiguration.Google.SearchEngineId;
|
||||
|
||||
if (googleApiKey is null || googleSearchEngineId is null)
|
||||
{
|
||||
Console.WriteLine("Google credentials not found. Skipping example.");
|
||||
}
|
||||
else
|
||||
{
|
||||
using var googleConnector = new GoogleConnector(
|
||||
apiKey: googleApiKey,
|
||||
searchEngineId: googleSearchEngineId);
|
||||
var google = new WebSearchEnginePlugin(googleConnector);
|
||||
kernel.ImportPluginFromObject(new WebSearchEnginePlugin(googleConnector), "google");
|
||||
// ReSharper disable once ArrangeThisQualifier
|
||||
await Example1Async(kernel, "google");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Example1Async(Kernel kernel, string searchPluginName)
|
||||
{
|
||||
Console.WriteLine("======== Bing and Google Search Plugins ========");
|
||||
|
||||
// Run
|
||||
var question = "What's the largest building in the world?";
|
||||
var function = kernel.Plugins[searchPluginName]["search"];
|
||||
var result = await kernel.InvokeAsync(function, new() { ["query"] = question });
|
||||
|
||||
Console.WriteLine(question);
|
||||
Console.WriteLine($"----{searchPluginName}----");
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
|
||||
/* OUTPUT:
|
||||
|
||||
What's the largest building in the world?
|
||||
----
|
||||
The Aerium near Berlin, Germany is the largest uninterrupted volume in the world, while Boeing's
|
||||
factory in Everett, Washington, United States is the world's largest building by volume. The AvtoVAZ
|
||||
main assembly building in Tolyatti, Russia is the largest building in area footprint.
|
||||
----
|
||||
The Aerium near Berlin, Germany is the largest uninterrupted volume in the world, while Boeing's
|
||||
factory in Everett, Washington, United States is the world's ...
|
||||
*/
|
||||
}
|
||||
|
||||
private async Task Example2Async(Kernel kernel)
|
||||
{
|
||||
Console.WriteLine("======== Use Search Plugin to answer user questions ========");
|
||||
|
||||
const string SemanticFunction = """
|
||||
Answer questions only when you know the facts or the information is provided.
|
||||
When you don't have sufficient information you reply with a list of commands to find the information needed.
|
||||
When answering multiple questions, use a bullet point list.
|
||||
Note: make sure single and double quotes are escaped using a backslash char.
|
||||
|
||||
[COMMANDS AVAILABLE]
|
||||
- bing.search
|
||||
|
||||
[INFORMATION PROVIDED]
|
||||
{{ $externalInformation }}
|
||||
|
||||
[EXAMPLE 1]
|
||||
Question: what's the biggest lake in Italy?
|
||||
Answer: Lake Garda, also known as Lago di Garda.
|
||||
|
||||
[EXAMPLE 2]
|
||||
Question: what's the biggest lake in Italy? What's the smallest positive number?
|
||||
Answer:
|
||||
* Lake Garda, also known as Lago di Garda.
|
||||
* The smallest positive number is 1.
|
||||
|
||||
[EXAMPLE 3]
|
||||
Question: what's Ferrari stock price? Who is the current number one female tennis player in the world?
|
||||
Answer:
|
||||
{{ '{{' }} bing.search "what\\'s Ferrari stock price?" {{ '}}' }}.
|
||||
{{ '{{' }} bing.search "Who is the current number one female tennis player in the world?" {{ '}}' }}.
|
||||
|
||||
[END OF EXAMPLES]
|
||||
|
||||
[TASK]
|
||||
Question: {{ $question }}.
|
||||
Answer:
|
||||
""";
|
||||
|
||||
var question = "Who is the most followed person on TikTok right now? What's the exchange rate EUR:USD?";
|
||||
Console.WriteLine(question);
|
||||
|
||||
var oracle = kernel.CreateFunctionFromPrompt(SemanticFunction, new OpenAIPromptExecutionSettings() { MaxTokens = 150, Temperature = 0, TopP = 1 });
|
||||
|
||||
var answer = await kernel.InvokeAsync(oracle, new KernelArguments()
|
||||
{
|
||||
["question"] = question,
|
||||
["externalInformation"] = string.Empty
|
||||
});
|
||||
|
||||
var result = answer.GetValue<string>()!;
|
||||
|
||||
// If the answer contains commands, execute them using the prompt renderer.
|
||||
if (result.Contains("bing.search", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var promptTemplateFactory = new KernelPromptTemplateFactory();
|
||||
var promptTemplate = promptTemplateFactory.Create(new PromptTemplateConfig(result));
|
||||
|
||||
Console.WriteLine("---- Fetching information from Bing...");
|
||||
var information = await promptTemplate.RenderAsync(kernel);
|
||||
|
||||
Console.WriteLine("Information found:");
|
||||
Console.WriteLine(information);
|
||||
|
||||
// Run the prompt function again, now including information from Bing
|
||||
answer = await kernel.InvokeAsync(oracle, new KernelArguments()
|
||||
{
|
||||
["question"] = question,
|
||||
// The rendered prompt contains the information retrieved from search engines
|
||||
["externalInformation"] = information
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("AI had all the information, no need to query Bing.");
|
||||
}
|
||||
|
||||
Console.WriteLine("---- ANSWER:");
|
||||
Console.WriteLine(answer.GetValue<string>());
|
||||
|
||||
/* OUTPUT:
|
||||
|
||||
Who is the most followed person on TikTok right now? What's the exchange rate EUR:USD?
|
||||
---- Fetching information from Bing...
|
||||
Information found:
|
||||
|
||||
Khaby Lame is the most-followed user on TikTok. This list contains the top 50 accounts by number
|
||||
of followers on the Chinese social media platform TikTok, which was merged with musical.ly in 2018.
|
||||
[1] The most-followed individual on the platform is Khaby Lame, with over 153 million followers..
|
||||
EUR – Euro To USD – US Dollar 1.00 Euro = 1.10 37097 US Dollars 1 USD = 0.906035 EUR We use the
|
||||
mid-market rate for our Converter. This is for informational purposes only. You won’t receive this
|
||||
rate when sending money. Check send rates Convert Euro to US Dollar Convert US Dollar to Euro..
|
||||
---- ANSWER:
|
||||
|
||||
* The most followed person on TikTok right now is Khaby Lame, with over 153 million followers.
|
||||
* The exchange rate for EUR to USD is 1.1037097 US Dollars for 1 Euro.
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel.Data;
|
||||
using Microsoft.SemanticKernel.Plugins.Web.Bing;
|
||||
|
||||
namespace Search;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to perform function calling with an <see cref="ITextSearch"/>.
|
||||
/// </summary>
|
||||
public class Bing_FunctionCallingWithTextSearch(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Show how to create a default <see cref="KernelPlugin"/> from an <see cref="BingTextSearch"/> and use it with
|
||||
/// function calling to have the LLM include grounding context in it's response.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task FunctionCallingWithBingTextSearchAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
|
||||
// Create a search service with Bing search
|
||||
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");
|
||||
kernel.Plugins.Add(searchPlugin);
|
||||
|
||||
// Invoke prompt and use text search plugin to provide grounding information
|
||||
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
KernelArguments arguments = new(settings);
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("What is the Semantic Kernel? Search for 5 references.", arguments));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a default <see cref="KernelPlugin"/> from an <see cref="BingTextSearch"/> and use it with
|
||||
/// function calling and have the LLM include links in the final response.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task FunctionCallingWithBingTextSearchIncludingCitationsAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
|
||||
// Create a search service with Bing search
|
||||
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");
|
||||
kernel.Plugins.Add(searchPlugin);
|
||||
|
||||
// Invoke prompt and use text search plugin to provide grounding information
|
||||
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
KernelArguments arguments = new(settings);
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("What is the Semantic Kernel? Include citations to the relevant information where it is referenced in the response.", arguments));
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage
|
||||
/// <summary>
|
||||
/// Show how to create a default <see cref="KernelPlugin"/> from an <see cref="BingTextSearch"/> and use it with
|
||||
/// function calling to have the LLM include grounding context from the Microsoft Dev Blogs site in it's response.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task FunctionCallingWithBingTextSearchUsingDevBlogsSiteAsync()
|
||||
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
|
||||
// Create a search service with Bing search
|
||||
var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey));
|
||||
|
||||
// Build a text search plugin with Bing search and add to the kernel
|
||||
var filter = new TextSearchFilter().Equality("site", "devblogs.microsoft.com");
|
||||
var searchOptions = new TextSearchOptions() { Filter = filter };
|
||||
var searchPlugin = KernelPluginFactory.CreateFromFunctions(
|
||||
"SearchPlugin", "Search Microsoft Developer Blogs site only",
|
||||
[textSearch.CreateGetTextSearchResults(searchOptions: searchOptions)]);
|
||||
kernel.Plugins.Add(searchPlugin);
|
||||
|
||||
// Invoke prompt and use text search plugin to provide grounding information
|
||||
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
KernelArguments arguments = new(settings);
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("What is the Semantic Kernel? Include citations to the relevant information where it is referenced in the response.", arguments));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a default <see cref="KernelPlugin"/> from an <see cref="BingTextSearch"/> and use it with
|
||||
/// function calling to have the LLM include grounding context from the Microsoft Dev Blogs site in it's response.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task FunctionCallingWithBingTextSearchUsingSiteArgumentAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
|
||||
// Create a search service with Bing search
|
||||
var textSearch = new BingTextSearch(new(TestConfiguration.Bing.ApiKey));
|
||||
|
||||
// Build a text search plugin with Bing search and add to the kernel
|
||||
var searchPlugin = KernelPluginFactory.CreateFromFunctions("SearchPlugin", "Search specified site", [CreateSearchBySite(textSearch)]);
|
||||
kernel.Plugins.Add(searchPlugin);
|
||||
|
||||
// Invoke prompt and use text search plugin to provide grounding information
|
||||
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
KernelArguments arguments = new(settings);
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("What is the Semantic Kernel? Only include results from techcommunity.microsoft.com. Include citations to the relevant information where it is referenced in the response.", arguments));
|
||||
}
|
||||
|
||||
private static KernelFunction CreateSearchBySite(BingTextSearch textSearch, TextSearchFilter? filter = null)
|
||||
{
|
||||
var options = new KernelFunctionFromMethodOptions()
|
||||
{
|
||||
FunctionName = "Search",
|
||||
Description = "Perform a search for content related to the specified query and optionally from the specified domain.",
|
||||
Parameters =
|
||||
[
|
||||
new KernelParameterMetadata("query") { Description = "What to search for", IsRequired = true },
|
||||
new KernelParameterMetadata("top") { Description = "Number of results", IsRequired = false, DefaultValue = 5 },
|
||||
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>) },
|
||||
};
|
||||
|
||||
return textSearch.CreateSearch(options);
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Microsoft.SemanticKernel.Data;
|
||||
using Microsoft.SemanticKernel.Plugins.Web.Bing;
|
||||
|
||||
namespace Search;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to create and use a <see cref="BingTextSearch"/>.
|
||||
/// </summary>
|
||||
public class Bing_TextSearch(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="BingTextSearch"/> and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingBingTextSearchAsync()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch instance using Bing search
|
||||
var textSearch = new BingTextSearch(apiKey: TestConfiguration.Bing.ApiKey, options: new() { HttpClient = httpClient });
|
||||
|
||||
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 });
|
||||
Console.WriteLine("--- String Results ---\n");
|
||||
await foreach (string result in stringResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Search and return results as TextSearchResult items
|
||||
KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 4, Skip = 4 });
|
||||
Console.WriteLine("\n--- Text Search Results ---\n");
|
||||
await foreach (TextSearchResult result in textResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Name: {result.Name}");
|
||||
Console.WriteLine($"Value: {result.Value}");
|
||||
Console.WriteLine($"Link: {result.Link}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Search and return s results as BingWebPage items
|
||||
KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 4, Skip = 8 });
|
||||
Console.WriteLine("\n--- Bing Web Page Results ---\n");
|
||||
await foreach (BingWebPage result in fullResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Name: {result.Name}");
|
||||
Console.WriteLine($"Snippet: {result.Snippet}");
|
||||
Console.WriteLine($"Url: {result.Url}");
|
||||
Console.WriteLine($"DisplayUrl: {result.DisplayUrl}");
|
||||
Console.WriteLine($"DateLastCrawled: {result.DateLastCrawled}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="BingTextSearch"/> with a custom mapper and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingBingTextSearchWithACustomMapperAsync()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch instance using Bing search
|
||||
var textSearch = new BingTextSearch(apiKey: TestConfiguration.Bing.ApiKey, options: new()
|
||||
{
|
||||
HttpClient = httpClient,
|
||||
StringMapper = new TestTextSearchStringMapper(),
|
||||
});
|
||||
|
||||
var query = "What is the Semantic Kernel?";
|
||||
|
||||
// Search with TextSearchResult textResult type
|
||||
KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 2, Skip = 0 });
|
||||
Console.WriteLine("--- Serialized JSON Results ---");
|
||||
await foreach (string result in stringResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="BingTextSearch"/> with a custom mapper and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingBingTextSearchWithASiteFilterAsync()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch instance using Bing search
|
||||
var textSearch = new BingTextSearch(apiKey: TestConfiguration.Bing.ApiKey, options: new()
|
||||
{
|
||||
HttpClient = httpClient,
|
||||
StringMapper = new TestTextSearchStringMapper(),
|
||||
});
|
||||
|
||||
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);
|
||||
Console.WriteLine("--- Microsoft Developer Blogs Results ---");
|
||||
await foreach (TextSearchResult result in textResults.Results)
|
||||
{
|
||||
Console.WriteLine(result.Link);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
/// <summary>
|
||||
/// Show how to use enhanced LINQ filtering with BingTextSearch for type-safe searches.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingBingTextSearchWithLinqFilteringAsync()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch<BingWebPage> instance for type-safe LINQ filtering
|
||||
ITextSearch<BingWebPage> textSearch = new BingTextSearch(apiKey: TestConfiguration.Bing.ApiKey, options: new() { HttpClient = httpClient });
|
||||
|
||||
var query = "Semantic Kernel AI";
|
||||
|
||||
// Example 1: Filter by language (English only)
|
||||
Console.WriteLine("——— Example 1: Language Filter (English) ———\n");
|
||||
var languageOptions = new TextSearchOptions<BingWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Filter = page => page.Language == "en"
|
||||
};
|
||||
var languageResults = await textSearch.SearchAsync(query, languageOptions);
|
||||
await foreach (string result in languageResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Example 2: Filter by family-friendly content
|
||||
Console.WriteLine("\n——— Example 2: Family Friendly Filter ———\n");
|
||||
var familyFriendlyOptions = new TextSearchOptions<BingWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Filter = page => page.IsFamilyFriendly == true
|
||||
};
|
||||
var familyFriendlyResults = await textSearch.SearchAsync(query, familyFriendlyOptions);
|
||||
await foreach (string result in familyFriendlyResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Example 3: Compound AND filtering (language + family-friendly)
|
||||
Console.WriteLine("\n——— Example 3: Compound Filter (English + Family Friendly) ———\n");
|
||||
var compoundOptions = new TextSearchOptions<BingWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Filter = page => page.Language == "en" && page.IsFamilyFriendly == true
|
||||
};
|
||||
var compoundResults = await textSearch.GetSearchResultsAsync(query, compoundOptions);
|
||||
await foreach (BingWebPage page in compoundResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Name: {page.Name}");
|
||||
Console.WriteLine($"Snippet: {page.Snippet}");
|
||||
Console.WriteLine($"Language: {page.Language}");
|
||||
Console.WriteLine($"Family Friendly: {page.IsFamilyFriendly}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Example 4: Complex compound filtering with nullable checks
|
||||
Console.WriteLine("\n——— Example 4: Complex Compound Filter (Language + Site + Family Friendly) ———\n");
|
||||
var complexOptions = new TextSearchOptions<BingWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Filter = page => page.Language == "en" &&
|
||||
page.IsFamilyFriendly == true &&
|
||||
page.DisplayUrl != null && page.DisplayUrl.Contains("microsoft")
|
||||
};
|
||||
var complexResults = await textSearch.GetSearchResultsAsync(query, complexOptions);
|
||||
await foreach (BingWebPage page in complexResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Name: {page.Name}");
|
||||
Console.WriteLine($"Display URL: {page.DisplayUrl}");
|
||||
Console.WriteLine($"Language: {page.Language}");
|
||||
Console.WriteLine($"Family Friendly: {page.IsFamilyFriendly}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
|
||||
#region private
|
||||
/// <summary>
|
||||
/// Test mapper which converts an arbitrary search result to a string using JSON serialization.
|
||||
/// </summary>
|
||||
private sealed class TestTextSearchStringMapper : ITextSearchStringMapper
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string MapFromResultToString(object result)
|
||||
{
|
||||
return JsonSerializer.Serialize(result);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Google.Apis.Http;
|
||||
using Microsoft.SemanticKernel.Data;
|
||||
using Microsoft.SemanticKernel.Plugins.Web.Google;
|
||||
|
||||
namespace Search;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to create and use a <see cref="GoogleTextSearch"/>.
|
||||
/// </summary>
|
||||
public class Google_TextSearch(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="GoogleTextSearch"/> and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingGoogleTextSearchAsync()
|
||||
{
|
||||
// Create an ITextSearch instance using Google search
|
||||
var textSearch = new GoogleTextSearch(
|
||||
initializer: new() { ApiKey = TestConfiguration.Google.ApiKey, HttpClientFactory = new CustomHttpClientFactory(this.Output) },
|
||||
searchEngineId: TestConfiguration.Google.SearchEngineId);
|
||||
|
||||
var query = "What is the Semantic Kernel?";
|
||||
|
||||
// Search and return results as string items
|
||||
KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new TextSearchOptions { Top = 4, Skip = 0 });
|
||||
Console.WriteLine("——— String Results ———\n");
|
||||
await foreach (string result in stringResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine(new string('—', HorizontalRuleLength));
|
||||
}
|
||||
|
||||
// Search and return results as TextSearchResult items
|
||||
KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new TextSearchOptions { Top = 4, Skip = 4 });
|
||||
Console.WriteLine("\n——— Text Search Results ———\n");
|
||||
await foreach (TextSearchResult result in textResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Name: {result.Name}");
|
||||
Console.WriteLine($"Value: {result.Value}");
|
||||
Console.WriteLine($"Link: {result.Link}");
|
||||
Console.WriteLine(new string('—', HorizontalRuleLength));
|
||||
}
|
||||
|
||||
// Search and return results as Google.Apis.CustomSearchAPI.v1.Data.Result items
|
||||
KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new TextSearchOptions { Top = 4, Skip = 8 });
|
||||
Console.WriteLine("\n——— Google Web Page Results ———\n");
|
||||
await foreach (Google.Apis.CustomSearchAPI.v1.Data.Result result in fullResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Title: {result.Title}");
|
||||
Console.WriteLine($"Snippet: {result.Snippet}");
|
||||
Console.WriteLine($"Link: {result.Link}");
|
||||
Console.WriteLine($"DisplayLink: {result.DisplayLink}");
|
||||
Console.WriteLine($"Kind: {result.Kind}");
|
||||
Console.WriteLine(new string('—', HorizontalRuleLength));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="GoogleTextSearch"/> with a custom mapper and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingGoogleTextSearchWithACustomMapperAsync()
|
||||
{
|
||||
// Create an ITextSearch instance using Google search
|
||||
var textSearch = new GoogleTextSearch(
|
||||
searchEngineId: TestConfiguration.Google.SearchEngineId,
|
||||
apiKey: TestConfiguration.Google.ApiKey,
|
||||
options: new() { StringMapper = new TestTextSearchStringMapper() });
|
||||
|
||||
var query = "What is the Semantic Kernel?";
|
||||
|
||||
// Search with TextSearchResult textResult type
|
||||
KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new TextSearchOptions { Top = 2, Skip = 0 });
|
||||
Console.WriteLine("--- Serialized JSON Results ---");
|
||||
await foreach (string result in stringResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine(new string('-', HorizontalRuleLength));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="GoogleTextSearch"/> with a custom mapper and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingGoogleTextSearchWithASiteSearchFilterAsync()
|
||||
{
|
||||
// Create an ITextSearch instance using Google search
|
||||
var textSearch = new GoogleTextSearch(
|
||||
initializer: new() { ApiKey = TestConfiguration.Google.ApiKey, HttpClientFactory = new CustomHttpClientFactory(this.Output) },
|
||||
searchEngineId: TestConfiguration.Google.SearchEngineId);
|
||||
|
||||
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);
|
||||
Console.WriteLine("--- Microsoft Developer Blogs Results ---");
|
||||
await foreach (TextSearchResult result in textResults.Results)
|
||||
{
|
||||
Console.WriteLine(result.Link);
|
||||
Console.WriteLine(new string('-', HorizontalRuleLength));
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
/// <summary>
|
||||
/// Show how to use enhanced LINQ filtering with GoogleTextSearch including Contains, NOT, FileType, and compound AND expressions.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingGoogleTextSearchWithEnhancedLinqFilteringAsync()
|
||||
{
|
||||
// Create an ITextSearch<GoogleWebPage> instance using Google search
|
||||
var textSearch = new GoogleTextSearch(
|
||||
initializer: new() { ApiKey = TestConfiguration.Google.ApiKey, HttpClientFactory = new CustomHttpClientFactory(this.Output) },
|
||||
searchEngineId: TestConfiguration.Google.SearchEngineId);
|
||||
|
||||
var query = "Semantic Kernel AI";
|
||||
|
||||
// Example 1: Simple equality filtering
|
||||
Console.WriteLine("——— Example 1: Equality Filter (DisplayLink) ———\n");
|
||||
var equalityOptions = new TextSearchOptions<GoogleWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Skip = 0,
|
||||
Filter = page => page.DisplayLink == "microsoft.com"
|
||||
};
|
||||
var equalityResults = await textSearch.SearchAsync(query, equalityOptions);
|
||||
await foreach (string result in equalityResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine(new string('—', HorizontalRuleLength));
|
||||
}
|
||||
|
||||
// Example 2: Contains filtering
|
||||
Console.WriteLine("\n——— Example 2: Contains Filter (Title) ———\n");
|
||||
var containsOptions = new TextSearchOptions<GoogleWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Skip = 0,
|
||||
Filter = page => page.Title != null && page.Title.Contains("AI")
|
||||
};
|
||||
var containsResults = await textSearch.SearchAsync(query, containsOptions);
|
||||
await foreach (string result in containsResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine(new string('—', HorizontalRuleLength));
|
||||
}
|
||||
|
||||
// Example 3: NOT Contains filtering (exclusion)
|
||||
Console.WriteLine("\n——— Example 3: NOT Contains Filter (Exclude 'deprecated') ———\n");
|
||||
var notContainsOptions = new TextSearchOptions<GoogleWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Skip = 0,
|
||||
Filter = page => page.Title != null && !page.Title.Contains("deprecated")
|
||||
};
|
||||
var notContainsResults = await textSearch.SearchAsync(query, notContainsOptions);
|
||||
await foreach (string result in notContainsResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine(new string('—', HorizontalRuleLength));
|
||||
}
|
||||
|
||||
// Example 4: FileFormat filtering
|
||||
Console.WriteLine("\n——— Example 4: FileFormat Filter (PDF files) ———\n");
|
||||
var fileFormatOptions = new TextSearchOptions<GoogleWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Skip = 0,
|
||||
Filter = page => page.FileFormat == "pdf"
|
||||
};
|
||||
var fileFormatResults = await textSearch.SearchAsync(query, fileFormatOptions);
|
||||
await foreach (string result in fileFormatResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine(new string('—', HorizontalRuleLength));
|
||||
}
|
||||
|
||||
// Example 5: Compound AND filtering (multiple conditions)
|
||||
Console.WriteLine("\n——— Example 5: Compound AND Filter (Title + Site) ———\n");
|
||||
var compoundOptions = new TextSearchOptions<GoogleWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Skip = 0,
|
||||
Filter = page => page.Title != null && page.Title.Contains("Semantic") &&
|
||||
page.DisplayLink != null && page.DisplayLink.Contains("microsoft")
|
||||
};
|
||||
var compoundResults = await textSearch.SearchAsync(query, compoundOptions);
|
||||
await foreach (string result in compoundResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine(new string('—', HorizontalRuleLength));
|
||||
}
|
||||
|
||||
// Example 6: Complex compound filtering (equality + contains + exclusion)
|
||||
Console.WriteLine("\n——— Example 6: Complex Compound Filter (FileFormat + Contains + NOT Contains) ———\n");
|
||||
var complexOptions = new TextSearchOptions<GoogleWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Skip = 0,
|
||||
Filter = page => page.FileFormat == "pdf" &&
|
||||
page.Title != null && page.Title.Contains("AI") &&
|
||||
page.Snippet != null && !page.Snippet.Contains("deprecated")
|
||||
};
|
||||
var complexResults = await textSearch.SearchAsync(query, complexOptions);
|
||||
await foreach (string result in complexResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine(new string('—', HorizontalRuleLength));
|
||||
}
|
||||
}
|
||||
|
||||
#region private
|
||||
private const int HorizontalRuleLength = 80;
|
||||
|
||||
/// <summary>
|
||||
/// Test mapper which converts an arbitrary search result to a string using JSON serialization.
|
||||
/// </summary>
|
||||
private sealed class TestTextSearchStringMapper : ITextSearchStringMapper
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string MapFromResultToString(object result)
|
||||
{
|
||||
return JsonSerializer.Serialize(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ConfigurableMessageHandler"/> which logs HTTP responses.
|
||||
/// </summary>
|
||||
private sealed class LoggingConfigurableMessageHandler(HttpMessageHandler innerHandler, ITestOutputHelper output) : ConfigurableMessageHandler(innerHandler)
|
||||
{
|
||||
private static readonly JsonSerializerOptions s_jsonSerializerOptions = new() { WriteIndented = true };
|
||||
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Log the request details
|
||||
if (request.Content is not null)
|
||||
{
|
||||
var content = await request.Content.ReadAsStringAsync(cancellationToken);
|
||||
this._output.WriteLine("=== REQUEST ===");
|
||||
try
|
||||
{
|
||||
string formattedContent = JsonSerializer.Serialize(JsonElement.Parse(content), s_jsonSerializerOptions);
|
||||
this._output.WriteLine(formattedContent);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
this._output.WriteLine(content);
|
||||
}
|
||||
this._output.WriteLine(string.Empty);
|
||||
}
|
||||
|
||||
// Call the next handler in the pipeline
|
||||
var response = await base.SendAsync(request, cancellationToken);
|
||||
|
||||
if (response.Content is not null)
|
||||
{
|
||||
// Log the response details
|
||||
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
this._output.WriteLine("=== RESPONSE ===");
|
||||
this._output.WriteLine(responseContent);
|
||||
this._output.WriteLine(string.Empty);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="Google.Apis.Http.IHttpClientFactory"/> which uses the <see cref="LoggingConfigurableMessageHandler"/>.
|
||||
/// </summary>
|
||||
private sealed class CustomHttpClientFactory(ITestOutputHelper output) : Google.Apis.Http.IHttpClientFactory
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args)
|
||||
{
|
||||
ConfigurableMessageHandler messageHandler = new LoggingConfigurableMessageHandler(new HttpClientHandler(), this._output);
|
||||
var configurableHttpClient = new ConfigurableHttpClient(messageHandler);
|
||||
return configurableHttpClient;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Azure;
|
||||
using Azure.Search.Documents;
|
||||
using Azure.Search.Documents.Indexes;
|
||||
using Azure.Search.Documents.Models;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Embeddings;
|
||||
|
||||
namespace Search;
|
||||
|
||||
public class AzureAISearchPlugin(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows how to register Azure AI Search service as a plugin and work with custom index schema.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task AzureAISearchPluginAsync()
|
||||
{
|
||||
// Azure AI Search configuration
|
||||
Uri endpoint = new(TestConfiguration.AzureAISearch.Endpoint);
|
||||
AzureKeyCredential keyCredential = new(TestConfiguration.AzureAISearch.ApiKey);
|
||||
|
||||
// Create kernel builder
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
|
||||
// SearchIndexClient from Azure .NET SDK to perform search operations.
|
||||
kernelBuilder.Services.AddSingleton<SearchIndexClient>((_) => new SearchIndexClient(endpoint, keyCredential));
|
||||
|
||||
// Custom AzureAISearchService to configure request parameters and make a request.
|
||||
kernelBuilder.Services.AddSingleton<IAzureAISearchService, AzureAISearchService>();
|
||||
|
||||
// Embedding generation service to convert string query to vector
|
||||
kernelBuilder.AddOpenAIEmbeddingGenerator("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
// Chat completion service to ask questions based on data from Azure AI Search index.
|
||||
kernelBuilder.AddOpenAIChatCompletion("gpt-4", TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
// Register Azure AI Search Plugin
|
||||
kernelBuilder.Plugins.AddFromType<MyAzureAISearchPlugin>();
|
||||
|
||||
// Create kernel
|
||||
var kernel = kernelBuilder.Build();
|
||||
|
||||
// Query with index name
|
||||
// The final prompt will look like this "Emily and David are...(more text based on data). Who is David?".
|
||||
var result1 = await kernel.InvokePromptAsync(
|
||||
"{{search 'David' collection='index-1'}} Who is David?");
|
||||
|
||||
Console.WriteLine(result1);
|
||||
|
||||
// Query with index name and search fields.
|
||||
// Search fields are optional. Since one index may contain multiple searchable fields,
|
||||
// it's possible to specify which fields should be used during search for each request.
|
||||
var arguments = new KernelArguments { ["searchFields"] = JsonSerializer.Serialize(new List<string> { "vector" }) };
|
||||
|
||||
// The final prompt will look like this "Elara is...(more text based on data). Who is Elara?".
|
||||
var result2 = await kernel.InvokePromptAsync(
|
||||
"{{search 'Story' collection='index-2' searchFields=$searchFields}} Who is Elara?",
|
||||
arguments);
|
||||
|
||||
Console.WriteLine(result2);
|
||||
}
|
||||
|
||||
#region Index Schema
|
||||
|
||||
/// <summary>
|
||||
/// Custom index schema. It may contain any fields that exist in search index.
|
||||
/// </summary>
|
||||
private sealed class IndexSchema
|
||||
{
|
||||
[JsonPropertyName("chunk_id")]
|
||||
public string ChunkId { get; set; }
|
||||
|
||||
[JsonPropertyName("parent_id")]
|
||||
public string ParentId { get; set; }
|
||||
|
||||
[JsonPropertyName("chunk")]
|
||||
public string Chunk { get; set; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("vector")]
|
||||
public ReadOnlyMemory<float> Vector { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Azure AI Search Service
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction for Azure AI Search service.
|
||||
/// </summary>
|
||||
private interface IAzureAISearchService
|
||||
{
|
||||
Task<string?> SearchAsync(
|
||||
string collectionName,
|
||||
ReadOnlyMemory<float> vector,
|
||||
List<string>? searchFields = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of Azure AI Search service.
|
||||
/// </summary>
|
||||
private sealed class AzureAISearchService(SearchIndexClient indexClient) : IAzureAISearchService
|
||||
{
|
||||
private readonly List<string> _defaultVectorFields = ["vector"];
|
||||
|
||||
private readonly SearchIndexClient _indexClient = indexClient;
|
||||
|
||||
public async Task<string?> SearchAsync(
|
||||
string collectionName,
|
||||
ReadOnlyMemory<float> vector,
|
||||
List<string>? searchFields = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Get client for search operations
|
||||
SearchClient searchClient = this._indexClient.GetSearchClient(collectionName);
|
||||
|
||||
// Use search fields passed from Plugin or default fields configured in this class.
|
||||
List<string> fields = searchFields is { Count: > 0 } ? searchFields : this._defaultVectorFields;
|
||||
|
||||
// Configure request parameters
|
||||
VectorizedQuery vectorQuery = new(vector);
|
||||
fields.ForEach(vectorQuery.Fields.Add);
|
||||
|
||||
SearchOptions searchOptions = new() { VectorSearch = new() { Queries = { vectorQuery } } };
|
||||
|
||||
// Perform search request
|
||||
Response<SearchResults<IndexSchema>> response = await searchClient.SearchAsync<IndexSchema>(searchOptions, cancellationToken);
|
||||
|
||||
List<IndexSchema> results = [];
|
||||
|
||||
// Collect search results
|
||||
await foreach (SearchResult<IndexSchema> result in response.Value.GetResultsAsync())
|
||||
{
|
||||
results.Add(result.Document);
|
||||
}
|
||||
|
||||
// Return text from first result.
|
||||
// In real applications, the logic can check document score, sort and return top N results
|
||||
// or aggregate all results in one text.
|
||||
// The logic and decision which text data to return should be based on business scenario.
|
||||
return results.FirstOrDefault()?.Chunk;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Azure AI Search SK Plugin
|
||||
|
||||
/// <summary>
|
||||
/// Azure AI Search SK Plugin.
|
||||
/// It uses <see cref="ITextEmbeddingGenerationService"/> to convert string query to vector.
|
||||
/// It uses <see cref="IAzureAISearchService"/> to perform a request to Azure AI Search.
|
||||
/// </summary>
|
||||
private sealed class MyAzureAISearchPlugin(
|
||||
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
|
||||
AzureAISearchPlugin.IAzureAISearchService searchService)
|
||||
{
|
||||
private readonly IEmbeddingGenerator<string, Embedding<float>> _embeddingGenerator = embeddingGenerator;
|
||||
private readonly IAzureAISearchService _searchService = searchService;
|
||||
|
||||
[KernelFunction("Search")]
|
||||
public async Task<string> SearchAsync(
|
||||
string query,
|
||||
string collection,
|
||||
List<string>? searchFields = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Convert string query to vector
|
||||
ReadOnlyMemory<float> embedding = (await this._embeddingGenerator.GenerateAsync(query, cancellationToken: cancellationToken)).Vector;
|
||||
|
||||
// Perform search
|
||||
return await this._searchService.SearchAsync(collection, embedding, searchFields, cancellationToken) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Microsoft.SemanticKernel.Data;
|
||||
using Microsoft.SemanticKernel.Plugins.Web.Tavily;
|
||||
|
||||
namespace Search;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to create and use a <see cref="TavilyTextSearch"/>.
|
||||
/// </summary>
|
||||
public class Tavily_TextSearch(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
#pragma warning disable CS0618 // Suppress obsolete warnings for legacy TextSearchOptions/TextSearchFilter usage
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="TavilyTextSearch"/> and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingTavilyTextSearch()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch instance using Tavily search
|
||||
var textSearch = new TavilyTextSearch(apiKey: TestConfiguration.Tavily.ApiKey, options: new() { HttpClient = httpClient, IncludeRawContent = true });
|
||||
|
||||
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 });
|
||||
Console.WriteLine("--- String Results ---\n");
|
||||
await foreach (string result in stringResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Search and return results as TextSearchResult items
|
||||
KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 4 });
|
||||
Console.WriteLine("\n--- Text Search Results ---\n");
|
||||
await foreach (TextSearchResult result in textResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Name: {result.Name}");
|
||||
Console.WriteLine($"Value: {result.Value}");
|
||||
Console.WriteLine($"Link: {result.Link}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Search and return s results as TavilySearchResult items
|
||||
KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 4 });
|
||||
Console.WriteLine("\n--- Tavily Web Page Results ---\n");
|
||||
await foreach (TavilySearchResult result in fullResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Name: {result.Title}");
|
||||
Console.WriteLine($"Content: {result.Content}");
|
||||
Console.WriteLine($"Url: {result.Url}");
|
||||
Console.WriteLine($"RawContent: {result.RawContent}");
|
||||
Console.WriteLine($"Score: {result.Score}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="TavilyTextSearch"/> and use it to perform a text search which returns an answer.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingTavilyTextSearchToGetAnAnswer()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch instance using Tavily search
|
||||
var textSearch = new TavilyTextSearch(apiKey: TestConfiguration.Tavily.ApiKey, options: new() { HttpClient = httpClient, IncludeAnswer = true });
|
||||
|
||||
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 });
|
||||
Console.WriteLine("--- String Results ---\n");
|
||||
await foreach (string result in stringResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="TavilyTextSearch"/> and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingTavilyTextSearchAndIncludeEverything()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch instance using Tavily search
|
||||
var textSearch = new TavilyTextSearch(
|
||||
apiKey: TestConfiguration.Tavily.ApiKey,
|
||||
options: new()
|
||||
{
|
||||
HttpClient = httpClient,
|
||||
IncludeRawContent = true,
|
||||
IncludeImages = true,
|
||||
IncludeImageDescriptions = true,
|
||||
IncludeAnswer = true,
|
||||
});
|
||||
|
||||
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 });
|
||||
Console.WriteLine("\n--- Tavily Web Page Results ---\n");
|
||||
await foreach (TavilySearchResult result in fullResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Name: {result.Title}");
|
||||
Console.WriteLine($"Content: {result.Content}");
|
||||
Console.WriteLine($"Url: {result.Url}");
|
||||
Console.WriteLine($"RawContent: {result.RawContent}");
|
||||
Console.WriteLine($"Score: {result.Score}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="TavilyTextSearch"/> with a custom mapper and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingTavilyTextSearchWithACustomMapperAsync()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch instance using Tavily search
|
||||
var textSearch = new TavilyTextSearch(apiKey: TestConfiguration.Tavily.ApiKey, options: new()
|
||||
{
|
||||
HttpClient = httpClient,
|
||||
StringMapper = new TestTextSearchStringMapper(),
|
||||
});
|
||||
|
||||
var query = "What is the Semantic Kernel?";
|
||||
|
||||
// Search with TextSearchResult textResult type
|
||||
KernelSearchResults<string> stringResults = await textSearch.SearchAsync(query, new() { Top = 2 });
|
||||
Console.WriteLine("--- Serialized JSON Results ---");
|
||||
await foreach (string result in stringResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="TavilyTextSearch"/> with a custom mapper and use it to perform a text search.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingTavilyTextSearchWithAnIncludeDomainFilterAsync()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch instance using Tavily search
|
||||
var textSearch = new TavilyTextSearch(apiKey: TestConfiguration.Tavily.ApiKey, options: new()
|
||||
{
|
||||
HttpClient = httpClient,
|
||||
StringMapper = new TestTextSearchStringMapper(),
|
||||
});
|
||||
|
||||
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);
|
||||
Console.WriteLine("--- Microsoft Developer Blogs Results ---");
|
||||
await foreach (TextSearchResult result in textResults.Results)
|
||||
{
|
||||
Console.WriteLine(result.Link);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
/// <summary>
|
||||
/// Show how to use enhanced LINQ filtering with TavilyTextSearch for type-safe searches with Title.Contains() support.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingTavilyTextSearchWithLinqFilteringAsync()
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
LoggingHandler handler = new(new HttpClientHandler(), this.Output);
|
||||
using HttpClient httpClient = new(handler);
|
||||
|
||||
// Create an ITextSearch<TavilyWebPage> instance for type-safe LINQ filtering
|
||||
ITextSearch<TavilyWebPage> textSearch = new TavilyTextSearch(apiKey: TestConfiguration.Tavily.ApiKey, options: new() { HttpClient = httpClient });
|
||||
|
||||
var query = "Semantic Kernel AI";
|
||||
|
||||
// Example 1: Filter results by title content using Contains
|
||||
Console.WriteLine("——— Example 1: Title Contains Filter ———\n");
|
||||
var titleContainsOptions = new TextSearchOptions<TavilyWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Filter = page => page.Title != null && page.Title.Contains("Kernel")
|
||||
};
|
||||
var titleResults = await textSearch.SearchAsync(query, titleContainsOptions);
|
||||
await foreach (string result in titleResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Example 2: Compound AND filtering (title contains + NOT contains)
|
||||
Console.WriteLine("\n——— Example 2: Compound Filter (Title Contains + Exclusion) ———\n");
|
||||
var compoundOptions = new TextSearchOptions<TavilyWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Filter = page => page.Title != null && page.Title.Contains("AI") &&
|
||||
page.Content != null && !page.Content.Contains("deprecated")
|
||||
};
|
||||
var compoundResults = await textSearch.SearchAsync(query, compoundOptions);
|
||||
await foreach (string result in compoundResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Example 3: Get full results with LINQ filtering
|
||||
Console.WriteLine("\n——— Example 3: Full Results with Title Filter ———\n");
|
||||
var fullResultsOptions = new TextSearchOptions<TavilyWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Filter = page => page.Title != null && page.Title.Contains("Semantic")
|
||||
};
|
||||
var fullResults = await textSearch.GetSearchResultsAsync(query, fullResultsOptions);
|
||||
await foreach (TavilyWebPage page in fullResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Title: {page.Title}");
|
||||
Console.WriteLine($"Content: {page.Content}");
|
||||
Console.WriteLine($"URL: {page.Url}");
|
||||
Console.WriteLine($"Score: {page.Score}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Example 4: Complex compound filtering with multiple conditions
|
||||
Console.WriteLine("\n——— Example 4: Complex Compound Filter (Title + Content + URL) ———\n");
|
||||
var complexOptions = new TextSearchOptions<TavilyWebPage>
|
||||
{
|
||||
Top = 2,
|
||||
Filter = page => page.Title != null && page.Title.Contains("Kernel") &&
|
||||
page.Content != null && page.Content.Contains("AI") &&
|
||||
page.Url != null && page.Url.ToString().Contains("microsoft")
|
||||
};
|
||||
var complexResults = await textSearch.GetSearchResultsAsync(query, complexOptions);
|
||||
await foreach (TavilyWebPage page in complexResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Title: {page.Title}");
|
||||
Console.WriteLine($"URL: {page.Url}");
|
||||
Console.WriteLine($"Score: {page.Score}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
|
||||
#region private
|
||||
/// <summary>
|
||||
/// Test mapper which converts an arbitrary search result to a string using JSON serialization.
|
||||
/// </summary>
|
||||
private sealed class TestTextSearchStringMapper : ITextSearchStringMapper
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string MapFromResultToString(object result)
|
||||
{
|
||||
return JsonSerializer.Serialize(result);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
using Microsoft.SemanticKernel.Data;
|
||||
using OpenAI;
|
||||
|
||||
namespace Search;
|
||||
|
||||
/// <summary>
|
||||
/// This example shows how to create and use a <see cref="VectorStoreTextSearch{TRecord}"/> instance.
|
||||
/// </summary>
|
||||
public class VectorStore_TextSearch(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Show how to create a <see cref="VectorStoreTextSearch{TRecord}"/> and use it to perform a text search
|
||||
/// on top of the <see cref="InMemoryVectorStore"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task UsingInMemoryVectorStoreRecordTextSearchAsync()
|
||||
{
|
||||
// Create an embedding generation service.
|
||||
var embeddingGenerator = new OpenAIClient(TestConfiguration.OpenAI.ApiKey)
|
||||
.GetEmbeddingClient(TestConfiguration.OpenAI.EmbeddingModelId)
|
||||
.AsIEmbeddingGenerator();
|
||||
|
||||
// Construct an InMemory vector store.
|
||||
var vectorStore = new InMemoryVectorStore(new() { EmbeddingGenerator = embeddingGenerator });
|
||||
var collectionName = "records";
|
||||
|
||||
// Delegate which will create a record.
|
||||
static DataModel CreateRecord(string text)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Key = Guid.NewGuid(),
|
||||
Text = text
|
||||
};
|
||||
}
|
||||
|
||||
// Create a record collection from a list of strings using the provided delegate.
|
||||
string[] lines =
|
||||
[
|
||||
"Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your C#, Python, or Java codebase. It serves as an efficient middleware that enables rapid delivery of enterprise-grade solutions.",
|
||||
"Semantic Kernel is a new AI SDK, and a simple and yet powerful programming model that lets you add large language capabilities to your app in just a matter of minutes. It uses natural language prompting to create and execute semantic kernel AI tasks across multiple languages and platforms.",
|
||||
"In this guide, you learned how to quickly get started with Semantic Kernel by building a simple AI agent that can interact with an AI service and run your code. To see more examples and learn how to build more complex AI agents, check out our in-depth samples."
|
||||
];
|
||||
var collection = await CreateCollectionFromListAsync<Guid, DataModel>(
|
||||
vectorStore, collectionName, lines, CreateRecord);
|
||||
|
||||
// Create a text search instance using the InMemory vector store.
|
||||
var textSearch = new VectorStoreTextSearch<DataModel>(collection);
|
||||
await ExecuteSearchesAsync(textSearch);
|
||||
|
||||
// Create a text search instance using a vectorized search wrapper around the InMemory vector store.
|
||||
textSearch = new VectorStoreTextSearch<DataModel>(collection);
|
||||
await ExecuteSearchesAsync(textSearch);
|
||||
}
|
||||
|
||||
private async Task ExecuteSearchesAsync(VectorStoreTextSearch<DataModel> textSearch)
|
||||
{
|
||||
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 });
|
||||
Console.WriteLine("--- String Results ---\n");
|
||||
await foreach (string result in stringResults.Results)
|
||||
{
|
||||
Console.WriteLine(result);
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Search and return results as TextSearchResult items
|
||||
KernelSearchResults<TextSearchResult> textResults = await textSearch.GetTextSearchResultsAsync(query, new() { Top = 2, Skip = 0 });
|
||||
Console.WriteLine("\n--- Text Search Results ---\n");
|
||||
await foreach (TextSearchResult result in textResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Name: {result.Name}");
|
||||
Console.WriteLine($"Value: {result.Value}");
|
||||
Console.WriteLine($"Link: {result.Link}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
|
||||
// Search and returns results as DataModel items
|
||||
KernelSearchResults<object> fullResults = await textSearch.GetSearchResultsAsync(query, new() { Top = 2, Skip = 0 });
|
||||
Console.WriteLine("\n--- DataModel Results ---\n");
|
||||
await foreach (DataModel result in fullResults.Results)
|
||||
{
|
||||
Console.WriteLine($"Key: {result.Key}");
|
||||
Console.WriteLine($"Text: {result.Text}");
|
||||
Console.WriteLine($"Embedding: {result.Embedding.Length}");
|
||||
WriteHorizontalRule();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to create a record.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Type of the record key.</typeparam>
|
||||
/// <typeparam name="TRecord">Type of the record.</typeparam>
|
||||
internal delegate TRecord CreateRecord<TKey, TRecord>(string text) where TKey : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="VectorStoreCollection{TKey, TRecord}"/> from a list of strings by:
|
||||
/// 1. Creating an instance of <see cref="InMemoryCollection{TKey, TRecord}"/>
|
||||
/// 2. Generating embeddings for each string.
|
||||
/// 3. Creating a record with a valid key for each string and it's embedding.
|
||||
/// 4. Insert the records into the collection.
|
||||
/// </summary>
|
||||
/// <param name="vectorStore">Instance of <see cref="VectorStore"/> used to created the collection.</param>
|
||||
/// <param name="collectionName">The collection name.</param>
|
||||
/// <param name="entries">A list of strings.</param>
|
||||
/// <param name="createRecord">A delegate which can create a record with a valid key for each string and it's embedding.</param>
|
||||
internal static async Task<VectorStoreCollection<TKey, TRecord>> CreateCollectionFromListAsync<TKey, TRecord>(
|
||||
VectorStore vectorStore,
|
||||
string collectionName,
|
||||
string[] entries,
|
||||
CreateRecord<TKey, TRecord> createRecord)
|
||||
where TKey : notnull
|
||||
where TRecord : class
|
||||
{
|
||||
// Get and create collection if it doesn't exist.
|
||||
var collection = vectorStore.GetCollection<TKey, TRecord>(collectionName);
|
||||
await collection.EnsureCollectionExistsAsync().ConfigureAwait(false);
|
||||
|
||||
// Generate the records and upsert them.
|
||||
var records = entries.Select(x => createRecord(x));
|
||||
await collection.UpsertAsync(records);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample model class that represents a record entry.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that each property is decorated with an attribute that specifies how the property should be treated by the vector store.
|
||||
/// This allows us to create a collection in the vector store and upsert and retrieve instances of this class without any further configuration.
|
||||
/// </remarks>
|
||||
private sealed class DataModel
|
||||
{
|
||||
[VectorStoreKey]
|
||||
[TextSearchResultName]
|
||||
public Guid Key { get; init; }
|
||||
|
||||
[VectorStoreData]
|
||||
[TextSearchResultValue]
|
||||
public string Text { get; init; }
|
||||
|
||||
[VectorStoreVector(1536)]
|
||||
public string Embedding => this.Text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Plugins.Web;
|
||||
|
||||
namespace Search;
|
||||
|
||||
public class WebSearchQueriesPlugin(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunAsync()
|
||||
{
|
||||
Console.WriteLine("======== WebSearchQueries ========");
|
||||
|
||||
Kernel kernel = new();
|
||||
|
||||
// Load native plugins
|
||||
var bing = kernel.ImportPluginFromType<SearchUrlPlugin>("search");
|
||||
|
||||
// Run
|
||||
var ask = "What's the tallest building in Europe?";
|
||||
var result = await kernel.InvokeAsync(bing["BingSearchUrl"], new() { ["query"] = ask });
|
||||
|
||||
Console.WriteLine(ask + "\n");
|
||||
Console.WriteLine(result.GetValue<string>());
|
||||
|
||||
/* Expected output:
|
||||
* ======== WebSearchQueries ========
|
||||
* What's the tallest building in Europe?
|
||||
*
|
||||
* https://www.bing.com/search?q=What%27s%20the%20tallest%20building%20in%20Europe%3F
|
||||
* == DONE ==
|
||||
*/
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user