Add AI capabilities to your C# application using Semantic Kernel from Microsoft (Part 2)

In the second part of this tutorial, we will apply what we've learned in Part 1 and utilize the Semantic Kernel SDK in a simple C# console app.

Semantic Kernel by Microsoft: Adding AI superpowers to your C# app - Part 2
Semantic Kernel by Microsoft: Adding AI superpowers to your C# app - Part 2
💡
In the second part of this tutorial, we're going to bring Factman to life. If you haven't already, make sure you go through Part 1 then come back to continue reading.

Using the Semantic Kernel SDK

On my machine, I am running macOS Ventura on an Apple M1 Pro chip, but the same steps should work on any environment.

Prerequisites

  • .NET Core / CLI
  • Semantic Kernel SDK
  • OpenAI API Key
  • Visual Studio Code (or your preferred IDE)

Step 1: Create a new C# Console app

In your terminal window type the following: mkdir SKDemo && cd SKDemo. Then, create a new C# console app by typing: dotnet new console.

Now, your SKDemo root directory should look like this:

Folder structure screenshot
Folder structure screenshot

Install the Semantic Kernel SDK package from NuGet by entering the following command:

dotnet add package Microsoft.SemanticKernel --version 1.0.1

💡
At the time of writing version 1.0.1 is the latest stable release. You may choose to install a newer version.

In our Program.cs file, let's prepare a few things:

// Import the Kernel class from the Microsoft.SemanticKernel namespace
using Kernel = Microsoft.SemanticKernel.Kernel;

// Create a new Kernel builder
var builder = Kernel.CreateBuilder();

// Add OpenAI Chat Completion to the builder
builder.AddOpenAIChatCompletion(
         "gpt-3.5-turbo",
         "OPENAI_API_KEY");
⚠️
Don't forget to replace "OPENAI_API_KEY" with your actual OpenAI API Key. You can get it from the OpenAI website.

We're now ready to start building our Plugins and Functions.

Step 2: Create the Plugins folder

Great, you've made it this far so you're serious about using Semantic Kernel. If you recall from Part 1 of this tutorial, we're going to build two plugins:

  1. FactmanPlugin: Responsible for all myth-related tasks and will include the following functions:
    1. FindMyth: Finds a common myth about AI.
    2. BustMyth: Fact-checks and busts the myth.
    3. AdaptMessage: Adjust the content to align with the specific posting guidelines of a given social media platform.
  2. SocialPlugin: Our second plugin will be used by the Kernel to simulate posting to social media platforms using only one function: Post.

Let's create our Plugins folder, then we'll create another nested folder for our Plugin: FactmanPlugin.

Make sure you're in the root folder SKDemo. Then, in your terminal window type the following:

  • mkdir Plugins && cd Plugins
  • mkdir FactmanPlugin && cd FactmanPlugin
Folder structure screenshot
Folder structure with Plugins and FactmanPlugin folders

Step 3: Create the FindMyth function

Since we will instruct the LLM and ask it to generate a common myth about AI, this function will be of type Prompt.

💡
A quick reminder from Part 1 of this tutorial: Plugins can be Prompts or Native Functions.
  1. Create FindMyth folder: While in the FactmanPlugin directory, type the following: mkdir FindMyth && cd FindMyth

Now, in the FindMyth directory, we'll need two files: skprompt.txt and config.json.

  1. Create skprompt.txt file: This file will contain the natural language prompt for generating AI myths. Create it using the touch command: touch skprompt.txt

The following should go into the skprompt.txt file:

GENERATE A MYTH ABOUT ARTIFICIAL INTELLIGENCE

MYTH MUST BE:
- G RATED
- WORKPLACE AND FAMILY SAFE
- NO SEXISM, RACISM OR OTHER BIAS/BIGOTRY

DO NOT INVENT MYTHS ABOUT REAL PEOPLE OR ORGANIZATIONS

Prompt template: Feel free to play around with this to suit your needs

  1. Create config.json file: This file will be used for our function’s configuration settings. Create it using the touch command: touch config.json.

The following JSON should go into the config.json file:

{
  "schema": 1,
  "description": "Find a common myth about AI",
  "execution_settings": {
    "default": {
      "max_tokens": 1000,
      "temperature": 0.9,
      "top_p": 0.0,
      "presence_penalty": 0.0,
      "frequency_penalty": 0.0
    }
  }
}

JSON schema definition of our function

