// Copyright (c) Microsoft. All rights reserved.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
namespace ChatCompletion;
///
/// This example shows how to access object in Semantic Kernel functions using
/// and .
/// This scenario can be useful with auto function calling,
/// when logic in SK functions depends on results from previous messages in the same chat history.
///
public sealed class ChatHistoryInFunctions(ITestOutputHelper output) : BaseTest(output)
{
///
/// This method passes an instance of to SK function using property.
/// This approach should be used with caution for cases when Kernel is registered in application as singleton.
/// For singleton Kernel, check examples and .
///
[Fact]
public async Task UsingKernelDataAsync()
{
// Initialize kernel.
var kernel = GetKernel();
// Import plugin.
kernel.ImportPluginFromObject(new DataPlugin(this.Output));
// Get chat completion service.
var chatCompletionService = kernel.GetRequiredService();
// Initialize chat history with prompt.
var chatHistory = new ChatHistory();
chatHistory.AddUserMessage("I want to get an information about featured products, product reviews and daily summary.");
// Initialize execution settings with enabled auto function calling.
var executionSettings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// Set chat history in kernel data to access it in a function.
kernel.Data[nameof(ChatHistory)] = chatHistory;
// Send a request.
var result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);
// Each function will receive a greater number of messages in chat history, because chat history is populated
// with results of previous functions.
Console.WriteLine($"Result: {result}");
// Output:
// GetFeaturedProducts - Chat History Message Count: 2
// GetProductReviews - Chat History Message Count: 3
// GetDailySalesSummary - Chat History Message Count: 4
// Result: Here's the information you requested...
}
///
/// This method passes an instance of to SK function using
/// and filter.
/// The plugin has access to , so it's possible to find a chat history in arguments by property name.
///
[Fact]
public async Task UsingKernelArgumentsAndFilterOption1Async()
{
// Initialize kernel.
var kernel = GetKernel();
// Import plugin.
kernel.ImportPluginFromObject(new DataPlugin(this.Output));
// Add filter.
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter());
// Initialize execution settings with enabled auto function calling.
var executionSettings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// Send a request.
var result = await kernel.InvokePromptAsync("I want to get an information about featured products, product reviews and daily summary.", new(executionSettings));
// Each function will receive a greater number of messages in chat history, because chat history is populated
// with results of previous functions.
Console.WriteLine($"Result: {result}");
// Output:
// GetFeaturedProducts - Chat History Message Count: 2
// GetProductReviews - Chat History Message Count: 3
// GetDailySalesSummary - Chat History Message Count: 4
// Result: Here's the information you requested...
}
///
/// This method passes an instance of to SK function using
/// and filter.
/// The plugin has access to directly, since it's automatically injected from
/// into the function by argument name.
///
[Fact]
public async Task UsingKernelArgumentsAndFilterOption2Async()
{
// Initialize kernel.
var kernel = GetKernel();
// Import plugin.
kernel.ImportPluginFromObject(new EmailPlugin(this.Output));
// Add filter.
kernel.AutoFunctionInvocationFilters.Add(new AutoFunctionInvocationFilter());
// Initialize execution settings with enabled auto function calling.
var executionSettings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// Send a request.
var result = await kernel.InvokePromptAsync("Send email to test@contoso.com", new(executionSettings));
Console.WriteLine($"Result: {result}");
// Output:
// SendEmail - Chat History Message Count: 2
// Result: Email has been sent to test@contoso.com.
}
#region private
///
/// Implementation of to set chat history in
/// before invoking a function.
///
private sealed class AutoFunctionInvocationFilter : IAutoFunctionInvocationFilter
{
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func next)
{
// Set chat history in kernel arguments.
if (context.Arguments is not null)
{
// nameof(ChatHistory) is used for demonstration purposes.
// Any name can be used here, as long as it is effective for the intended purpose.
// However, the same name must be used when retrieving chat history from the KernelArguments instance
// or when the ChatHistory parameter is directly injected into a function.
context.Arguments[nameof(ChatHistory)] = context.ChatHistory;
}
// Invoke next filter in pipeline or function.
await next(context);
}
}
///
/// Data plugin for demonstration purposes, where methods accept and
/// as parameters.
///
private sealed class DataPlugin(ITestOutputHelper output)
{
[KernelFunction]
public List GetFeaturedProducts(Kernel kernel, KernelArguments arguments)
{
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
if (chatHistory is not null)
{
output.WriteLine($"{nameof(GetFeaturedProducts)} - Chat History Message Count: {chatHistory.Count}");
}
return ["Laptop", "Smartphone", "Smartwatch"];
}
[KernelFunction]
public Dictionary> GetProductReviews(Kernel kernel, KernelArguments arguments)
{
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
if (chatHistory is not null)
{
output.WriteLine($"{nameof(GetProductReviews)} - Chat History Message Count: {chatHistory.Count}");
}
return new()
{
["Laptop"] = ["Excellent performance!", "Battery life could be better."],
["Smartphone"] = ["Amazing camera!", "Very responsive."],
["Smartwatch"] = ["Stylish design", "Could use more apps."],
};
}
[KernelFunction]
public string GetDailySalesSummary(Kernel kernel, KernelArguments arguments)
{
var chatHistory = GetChatHistory(kernel.Data) ?? GetChatHistory(arguments);
if (chatHistory is not null)
{
output.WriteLine($"{nameof(GetDailySalesSummary)} - Chat History Message Count: {chatHistory.Count}");
}
const int OrdersProcessed = 50;
const decimal TotalRevenue = 12345.67m;
return $"Today's Sales: {OrdersProcessed} orders processed, total revenue: ${TotalRevenue}.";
}
private static ChatHistory? GetChatHistory(IDictionary data)
{
if (data.TryGetValue(nameof(ChatHistory), out object? chatHistoryObj) &&
chatHistoryObj is ChatHistory chatHistory)
{
return chatHistory;
}
return null;
}
}
///
/// Email plugin for demonstration purposes, where method accepts as parameter.
///
private sealed class EmailPlugin(ITestOutputHelper output)
{
[KernelFunction]
public string SendEmail(string to, ChatHistory? chatHistory = null)
{
if (chatHistory is not null)
{
output.WriteLine($"{nameof(SendEmail)} - Chat History Message Count: {chatHistory.Count}");
}
// Simulate the email-sending process by notifying the AI model that the email was sent.
return $"Email has been sent to {to}";
}
}
///
/// Helper method to initialize .
///
private static Kernel GetKernel()
{
return Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "gpt-4o",
apiKey: TestConfiguration.OpenAI.ApiKey)
.Build();
}
#endregion
}