post-thumb

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
  • 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
  • It can be defined inline:

    
        [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!
        }
    
    As you can see in that example. When you create one 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 prompt
      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}}
      
    • 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";
    }
}
and execution:

    [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.

comments powered by Disqus

Are you still here? Subscribe for more content!