Step 3: Create the BustMyth function

BustMyth is also a function of type Prompt since we're also prompting the LLM to bust a given myth.

Let's go back to the Plugin directory FactmanPlugin and create another sub-directory. We'll call it BustMyth.

  1. In your terminal: Start by going back to our Plugin directory by typing cd ..
  2. Create BustMyth directory: Now, let's create a new directory named BustMyth. This directory will house the files necessary for our function. Enter the following command: mkdir BustMyth && cd BustMyth.

Again, we'll need these two files: skprompt.txt and config.json.

  1. Create skprompt.txt file: In your terminal type touch skprompt.txt and hit return.

The following should go into the skprompt.txt file:

GIVEN THE MYTH BELOW
BUST IT IN A FUNNY WAY
THE AUDIENCE OF YOUR OUTPUT ARE THOSE WHO BELIEVE THE MYTH

YOUR RESPONSE SHOULD BE SAFE FOR ALL AGES

BE FACTUAL

++++
This is the myth:
{{$myth}}

Prompt template: Feel free to optimize this to your liking

  1. Create config.json file: This file will be used for our function’s configuration settings. Create it using the touch command: touch config.json

And here's the JSON contents of the config.json file:

{
    "schema": 1,
    "description": "Bust a given Myth",
    "execution_settings": {
      "default": {
        "max_tokens": 1000,
        "temperature": 0.9,
        "top_p": 0.0,
        "presence_penalty": 0.0,
        "frequency_penalty": 0.0
      }
    },
    "input_variables": [
      {
        "name": "myth",
        "description": "Myth to bust",
        "default": ""
      }
    ]
  }

JSON schema definition of our function

💡
As you can see, the difference here is that we added the input_variables property to the JSON schema since we're going to pass in a variable $myth in our skprompt.txt file.

Step 4: Create the AdaptMessage function

We'll do the same thing one more time for the AdaptMessage function. Let's go back to the FactmanPlugin directory by typing cd .. into the terminal.

Then, we'll create a new directory named AdaptMessage in the FactmanPlugin folder.

  1. In your terminal: Type the following command into the terminal window: mkdir AdaptMessage && cd AdaptMessage
  2. Create skprompt.txt file and paste its content from the text below:
YOU WRITE ENGAGING SOCIAL MEDIA POSTS
YOU ARE AN INFLUENCER ON {{$platform}}

ALWAYS START YOUR SENTENCE BY SAYING "Factman to the rescue!"
AND THEN LINE BREAK

TAKE THE BELOW IDEA AND REPURPOSE IT FOR YOUR AUDIENCE
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
THIS IS THE IDEA
{{$input}}

JSON schema definition of our function

  1. Create config.json file and paste its content from the text below:
{
    "schema": 1,
    "description": "Change a given input to match the post style of a given social media platform best practices",
    "execution_settings": {
      "default": {
        "max_tokens": 1000,
        "temperature": 0.9,
        "top_p": 0.0,
        "presence_penalty": 0.0,
        "frequency_penalty": 0.0
      }
    },
    "input_variables": [
      {
        "name": "input",
        "description": "Input text to be changed",
        "default": ""
      },
      {
        "name": "platform",
        "description": "Social media platform to use a reference to format given input so it matches the style of platform",
        "default": ""
      }
    ]
  }
💡
Here, since we have two inputs (\$input) and \$platform) in the prompt, we define them using the input_variables property in the JSON schema.

Step 5: Create the Post function

This one is a little bit different. Why? Because unlike the other three functions above, we're going to execute a C# function.

We will decorate the function Post with a special special Semantic Kernel attribute [KernelFunction]. This indicates to the Kernel that it can access the Post function to complete a task.

  1. Navigate to the Plugins directory: Go back to your Plugins directory.
  2. Create SocialPlugin.cs file: In the Plugins folder type the following into your terminal: touch SocialPlugin.cs to create the file.
  3. Normally, this would include all the social media functions. For this example, however, we're just going to add the Post function.

Here's the C# code that should go into the SocialPlugin.cs file:

using System.ComponentModel;
using Microsoft.SemanticKernel;

