chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,329 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Runtime.CompilerServices;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.InMemory;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
using Microsoft.SemanticKernel.Services;
namespace Optimization;
/// <summary>
/// This example shows how to use FrugalGPT techniques to reduce cost and improve LLM-related task performance.
/// More information here: https://arxiv.org/abs/2305.05176.
/// </summary>
public sealed class FrugalGPTWithFilters(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// One of the FrugalGPT techniques is to reduce prompt size when using few-shot prompts.
/// If prompt contains a lof of examples to help LLM to provide the best result, it's possible to send only a couple of them to reduce amount of tokens.
/// Vector similarity can be used to pick the best examples from example set for specific request.
/// Following example shows how to optimize email classification request by reducing prompt size with vector similarity search.
/// </summary>
[Fact]
public async Task ReducePromptSizeAsync()
{
// Define email classification examples with email body and labels.
var examples = new List<string>
{
"Hey, just checking in to see how you're doing! - Personal",
"Can you pick up some groceries on your way back home? We need milk and bread. - Personal, Tasks",
"Happy Birthday! Wishing you a fantastic day filled with love and joy. - Personal",
"Let's catch up over coffee this Saturday. It's been too long! - Personal, Events",
"Please review the attached document and provide your feedback by EOD. - Work",
"Our team meeting is scheduled for 10 AM tomorrow in the main conference room. - Work",
"The quarterly financial report is due next Monday. Ensure all data is updated. - Work, Tasks",
"Can you send me the latest version of the project plan? Thanks! - Work",
"You're invited to our annual summer picnic! RSVP by June 25th. - Events",
"Join us for a webinar on digital marketing trends this Thursday at 3 PM. - Events",
"Save the date for our charity gala on September 15th. We hope to see you there! - Events",
"Don't miss our customer appreciation event next week. Sign up now! - Events, Notifications",
"Your order has been shipped and will arrive by June 20th. - Notifications",
"We've updated our policies. Please review the changes. - Notifications",
"Your username was successfully changed. If this wasn't you, contact support immediately. - Notifications",
"The system upgrade will occur this weekend. - Notifications, Work",
"Don't forget to submit your timesheet by 5 PM today. - Tasks, Work",
"Pick up the dry cleaning before they close at 7 PM. - Tasks",
"Complete the online training module by the end of the week. - Tasks, Work",
"Send out the meeting invites for next week's project kickoff. - Tasks, Work"
};
// Initialize kernel with chat completion and embedding generation services.
// It's possible to combine different models from different AI providers to achieve the lowest token usage.
var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "gpt-4",
apiKey: TestConfiguration.OpenAI.ApiKey)
.AddOpenAIEmbeddingGenerator(
modelId: "text-embedding-3-small",
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();
// Initialize few-shot prompt.
var function = kernel.CreateFunctionFromPrompt(
new()
{
Template =
"""
Available classification labels: Personal, Work, Events, Notifications, Tasks
Email classification examples:
{{#each Examples}}
{{this}}
{{/each}}
Email body to classify:
{{Request}}
""",
TemplateFormat = "handlebars"
},
new HandlebarsPromptTemplateFactory()
);
// Define arguments with few-shot examples and actual email for classification.
var arguments = new KernelArguments
{
["Examples"] = examples,
["Request"] = "Your dentist appointment is tomorrow at 10 AM. Please remember to bring your insurance card."
};
// Invoke defined function to see initial result.
var result = await kernel.InvokeAsync(function, arguments);
Console.WriteLine(result); // Personal, Notifications
Console.WriteLine(result.Metadata?["Usage"]?.AsJson()); // Total tokens: ~430
// Add few-shot prompt optimization filter.
// The filter uses in-memory store for vector similarity search and text embedding generation service to generate embeddings.
var vectorStore = new InMemoryVectorStore();
var embeddingGenerator = kernel.GetRequiredService<IEmbeddingGenerator<string, Embedding<float>>>();
// Register optimization filter.
kernel.PromptRenderFilters.Add(new FewShotPromptOptimizationFilter(vectorStore, embeddingGenerator));
// Get result again and compare the usage.
result = await kernel.InvokeAsync(function, arguments);
Console.WriteLine(result); // Personal, Notifications
Console.WriteLine(result.Metadata?["Usage"]?.AsJson()); // Total tokens: ~150
}
/// <summary>
/// LLM cascade technique allows to use multiple LLMs sequentially starting from cheaper model,
/// evaluate LLM result and return it in case it meets the quality criteria. Otherwise, proceed with next LLM in queue,
/// until the result will be acceptable.
/// Following example uses mock result generation and evaluation for demonstration purposes.
/// Result evaluation examples including BERTScore, BLEU, METEOR and COMET metrics can be found here:
/// https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/Demos/QualityCheck.
/// </summary>
[Fact]
public async Task LLMCascadeAsync()
{
// Create kernel builder.
var builder = Kernel.CreateBuilder();
// Register chat completion services for demonstration purposes.
// This registration is similar to AddAzureOpenAIChatCompletion and AddOpenAIChatCompletion methods.
builder.Services.AddSingleton<IChatCompletionService>(new MockChatCompletionService("model1", "Hi there! I'm doing well, thank you! How about yourself?"));
builder.Services.AddSingleton<IChatCompletionService>(new MockChatCompletionService("model2", "Hello! I'm great, thanks for asking. How are you doing today?"));
builder.Services.AddSingleton<IChatCompletionService>(new MockChatCompletionService("model3", "Hey! I'm fine, thanks. How's your day going so far?"));
// Register LLM cascade filter with model execution order, acceptance criteria for result and service for output.
// In real use-cases, execution order should start from cheaper to more expensive models.
// If first model will produce acceptable result, then it will be returned immediately.
builder.Services.AddSingleton<IFunctionInvocationFilter>(new LLMCascadeFilter(
modelExecutionOrder: ["model1", "model2", "model3"],
acceptanceCriteria: result => result.Contains("Hey!"),
output: this.Output));
// Build kernel.
var kernel = builder.Build();
// Send a request.
var result = await kernel.InvokePromptAsync("Hi, how are you today?");
Console.WriteLine($"\nFinal result: {result}");
// Output:
// Executing request with model: model1
// Result from model1: Hi there! I'm doing well, thank you! How about yourself?
// Result does not meet the acceptance criteria, moving to the next model.
// Executing request with model: model2
// Result from model2: Hello! I'm great, thanks for asking. How are you doing today?
// Result does not meet the acceptance criteria, moving to the next model.
// Executing request with model: model3
// Result from model3: Hey! I'm fine, thanks. How's your day going so far?
// Returning result as it meets the acceptance criteria.
// Final result: Hey! I'm fine, thanks. How's your day going so far?
}
/// <summary>
/// Few-shot prompt optimization filter which takes all examples from kernel arguments and selects first <see cref="TopN"/> examples,
/// which are similar to original request.
/// </summary>
private sealed class FewShotPromptOptimizationFilter(
VectorStore vectorStore,
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator) : IPromptRenderFilter
{
/// <summary>
/// Maximum number of examples to use which are similar to original request.
/// </summary>
private const int TopN = 5;
/// <summary>
/// Collection name to use in vector store.
/// </summary>
private const string CollectionName = "examples";
public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
{
// Get examples and original request from arguments.
var examples = context.Arguments["Examples"] as List<string>;
var request = context.Arguments["Request"] as string;
if (examples is { Count: > 0 } && !string.IsNullOrEmpty(request))
{
var exampleRecords = new List<ExampleRecord>();
// Generate embedding for each example.
var embeddings = (await embeddingGenerator.GenerateAsync(examples));
// Create vector store record instances with example text and embedding.
for (var i = 0; i < examples.Count; i++)
{
exampleRecords.Add(new ExampleRecord
{
Id = Guid.NewGuid().ToString(),
Example = examples[i],
ExampleEmbedding = embeddings[i].Vector
});
}
// Create collection and upsert all vector store records for search.
// It's possible to do it only once and re-use the same examples for future requests.
var collection = vectorStore.GetCollection<string, ExampleRecord>(CollectionName);
await collection.EnsureCollectionExistsAsync(context.CancellationToken);
await collection.UpsertAsync(exampleRecords, cancellationToken: context.CancellationToken);
// Generate embedding for original request.
var requestEmbedding = await embeddingGenerator.GenerateAsync(request, cancellationToken: context.CancellationToken);
// Find top N examples which are similar to original request.
var topNExamples = (await collection.SearchAsync(requestEmbedding, top: TopN, cancellationToken: context.CancellationToken)
.ToListAsync(context.CancellationToken)).Select(l => l.Record).ToList();
// Override arguments to use only top N examples, which will be sent to LLM.
context.Arguments["Examples"] = topNExamples.Select(l => l.Example);
}
// Continue prompt rendering operation.
await next(context);
}
}
/// <summary>
/// Example of LLM cascade filter which will invoke a function using multiple LLMs in specific order,
/// until the result will meet specified acceptance criteria.
/// </summary>
private sealed class LLMCascadeFilter(
List<string> modelExecutionOrder,
Predicate<string> acceptanceCriteria,
ITestOutputHelper output) : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(Microsoft.SemanticKernel.FunctionInvocationContext context, Func<Microsoft.SemanticKernel.FunctionInvocationContext, Task> next)
{
// Get registered chat completion services from kernel.
var registeredServices = context.Kernel
.GetAllServices<IChatCompletionService>()
.Select(service => (ModelId: service.GetModelId()!, Service: service));
// Define order of execution.
var order = modelExecutionOrder
.Select((value, index) => new { Value = value, Index = index })
.ToDictionary(k => k.Value, v => v.Index);
// Sort services by specified order.
var orderedServices = registeredServices.OrderBy(service => order[service.ModelId]);
// Try to invoke a function with each service and check the result.
foreach (var service in orderedServices)
{
// Define execution settings with model ID.
context.Arguments.ExecutionSettings = new Dictionary<string, PromptExecutionSettings>
{
{ PromptExecutionSettings.DefaultServiceId, new() { ModelId = service.ModelId } }
};
output.WriteLine($"Executing request with model: {service.ModelId}");
// Invoke a function.
await next(context);
// Get a result.
var result = context.Result.ToString()!;
output.WriteLine($"Result from {service.ModelId}: {result}");
// Check if result meets specified acceptance criteria.
// If yes, stop execution loop, so last result will be returned.
if (acceptanceCriteria(result))
{
output.WriteLine("Returning result as it meets the acceptance criteria.");
return;
}
// Otherwise, proceed with next model.
output.WriteLine("Result does not meet the acceptance criteria, moving to the next model.\n");
}
// If LLMs didn't return acceptable result, the last result will be returned.
// It's also possible to throw an exception in such cases if needed.
// throw new Exception("Models didn't return a result that meets the acceptance criteria").
}
}
/// <summary>
/// Mock chat completion service for demonstration purposes.
/// </summary>
private sealed class MockChatCompletionService(string modelId, string mockResult) : IChatCompletionService
{
public IReadOnlyDictionary<string, object?> Attributes => new Dictionary<string, object?> { { AIServiceExtensions.ModelIdKey, modelId } };
public Task<IReadOnlyList<ChatMessageContent>> GetChatMessageContentsAsync(
ChatHistory chatHistory,
PromptExecutionSettings? executionSettings = null,
Kernel? kernel = null,
CancellationToken cancellationToken = default)
{
return Task.FromResult<IReadOnlyList<ChatMessageContent>>([new ChatMessageContent(AuthorRole.Assistant, mockResult)]);
}
public async IAsyncEnumerable<StreamingChatMessageContent> GetStreamingChatMessageContentsAsync(
ChatHistory chatHistory,
PromptExecutionSettings? executionSettings = null,
Kernel? kernel = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
yield return new StreamingChatMessageContent(AuthorRole.Assistant, mockResult);
}
}
private sealed class ExampleRecord
{
[VectorStoreKey]
public string Id { get; set; }
[VectorStoreData]
public string Example { get; set; }
[VectorStoreVector(1536)]
public ReadOnlyMemory<float> ExampleEmbedding { get; set; }
}
}
@@ -0,0 +1,430 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace Optimization;
/// <summary>
/// Single kernel instance may have multiple imported plugins/functions. It's possible to enable automatic function calling,
/// so AI model will decide which functions to call for specific request.
/// In case there are a lot of plugins/functions in application, some of them (or all of them) need to be shared with the model.
/// This example shows how to use different plugin/function selection strategies, to share with AI only those functions,
/// which are related to specific request.
/// This technique should decrease token usage, as fewer functions will be shared with AI.
/// It also helps to handle the scenario with a general purpose chat experience for a large enterprise,
/// where there are so many plugins, that it's impossible to share all of them with AI model in a single request.
/// </summary>
public sealed class PluginSelectionWithFilters(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// This method shows how to select best functions to share with AI using vector similarity search.
/// </summary>
[Fact]
public async Task UsingVectorSearchWithKernelAsync()
{
// Initialize kernel with chat completion and embedding generation services.
// It's possible to combine different models from different AI providers to achieve the lowest token usage.
var builder = Kernel
.CreateBuilder()
.AddOpenAIChatCompletion("gpt-4", TestConfiguration.OpenAI.ApiKey)
.AddOpenAIEmbeddingGenerator("text-embedding-3-small", TestConfiguration.OpenAI.ApiKey);
// Add logging.
var logger = this.LoggerFactory.CreateLogger<PluginSelectionWithFilters>();
builder.Services.AddSingleton<ILogger>(logger);
// Add vector store to keep functions and search for the most relevant ones for specific request.
builder.Services.AddInMemoryVectorStore();
// Add helper components defined in this example.
builder.Services.AddSingleton<IFunctionProvider, FunctionProvider>();
builder.Services.AddSingleton<IFunctionKeyProvider, FunctionKeyProvider>();
builder.Services.AddSingleton<IPluginStore, PluginStore>();
var kernel = builder.Build();
// Import plugins with different features.
kernel.ImportPluginFromType<TimePlugin>();
kernel.ImportPluginFromType<WeatherPlugin>();
kernel.ImportPluginFromType<EmailPlugin>();
kernel.ImportPluginFromType<NewsPlugin>();
kernel.ImportPluginFromType<CalendarPlugin>();
// Get registered plugin store to save information about plugins.
var pluginStore = kernel.GetRequiredService<IPluginStore>();
// Save information about kernel plugins in plugin store.
const string CollectionName = "functions";
await pluginStore.SaveAsync(CollectionName, kernel.Plugins);
// Enable automatic function calling by default.
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
// Define kernel arguments with specific request.
var kernelArguments = new KernelArguments(executionSettings) { ["Request"] = "Provide latest headlines" };
// Invoke the request without plugin selection filter first for comparison purposes.
Console.WriteLine("Run without filter:");
var result = await kernel.InvokePromptAsync("{{$Request}}", kernelArguments);
Console.WriteLine(result);
Console.WriteLine(result.Metadata?["Usage"]?.AsJson()); // All functions were shared with AI. Total tokens: ~250
// Define plugin selection filter.
var filter = new PluginSelectionFilter(
functionProvider: kernel.GetRequiredService<IFunctionProvider>(),
logger: kernel.GetRequiredService<ILogger>(),
collectionName: CollectionName,
numberOfBestFunctions: 1);
// Add filter to kernel.
kernel.FunctionInvocationFilters.Add(filter);
// Invoke the request with plugin selection filter.
Console.WriteLine("\nRun with filter:");
// FunctionChoiceBehavior.Auto() is used here as well as defined above.
// In case there will be related functions found for specific request, the FunctionChoiceBehavior will be updated in filter to
// FunctionChoiceBehavior.Auto(functions) - this will allow to share only related set of functions with AI.
result = await kernel.InvokePromptAsync("{{$Request}}", kernelArguments);
Console.WriteLine(result);
Console.WriteLine(result.Metadata?["Usage"]?.AsJson()); // Just one function was shared with AI. Total tokens: ~150
}
[Fact]
public async Task UsingVectorSearchWithChatCompletionAsync()
{
// Initialize kernel with chat completion and embedding generation services.
// It's possible to combine different models from different AI providers to achieve the lowest token usage.
var builder = Kernel
.CreateBuilder()
.AddOpenAIChatCompletion("gpt-4", TestConfiguration.OpenAI.ApiKey)
.AddOpenAIEmbeddingGenerator("text-embedding-3-small", TestConfiguration.OpenAI.ApiKey);
// Add logging.
var logger = this.LoggerFactory.CreateLogger<PluginSelectionWithFilters>();
builder.Services.AddSingleton<ILogger>(logger);
// Add vector store to keep functions and search for the most relevant ones for specific request.
builder.Services.AddInMemoryVectorStore();
// Add helper components defined in this example.
builder.Services.AddSingleton<IFunctionProvider, FunctionProvider>();
builder.Services.AddSingleton<IFunctionKeyProvider, FunctionKeyProvider>();
builder.Services.AddSingleton<IPluginStore, PluginStore>();
var kernel = builder.Build();
// Import plugins with different features.
kernel.ImportPluginFromType<TimePlugin>();
kernel.ImportPluginFromType<WeatherPlugin>();
kernel.ImportPluginFromType<EmailPlugin>();
kernel.ImportPluginFromType<NewsPlugin>();
kernel.ImportPluginFromType<CalendarPlugin>();
// Get registered plugin store to save information about plugins.
var pluginStore = kernel.GetRequiredService<IPluginStore>();
// Store information about kernel plugins in plugin store.
const string CollectionName = "functions";
await pluginStore.SaveAsync(CollectionName, kernel.Plugins);
// Enable automatic function calling by default.
var executionSettings = new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
// Get function provider and find best functions for specified prompt.
var functionProvider = kernel.GetRequiredService<IFunctionProvider>();
const string Prompt = "Provide latest headlines";
var bestFunctions = await functionProvider.GetBestFunctionsAsync(CollectionName, Prompt, kernel.Plugins, numberOfBestFunctions: 1);
// If any found, update execution settings to share only selected functions.
if (bestFunctions.Count > 0)
{
bestFunctions.ForEach(function
=> logger.LogInformation("Best function found: {PluginName}-{FunctionName}", function.PluginName, function.Name));
// Share only selected functions with AI.
executionSettings.FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(bestFunctions);
}
// Get chat completion service and execute a request.
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
var chatHistory = new ChatHistory();
chatHistory.AddUserMessage(Prompt);
var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
Console.WriteLine(result);
Console.WriteLine(result.Metadata?["Usage"]?.AsJson()); // Just one function was shared with AI. Total tokens: ~150
}
/// <summary>
/// Filter which performs vector similarity search on imported functions in <see cref="Kernel"/>
/// to select the best ones to share with AI.
/// </summary>
private sealed class PluginSelectionFilter(
IFunctionProvider functionProvider,
ILogger logger,
string collectionName,
int numberOfBestFunctions) : IFunctionInvocationFilter
{
public async Task OnFunctionInvocationAsync(Microsoft.SemanticKernel.FunctionInvocationContext context, Func<Microsoft.SemanticKernel.FunctionInvocationContext, Task> next)
{
var request = GetRequestArgument(context.Arguments);
// Execute plugin selection logic for "InvokePrompt" function only, as main entry point.
if (context.Function.Name.Contains(nameof(KernelExtensions.InvokePromptAsync)) && !string.IsNullOrWhiteSpace(request))
{
// Get imported plugins in kernel.
var plugins = context.Kernel.Plugins;
// Find best functions for original request.
var bestFunctions = await functionProvider.GetBestFunctionsAsync(collectionName, request, plugins, numberOfBestFunctions);
// If any found, update execution settings and execute the request.
if (bestFunctions.Count > 0)
{
bestFunctions.ForEach(function
=> logger.LogInformation("Best function found: {PluginName}-{FunctionName}", function.PluginName, function.Name));
var updatedExecutionSettings = GetExecutionSettings(context.Arguments, bestFunctions);
if (updatedExecutionSettings is not null)
{
// Update execution settings.
context.Arguments.ExecutionSettings = updatedExecutionSettings;
// Execute the request.
await next(context);
return;
}
}
}
// Otherwise, execute a request with default logic, where all plugins will be shared.
await next(context);
}
private static Dictionary<string, PromptExecutionSettings>? GetExecutionSettings(KernelArguments arguments, List<KernelFunction> functions)
{
var promptExecutionSettings = arguments.ExecutionSettings?[PromptExecutionSettings.DefaultServiceId];
if (promptExecutionSettings is not null and OpenAIPromptExecutionSettings openAIPromptExecutionSettings)
{
// Share only selected functions with AI.
openAIPromptExecutionSettings.FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(functions);
return new() { [PromptExecutionSettings.DefaultServiceId] = openAIPromptExecutionSettings };
}
return null;
}
private static string? GetRequestArgument(KernelArguments arguments)
=> arguments.TryGetValue("Request", out var requestObj) && requestObj is string request ? request : null;
}
#region Helper components
/// <summary>
/// Helper function key provider.
/// </summary>
public interface IFunctionKeyProvider
{
string GetFunctionKey(KernelFunction kernelFunction);
}
/// <summary>
/// Helper function provider to get best functions for specific request.
/// </summary>
public interface IFunctionProvider
{
Task<List<KernelFunction>> GetBestFunctionsAsync(
string collectionName,
string request,
KernelPluginCollection plugins,
int numberOfBestFunctions,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Helper plugin store to save information about imported plugins in vector database.
/// </summary>
public interface IPluginStore
{
Task SaveAsync(string collectionName, KernelPluginCollection plugins, CancellationToken cancellationToken = default);
}
public class FunctionKeyProvider : IFunctionKeyProvider
{
public string GetFunctionKey(KernelFunction kernelFunction)
{
return !string.IsNullOrWhiteSpace(kernelFunction.PluginName) ?
$"{kernelFunction.PluginName}-{kernelFunction.Name}" :
kernelFunction.Name;
}
}
public class FunctionProvider(
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
VectorStore vectorStore,
IFunctionKeyProvider functionKeyProvider) : IFunctionProvider
{
public async Task<List<KernelFunction>> GetBestFunctionsAsync(
string collectionName,
string request,
KernelPluginCollection plugins,
int numberOfBestFunctions,
CancellationToken cancellationToken = default)
{
// Generate embedding for original request.
var requestEmbedding = await embeddingGenerator.GenerateAsync(request, cancellationToken: cancellationToken);
var collection = vectorStore.GetCollection<string, FunctionRecord>(collectionName);
await collection.EnsureCollectionExistsAsync(cancellationToken);
// Find best functions to call for original request.
var recordKeys = (await collection.SearchAsync(requestEmbedding, top: numberOfBestFunctions, cancellationToken: cancellationToken)
.ToListAsync(cancellationToken)).Select(l => l.Record.Id);
return plugins
.SelectMany(plugin => plugin)
.Where(function => recordKeys.Contains(functionKeyProvider.GetFunctionKey(function)))
.ToList();
}
}
public class PluginStore(
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
VectorStore vectorStore,
IFunctionKeyProvider functionKeyProvider) : IPluginStore
{
public async Task SaveAsync(string collectionName, KernelPluginCollection plugins, CancellationToken cancellationToken = default)
{
// Collect data about imported functions in kernel.
var functionRecords = new List<FunctionRecord>();
var functionsData = GetFunctionsData(plugins);
// Generate embedding for each function.
var embeddings = await embeddingGenerator
.GenerateAsync(functionsData.Select(l => l.TextToVectorize).ToArray(), cancellationToken: cancellationToken);
// Create vector store record instances with function information and embedding.
for (var i = 0; i < functionsData.Count; i++)
{
var (function, functionInfo) = functionsData[i];
functionRecords.Add(new FunctionRecord
{
Id = functionKeyProvider.GetFunctionKey(function),
FunctionInfo = functionInfo,
FunctionInfoEmbedding = embeddings[i].Vector
});
}
// Create collection and upsert all vector store records for search.
// It's possible to do it only once and re-use the same functions for future requests.
var collection = vectorStore.GetCollection<string, FunctionRecord>(collectionName);
await collection.EnsureCollectionExistsAsync(cancellationToken);
await collection.UpsertAsync(functionRecords, cancellationToken: cancellationToken);
}
private static List<(KernelFunction Function, string TextToVectorize)> GetFunctionsData(KernelPluginCollection plugins)
=> plugins
.SelectMany(plugin => plugin)
.Select(function => (function, $"Plugin name: {function.PluginName}. Function name: {function.Name}. Description: {function.Description}"))
.ToList();
}
#endregion
#region Sample Plugins
private sealed class TimePlugin
{
[KernelFunction, Description("Provides the current date and time.")]
public string GetCurrentTime() => DateTime.Now.ToString("R");
}
private sealed class WeatherPlugin
{
[KernelFunction, Description("Provides weather information for various cities.")]
public string GetWeather(string cityName) => cityName switch
{
"Boston" => "61 and rainy",
"London" => "55 and cloudy",
"Miami" => "80 and sunny",
"Paris" => "60 and rainy",
"Tokyo" => "50 and sunny",
"Sydney" => "75 and sunny",
"Tel Aviv" => "80 and sunny",
_ => "No information",
};
}
private sealed class EmailPlugin(ILogger logger)
{
[KernelFunction, Description("Sends email to recipient with subject and body.")]
public void SendEmail(string from, string to, string subject, string body)
{
logger.LogInformation("Email has been sent successfully.");
}
}
private sealed class NewsPlugin
{
[KernelFunction, Description("Provides the latest news headlines.")]
public List<string> GetLatestHeadlines() =>
[
"Tourism Industry Sees Record Growth",
"Tech Company Releases New Product",
"Sports Team Wins Championship",
"New Study Reveals Health Benefits of Walking"
];
}
private sealed class CalendarPlugin
{
[KernelFunction, Description("Provides a list of upcoming events.")]
public List<string> GetUpcomingEvents() =>
[
"Meeting with Bob on June 22",
"Project deadline on June 30",
"Dentist appointment on July 5",
"Vacation starts on July 12"
];
}
#endregion
#region Vector Store Record
private sealed class FunctionRecord
{
[VectorStoreKey]
public string Id { get; set; }
[VectorStoreData]
public string FunctionInfo { get; set; }
[VectorStoreVector(1536)]
public ReadOnlyMemory<float> FunctionInfoEmbedding { get; set; }
}
#endregion
}