Introduction
If you are a developer who wants to build AI-first apps with natural language processing and large language models, you might be interested in Semantic Kernel (SK), a lightweight and open-source SDK that aims to simplify the integration of AI with conventional programming languages.
SK is part of the CoPilot Stack and Microsoft is using it in its own CoPilots.
SK allows you to create and orchestrate semantic functions, native functions, memories, and connectors using C# or Python. Much like LangChain, it supports prompt templating, chaining, and memory with vectors (embeddings).
You can also use SK’s planner to automatically generate and execute complex tasks based on a user’s goals. This is similar to LangChain’s Agents & Tools capabilities. In this blog post, we will introduce some of the features and benefits of SK’s Planner, and show you how to use it in your own applications. I am still learning so I am going to stick to the basics! 😃

SK’s Planner allows you to create and execute plans based on semantic queries. You start by providing it a goal (an ask). The goal could be: “Create a photo of a meal with these ingredients: {list of ingredients}”. To achieve the goal, the planner can use plugins to generate and execute the plan. For the goal above, suppose we have two plugins:
- Recipe plugin: creates a recipe based on starter ingredients
- Image description plugin: creates an image description based on any input
The recipe plugin takes a list of ingredients as input while the image description plugin can take the recipe as input and generate an image description of it. That image description could be used by DALL-E to generate an actual image.
Note: at the time of writing, Microsoft was on the verge of using the word plugin instead of skill. In the code, you will see references to skills but that will go away. This post already the word plugins instead of skills.
Creating plugins
Plugins make expertise available to SK and consist of one or more functions. A function can be either:
- an LLM prompt: a semantic function
- native computer code: a native function
A plugin is a container where functions live. Think of it as a folder with each subfolder containing a function. For example:
A semantic function is a prompt with placeholders for one or more input variables. The prompt is in skprompt.txt. The Recipe function uses the following prompt:
Write a recipe with the starter ingredients below and be specific about the steps to take and the amount of ingredients to use:
{{$input}}
The file config.json contains metadata about the function and LLM completion settings such as max_tokens and temperature. For example:
{
"schema": 1,
"type": "completion",
"description": "Creates a recipe from starting ingredients",
"completion": {
"max_tokens": 256,
"temperature": 0,
"top_p": 0,
"presence_penalty": 0,
"frequency_penalty": 0
},
"input": {
"parameters": [
{
"name": "input",
"description": "Input for this semantic function.",
"defaultValue": ""
}
]
},
"default_backends": []
}
From your code, you can simply run this function to create a recipe. The plugin above is similar to a PromptTemplate in LangChain that you can combine with an LLM into a chain. You would then simply run the chain to get the output (a recipe). SK supports creating functions inline in your code as well, similar to how LangChain works.
Using the Planner
As stated above, the Planner can use plugins to reach the goal provided by a user’s ask. It actually works its way backward from the goal to create the plan:

