Introducing Microsoft Semantic Kernel: A New SDK for AI Orchestration

Summary: This post introduces Microsoft’s newly released Semantic Kernel, an open-source SDK that enables developers to orchestrate AI capabilities in their applications. We’ll explore its architecture, core concepts, and demonstrate how to build your first AI-powered application using this powerful framework.

Introduction

On March 17, 2023, Microsoft released Semantic Kernel (SK), an exciting new open-source SDK designed to help developers integrate and orchestrate AI capabilities in their applications. As AI models like GPT-4 become increasingly powerful, the challenge shifts from “can AI do this?” to “how do we effectively integrate AI into our applications?” Semantic Kernel aims to solve this problem by providing a structured framework for AI orchestration.

In this post, we’ll explore what Semantic Kernel is, its core architecture, and how .NET developers can use it to build AI-powered applications. We’ll walk through practical examples to demonstrate how Semantic Kernel can help you leverage the power of large language models (LLMs) like GPT-4 in your .NET projects.

What is Semantic Kernel?

Semantic Kernel is an open-source SDK that provides a framework for integrating AI services like OpenAI’s GPT-4 and Azure OpenAI Service into your applications. It’s designed to be lightweight, extensible, and model-agnostic, allowing developers to:

  1. Combine AI with traditional code: Seamlessly mix AI capabilities with conventional programming
  2. Orchestrate multiple AI models: Coordinate different AI models and services in a unified way
  3. Build contextual memory: Manage conversation history and other contextual information
  4. Create reusable AI skills: Package AI capabilities as modular, reusable components
  5. Maintain control and flexibility: Keep control over how AI is used in your applications

At its core, Semantic Kernel is about making AI more accessible and manageable for developers, bridging the gap between powerful AI models and practical software development.

Core Concepts in Semantic Kernel

Before diving into code, let’s understand the key concepts that make up Semantic Kernel:

1. Kernel

The kernel is the central orchestration component that manages the execution of skills and functions. It’s responsible for coordinating between your application, AI services, and other components.

2. Skills

Skills are collections of related functions that provide specific capabilities. There are two types of skills in Semantic Kernel:

  • Semantic Skills: AI-powered functions defined using natural language (prompts)
  • Native Skills: Traditional code functions written in C# or other programming languages

3. Semantic Functions

Semantic functions use natural language prompts to define their behavior. These prompts are sent to language models like GPT-4, which generate responses based on the input.

4. Memory

Semantic Kernel provides memory abstractions to store and retrieve information, enabling contextual awareness in your AI applications.

5. Planner

The planner component can automatically create execution plans by breaking down complex tasks into sequences of skill functions.

Setting Up Semantic Kernel in a .NET Project

Let’s start by setting up a new .NET project and installing the Semantic Kernel package:

csharp

dotnet new console -n SemanticKernelIntro
cd SemanticKernelIntro
dotnet add package Microsoft.SemanticKernel --version 0.10.0.1-preview

Now, let’s create a simple program that initializes the kernel and connects to Azure OpenAI Service:

csharp

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AI.OpenAI;
using System;
using System.Threading.Tasks;

namespace SemanticKernelIntro
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Initializing Semantic Kernel...");

            // Initialize the kernel with Azure OpenAI
            IKernel kernel = Kernel.Builder
                .WithAzureChatCompletionService(
                    deploymentName: "gpt-35-turbo", // Your Azure OpenAI deployment name
                    endpoint: "https://your-resource-name.openai.azure.com/",
                    apiKey: "your-azure-openai-api-key" )
                .Build();

            Console.WriteLine("Kernel initialized successfully!");
            
            // We'll add more code here in the following sections
        }
    }
}

If you prefer to use OpenAI’s API directly instead of Azure OpenAI Service, you can use this configuration:

csharp

IKernel kernel = Kernel.Builder
    .WithOpenAIChatCompletionService(
        modelId: "gpt-3.5-turbo", // or "gpt-4" if you have access
        apiKey: "your-openai-api-key")
    .Build();

Creating Your First Semantic Function

Now that we have our kernel set up, let’s create a simple semantic function that generates a short story based on a given topic:

csharp

