July 23rd, 2024

Planning with Semantic Kernel using Automatic Function Calling

Hello, everyone!

AI planning is a powerful tool that allows to generate and execute complex workflows in applications based on specified goal. In Semantic Kernel, it’s possible to implement planning logic using FunctionCallingStepwisePlanner class. Today we want to introduce a new way how to achieve the same results by using Automatic Function Calling. This approach produces the results more reliably and uses fewer tokens comparing to FunctionCallingStepwisePlanner.

Setup

For demonstration purposes, we will use 2 plugins to get current date/time and weather:

public sealed class TimePlugin
{
    [KernelFunction]
    [Description("Retrieves the current time in UTC")]
    public string GetCurrentUtcTime() => DateTime.UtcNow.ToString("R");
}

public sealed class WeatherPlugin
{
    [KernelFunction]
    [Description("Gets the current weather for the specified city")]
    public string GetWeatherForCity(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",
        };
}

For simplicity, let’s define a helper function to initialize Kernel instance to use it across examples in this article:

public static Kernel GetKernel()
{
    Kernel kernel = Kernel
        .CreateBuilder()
        .AddOpenAIChatCompletion("gpt-4", Environment.GetEnvironmentVariable("OpenAI__ApiKey"))
        .Build();

    // Import sample plugins.
    kernel.ImportPluginFromType<TimePlugin>();
    kernel.ImportPluginFromType<WeatherPlugin>();

    return kernel;
}

Old approach with FunctionCallingStepwisePlanner

Here is an example how to generate and execute a plan with FunctionCallingStepwisePlanner:

// Get kernel
Kernel kernel = GetKernel();

// Initialize planner
FunctionCallingStepwisePlanner planner = new();

// Generate and execute a plan
FunctionCallingStepwisePlannerResult plannerResult = await planner.ExecuteAsync(kernel, "Check current UTC time and return current weather in Boston city.");

Console.WriteLine($"Planner execution result: {plannerResult.FinalAnswer}");

// Output:
// Planner execution result: The current UTC time is Sat, 06 Jul 2024 02:11:10 GMT and the weather in Boston is 61 and rainy.

It’s possible to get access to generated plan and see details for each step by using plannerResult.ChatHistory. In current scenario with 2 plugins and defined goal, approximately 1380 tokens were used.

New approach with Automatic Function Calling

For Automatic Function Calling, Kernel object should be used with ToolCallBehavior.AutoInvokeKernelFunctions configured in execution settings. Here is an example:

// Get kernel
Kernel kernel = GetKernel();

// Enable Automatic Function Calling
OpenAIPromptExecutionSettings executionSettings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };

// Generate and execute a plan
FunctionResult result = await kernel.InvokePromptAsync("Check current UTC time and return current weather in Boston city.", new(executionSettings));

Console.WriteLine($"Auto Function Calling execution result: {result}");

// Output:
// Auto Function Calling execution result: The current UTC time is Sat, 06 Jul 2024 02:11:16 GMT. The weather right now in Boston is 61 degrees and rainy.

With Automatic Function Calling, planning result is the same, but token usage is significantly smaller – approximately 240 tokens.

Get generated plan with Automatic Function Calling

In both FunctionCallingStepwisePlanner and Automatic Function Calling approaches, generated plan lives in ChatHistory object. To get access to generated plan with Automatic Function Calling, IChatCompletionService should be used:

// Get kernel
Kernel kernel = GetKernel();

// Get chat completion service
IChatCompletionService chatCompletionService = kernel.GetRequiredService();

// Enable Automatic Function Calling
OpenAIPromptExecutionSettings executionSettings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions };

// Initialize chat history
ChatHistory chatHistory = new();
chatHistory.AddUserMessage("Check current UTC time and return current weather in Boston city.");

// Generate and execute a plan
ChatMessageContent result = await chatCompletionService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel);

// Result variable contains the result of executed plan.
// ChatHistory contains generated plan with details for each step - function, arguments, function invocation result.

Summary

Provided example shows how to use a new recommended approach for planning with Automatic Function Calling to achieve specified goal.

By following links, you can find and run more examples for different planning scenarios such as telemetry for plan generation and execution, plan caching for reusability, using filters to control plan execution and more:

We’re always interested in hearing from you. If you have feedback, questions or want to discuss further, feel free to reach out to us and the community on the discussion boards on GitHub! We would also love your support, if you’ve enjoyed using Semantic Kernel, give us a star on GitHub.

Author

Dmytro Struk
Senior Software Engineer

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Rodrigo Liberoff Vázquez

    Nice!

    Does this mean that the planners (like the `FunctionCallingStepwisePlanner ` or the `HandlebarsPlanner`) are being abandoned?

    Thank you!