chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.AzureAIInference;
|
||||
|
||||
namespace FunctionCalling;
|
||||
|
||||
public class AzureAIInference_FunctionCalling : BaseTest
|
||||
{
|
||||
private readonly LoggingHandler _handler;
|
||||
private readonly HttpClient _httpClient;
|
||||
private bool _isDisposed;
|
||||
|
||||
public AzureAIInference_FunctionCalling(ITestOutputHelper output) : base(output)
|
||||
{
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
this._handler = new LoggingHandler(new HttpClientHandler(), this.Output);
|
||||
this._httpClient = new(this._handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task FunctionCallingAsync()
|
||||
{
|
||||
var kernel = CreateKernel();
|
||||
AzureAIInferencePromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Given the current time of day and weather, what is the likely color of the sky in Boston?", new(settings)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task FunctionCallingWithPromptExecutionSettingsAsync()
|
||||
{
|
||||
var kernel = CreateKernel();
|
||||
PromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Given the current time of day and weather, what is the likely color of the sky in Boston?", new(settings)));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!this._isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this._handler.Dispose();
|
||||
this._httpClient.Dispose();
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private Kernel CreateKernel()
|
||||
{
|
||||
// Create kernel
|
||||
var kernel = Kernel.CreateBuilder()
|
||||
.AddAzureAIInferenceChatCompletion(
|
||||
modelId: TestConfiguration.AzureAIInference.ChatModelId,
|
||||
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
|
||||
apiKey: TestConfiguration.AzureAIInference.ApiKey,
|
||||
httpClient: this._httpClient)
|
||||
.Build();
|
||||
|
||||
// Add a plugin with some helper functions we want to allow the model to call.
|
||||
kernel.ImportPluginFromFunctions("HelperFunctions",
|
||||
[
|
||||
kernel.CreateFunctionFromMethod(() => new List<string> { "Squirrel Steals Show", "Dog Wins Lottery" }, "GetLatestNewsTitles", "Retrieves latest news titles."),
|
||||
kernel.CreateFunctionFromMethod(() => DateTime.UtcNow.ToString("R"), "GetCurrentUtcDateTime", "Retrieves the current date time in UTC."),
|
||||
kernel.CreateFunctionFromMethod((string cityName, string currentDateTime) =>
|
||||
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",
|
||||
_ => "31 and snowing",
|
||||
}, "GetWeatherForCity", "Gets the current weather for the specified city"),
|
||||
]);
|
||||
|
||||
return kernel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace FunctionCalling;
|
||||
|
||||
/// <summary>
|
||||
/// These samples demonstrate how to advertise functions to AI model based on a context.
|
||||
/// </summary>
|
||||
public class ContextDependentAdvertising(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This sample demonstrates how to advertise functions to AI model based on the context of the chat history.
|
||||
/// It advertises functions to the AI model based on the game state.
|
||||
/// For example, if the maze has not been created, advertise the create maze function only to prevent the AI model
|
||||
/// from adding traps or treasures to the maze before it is created.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task AdvertiseFunctionsDependingOnContextPerUserInteractionAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Tracking number of iterations to avoid infinite loop.
|
||||
int maxIteration = 10;
|
||||
int iteration = 0;
|
||||
|
||||
// Define the functions for AI model to call.
|
||||
var gameUtils = kernel.ImportPluginFromType<GameUtils>();
|
||||
KernelFunction createMaze = gameUtils["CreateMaze"];
|
||||
KernelFunction addTraps = gameUtils["AddTrapsToMaze"];
|
||||
KernelFunction addTreasures = gameUtils["AddTreasuresToMaze"];
|
||||
KernelFunction playGame = gameUtils["PlayGame"];
|
||||
|
||||
ChatHistory chatHistory = [];
|
||||
chatHistory.AddUserMessage("I would like to play a maze game with a lot of tricky traps and shiny treasures.");
|
||||
|
||||
// Loop until the game has started or the max iteration is reached.
|
||||
while (!chatHistory.Any(item => item.Content?.Contains("Game started.") ?? false) && iteration < maxIteration)
|
||||
{
|
||||
List<KernelFunction> functionsToAdvertise = [];
|
||||
|
||||
// Decide game state based on chat history.
|
||||
bool mazeCreated = chatHistory.Any(item => item.Content?.Contains("Maze created.") ?? false);
|
||||
bool trapsAdded = chatHistory.Any(item => item.Content?.Contains("Traps added to the maze.") ?? false);
|
||||
bool treasuresAdded = chatHistory.Any(item => item.Content?.Contains("Treasures added to the maze.") ?? false);
|
||||
|
||||
// The maze has not been created yet so advertise the create maze function.
|
||||
if (!mazeCreated)
|
||||
{
|
||||
functionsToAdvertise.Add(createMaze);
|
||||
}
|
||||
// The maze has been created so advertise the adding traps and treasures functions.
|
||||
else if (mazeCreated && (!trapsAdded || !treasuresAdded))
|
||||
{
|
||||
functionsToAdvertise.Add(addTraps);
|
||||
functionsToAdvertise.Add(addTreasures);
|
||||
}
|
||||
// Both traps and treasures have been added so advertise the play game function.
|
||||
else if (treasuresAdded && trapsAdded)
|
||||
{
|
||||
functionsToAdvertise.Add(playGame);
|
||||
}
|
||||
|
||||
// Provide the functions to the AI model.
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Required(functionsToAdvertise) };
|
||||
|
||||
// Prompt the AI model.
|
||||
ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings, kernel);
|
||||
|
||||
Console.WriteLine(result);
|
||||
|
||||
iteration++;
|
||||
}
|
||||
}
|
||||
|
||||
private static Kernel CreateKernel()
|
||||
{
|
||||
// Create kernel
|
||||
IKernelBuilder builder = Kernel.CreateBuilder();
|
||||
|
||||
builder.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
private sealed class GameUtils
|
||||
{
|
||||
[KernelFunction]
|
||||
public static string CreateMaze() => "Maze created.";
|
||||
|
||||
[KernelFunction]
|
||||
public static string AddTrapsToMaze() => "Traps added to the maze.";
|
||||
|
||||
[KernelFunction]
|
||||
public static string AddTreasuresToMaze() => "Treasures added to the maze.";
|
||||
|
||||
[KernelFunction]
|
||||
public static string PlayGame() => "Game started.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace FunctionCalling;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate how to enable and configure various aspects of function calling model in SK using the different function choice behaviors:
|
||||
/// <see cref="FunctionChoiceBehavior.Auto"/>, <see cref="FunctionChoiceBehavior.Required"/>, and <see cref="FunctionChoiceBehavior.None"/>.
|
||||
/// The behaviors define the following aspect of function calling model:
|
||||
/// 1. Function advertising - the list of functions to provide to the AI model. All three can advertise all kernel functions or a specified subset of them.
|
||||
/// 2. Function calling behavior - whether the AI model automatically selects functions to call, is forced to call provided functions, or has to describe which functions it would call without calling them to complete the prompt.
|
||||
/// 3. Function invocation - whether functions are invoked automatically by SK or manually by a caller and whether they are invoked sequentially or concurrently(not supported in auto-invocation mode yet)
|
||||
///
|
||||
/// ** Function advertising **
|
||||
/// All three behaviors have the `functions` parameter of type <see cref="IEnumerable{KernelFunction}"/>. By default, it is null,
|
||||
/// which means all kernel functions are provided or advertised to the AI model. If a list of functions is provided,
|
||||
/// only those functions are advertised to the AI model. An empty list means no functions are provided to the AI model,
|
||||
/// which is equivalent to disabling function calling.
|
||||
///
|
||||
/// ** Function calling behavior **
|
||||
/// The <see cref="FunctionChoiceBehavior.Auto"/> behavior allows the model to decide whether to call the functions and, if so, which ones to call.
|
||||
/// The <see cref="FunctionChoiceBehavior.Required"/> behavior forces the model to call the provided functions. The behavior advertises functions in the first
|
||||
/// request to the AI model only and stops advertising them in subsequent requests to prevent an infinite loop where the model keeps calling functions repeatedly.
|
||||
/// The <see cref="FunctionChoiceBehavior.None"/> behavior tells the AI model to use the provided functions without calling them to generate a response.
|
||||
/// This behavior is useful for dry runs when you want to see which functions the model would call without actually invoking them.
|
||||
///
|
||||
/// ** Function invocation **
|
||||
/// The <see cref="FunctionChoiceBehavior.Auto"/> and <see cref="FunctionChoiceBehavior.Required"/> supports two modes of function invocation: manual and automatic:
|
||||
/// * Automatic function invocation mode causes all functions chosen by the AI model to be automatically invoked by SK.
|
||||
/// The results of these function invocations are added to the chat history and sent to the model automatically in the following request.
|
||||
/// The model then reasons about the chat history and then calls functions again or generates the final response.
|
||||
/// This approach is fully automated and requires no manual intervention from the caller. The automatic invocation mode is enabled by default.
|
||||
/// * Manual invocation mode returns all function calls requested by the AI model to the SK caller. The caller is fully responsible
|
||||
/// for the invocation phase where they may decide which function to call, how to handle exceptions, call them in parallel or sequentially, etc.
|
||||
/// The caller then adds the function results/exceptions to the chat history and returns it to the model, which reasons about it
|
||||
/// and then calls functions again or generates the final response. This invocation mode provides more control over the function invocation phase to the caller.
|
||||
/// To enable manual invocation, the caller needs to set the `autoInvoke` parameter to `false` when specifying either <see cref="FunctionChoiceBehavior.Auto"/>
|
||||
/// or <see cref="FunctionChoiceBehavior.Required"/> in the <see cref="PromptExecutionSettings"/>.
|
||||
///
|
||||
/// ** Options **
|
||||
/// The following aspects of the function choice behaviors can be changed via the `options` constructor's parameter of type <see cref="FunctionChoiceBehaviorOptions"/> each behavior accepts:
|
||||
/// * The <see cref="FunctionChoiceBehaviorOptions.AllowConcurrentInvocation"/> option enables concurrent invocation of functions by SK.
|
||||
/// By default, this option is set to false, meaning that functions are invoked sequentially. Concurrent invocation is only possible if the AI model can
|
||||
/// call or select multiple functions for invocation in a single request; otherwise, there is no distinction between sequential and concurrent invocation.
|
||||
/// * The <see cref="FunctionChoiceBehaviorOptions.AllowParallelCalls"/> option instructs the AI model to call multiple functions in one request if the model supports parallel function calls.
|
||||
/// By default, this option is set to null, meaning that the AI model default value will be used.
|
||||
///
|
||||
/// The following table summarizes the effects of various combinations of the AllowParallelCalls and AllowConcurrentInvocation options:
|
||||
///
|
||||
/// | AllowParallelCalls | AllowConcurrentInvocation | # of functions chosen per AI roundtrip | Concurrent Invocation by SK |
|
||||
/// |---------------------|---------------------------|-----------------------------------------|-----------------------------|
|
||||
/// | false | false | one | false |
|
||||
/// | false | true | one | false* |
|
||||
/// | true | false | multiple | false |
|
||||
/// | true | true | multiple | true |
|
||||
///
|
||||
/// `*` There's only one function to invoke.
|
||||
/// </summary>
|
||||
public class FunctionCalling(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model and invokes them automatically.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunPromptWithAutoFunctionChoiceBehaviorAdvertisingAllKernelFunctionsInvokedAutomaticallyAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("What is the likely color of the sky in Boston today?", new(settings)));
|
||||
|
||||
// Expected output: "Boston is currently experiencing a rainy day, hence, the likely color of the sky in Boston is grey."
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of <see cref="FunctionChoiceBehavior.Required"/> that advertises only one function to the AI model and invokes it automatically.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunPromptWithRequiredFunctionChoiceBehaviorAdvertisingOneFunctionInvokedAutomaticallyAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
KernelFunction getWeatherFunction = kernel.Plugins.GetFunction("HelperFunctions", "GetWeatherForCity");
|
||||
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Required(functions: [getWeatherFunction]) };
|
||||
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Given that it is now the 9th of September 2024, 11:29 AM, what is the likely color of the sky in Boston?", new(settings)));
|
||||
|
||||
// Expected output: "The sky in Boston is likely to be grey due to the rain."
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of <see cref="FunctionChoiceBehavior.None"/> that advertises all kernel functions to the AI model.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunPromptWithNoneFunctionChoiceBehaviorAdvertisingAllKernelFunctionsAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.None() };
|
||||
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Tell me which provided functions I would need to call to get the color of the sky in Boston for today.", new(settings)));
|
||||
|
||||
// Expected output: "You would first call the `HelperFunctions-GetCurrentUtcDateTime` function to get the current date time in UTC. Then, you would use the `HelperFunctions-GetWeatherForCity` function,
|
||||
// passing in the city name as 'Boston' and the retrieved UTC date time. Note, however, that these functions won't directly tell you the color of the sky.
|
||||
// The `GetWeatherForCity` function would provide weather data, and you may infer the general sky condition (e.g., clear, cloudy, rainy) based on this data, but it would not specify the color of the sky."
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of <see cref="FunctionChoiceBehavior.Auto"/> in YAML prompt template config that advertises all kernel functions to the AI model and invokes them automatically.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunPromptTemplateConfigWithAutoFunctionChoiceBehaviorAdvertisingAllKernelFunctionsInvokedAutomaticallyAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// The `function_choice_behavior.functions` property is omitted which is equivalent to providing all kernel functions to the AI model.
|
||||
string promptTemplateConfig = """
|
||||
template_format: semantic-kernel
|
||||
template: What is the likely color of the sky in Boston today?
|
||||
execution_settings:
|
||||
default:
|
||||
function_choice_behavior:
|
||||
type: auto
|
||||
""";
|
||||
|
||||
KernelFunction promptFunction = KernelFunctionYaml.FromPromptYaml(promptTemplateConfig);
|
||||
|
||||
Console.WriteLine(await kernel.InvokeAsync(promptFunction));
|
||||
|
||||
// Expected output: "Given that it's currently raining in Boston, the sky is likely to be gray."
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of <see cref="FunctionChoiceBehavior.Auto"/> in YAML prompt template config that advertises one kernel function to the AI model and invokes it automatically.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunPromptTemplateConfigWithAutoFunctionChoiceBehaviorAdvertisingOneFunctionInvokedAutomaticallyAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// Only the `HelperFunctions.GetWeatherForCity` function which is added to the `function_choice_behavior.functions` list, is advertised to the AI model.
|
||||
string promptTemplateConfig = """
|
||||
template_format: semantic-kernel
|
||||
template: Given that it is now the 9th of September 2024, 11:29 AM, what is the likely color of the sky in Boston?
|
||||
execution_settings:
|
||||
default:
|
||||
function_choice_behavior:
|
||||
type: auto
|
||||
functions:
|
||||
- HelperFunctions.GetWeatherForCity
|
||||
""";
|
||||
|
||||
KernelFunction promptFunction = KernelFunctionYaml.FromPromptYaml(promptTemplateConfig);
|
||||
|
||||
Console.WriteLine(await kernel.InvokeAsync(promptFunction));
|
||||
|
||||
// Expected output: "The color of the sky in Boston is likely to be grey due to the rain."
|
||||
}
|
||||
|
||||
[Fact]
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of the non-streaming chat completion API with <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model and invokes them automatically.
|
||||
/// </summary>
|
||||
public async Task RunNonStreamingChatCompletionApiWithAutomaticFunctionInvocationAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// To enable automatic function invocation, set the `autoInvoke` parameter to `true` in the line below or omit it as it is `true` by default.
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(
|
||||
"What is the likely color of the sky in Boston today?",
|
||||
settings,
|
||||
kernel);
|
||||
|
||||
// Assert
|
||||
Console.WriteLine(result);
|
||||
|
||||
// Expected output: "The likely color of the sky in Boston is gray due to the current rainy weather."
|
||||
}
|
||||
|
||||
[Fact]
|
||||
/// <summary>
|
||||
/// This example demonstrates the usage of the streaming chat completion API with <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model and invokes them automatically.
|
||||
/// </summary>
|
||||
public async Task RunStreamingChatCompletionApiWithAutomaticFunctionInvocationAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// To enable automatic function invocation, set the `autoInvoke` parameter to `true` in the line below or omit it as it is `true` by default.
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
// Act
|
||||
await foreach (var update in chatCompletionService.GetStreamingChatMessageContentsAsync(
|
||||
"What is the likely color of the sky in Boston today?",
|
||||
settings,
|
||||
kernel))
|
||||
{
|
||||
stringBuilder.Append(update.Content);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Console.WriteLine(stringBuilder.ToString());
|
||||
|
||||
// Expected output: "Given that it's currently daytime and rainy in Boston, the sky is likely to be grey or overcast."
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates the usage of the non-streaming chat completion API with <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model and invokes them manually.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunNonStreamingChatCompletionApiWithManualFunctionInvocationAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// To enable manual function invocation, set the `autoInvoke` parameter to `false`.
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = Microsoft.SemanticKernel.FunctionChoiceBehavior.Auto(autoInvoke: false) };
|
||||
|
||||
ChatHistory chatHistory = [];
|
||||
chatHistory.AddUserMessage("What is the likely color of the sky in Boston today?");
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Start or continue chat based on the chat history
|
||||
ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings, kernel);
|
||||
if (result.Content is not null)
|
||||
{
|
||||
Console.Write(result.Content);
|
||||
// Expected output: "The color of the sky in Boston is likely to be gray due to the rainy weather."
|
||||
}
|
||||
|
||||
// Get function calls from the chat message content and quit the chat loop if no function calls are found.
|
||||
IEnumerable<FunctionCallContent> functionCalls = FunctionCallContent.GetFunctionCalls(result);
|
||||
if (!functionCalls.Any())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Preserving the original chat message content with function calls in the chat history.
|
||||
chatHistory.Add(result);
|
||||
|
||||
// Iterating over the requested function calls and invoking them sequentially.
|
||||
// The code can easily be modified to invoke functions in concurrently if needed.
|
||||
foreach (FunctionCallContent functionCall in functionCalls)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Invoking the function
|
||||
FunctionResultContent resultContent = await functionCall.InvokeAsync(kernel);
|
||||
|
||||
// Adding the function result to the chat history
|
||||
chatHistory.Add(resultContent.ToChatMessage());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Adding function exception to the chat history.
|
||||
chatHistory.Add(new FunctionResultContent(functionCall, ex).ToChatMessage());
|
||||
// or
|
||||
//chatHistory.Add(new FunctionResultContent(functionCall, "Error details that the AI model can reason about.").ToChatMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates the usage of the streaming chat completion API with <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model and invokes them manually.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RunStreamingChatCompletionApiWithManualFunctionCallingAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// To enable manual function invocation, set the `autoInvoke` parameter to `false`.
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = Microsoft.SemanticKernel.FunctionChoiceBehavior.Auto(autoInvoke: false) };
|
||||
|
||||
// Create chat history with the initial user message
|
||||
ChatHistory chatHistory = [];
|
||||
chatHistory.AddUserMessage("What is the likely color of the sky in Boston today?");
|
||||
|
||||
while (true)
|
||||
{
|
||||
AuthorRole? authorRole = null;
|
||||
var fccBuilder = new FunctionCallContentBuilder();
|
||||
|
||||
// Start or continue streaming chat based on the chat history
|
||||
await foreach (var streamingContent in chatCompletionService.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel))
|
||||
{
|
||||
if (streamingContent.Content is not null)
|
||||
{
|
||||
Console.Write(streamingContent.Content);
|
||||
// Streamed output: "The color of the sky in Boston is likely to be gray due to the rainy weather."
|
||||
}
|
||||
authorRole ??= streamingContent.Role;
|
||||
fccBuilder.Append(streamingContent);
|
||||
}
|
||||
|
||||
// Build the function calls from the streaming content and quit the chat loop if no function calls are found
|
||||
var functionCalls = fccBuilder.Build();
|
||||
if (!functionCalls.Any())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Creating and adding chat message content to preserve the original function calls in the chat history.
|
||||
// The function calls are added to the chat message a few lines below.
|
||||
var fcContent = new ChatMessageContent(role: authorRole ?? default, content: null);
|
||||
chatHistory.Add(fcContent);
|
||||
|
||||
// Iterating over the requested function calls and invoking them.
|
||||
// The code can easily be modified to invoke functions in concurrently if needed.
|
||||
foreach (var functionCall in functionCalls)
|
||||
{
|
||||
// Adding the original function call to the chat message content
|
||||
fcContent.Items.Add(functionCall);
|
||||
|
||||
// Invoking the function
|
||||
var functionResult = await functionCall.InvokeAsync(kernel);
|
||||
|
||||
// Adding the function result to the chat history
|
||||
chatHistory.Add(functionResult.ToChatMessage());
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how a simulated function can be added to the chat history a manual function mode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Simulated functions are not called or requested by the AI model but are added to the chat history by the caller.
|
||||
/// They provide a way for callers to add additional information that, if provided via the prompt, would be ignored due to the model training.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task RunNonStreamingPromptWithSimulatedFunctionAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
// Enabling manual function invocation
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = Microsoft.SemanticKernel.FunctionChoiceBehavior.Auto(autoInvoke: false) };
|
||||
|
||||
ChatHistory chatHistory = [];
|
||||
chatHistory.AddUserMessage("What is the likely color of the sky in Boston today?");
|
||||
|
||||
while (true)
|
||||
{
|
||||
ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, settings, kernel);
|
||||
if (result.Content is not null)
|
||||
{
|
||||
Console.Write(result.Content);
|
||||
// Expected output: "Considering the current weather conditions in Boston with a tornado watch in effect resulting in potential severe thunderstorms,
|
||||
// the sky color is likely unusual such as green, yellow, or dark gray. Please stay safe and follow instructions from local authorities."
|
||||
}
|
||||
|
||||
chatHistory.Add(result); // Adding AI model response containing function calls(requests) to chat history as it's required by the models.
|
||||
|
||||
IEnumerable<FunctionCallContent> functionCalls = FunctionCallContent.GetFunctionCalls(result);
|
||||
if (!functionCalls.Any())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (FunctionCallContent functionCall in functionCalls)
|
||||
{
|
||||
FunctionResultContent resultContent = await functionCall.InvokeAsync(kernel); // Invoking each function.
|
||||
|
||||
chatHistory.Add(resultContent.ToChatMessage());
|
||||
}
|
||||
|
||||
// Adding a simulated function call to the connector response message
|
||||
FunctionCallContent simulatedFunctionCall = new("weather-alert", id: "call_123");
|
||||
result.Items.Add(simulatedFunctionCall);
|
||||
|
||||
// Adding a simulated function result to chat history
|
||||
string simulatedFunctionResult = "A Tornado Watch has been issued, with potential for severe thunderstorms causing unusual sky colors like green, yellow, or dark gray. Stay informed and follow safety instructions from authorities.";
|
||||
chatHistory.Add(new FunctionResultContent(simulatedFunctionCall, simulatedFunctionResult).ToChatMessage());
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how to disable function calling.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task DisableFunctionCallingAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// Supplying an empty list to the `functions` parameter disables function calling.
|
||||
// Alternatively, either omit assigning anything to the `FunctionChoiceBehavior` property or assign null to it to also disable function calling.
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(functions: []) };
|
||||
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("What is the likely color of the sky in Boston today?", new(settings)));
|
||||
|
||||
// Expected output: "Sorry, I cannot answer this question as it requires real-time information which I, as a text-based model, cannot access."
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This example demonstrates how to disable function calling in the YAML prompt template config.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task DisableFunctionCallingInPromptTemplateConfigAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// The `function_choice_behavior.functions` property is an empty list which disables function calling.
|
||||
// Alternatively, you can omit the `function_choice_behavior` property to disable function calling.
|
||||
string promptTemplateConfig = """
|
||||
template_format: semantic-kernel
|
||||
template: Given that it is now the 9th of September 2024, 11:29 AM, what is the likely color of the sky in Boston?
|
||||
execution_settings:
|
||||
default:
|
||||
function_choice_behavior:
|
||||
type: auto
|
||||
functions: []
|
||||
""";
|
||||
|
||||
KernelFunction promptFunction = KernelFunctionYaml.FromPromptYaml(promptTemplateConfig);
|
||||
|
||||
Console.WriteLine(await kernel.InvokeAsync(promptFunction));
|
||||
|
||||
// Expected output: "As an AI, I don't have real-time data or live feed to provide current weather conditions or the color of the sky."
|
||||
}
|
||||
|
||||
[Fact]
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of the non-streaming chat completion API with <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model and invokes them automatically in concurrent manner.
|
||||
/// </summary>
|
||||
public async Task RunNonStreamingChatCompletionApiWithConcurrentFunctionInvocationOptionAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// The `AllowConcurrentInvocation` option enables concurrent invocation of functions.
|
||||
FunctionChoiceBehaviorOptions options = new() { AllowConcurrentInvocation = true };
|
||||
|
||||
// To enable automatic function invocation, set the `autoInvoke` parameter to `true` in the line below or omit it as it is `true` by default.
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: options) };
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(
|
||||
"Good morning! What’s the current time and latest news headlines?",
|
||||
settings,
|
||||
kernel);
|
||||
|
||||
// Assert
|
||||
Console.WriteLine(result);
|
||||
|
||||
// Expected output: Good morning! The current UTC time is 07:47 on October 22, 2024. Here are the latest news headlines: 1. Squirrel Steals Show - Discover the unexpected star of a recent event. 2. Dog Wins Lottery - Unbelievably, a lucky canine has hit the jackpot.
|
||||
}
|
||||
|
||||
[Fact]
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of the non-streaming chat completion API with <see cref="FunctionChoiceBehavior.Auto"/> that
|
||||
/// advertises all kernel functions to the AI model and instructs the model to call multiple functions in parallel.
|
||||
/// </summary>
|
||||
public async Task RunNonStreamingChatCompletionApiWithParallelFunctionCallOptionAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// The `AllowParallelCalls` option instructs the AI model to call multiple functions in parallel if the model supports parallel function calls.
|
||||
FunctionChoiceBehaviorOptions options = new() { AllowParallelCalls = true };
|
||||
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: options) };
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(
|
||||
"Good morning! What’s the current time and latest news headlines?",
|
||||
settings,
|
||||
kernel);
|
||||
|
||||
// Assert
|
||||
Console.WriteLine(result);
|
||||
|
||||
// Expected output: Good morning! The current UTC time is 07:47 on October 22, 2024. Here are the latest news headlines: 1. Squirrel Steals Show - Discover the unexpected star of a recent event. 2. Dog Wins Lottery - Unbelievably, a lucky canine has hit the jackpot.
|
||||
}
|
||||
|
||||
[Fact]
|
||||
/// <summary>
|
||||
/// This example demonstrates usage of the non-streaming chat completion API with <see cref="FunctionChoiceBehavior.Auto"/> that
|
||||
/// advertises all kernel functions to the AI model, instructs the model to call multiple functions in parallel, and invokes them concurrently.
|
||||
/// </summary>
|
||||
public async Task RunNonStreamingChatCompletionApiWithParallelFunctionCallAndConcurrentFunctionInvocationOptionsAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// The `AllowParallelCalls` option instructs the AI model to call multiple functions in parallel if the model supports parallel function calls.
|
||||
// The `AllowConcurrentInvocation` option enables concurrent invocation of the functions.
|
||||
FunctionChoiceBehaviorOptions options = new() { AllowParallelCalls = true, AllowConcurrentInvocation = true };
|
||||
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: options) };
|
||||
|
||||
IChatCompletionService chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||
|
||||
ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(
|
||||
"Good morning! What’s the current time and latest news headlines?",
|
||||
settings,
|
||||
kernel);
|
||||
|
||||
// Assert
|
||||
Console.WriteLine(result);
|
||||
|
||||
// Expected output: Good morning! The current UTC time is 07:47 on October 22, 2024. Here are the latest news headlines: 1. Squirrel Steals Show - Discover the unexpected star of a recent event. 2. Dog Wins Lottery - Unbelievably, a lucky canine has hit the jackpot.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a kernel with the OpenAI chat completion model and some helper functions.
|
||||
/// </summary>
|
||||
/// <param name="output">Optionally set this to log the function calling requests and responses</param>
|
||||
private static Kernel CreateKernel(ITestOutputHelper? output = null)
|
||||
{
|
||||
// Create kernel
|
||||
IKernelBuilder builder = Kernel.CreateBuilder();
|
||||
|
||||
// Create a logging handler to output HTTP requests and responses
|
||||
if (output is not null)
|
||||
{
|
||||
builder.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
}
|
||||
|
||||
Kernel kernel = builder.Build();
|
||||
|
||||
// Add a plugin with some helper functions we want to allow the model to call.
|
||||
kernel.ImportPluginFromFunctions("HelperFunctions",
|
||||
[
|
||||
kernel.CreateFunctionFromMethod(() => new List<string> { "Squirrel Steals Show", "Dog Wins Lottery" }, "GetLatestNewsTitles", "Retrieves latest news titles."),
|
||||
kernel.CreateFunctionFromMethod(() => DateTime.UtcNow.ToString("R"), "GetCurrentDateTimeInUtc", "Retrieves the current date time in UTC."),
|
||||
kernel.CreateFunctionFromMethod((string cityName, string currentDateTimeInUtc) =>
|
||||
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",
|
||||
_ => "31 and snowing",
|
||||
}, "GetWeatherForCity", "Gets the current weather for the specified city and specified date time."),
|
||||
]);
|
||||
|
||||
return kernel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace FunctionCalling;
|
||||
|
||||
/// <summary>
|
||||
/// These samples illustrate how function return type metadata can be communicated to the AI model, allowing it to reason about the function's return value.
|
||||
/// Currently, there is no well-defined, industry-wide standard for providing function return type metadata to AI models.
|
||||
/// Until such a standard is established, the following techniques can be considered for scenarios where the names of return type properties are insufficient
|
||||
/// for AI models to reason about their content, or where additional context or handling instructions need to be associated with the return type to model or enhance
|
||||
/// your scenarios.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The properties of the WeatherData classes used in the samples are intentionally given generic names(e.g., Data1, Data2, Data3, Data4) to abstract their meanings
|
||||
/// for samples purposes only.This approach prevents the model from making assumptions about their content based solely on their names and encourages the model to
|
||||
/// utilize other return type metadata, such as descriptions or schemas, to reason about their content.
|
||||
/// Before employing any of these techniques, it is recommended to ensure that the property names of the return types of your functions are descriptive enough
|
||||
/// to convey their purpose/content.
|
||||
/// </remarks>
|
||||
public class FunctionCalling_ReturnMetadata(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[Fact]
|
||||
/// <summary>
|
||||
/// This sample demonstrates how to describe the return type of a function to the AI model using the function description attribute.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This information is provided to the AI model during the function advertisement step.
|
||||
/// The description includes only the property names and their descriptions, without any type information.
|
||||
/// This approach may be useful when type information is not critical and minimizing token consumption is a priority.
|
||||
/// Additionally, type information in the description must be added manually and updated each time the return type changes.
|
||||
/// </remarks>
|
||||
public async Task ProvideFunctionReturnTypeDescriptionInFunctionDescriptionAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// Import plugin that has a return type described in the function description.
|
||||
kernel.ImportPluginFromType<WeatherPlugin1>();
|
||||
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
|
||||
FunctionResult result = await kernel.InvokePromptAsync("What is the current weather?", new(settings));
|
||||
|
||||
Console.WriteLine(result);
|
||||
// Output: The current weather is as follows:
|
||||
// - Temperature: 35°C
|
||||
// - Humidity: 20%
|
||||
// - Dew Point: 10°C
|
||||
// - Wind Speed: 15 km/h
|
||||
}
|
||||
|
||||
[Fact]
|
||||
/// <summary>
|
||||
/// This sample demonstrates how to provide the return type schema of a function to the AI model using the function description attribute.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This information is supplied to the AI model during the function advertisement step.
|
||||
/// The description includes the return type schema in JSON format, detailing the property names, descriptions, and types.
|
||||
/// This approach is recommended when type information is essential.
|
||||
/// As with the previous sample, the return type schema must be added manually and updated each time the return type changes.
|
||||
/// </remarks>
|
||||
public async Task ProvideFunctionReturnTypeSchemaInFunctionDescriptionAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
// Import plugin that has a return type schema in the function description.
|
||||
kernel.ImportPluginFromType<WeatherPlugin2>();
|
||||
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
|
||||
FunctionResult result = await kernel.InvokePromptAsync("What is the current weather?", new(settings));
|
||||
|
||||
Console.WriteLine(result);
|
||||
// Output: The current weather details is as follows:
|
||||
// - Temperature: 35°C
|
||||
// - Humidity: 20%
|
||||
// - Dew Point: 10°C
|
||||
// - Wind Speed: 15 km/h
|
||||
}
|
||||
|
||||
[Fact]
|
||||
/// <summary>
|
||||
/// This sample demonstrates how to provide the return type schema of a function to the AI model as part of the function's return value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This information is supplied to the AI model during the function invocation step, rather than during the function advertisement step.
|
||||
/// This approach can help reduce token consumption, particularly in situations where only a few out of many available functions are called.
|
||||
/// The return type schema for the functions invoked by the AI model will be returned to the AI model along with the invocation result,
|
||||
/// while the schemas for the return types of functions that were not invoked will never be provided.
|
||||
/// This method does not require the return type schema to be provided manually and updated each time the return type changes, as the schema
|
||||
/// is extracted automatically by SK.
|
||||
/// </remarks>
|
||||
public async Task ProvideFunctionReturnTypeSchemaAsPartOfFunctionReturnValueAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernel();
|
||||
|
||||
/// Register the auto function invocation filter that replaces the original function's result
|
||||
/// with a new result that includes both the original result and its schema.
|
||||
kernel.AutoFunctionInvocationFilters.Add(new AddReturnTypeSchemaFilter());
|
||||
|
||||
// Import the plugin that provides descriptions for the return type properties.
|
||||
// This additional information is used when extracting the schema from the return type.
|
||||
kernel.ImportPluginFromType<WeatherPlugin3>();
|
||||
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
|
||||
FunctionResult result = await kernel.InvokePromptAsync("What is the current weather?", new(settings));
|
||||
|
||||
Console.WriteLine(result);
|
||||
// Output: The current weather conditions are as follows:
|
||||
// - Temperature: 35°C
|
||||
// - Humidity: 20 %
|
||||
// - Dew Point: 10°C
|
||||
// - Wind Speed: 15 km/h
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A plugin that provides the current weather data and describes the return type in the function <see cref="DescriptionAttribute"/>.
|
||||
/// </summary>
|
||||
private sealed class WeatherPlugin1
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Returns current weather: Data1 - Temperature (°C), Data2 - Humidity (%), Data3 - Dew Point (°C), Data4 - Wind Speed (km/h)")]
|
||||
public WeatherData GetWeatherData()
|
||||
{
|
||||
return new WeatherData()
|
||||
{
|
||||
Data1 = 35.0, // Temperature in degrees Celsius
|
||||
Data2 = 20.0, // Humidity in percentage
|
||||
Data3 = 10.0, // Dew point in degrees Celsius
|
||||
Data4 = 15.0 // Wind speed in kilometers per hour
|
||||
};
|
||||
}
|
||||
public sealed class WeatherData
|
||||
{
|
||||
public double Data1 { get; set; }
|
||||
public double Data2 { get; set; }
|
||||
public double Data3 { get; set; }
|
||||
public double Data4 { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A plugin that provides the current weather data and specifies the return type schema in the function <see cref="DescriptionAttribute"/>.
|
||||
/// </summary>
|
||||
private sealed class WeatherPlugin2
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("""Returns current weather: {"type":"object","properties":{"Data1":{"description":"Temperature (°C)","type":"number"},"Data2":{"description":"Humidity(%)","type":"number"}, Data3":{"description":"Dew point (°C)","type":"number"},"Data4":{"description":"Wind speed (km/h)","type":"number"}}}""")]
|
||||
public WeatherData GetWeatherData()
|
||||
{
|
||||
return new WeatherData()
|
||||
{
|
||||
Data1 = 35.0, // Temperature in degrees Celsius
|
||||
Data2 = 20.0, // Humidity in percentage
|
||||
Data3 = 10.0, // Dew point in degrees Celsius
|
||||
Data4 = 15.0 // Wind speed in kilometers per hour
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class WeatherData
|
||||
{
|
||||
public double Data1 { get; set; }
|
||||
public double Data2 { get; set; }
|
||||
public double Data3 { get; set; }
|
||||
public double Data4 { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A plugin that provides the current weather data and provides descriptions for the return type properties.
|
||||
/// </summary>
|
||||
private sealed class WeatherPlugin3
|
||||
{
|
||||
[KernelFunction]
|
||||
public WeatherData GetWeatherData()
|
||||
{
|
||||
return new WeatherData()
|
||||
{
|
||||
Data1 = 35.0, // Temperature in degrees Celsius
|
||||
Data2 = 20.0, // Humidity in percentage
|
||||
Data3 = 10.0, // Dew point in degrees Celsius
|
||||
Data4 = 15.0 // Wind speed in kilometers per hour
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class WeatherData
|
||||
{
|
||||
[Description("Temp (°C)")]
|
||||
public double Data1 { get; set; }
|
||||
|
||||
[Description("Humidity (%)")]
|
||||
public double Data2 { get; set; }
|
||||
|
||||
[Description("Dew point (°C)")]
|
||||
public double Data3 { get; set; }
|
||||
|
||||
[Description("Wind speed (km/h)")]
|
||||
public double Data4 { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A auto function invocation filter that replaces the original function's result with a new result that includes both the original result and its schema.
|
||||
/// </summary>
|
||||
private sealed class AddReturnTypeSchemaFilter : IAutoFunctionInvocationFilter
|
||||
{
|
||||
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
|
||||
{
|
||||
// Invoke the function
|
||||
await next(context);
|
||||
|
||||
// Crete the result with the schema
|
||||
FunctionResultWithSchema resultWithSchema = new()
|
||||
{
|
||||
Value = context.Result.GetValue<object>(), // Get the original result
|
||||
Schema = context.Function.Metadata.ReturnParameter?.Schema // Get the function return type schema
|
||||
};
|
||||
|
||||
// Return the result with the schema instead of the original one
|
||||
context.Result = new FunctionResult(context.Result, resultWithSchema);
|
||||
}
|
||||
|
||||
private sealed class FunctionResultWithSchema
|
||||
{
|
||||
public object? Value { get; set; }
|
||||
|
||||
public KernelJsonSchema? Schema { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of the <see cref="Kernel"/> with the OpenAI chat completion service.
|
||||
/// </summary>
|
||||
private static Kernel CreateKernel()
|
||||
{
|
||||
// Create kernel
|
||||
IKernelBuilder builder = Kernel.CreateBuilder();
|
||||
|
||||
builder.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Resources;
|
||||
|
||||
namespace FunctionCalling;
|
||||
|
||||
/// <summary>
|
||||
/// This sample demonstrates the way SK plugins can share local state to save and retrieve data.
|
||||
/// </summary>
|
||||
public class FunctionCalling_SharedState(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// This sample demonstrates a scenario where a text is summarized, translated, and printed to the console.
|
||||
/// The process is orchestrated by an AI model that calls plugins to execute each step.
|
||||
/// When the first plugin is called, it summarizes the provided text and stores it in the local state, returning a state ID to the AI model.
|
||||
/// The next plugin is called to translate the text stored in the local state using the state ID returned by the first plugin.
|
||||
/// The plugin translates the text and stores the translation in the local state as well, returning a new state ID to the AI model.
|
||||
/// The last plugin is called by the AI model to print the translated text to the console using the state ID returned by the second plugin.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task SaveSharedStateInLocalStoreAsync()
|
||||
{
|
||||
IKernelBuilder builder = Kernel.CreateBuilder();
|
||||
builder.AddOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey);
|
||||
|
||||
// Register the output helper used by the ConsolePlugin
|
||||
builder.Services.AddSingleton(this.Output);
|
||||
|
||||
// Register the state service
|
||||
builder.Services.AddSingleton<LocalStateService>();
|
||||
|
||||
// Register the plugins
|
||||
builder.Plugins.AddFromType<SummarizationPlugin>();
|
||||
builder.Plugins.AddFromType<TranslationPlugin>();
|
||||
builder.Plugins.AddFromType<ConsolePlugin>();
|
||||
|
||||
Kernel kernel = builder.Build();
|
||||
|
||||
// Enable function calling
|
||||
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
|
||||
|
||||
// Call the AI model to summarize, translate, and print the translation
|
||||
string textToSummarizeAndTranslate = EmbeddedResource.Read("travel-destination-overview.txt");
|
||||
|
||||
FunctionResult result = await kernel.InvokePromptAsync($"Summarize the text, translate to English and display the result: {textToSummarizeAndTranslate}", new(settings));
|
||||
|
||||
Console.WriteLine(result);
|
||||
|
||||
// Expected output: Ireland is an attractive travel destination with impressive landscapes, rich culture, and famous attractions such as Dublin, Trinity College, the Book of Kells, and the Guinness Storehouse.
|
||||
// In addition to urban experiences, it offers nature enthusiasts numerous outdoor activities, such as exploring the Ring of Kerry, the Cliffs of Moher, and numerous national parks and hiking trails.
|
||||
}
|
||||
|
||||
private sealed class SummarizationPlugin(LocalStateService stateService)
|
||||
{
|
||||
[KernelFunction, Description("Summarize the text and store the summary in state. Returns the state ID.")]
|
||||
public async Task<string> Summarize(Kernel kernel, string text)
|
||||
{
|
||||
// Use AI model to summarize the text
|
||||
FunctionResult result = await kernel.InvokePromptAsync($"Summarize the key points of the text in two sentences: {text}");
|
||||
|
||||
// Store the summary in state
|
||||
string stateId = Guid.NewGuid().ToString();
|
||||
|
||||
stateService.SetState(stateId, result.ToString());
|
||||
|
||||
return stateId;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TranslationPlugin(LocalStateService stateService)
|
||||
{
|
||||
[KernelFunction, Description("Translate the text from state identified by stateId to the specified language and store the translation in state. Returns the state ID.")]
|
||||
public async Task<string> Translate(Kernel kernel, string stateId, string language)
|
||||
{
|
||||
// Retrieve the text for translation from state
|
||||
string textToTranslate = stateService.GetState(stateId);
|
||||
|
||||
// Use AI model to translate the text. Alternatively, a translation service could be used.
|
||||
FunctionResult result = await kernel.InvokePromptAsync($"Translate the text: {textToTranslate} to {language}");
|
||||
|
||||
// Store the translation in state
|
||||
string targetStateId = Guid.NewGuid().ToString();
|
||||
|
||||
stateService.SetState(targetStateId, result.ToString());
|
||||
|
||||
return targetStateId;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ConsolePlugin(LocalStateService stateService, ITestOutputHelper outputHelper)
|
||||
{
|
||||
[KernelFunction, Description("Print the text from state identified by stateId to the console.")]
|
||||
public void Print(string stateId)
|
||||
{
|
||||
// Retrieve the text from state
|
||||
string text = stateService.GetState(stateId);
|
||||
|
||||
outputHelper.WriteLine(text);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LocalStateService
|
||||
{
|
||||
private readonly Dictionary<string, string> _state = [];
|
||||
|
||||
public string GetState(string id)
|
||||
{
|
||||
if (this._state.TryGetValue(id, out string? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
throw new KeyNotFoundException($"State with ID {id} not found.");
|
||||
}
|
||||
|
||||
public void SetState(string id, string value)
|
||||
{
|
||||
this._state[id] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.Google;
|
||||
using xRetry;
|
||||
|
||||
namespace FunctionCalling;
|
||||
|
||||
/// <summary>
|
||||
/// These examples demonstrate two ways functions called by the Gemini LLM can be invoked using the SK streaming and non-streaming AI API:
|
||||
///
|
||||
/// 1. Automatic Invocation by SK (with and without nullable properties):
|
||||
/// Functions called by the LLM are invoked automatically by SK. The results of these function invocations
|
||||
/// are automatically added to the chat history and returned to the LLM. The LLM reasons about the chat history
|
||||
/// and generates the final response.
|
||||
/// This approach is fully automated and requires no manual intervention from the caller.
|
||||
///
|
||||
/// 2. Manual Invocation by a Caller:
|
||||
/// Functions called by the LLM are returned to the AI API caller. The caller controls the invocation phase where
|
||||
/// they may decide which function to call, when to call them, how to handle exceptions, call them in parallel or sequentially, etc.
|
||||
/// The caller then adds the function results or exceptions to the chat history and returns it to the LLM, which reasons about it
|
||||
/// and generates the final response.
|
||||
/// This approach is manual and provides more control over the function invocation phase to the caller.
|
||||
/// </summary>
|
||||
public sealed class Gemini_FunctionCalling(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
[RetryFact]
|
||||
public async Task GoogleAIChatCompletionWithFunctionCalling()
|
||||
{
|
||||
Console.WriteLine("============= Google AI - Gemini Chat Completion with function calling =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.Gemini.ModelId);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.GoogleAI.Gemini.ModelId,
|
||||
apiKey: TestConfiguration.GoogleAI.ApiKey)
|
||||
.Build();
|
||||
|
||||
await this.RunSampleAsync(kernel);
|
||||
}
|
||||
|
||||
[RetryFact]
|
||||
public async Task VertexAIChatCompletionWithFunctionCalling()
|
||||
{
|
||||
Console.WriteLine("============= Vertex AI - Gemini Chat Completion with function calling =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Location);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Gemini.ModelId);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddVertexAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
bearerKey: TestConfiguration.VertexAI.BearerKey,
|
||||
location: TestConfiguration.VertexAI.Location,
|
||||
projectId: TestConfiguration.VertexAI.ProjectId)
|
||||
.Build();
|
||||
|
||||
// To generate bearer key, you need installed google sdk or use Google web console with command:
|
||||
//
|
||||
// gcloud auth print-access-token
|
||||
//
|
||||
// Above code pass bearer key as string, it is not recommended way in production code,
|
||||
// especially if IChatCompletionService will be long-lived, tokens generated by google sdk lives for 1 hour.
|
||||
// You should use bearer key provider, which will be used to generate token on demand:
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Kernel kernel = Kernel.CreateBuilder()
|
||||
// .AddVertexAIGeminiChatCompletion(
|
||||
// modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
// bearerKeyProvider: () =>
|
||||
// {
|
||||
// // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
|
||||
// // This delegate will be called on every request,
|
||||
// // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
|
||||
// return GetBearerKey();
|
||||
// },
|
||||
// location: TestConfiguration.VertexAI.Location,
|
||||
// projectId: TestConfiguration.VertexAI.ProjectId);
|
||||
|
||||
await this.RunSampleAsync(kernel);
|
||||
}
|
||||
|
||||
[RetryFact]
|
||||
public async Task GoogleAIFunctionCallingNullable()
|
||||
{
|
||||
Console.WriteLine("============= Google AI - Gemini Chat Completion with function calling (nullable properties) =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.GoogleAI.ApiKey);
|
||||
|
||||
var kernelBuilder = Kernel.CreateBuilder()
|
||||
.AddGoogleAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
apiKey: TestConfiguration.GoogleAI.ApiKey);
|
||||
|
||||
kernelBuilder.Plugins.AddFromType<MyWeatherPlugin>();
|
||||
|
||||
var promptExecutionSettings = new GeminiPromptExecutionSettings()
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
|
||||
};
|
||||
|
||||
var kernel = kernelBuilder.Build();
|
||||
|
||||
var response = await kernel.InvokePromptAsync("Hi, what's the weather in New York?", new(promptExecutionSettings));
|
||||
|
||||
Console.WriteLine(response.ToString());
|
||||
}
|
||||
|
||||
private sealed class MyWeatherPlugin
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Get the weather for a given location.")]
|
||||
private string GetWeather(WeatherRequest request)
|
||||
{
|
||||
return $"The weather in {request?.Location} is sunny.";
|
||||
}
|
||||
}
|
||||
|
||||
[RetryFact]
|
||||
public async Task VertexAIFunctionCallingNullable()
|
||||
{
|
||||
Console.WriteLine("============= Vertex AI - Gemini Chat Completion with function calling (nullable properties) =============");
|
||||
|
||||
Assert.NotNull(TestConfiguration.VertexAI.BearerKey);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.Location);
|
||||
Assert.NotNull(TestConfiguration.VertexAI.ProjectId);
|
||||
|
||||
var kernelBuilder = Kernel.CreateBuilder()
|
||||
.AddVertexAIGeminiChatCompletion(
|
||||
modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
bearerKey: TestConfiguration.VertexAI.BearerKey,
|
||||
location: TestConfiguration.VertexAI.Location,
|
||||
projectId: TestConfiguration.VertexAI.ProjectId);
|
||||
|
||||
// To generate bearer key, you need installed google sdk or use Google web console with command:
|
||||
//
|
||||
// gcloud auth print-access-token
|
||||
//
|
||||
// Above code pass bearer key as string, it is not recommended way in production code,
|
||||
// especially if IChatCompletionService will be long-lived, tokens generated by google sdk lives for 1 hour.
|
||||
// You should use bearer key provider, which will be used to generate token on demand:
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Kernel kernel = Kernel.CreateBuilder()
|
||||
// .AddVertexAIGeminiChatCompletion(
|
||||
// modelId: TestConfiguration.VertexAI.Gemini.ModelId,
|
||||
// bearerKeyProvider: () =>
|
||||
// {
|
||||
// // This is just example, in production we recommend using Google SDK to generate your BearerKey token.
|
||||
// // This delegate will be called on every request,
|
||||
// // when providing the token consider using caching strategy and refresh token logic when it is expired or close to expiration.
|
||||
// return GetBearerKey();
|
||||
// },
|
||||
// location: TestConfiguration.VertexAI.Location,
|
||||
// projectId: TestConfiguration.VertexAI.ProjectId);
|
||||
|
||||
kernelBuilder.Plugins.AddFromType<MyWeatherPlugin>();
|
||||
|
||||
var promptExecutionSettings = new GeminiPromptExecutionSettings()
|
||||
{
|
||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
|
||||
};
|
||||
var kernel = kernelBuilder.Build();
|
||||
var response = await kernel.InvokePromptAsync("Hi, what's the weather in New York?", new(promptExecutionSettings));
|
||||
Console.WriteLine(response.ToString());
|
||||
}
|
||||
|
||||
private async Task RunSampleAsync(Kernel kernel)
|
||||
{
|
||||
// Add a plugin with some helper functions we want to allow the model to utilize.
|
||||
kernel.ImportPluginFromFunctions("HelperFunctions",
|
||||
[
|
||||
kernel.CreateFunctionFromMethod(() => DateTime.UtcNow.ToString("R"), "GetCurrentUtcTime", "Retrieves the current time in UTC."),
|
||||
kernel.CreateFunctionFromMethod((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",
|
||||
_ => "31 and snowing",
|
||||
}, "Get_Weather_For_City", "Gets the current weather for the specified city"),
|
||||
]);
|
||||
|
||||
Console.WriteLine("======== Example 1: Use automated function calling with a non-streaming prompt ========");
|
||||
{
|
||||
GeminiPromptExecutionSettings settings = new() { ToolCallBehavior = GeminiToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
Console.WriteLine(await kernel.InvokePromptAsync(
|
||||
"Check current UTC time, and return current weather in Paris city", new(settings)));
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine("======== Example 2: Use automated function calling with a streaming prompt ========");
|
||||
{
|
||||
GeminiPromptExecutionSettings settings = new() { ToolCallBehavior = GeminiToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
await foreach (var update in kernel.InvokePromptStreamingAsync(
|
||||
"Check current UTC time, and return current weather in Boston city", new(settings)))
|
||||
{
|
||||
Console.Write(update);
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine("======== Example 3: Use manual function calling with a non-streaming prompt ========");
|
||||
{
|
||||
var chat = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var chatHistory = new ChatHistory();
|
||||
|
||||
GeminiPromptExecutionSettings settings = new() { ToolCallBehavior = GeminiToolCallBehavior.EnableKernelFunctions };
|
||||
chatHistory.AddUserMessage("Check current UTC time, and return current weather in London city");
|
||||
while (true)
|
||||
{
|
||||
var result = (GeminiChatMessageContent)await chat.GetChatMessageContentAsync(chatHistory, settings, kernel);
|
||||
|
||||
if (result.Content is not null)
|
||||
{
|
||||
Console.Write(result.Content);
|
||||
}
|
||||
|
||||
if (result.ToolCalls is not { Count: > 0 })
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
chatHistory.Add(result);
|
||||
foreach (var toolCall in result.ToolCalls)
|
||||
{
|
||||
KernelArguments? arguments = null;
|
||||
if (kernel.Plugins.TryGetFunction(toolCall.PluginName, toolCall.FunctionName, out var function))
|
||||
{
|
||||
// Add parameters to arguments
|
||||
if (toolCall.Arguments is not null)
|
||||
{
|
||||
arguments = [];
|
||||
foreach (var parameter in toolCall.Arguments)
|
||||
{
|
||||
arguments[parameter.Key] = parameter.Value?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Unable to find function. Please try again!");
|
||||
continue;
|
||||
}
|
||||
|
||||
var functionResponse = await function.InvokeAsync(kernel, arguments);
|
||||
Assert.NotNull(functionResponse);
|
||||
|
||||
var calledToolResult = new GeminiFunctionToolResult(toolCall, functionResponse);
|
||||
|
||||
chatHistory.Add(new GeminiChatMessageContent(calledToolResult));
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
/* Uncomment this to try in a console chat loop.
|
||||
Console.WriteLine("======== Example 4: Use automated function calling with a streaming chat ========");
|
||||
{
|
||||
GeminiPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
var chat = kernel.GetRequiredService<IChatCompletionService>();
|
||||
var chatHistory = new ChatHistory();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.Write("Question (Type \"quit\" to leave): ");
|
||||
string question = Console.ReadLine() ?? string.Empty;
|
||||
if (question == "quit")
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
chatHistory.AddUserMessage(question);
|
||||
System.Text.StringBuilder sb = new();
|
||||
await foreach (var update in chat.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel))
|
||||
{
|
||||
if (update.Content is not null)
|
||||
{
|
||||
Console.Write(update.Content);
|
||||
sb.Append(update.Content);
|
||||
}
|
||||
}
|
||||
|
||||
chatHistory.AddAssistantMessage(sb.ToString());
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private sealed class WeatherRequest
|
||||
{
|
||||
public string? Location { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
|
||||
namespace FunctionCalling;
|
||||
|
||||
/// <summary>
|
||||
/// This sample shows different options for calling functions with multiple parameters.
|
||||
/// The scenario is to search for invoices by customer name, purchase order, or vendor number.
|
||||
///
|
||||
/// The first sample uses multiple functions, one for each search criteria. One issue is that
|
||||
/// as the number of functions increases then the reliability of the AI model to select the correct
|
||||
/// function may decrease. To help avoid this issue, you can try filtering which functions are advertised
|
||||
/// to the AI model e.g. if your application has come context information which indicates a purchase order
|
||||
/// is available then you can filter out the customer name and vendor number functions.
|
||||
///
|
||||
/// The second sample uses a single function that takes an object with all search criteria. In this case some
|
||||
/// of the search criteria are optional. Again as the number of parameters increases then the reliability of the
|
||||
/// AI model may decrease. One advantage of this approach is that if the AI model can extra multiple search criteria
|
||||
/// for the users ask then your plugin can use this information to provide more reliable results.
|
||||
///
|
||||
/// For both options care should be taken to validate the parameters that the AI model provides. E.g. the customer
|
||||
/// name could be wrong or the purchase order could be invalid. It is worth catching these errors and responding the
|
||||
/// AI model with a message that explains what has gone wrong to see how it responds. It may be able to retry the search
|
||||
/// and get a successful response on the second attempt. Or it may decide to revert pack to the human in the loop to ask
|
||||
/// for more information.
|
||||
/// </summary>
|
||||
public class MultipleFunctionsVsParameters(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows how to use multiple Search By functions to search for invoices by customer name, purchase order, or vendor number.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InvoiceSearchBySampleAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.Services.AddSingleton<IAutoFunctionInvocationFilter>(
|
||||
new AutoFunctionInvocationFilter(this.Output));
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
kernelBuilder.Plugins.AddFromType<InvoiceSearchBy>();
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
|
||||
await InvokePromptsAsync(kernel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows how to use a single Search function to search for invoices by customer name, purchase order, or vendor number.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InvoiceSearchSampleAsync()
|
||||
{
|
||||
// Create a kernel with OpenAI chat completion
|
||||
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
|
||||
kernelBuilder.Services.AddSingleton<IAutoFunctionInvocationFilter>(
|
||||
new AutoFunctionInvocationFilter(this.Output));
|
||||
kernelBuilder.AddOpenAIChatCompletion(
|
||||
modelId: TestConfiguration.OpenAI.ChatModelId,
|
||||
apiKey: TestConfiguration.OpenAI.ApiKey);
|
||||
kernelBuilder.Plugins.AddFromType<InvoiceSearch>();
|
||||
Kernel kernel = kernelBuilder.Build();
|
||||
|
||||
await InvokePromptsAsync(kernel);
|
||||
}
|
||||
|
||||
/// <summary>Invoke the various prompts we want to test.</summary>
|
||||
private async Task InvokePromptsAsync(Kernel kernel)
|
||||
{
|
||||
OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };
|
||||
Console.WriteLine("Prompt: Show me the invoices for customer named Contoso Industries.");
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Show me the invoices for customer named Contoso Industries.", new(settings)));
|
||||
Console.WriteLine("----------------------------------------------------");
|
||||
Console.WriteLine("Prompt: Show me the invoices for purchase order PO123.");
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Show me the invoices for purchase order PO123.", new(settings)));
|
||||
Console.WriteLine("----------------------------------------------------");
|
||||
Console.WriteLine("Prompt: Show me the invoices for vendor number VN123.");
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Show me the invoices for vendor number VN123.", new(settings)));
|
||||
Console.WriteLine("----------------------------------------------------");
|
||||
Console.WriteLine("Prompt: Show me the invoices for Contoso Industries.");
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Show me the invoices for Contoso Industries.", new(settings)));
|
||||
Console.WriteLine("----------------------------------------------------");
|
||||
Console.WriteLine("Prompt: Show me the invoices for PO123.");
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Show me the invoices for PO123.", new(settings)));
|
||||
Console.WriteLine("----------------------------------------------------");
|
||||
Console.WriteLine("Prompt: Show me the invoices for VN123.");
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Show me the invoices for VN123.", new(settings)));
|
||||
Console.WriteLine("----------------------------------------------------");
|
||||
Console.WriteLine("Prompt: Zeigen Sie mir die Rechnungen für Contoso Industries.");
|
||||
Console.WriteLine(await kernel.InvokePromptAsync("Zeigen Sie mir die Rechnungen für Contoso Industries.", new(settings)));
|
||||
Console.WriteLine("----------------------------------------------------");
|
||||
}
|
||||
|
||||
/// <summary>Shows available syntax for auto function invocation filter.</summary>
|
||||
private sealed class AutoFunctionInvocationFilter(ITestOutputHelper output) : IAutoFunctionInvocationFilter
|
||||
{
|
||||
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
|
||||
{
|
||||
var functionName = context.Function.Name;
|
||||
var arguments = context.Arguments;
|
||||
|
||||
// Output the details of the function being called
|
||||
output.WriteLine($"Function: {functionName} {JsonSerializer.Serialize(arguments)}");
|
||||
|
||||
// Calling next filter in pipeline or function itself.
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A plugin that provides methods to search for Invoices using different criteria.
|
||||
/// </summary>
|
||||
private sealed class InvoiceSearchBy
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Search for invoices by customer name.")]
|
||||
public IEnumerable<Invoice> SearchByCustomerName([Description("The customer name.")] string customerName)
|
||||
{
|
||||
return
|
||||
[
|
||||
new Invoice { CustomerName = customerName, PurchaseOrder = "PO123", VendorNumber = "VN123" },
|
||||
new Invoice { CustomerName = customerName, PurchaseOrder = "PO124", VendorNumber = "VN124" },
|
||||
new Invoice { CustomerName = customerName, PurchaseOrder = "PO125", VendorNumber = "VN125" },
|
||||
];
|
||||
}
|
||||
|
||||
[KernelFunction]
|
||||
[Description("Search for invoices by purchase order.")]
|
||||
public IEnumerable<Invoice> SearchByPurchaseOrder([Description("The purchase order. Purchase orders begin with a PO prefix.")] string purchaseOrder)
|
||||
{
|
||||
return
|
||||
[
|
||||
new Invoice { CustomerName = "Customer1", PurchaseOrder = purchaseOrder, VendorNumber = "VN123" },
|
||||
new Invoice { CustomerName = "Customer2", PurchaseOrder = purchaseOrder, VendorNumber = "VN124" },
|
||||
new Invoice { CustomerName = "Customer3", PurchaseOrder = purchaseOrder, VendorNumber = "VN125" },
|
||||
];
|
||||
}
|
||||
|
||||
[KernelFunction]
|
||||
[Description("Search for invoices by vendor number")]
|
||||
public IEnumerable<Invoice> SearchByVendorNumber([Description("The vendor number. Vendor numbers begin with a VN prefix.")] string vendorNumber)
|
||||
{
|
||||
return
|
||||
[
|
||||
new Invoice { CustomerName = "Customer1", PurchaseOrder = "PO123", VendorNumber = vendorNumber },
|
||||
new Invoice { CustomerName = "Customer2", PurchaseOrder = "PO124", VendorNumber = vendorNumber },
|
||||
new Invoice { CustomerName = "Customer3", PurchaseOrder = "PO125", VendorNumber = vendorNumber },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A plugin that provides methods to search for Invoices using different criteria.
|
||||
/// </summary>
|
||||
private sealed class InvoiceSearch
|
||||
{
|
||||
[KernelFunction]
|
||||
[Description("Search for invoices by customer name or purchase order or vendor number.")]
|
||||
public IEnumerable<Invoice> Search([Description("The invoice search request. It must contain either a customer name or a purchase order or a vendor number")] InvoiceSearchRequest searchRequest)
|
||||
{
|
||||
return
|
||||
[
|
||||
new Invoice
|
||||
{
|
||||
CustomerName = searchRequest.CustomerName ?? "Customer1",
|
||||
PurchaseOrder = searchRequest.PurchaseOrder ?? "PO123",
|
||||
VendorNumber = searchRequest.VendorNumber ?? "VN123"
|
||||
},
|
||||
new Invoice
|
||||
{
|
||||
CustomerName = searchRequest.CustomerName ?? "Customer2",
|
||||
PurchaseOrder = searchRequest.PurchaseOrder ?? "PO124",
|
||||
VendorNumber = searchRequest.VendorNumber ?? "VN124"
|
||||
},
|
||||
new Invoice
|
||||
{
|
||||
CustomerName = searchRequest.CustomerName ?? "Customer3",
|
||||
PurchaseOrder = searchRequest.PurchaseOrder ?? "PO125",
|
||||
VendorNumber = searchRequest.VendorNumber ?? "VN125"
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an invoice.
|
||||
/// </summary>
|
||||
private sealed class Invoice
|
||||
{
|
||||
public string CustomerName { get; set; }
|
||||
public string PurchaseOrder { get; set; }
|
||||
public string VendorNumber { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an invoice search request.
|
||||
/// </summary>
|
||||
[Description("The invoice search request.")]
|
||||
private sealed class InvoiceSearchRequest
|
||||
{
|
||||
[Description("Optional, customer name.")]
|
||||
public string? CustomerName { get; set; }
|
||||
[Description("Optional, purchase order. Purchase orders begin with a PN prefix.")]
|
||||
public string? PurchaseOrder { get; set; }
|
||||
[Description("Optional, vendor number. Vendor numbers begin with a VN prefix.")]
|
||||
public string? VendorNumber { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.HuggingFace;
|
||||
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
|
||||
using Microsoft.SemanticKernel.TextGeneration;
|
||||
|
||||
namespace FunctionCalling;
|
||||
|
||||
/// <summary>
|
||||
/// The following example shows how to use Semantic Kernel with the HuggingFace <see cref="HuggingFaceTextGenerationService"/>
|
||||
/// to implement function calling with the Nexus Raven model.
|
||||
/// </summary>
|
||||
/// <param name="output">The test output helper.</param>
|
||||
public class NexusRaven_FunctionCalling(ITestOutputHelper output) : BaseTest(output)
|
||||
{
|
||||
/// <summary>
|
||||
/// Nexus Raven endpoint
|
||||
/// </summary>
|
||||
private Uri RavenEndpoint => new("http://nexusraven.nexusflow.ai");
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the Nexus Raven model using Text Generation.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InvokeTextGenerationAsync()
|
||||
{
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddHuggingFaceTextGeneration(endpoint: RavenEndpoint)
|
||||
.Build();
|
||||
|
||||
var textGeneration = kernel.GetRequiredService<ITextGenerationService>();
|
||||
var prompt = "What is deep learning?";
|
||||
|
||||
var result = await textGeneration.GetTextContentsAsync(prompt);
|
||||
|
||||
Console.WriteLine(result[0].ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the Nexus Raven model with Function Calling.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task InvokeTextGenerationWithFunctionCallingAsync()
|
||||
{
|
||||
using var handler = new LoggingHandler(new HttpClientHandler(), this.Output);
|
||||
using var httpClient = new HttpClient(handler);
|
||||
|
||||
Kernel kernel = Kernel.CreateBuilder()
|
||||
.AddHuggingFaceTextGeneration(
|
||||
endpoint: RavenEndpoint,
|
||||
httpClient: httpClient)
|
||||
.Build();
|
||||
var plugin = ImportFunctions(kernel);
|
||||
var textGeneration = kernel.GetRequiredService<ITextGenerationService>();
|
||||
|
||||
// This Handlebars template is used to format the available KernelFunctions so
|
||||
// they can be understood by the NexusRaven model. The function name, signature and
|
||||
// description must be provided. NexusRaven can reason over the list of functions and
|
||||
// determine which ones need to be called for the current query.
|
||||
var template =
|
||||
""""
|
||||
{{#each (functions)}}
|
||||
Function:
|
||||
{{Name}}{{Signature}}
|
||||
"""
|
||||
{{Description}}
|
||||
"""
|
||||
{{/each}}
|
||||
|
||||
User Query:{{prompt}}<human_end>
|
||||
"""";
|
||||
|
||||
var prompt = "What is the weather like in Dublin?";
|
||||
var functions = plugin.Select(f => new FunctionDefinition { Name = f.Name, Description = f.Description, Signature = CreateSignature(f) }).ToList();
|
||||
var executionSettings = new HuggingFacePromptExecutionSettings { Temperature = 0.001F, MaxNewTokens = 1024, ReturnFullText = false, DoSample = false }; // , Stop = ["<bot_end>"]
|
||||
KernelArguments arguments = new(executionSettings) { { "prompt", prompt }, { "functions", functions } };
|
||||
|
||||
var factory = new HandlebarsPromptTemplateFactory();
|
||||
var promptTemplate = factory.Create(new PromptTemplateConfig(template) { TemplateFormat = "handlebars" });
|
||||
var rendered = await promptTemplate.RenderAsync(kernel, arguments);
|
||||
|
||||
Console.WriteLine(" Prompt:\n====================");
|
||||
Console.WriteLine(rendered);
|
||||
|
||||
var function = kernel.CreateFunctionFromPrompt(template, templateFormat: "handlebars", promptTemplateFactory: new HandlebarsPromptTemplateFactory());
|
||||
|
||||
var result = await kernel.InvokeAsync(function, arguments);
|
||||
|
||||
Console.WriteLine("\n Response:\n====================");
|
||||
Console.WriteLine(result.ToString());
|
||||
}
|
||||
|
||||
// The signature must be Python compliant and currently only supports primitive values
|
||||
private static string CreateSignature(KernelFunction function)
|
||||
{
|
||||
var signature = new StringBuilder();
|
||||
var parameters = function.Metadata.Parameters;
|
||||
signature.Append('(');
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
signature.Append(parameter.Name).Append(':').Append(GetType(parameter));
|
||||
}
|
||||
signature.Append(')');
|
||||
return signature.ToString();
|
||||
}
|
||||
|
||||
private static string GetType(KernelParameterMetadata parameter)
|
||||
{
|
||||
if (parameter.Schema is not null)
|
||||
{
|
||||
var rootElement = parameter.Schema.RootElement;
|
||||
if (rootElement.TryGetProperty("type", out var type))
|
||||
{
|
||||
return type.GetString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static KernelPlugin ImportFunctions(Kernel kernel)
|
||||
{
|
||||
return kernel.ImportPluginFromFunctions("WeatherPlugin",
|
||||
[
|
||||
kernel.CreateFunctionFromMethod(
|
||||
(string cityName) => "12°C\nWind: 11 KMPH\nHumidity: 48%\nMostly cloudy",
|
||||
"GetWeatherForCity",
|
||||
"Gets the current weather for the specified city",
|
||||
new List<KernelParameterMetadata>
|
||||
{
|
||||
new("cityName") { Description = "The city name", ParameterType = string.Empty.GetType() }
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function definition for use with Nexus Raven.
|
||||
/// </summary>
|
||||
private sealed class FunctionDefinition
|
||||
{
|
||||
public string Name { get; init; }
|
||||
public string Signature { get; init; }
|
||||
public string Description { get; init; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user