// Define a semantic function using a prompt
string promptTemplate = @"
Write a short story about {{$topic}} in the style of {{$author}}.
The story should be approximately 3-4 paragraphs long.
";

// Register the semantic function with the kernel
var storySkill = kernel.CreateSemanticFunction(promptTemplate, maxTokens: 1000, temperature: 0.7);

// Invoke the semantic function
var variables = new ContextVariables();
variables.Set("topic", "a robot learning to paint");
variables.Set("author", "Isaac Asimov");

Console.WriteLine("Generating story...");
var result = await storySkill.InvokeAsync(variables);

Console.WriteLine("\nGenerated Story:\n");
Console.WriteLine(result);

When you run this code, it will generate a short story about a robot learning to paint in the style of Isaac Asimov. The semantic function uses the prompt template with the variables we provided to generate the story.

Creating a Skill with Multiple Functions

In real applications, you’ll want to organize related functions into skills. Let’s create a WritingSkill with multiple semantic functions:

csharp

// Create a directory for our semantic skill definitions
Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "skills"));
Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "skills", "WritingSkill"));

// Create prompt files for our semantic functions
File.WriteAllText(
    Path.Combine(Directory.GetCurrentDirectory(), "skills", "WritingSkill", "ShortStory.txt"),
    @"Write a short story about {{$topic}} in the style of {{$author}}.
The story should be approximately 3-4 paragraphs long."
);

File.WriteAllText(
    Path.Combine(Directory.GetCurrentDirectory(), "skills", "WritingSkill", "Summarize.txt"),
    @"Summarize the following text in 3-4 sentences:

{{$input}}"
);

File.WriteAllText(
    Path.Combine(Directory.GetCurrentDirectory(), "skills", "WritingSkill", "Translate.txt"),
    @"Translate the following text from {{$source_language}} to {{$target_language}}:

{{$input}}"
);

// Import the skill from the directory
var writingSkill = kernel.ImportSemanticSkillFromDirectory(
    Path.Combine(Directory.GetCurrentDirectory(), "skills"), "WritingSkill");

// Now we can use the functions in this skill
var variables = new ContextVariables();
variables.Set("topic", "a robot learning to paint");
variables.Set("author", "Isaac Asimov");

var story = await kernel.RunAsync(variables, writingSkill["ShortStory"]);

Console.WriteLine("\nGenerated Story:\n");
Console.WriteLine(story);

// Use the Summarize function
variables = new ContextVariables();
variables.Set("input", story.ToString());

var summary = await kernel.RunAsync(variables, writingSkill["Summarize"]);

Console.WriteLine("\nSummary:\n");
Console.WriteLine(summary);

// Use the Translate function
variables = new ContextVariables();
variables.Set("input", summary.ToString());
variables.Set("source_language", "English");
variables.Set("target_language", "Spanish");

var translation = await kernel.RunAsync(variables, writingSkill["Translate"]);

Console.WriteLine("\nTranslation:\n");
Console.WriteLine(translation);

This example demonstrates how to create a skill with multiple semantic functions and how to use them together in a workflow.

Creating Native Skills

While semantic functions are powerful, you’ll often need to combine them with traditional code. Semantic Kernel allows you to create native skills using C# methods:

csharp

using Microsoft.SemanticKernel.SkillDefinition;
using System;
using System.ComponentModel;
using System.Threading.Tasks;

namespace SemanticKernelIntro.Skills
{
    public class MathSkill
    {
        [SKFunction, Description("Add two numbers together.")]
        public double Add(
            [Description("First number to add")] double a,
            [Description("Second number to add")] double b)
        {
            return a + b;
        }

        [SKFunction, Description("Subtract the second number from the first.")]
        public double Subtract(
            [Description("Number to subtract from")] double a,
            [Description("Number to subtract")] double b)
        {
            return a - b;
        }

        [SKFunction, Description("Multiply two numbers.")]
        public double Multiply(
            [Description("First number to multiply")] double a,
            [Description("Second number to multiply")] double b)
        {
            return a * b;
        }

        [SKFunction, Description("Divide the first number by the second.")]
        public double Divide(
            [Description("Number to divide")] double a,
            [Description("Number to divide by")] double b)
        {
            if (b == 0)
                throw new ArgumentException("Cannot divide by zero.");
            return a / b;
        }
    }
}