namespace Plugins;
public class SocialPlugin
{
    [KernelFunction]
    [Description("Simulate posting to a social media platform.")]
    public async Task Post(
    Kernel kernel,
    [Description("The name of the social media platform")] string platform,
    [Description("The original message to adapt to the given platform")] string message
)
    {
        await Task.Delay(1000);

        Console.WriteLine("Posting: " + message);
        switch (platform.ToLower())
        {
            case "x":
            case "twitter":
                Console.WriteLine("Simulating post to X/twitter...");
                break;
            case "linkedin":
                Console.WriteLine("Simulating post to LinkedIn...");
                break;
            case "facebook":
                Console.WriteLine("Simulating post to Facebook...");
                break;
            default:
                Console.WriteLine($"Unknown platform: {platform}");
                break;
        }
    }
}

To keep things simple in this example, we're not actually going to actually post anything to any platform, instead, we'll just print out that the function is called with the platform name.

So what is the purpose of the two special Semantic Kernel attributes that we added in the code?

  • [KernelFunction]: This indicates to the Kernel that it can use this function to achieve a specific goal.
  • [Description]: A description is necessary so that the AI knows what the purpose of the function is.

Your folder structure should now look identical to the below:

Screenshot from my machine showing complete Semantic Kernel folder structure
Complete Semantic Kernel folder structure

Step 6: Let's bring our Superhero to life

Let's get back to our Program.cs file and add a few lines of code to Invoke these functions using the Kernel.

  1. Add the following code to your Program.cs file:
builder.Plugins.AddFromType<SocialPlugin>();
builder.Plugins.AddFromPromptDirectory("./Plugins/FactmanPlugin");

// Build the kernel using the configured builder
var kernel = builder.Build();

Adding the Plugins to our builder

  1. Invoke the FindMyth function:
var commonMyth = await kernel.InvokeAsync("FactmanPlugin", "FindMyth");

The InvokeAsync method tells the Kernel to run the FindMyth function from our FactmanPlugin. It is going to pass the prompt we added to skprompt.txt to GPT-3.5 (or any other model that you define in Step 1) to generate the myth.

  1. Invoke the BustMyth function:
var bustedMyth = await kernel.InvokeAsync("FactmanPlugin", "BustMyth", new() {
    { "myth", commonMyth }
});

We need to pass our myth parameter which contains the value of the FindMyth function to the BustMyth function as shown above.

  1. Invoke the AdaptMessage function:
var optimizeResponse = await kernel.InvokeAsync("FactmanPlugin", "AdaptMessage", new() {
    { "input", bustedMyth },
    { "platform", "twitter" }
});

Here we we pass the bustedMyth value as input and we tell the AI that we're need to adapt this message to align with Twitter's best practices.

  1. Finally, Invoke the Post function and log the result:
var postToPlatform = await kernel.InvokeAsync("SocialPlugin", "Post", new() {
    { "platform", "x" },
    { "message", optimizeResponse }
});

Console.WriteLine(postToPlatform);

This tells the Kernel to post the result using our second plugin SocialPlugin by invoking the Post C# function.

  1. Run the code!

In your terminal type the following and hit return: dotnet run. You should get something back from the model similar to this:

BUSTED! 💥 Have no fear, my awesome followers! Artificial Intelligence might be smart, but it's far from stealing our jobs! 🙌 While AI can handle tasks like number crunching and data analysis, it still needs us for the real work. Think of AI as that well-meaning, yet clueless sidekick. 😅 So, even if they try to take over, just remind them that without us, they wouldn't even know how to make a cup of coffee ☕️ (let alone replace us)! Keep calm, stay focused, and keep rocking those jobs, folks! 💪 #HumansAreUnstoppable #AIisClueless #KeepWorkingHard
Simulating post to X/twitter...
Screenshot from my machine showing the final output
Screenshot from my machine showing the final output

Perfect! That's exactly what we wanted. In a few lines of code, we were able to invoke a series of events that took care of our task.

You will obviously need to fine-tune the prompts and functions for your specific use case but as I've shown you, Semantic Kernel makes it super easy to add AI capabilities to your C# app!

Source code

Final thoughts

Phew, I decided to split this tutorial into two sections so it's easier to follow and understand what Semantic Kernel is, how it works, and how you can integrate it into your app.

I find that Semantic Kernel is ideal if you have an existing C# app and you want to integrate AI capabilities into it. It's open-source and part of the .NET Framework so you know you'll get updates from Microsoft and the community.

I hope that the content was useful and will help you in your coding journey. Please subscribe to the blog (it's free) and follow me on X for more!

Thank you for reading ❤️


Further readings

More from Getting Started with AI

More from Microsoft