There are different types of planners like a sequential planner, an action planner, a custom planner, and more. In our example, we will use a sequential planner and keep things as simple as possible. We will only use semantic functions, no native code functions.
Time for some code. We will build a small .NET Console App based on the example above: create a recipe and generate a photo description for this recipe. Here is the code:
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI.ImageGeneration;
using System.Diagnostics;
using Microsoft.SemanticKernel.Planning;
using System.Text.Json;
var kernelSettings = KernelSettings.LoadSettings();
var kernelConfig = new KernelConfig();
kernelConfig.AddCompletionBackend(kernelSettings);
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder
.SetMinimumLevel(kernelSettings.LogLevel ?? LogLevel.Warning)
.AddConsole()
.AddDebug();
});
IKernel kernel = new KernelBuilder()
.WithLogger(loggerFactory.CreateLogger<IKernel>())
.WithConfiguration(kernelConfig)
.Configure(c =>
{
c.AddOpenAIImageGenerationService(kernelSettings.ApiKey);
})
.Build();
// used later to generate image with dallE
var dallE = kernel.GetService<IImageGeneration>();
// use SKs planner
var planner = new SequentialPlanner(kernel);
// depends on MySkills skill which has two semantic fucntions
// skills will be renamed to plugins in the future
var skillsDirectory = Path.Combine(System.IO.Directory.GetCurrentDirectory(), "skills");
var skill = kernel.ImportSemanticSkillFromDirectory(skillsDirectory, "MySkills");
// ask for starter ingredients
Console.Write("Enter ingredients: ");
string? input = Console.ReadLine();
if (!string.IsNullOrEmpty(input))
{
// define the ASK for the planner; the two semantic functions should be used by the plan
// note that these functions are too simple to be useful in a real application
// a single prompt to the model would be enough
var ask = "Create a photo of a meal with these ingredients:" + input;
// create the plan and print it to see if the functions are used correctly
var newPlan = await planner.CreatePlanAsync(ask);
Console.WriteLine("Updated plan:\n");
Console.WriteLine(JsonSerializer.Serialize(newPlan, new JsonSerializerOptions { WriteIndented = true }));
// run the plan; result should be an image description
var newPlanResult = await newPlan.InvokeAsync();
// generate the url to the images created by dalle
Console.WriteLine("Plan result: " + newPlanResult.ToString());
var imageURL = await dallE.GenerateImageAsync(newPlanResult.ToString(), 512, 512);
// display image in browser (MacOS!!!)
Process.Start("open", imageURL);
The code uses config/appsettings.json
which contains settings like serviceType, serviceId, and the API key to use with either OpenAI or Azure OpenAI. In my case, serviceType
is OpenAI
and the serviceId
is gpt-4
. Ensure you have gpt-4
access in OpenAI’s API. I actually wanted to use Azure OpenAI but I do not have access to DALL-E and I do not think SK would support it anyway.
After loading the settings, a KernelConfig
is created, and a completion backend gets added (using gpt-4
here). After setting up logging, a new kernel is created with a new KernelBuilder() with the following settings;
- a logger
- the configuration with the completion backend
- an image generation service (DALL-E2 here) which needs the OpenAI API key (retrieved from
kernelSettings
)
We can now create the planner with var planner = new SequentialPlanner(kernel);
and add skills (plugins) to the kernel. We add plugins from the skills/MySkills
folder in our project.
Now it’s just a matter of asking the user for some ingredients (stored in input) and creating the plan based on the ask. The ask is “Create a photo of a meal with these ingredients: …”
var ask = "Create a photo of a meal with these ingredients:" + input;
var newPlan = await planner.CreatePlanAsync(ask);
Note that CreatePlanAsync does not execute the plan, it just creates it. We can look at the plan with the following code:
Console.WriteLine("Updated plan:\n");
Console.WriteLine(JsonSerializer.Serialize(newPlan, new JsonSerializerOptions { WriteIndented = true }));
The output is something like this (note that there are typos in the ingredients but that’s ok, the model should understand):
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [],
"parameters": [
{
"Key": "INPUT",
"Value": "courgette, collieflower, steak, tomato"
}
],
"outputs": [
"RECIPE_RESULT"
],
"next_step_index": 0,
"name": "Recipe",
"skill_name": "MySkills",
"description": "Creates a recipe from starting ingredients"
},
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [],
"parameters": [
{
"Key": "INPUT",
"Value": "$RECIPE_RESULT"
}
],
"outputs": [
"RESULT__IMAGE_DESCRIPTION"
],
"next_step_index": 0,
"name": "ImageDesc",
"skill_name": "MySkills",
"description": "Generate image description for a photo of a recipe or meal"
}
],
"parameters": [
{
"Key": "INPUT",
"Value": ""
}
],
"outputs": [
"RESULT__IMAGE_DESCRIPTION"
],
"next_step_index": 0,
"name": "",
"skill_name": "Microsoft.SemanticKernel.Planning.Plan",
"description": "Create a photo of a meal with these ingredients:courgette, collieflower, steak, tomato"
}
The output shows that the two skills will be used in this case. The input to the ImageDesc plugin is the output from the Recipe plugin.
Note that if your ask has nothing to do with generating dishes and photos of a dish, the skills will still be used resulting in unexpected results.
If you do not provide any skills, the planner will just use itself as a skill and the result will be the original ask. That would still work in this case because the ask can be passed to Dall-E on its own!
With the plan created, we can now execute it and show the result:
var newPlanResult = await newPlan.InvokeAsync();
Console.WriteLine("Plan result: " + newPlanResult.ToString());
This should print the description of the photo. We can pass that result to DALL-E with:
var imageURL = await dallE.GenerateImageAsync(newPlanResult.ToString(), 512, 512);
Process.Start("open", imageURL); // works on MacOS
If I provide carrots and meat, then I get the following description and photo.
Description: A steaming pot of hearty carrot and meat stew is pictured. The pot is filled with chunks of lean ground beef, diced carrots, diced onion, minced garlic, and a rich tomato paste. Aromatic herbs of oregano and thyme are sprinkled on top, and the stew is finished with a drizzle of olive oil. The stew is ready to be served and is sure to be a delicious and comforting meal.
The photo:
Testing skills and plans
The Semantic Kernel extension for VS Code will find your plugins (skills) and allow you to execute them:
When you click the play icon next to the skill, you will be asked for input. The prompt will then be run by your selected model, with output to the screen:

You can also create plans and execute them in VS Code:
Above, a plan was created based on a goal. The plan included the two plugins and shows the inputs and outputs. By clicking on Execute Plan you can run it without having to write any code. The UI above allows you to inspect the generated plan and change it if it’s not performing as intended.
Conclusion
This concludes my quick look at the Planner functionality in Semantic Kernel with a simple plan and a couple of semantic skills. If you want to learn more, be sure to check these resources:
- Microsoft Learn
- GitHub: be sure to check out the notebooks to get started (Python and C#)