What is SemanticKernel how to use it?
When I have started my adventure with AI I explored the OpenAI API . It worked, but during my trip, deeper and deeper I discovered there were more and more concepts. All of them are important and we need to understand and implement like history, context, prompt sequential, external API requests. Everything can be done with API and you code, but there is something like SemanticKernel that can help you with it.
What is SemanticKernel?
It is an open-source library, created by Microsoft. You can understand it as an abstraction layer above AI API with some features to do all required work to integrate your app with AI. Creators describe it: Semantic Kernel is an SDK that integrates Large Language Models (LLMs) like OpenAI, Azure OpenAI, and Hugging Face ... and automatically orchestrate plugins with AI
Info
Remember! All of the examples you can find the CodePruner.com repository
How to create it. 1st step.
- Add Nuget
Microsoft.SemanticKernel
- Currently only beta is available. The version is
1.0.0-beta3
1.0.0-beta5
- Currently only beta is available. The version is
- Create a
KernelBuilder
to create a kernel[Fact] public void create_simple_semantic_kernel() { var builder = new KernelBuilder(); var model = "gpt-3.5-turbo"; var apiKey = "API_KEY"; builder.WithOpenAIChatCompletionService(model, apiKey); IKernel kernel = builder.Build(); Assert.NotNull(kernel); }
- You can also pass a console logger to the builder to see a bit more:
[Fact] public void create_semantic_kernel_with_console_logging() { var builder = new KernelBuilder(); var model = "gpt-3.5-turbo"; var apiKey = "API_KEY"; builder.WithOpenAIChatCompletionService(model, apiKey); // add nuget: Microsoft.Extensions.Logging.Console builder.WithLoggerFactory(LoggerFactory.Create(x => x.AddConsole())); IKernel kernel = builder.Build(); Assert.NotNull(kernel); }
Plugins in Sematic Kernel
The main concept present is in the SK is a plugin system. It is build with two types of plugin functions NativeFunction
and SemanticFunction
. Both of them are designed to extend or encapsulate a logic for specifics tasks. Let’s check some details and diffrences between them.
SemanticFunction
It is a way to create a predefined query to AI. It is nothing else like already prepared and configured, ready to use prompt to AI. It is build with:
Prompt
- It is a simple text
- It can include some placeholders to inject some values to the prompt
Configuration - It is a JSON file with the configuration for the prompt. like:
- Description - a description of the function.
- It it used for users to know the intention of the function,
- but also for a
planner
(we will talk about it later) to know what the function does.
- Temperature - to set how random or predictable the response should be
- MaxTokens - to set the length of the response
- and more
- Description - a description of the function.
It can be defined inline:
As you can see in that example. When you create one[Fact] public async Task semantic_function_bike_joke_inline() { var kernel = SemanticKernelBuilderFactory.Create().Build(); string bikeJokePrompt = """ Write a joke about cyclists It must be funny It should be sarcastic It should be in style of dad jokes It should be written in simple language It should be about bike and {{$input}} """; var bikeJokeRequestSettings = new OpenAIRequestSettings { MaxTokens = 100, Temperature = 0.8, }; var promptConfig = new PromptTemplateConfig(); promptConfig.ModelSettings.Add(bikeJokeRequestSettings); var promptTemplateFactory = new BasicPromptTemplateFactory(); var bikeJokePromptTemplate = promptTemplateFactory.Create(bikeJokePrompt, promptConfig); var bikeJokeFunction = kernel.RegisterSemanticFunction("BikePlugin", "BikeJoke", promptConfig, bikeJokePromptTemplate); var driverResponseResult = await kernel.RunAsync("Car driver", bikeJokeFunction); var driverResult = driverResponseResult.GetValue<string>(); Console.WriteLine(driverResult); // Example result: // Why did the car driver get jealous of the cyclist? // Because the cyclist was always "pedaling" their way to success while the car driver was stuck in traffic, "wheel-y" wishing they could join the fun! var devResponseResult = await kernel.RunAsync("Software developer", bikeJokeFunction); var devResult = devResponseResult.GetValue<string>(); Console.WriteLine(devResult); // Example result: // Why did the software developer become a cyclist? // Because they wanted to experience even more bugs on the road! }
SemanticFunction
and use it in different situations.or, better it can be defined in two seperate files:
skprompt.txt
- a text file with a function promptWrite a joke about cyclists It must be funny It should be sarcastic It should be in style of dad jokes It should be written in simple language It should be about bike and {{$input}}
config.json
- configuration{ "schema": 1, "description": "Generate a funny dad joke about cyclist and about a given job title", "models": [ { "max_tokens": 100, "temperature": 0.9, "top_p": 0.0, "presence_penalty": 0.0, "frequency_penalty": 0.0 } ], "input": { "parameters": [ { "name": "input", "description": "Job title", "defaultValue": "" } ] } }
and here you can see the example of usage:
[Fact] public async Task semantic_function_bike_joke_files() { var kernel = SemanticKernelBuilderFactory.Create().Build(); var pluginsPath = Path.Combine(System.IO.Directory.GetCurrentDirectory(), "Plugins"); var bikePluginFunctions = kernel.ImportSemanticFunctionsFromDirectory(pluginsPath, "BikePlugin"); var driverResponseResult = await kernel.RunAsync("UX Designer", bikePluginFunctions["BikeJoke"]); var driverResult = driverResponseResult.GetValue<string>(); Console.WriteLine(driverResult); // Example result: // Why did the cyclist become a UX Designer? // Because they wanted to experience the thrill of going in circles and getting nowhere, just like riding a bike! }
NativeFunction
It is a way to create your own part of code to run it thought the kernel. It can be responsible for calculations, doing API calls, saving or loading some data. The definition can look like that:
using System.ComponentModel;
using Microsoft.SemanticKernel;
namespace CodePruner.Examples.AI.ExploreSemanticKernel.Plugins;
public class BikeApiPlugin
{
[SKFunction, Description("Calculate what size of bike will fit you")]
public string CalculateBikeSize([Description("Person height in centimeters")] double height)
{
if (height < 160)
{
return "S";
}
else if (height < 180)
{
return "M";
}
else
{
return "L";
}
}
[SKFunction, Description("Get list of bikes for me based on passed criteria")]
public string BikeForMe(
[Description("The size of the bike that is fine for me")] string bikeSize,
[Description("The type o bike I want")] string bikeType)
{
// It can of course do a request to on API to get the real list of bike to recommend the user.
return $"You should get a {bikeSize} {bikeType} bike";
}
}
[Fact]
public async Task native_function_bike_size()
{
var kernel = SemanticKernelBuilderFactory.Create().Build();
var pluginFunctions = kernel.ImportFunctions(new BikeApiPlugin(), "BikeSizePlugin");
var responseResult = await kernel.RunAsync("175", pluginFunctions["CalculateBikeSize"]);
var result = responseResult.GetValue<string>();
Assert.Equal("M", result);
}
I know the current example doesn’t have too much sense. Why did I use the kernel to execute the function if I just could call it directly from BikeSizePlugin
?
- It is just a simple example to show you how to create
NativeFuction
. - It will give you a bit more sense when we combine them with a pipeline.
Pipeline
It is a way to running multiple functions, Semantic
or Native
in order with passing the output from one function to the following one. To achieve it you can configure it manually or use planner
. Let’s start with the 1st option:
Manual pipeline configuration
You can configure how the pipeline should look like. Check the example:
[Fact]
public async Task manual_pipeline()
{
var kernel = SemanticKernelBuilderFactory.Create().Build();
var getTobTitleFunction = kernel.CreateSemanticFunction(
"""
What is my Job, based on description?
Result should max two words like: Software Developer, Football Player, etc.
DESCRIPTION:
{{$input}}
""",
new OpenAIRequestSettings() {Temperature = 0.0});
var pluginsPath = Path.Combine(Directory.GetCurrentDirectory(), "Plugins");
kernel.ImportSemanticFunctionsFromDirectory(pluginsPath, "BikePlugin");
var responseResult = await kernel.RunAsync("I mostly create a code and sometimes I do a deploy. I do also some research in AI",
getTobTitleFunction,
kernel.Functions.GetFunction("BikePlugin", "BikeJoke"));
var result = responseResult.GetValue<string>();
Console.WriteLine(result);
// Example result:
// Why did the cyclist become an AI researcher?
// Because they wanted to pedal their way to the future, but realized they could just program a bike to do it for them! Talk about taking the easy route!
var responseResult2 = await kernel.RunAsync("I try to find a bad guys and put them in jail. I also like to drive a car and shoot a gun.",
getTobTitleFunction,
kernel.Functions.GetFunction("BikePlugin", "BikeJoke"));
var result2 = responseResult2.GetValue<string>();
Console.WriteLine(result2);
//Example result:
// Why did the Law Enforcement Officer give the cyclist a ticket?
// Because the cyclist was going too fast... for a snail! Talk about breaking the sound barrier on two wheels! 🚴♂️💨
// But hey, at least the snail didn't need a helmet! Safety first, right? 😄
}
Planner
You can do it in a different way. It still needs to be tested for more complex examples, but let see how it works in that example:
[Fact]
public async Task sequential_planner()
{
var kernel = SemanticKernelBuilderFactory.Create().Build();
var pluginsPath = Path.Combine(System.IO.Directory.GetCurrentDirectory(), "Plugins");
kernel.ImportSemanticFunctionsFromDirectory(pluginsPath, "BikePlugin");
kernel.ImportFunctions(new BikeApiPlugin(), "BikeSizePlugin");
var planner = new SequentialPlanner(kernel);
var plan = await planner.CreatePlanAsync(
"What bike size should I buy if I am 175cm tall? I also like riding in the forest. Mostly in hilly areas.");
Console.WriteLine(string.Join(",", plan.Steps.Select(x => $"{x.PluginName}.{x.Name}")));
// Example plan:
// BikeSizePlugin.CalculateBikeSize,BikeSizePlugin.BikeForMe
var responseResult = await kernel.RunAsync(plan);
var result = responseResult.GetValue<string>();
Console.WriteLine(result);
// Example result:
// You should get a M Mountain bike
}
As you can se, we just registered plugins and used SequentialPlanner
to prepare a pipeline for asked query. The planner can use Native or Semantic functions. Because it knows what the function does, what is the input and output.
Summary
Thank you that you are still here. I think you know main concepts of Semantic Kernel now. There are still some topics like: Context
and Memory
, but it is a topic for a different article.
Let me know if you have any questions or comments. I will be happy to answer them.