Now, let’s register and use this native skill:

csharp

// Register the native skill
var mathSkill = kernel.ImportSkill(new SemanticKernelIntro.Skills.MathSkill(), "Math");

// Use the native skill
var addResult = await kernel.RunAsync(
    new ContextVariables
    {
        ["a"] = "10",
        ["b"] = "20"
    },
    mathSkill["Add"]);

Console.WriteLine($"10 + 20 = {addResult}");

Combining Semantic and Native Skills

The real power of Semantic Kernel comes from combining semantic and native skills. Let’s create a simple example that uses both:

csharp

// Define a semantic function that generates a math problem
string mathProblemPrompt = @"
Generate a simple math problem involving {{$operation}} with two numbers between 1 and 100.
Only state the problem, don't provide the answer.
";

var generateMathProblemFunction = kernel.CreateSemanticFunction(mathProblemPrompt);

// Generate a math problem
var variables = new ContextVariables();
variables.Set("operation", "multiplication");

var mathProblem = await kernel.RunAsync(variables, generateMathProblemFunction);
Console.WriteLine($"Generated problem: {mathProblem}");

// Now, let's create a semantic function to extract the numbers from the problem
string extractNumbersPrompt = @"
Extract the two numbers from the following math problem.
Return only the two numbers separated by a comma, nothing else.

Problem: {{$input}}
";

var extractNumbersFunction = kernel.CreateSemanticFunction(extractNumbersPrompt);

// Extract the numbers
variables = new ContextVariables();
variables.Set("input", mathProblem.ToString());

var extractedNumbers = await kernel.RunAsync(variables, extractNumbersFunction);
Console.WriteLine($"Extracted numbers: {extractedNumbers}");

// Parse the extracted numbers
var numbers = extractedNumbers.ToString().Split(',');
if (numbers.Length == 2 && double.TryParse(numbers[0], out double a) && double.TryParse(numbers[1], out double b))
{
    // Use our native math skill to solve the problem
    var multiplyResult = await kernel.RunAsync(
        new ContextVariables
        {
            ["a"] = a.ToString(),
            ["b"] = b.ToString()
        },
        mathSkill["Multiply"]);

    Console.WriteLine($"The answer is: {multiplyResult}");
}
else
{
    Console.WriteLine("Failed to extract valid numbers from the problem.");
}

This example demonstrates how to combine semantic functions (generating a math problem and extracting numbers) with a native skill (performing the calculation) to create a more complex workflow.

Using Semantic Kernel Memory

Semantic Kernel also provides memory capabilities that allow your applications to store and retrieve information semantically. Let’s see how to use the memory system:

csharp

// First, let's set up a memory store using the volatile memory provider (for demonstration purposes)
using Microsoft.SemanticKernel.Memory;

// Add memory to our kernel
var memoryStore = new VolatileMemoryStore();
var embeddingGenerator = new AzureTextEmbeddingGenerationService(
    "text-embedding-ada-002", // Azure OpenAI embedding deployment name
    "https://your-resource-name.openai.azure.com/",
    "your-azure-openai-api-key" );

kernel.UseMemory(embeddingGenerator, memoryStore);

// Save some information to memory
await kernel.Memory.SaveInformationAsync("facts", "earth-fact-1", "The Earth is the third planet from the Sun.");
await kernel.Memory.SaveInformationAsync("facts", "earth-fact-2", "The Earth's circumference is approximately 40,075 kilometers.");
await kernel.Memory.SaveInformationAsync("facts", "mars-fact-1", "Mars is the fourth planet from the Sun.");
await kernel.Memory.SaveInformationAsync("facts", "mars-fact-2", "Mars is often called the Red Planet due to its reddish appearance.");

// Retrieve information semantically
var results = await kernel.Memory.SearchAsync("facts", "How big is the Earth?", limit: 1);
foreach (var result in results)
{
    Console.WriteLine($"Memory result (relevance: {result.Relevance}):");
    Console.WriteLine(result.Metadata.Text);
    Console.WriteLine();
}

This example demonstrates how to save information to memory and retrieve it semantically based on natural language queries.