Semantic Kernel - .Net V1 Migration Guide

Note

This document is not final and will get increasingly better!

This guide is intended to help you upgrade from a pre-v1 version of the .NET Semantic Kernel SDK to v1+. The pre-v1 version used as a reference for this document was the 0.26.231009 version which was the last version before the first beta release where the majority of the changes started to happen.

Package Changes

As a result of many packages being redefined, removed and renamed, also considering that we did a good cleanup and namespace simplification many of our old packages needed to be renamed, deprecated and removed. The table below shows the changes in our packages.

All packages that start with Microsoft.SemanticKernel were truncated with a .. prefix for brevity.

Previous Name V1 Name Version Reason
..Connectors.AI.HuggingFace ..Connectors.HuggingFace preview
..Connectors.AI.OpenAI ..Connectors.OpenAI v1
..Connectors.AI.Oobabooga MyIA.SemanticKernel.Connectors.AI.Oobabooga alpha Community driven connector
⚠️ Not ready for v1+ yet
..Connectors.Memory.Kusto ..Connectors.Kusto alpha
..Connectors.Memory.DuckDB ..Connectors.DuckDB alpha
..Connectors.Memory.Pinecone ..Connectors.Pinecone alpha
..Connectors.Memory.Redis ..Connectors.Redis alpha
..Connectors.Memory.Qdrant ..Connectors.Qdrant alpha
-- ..Connectors.Postgres alpha
..Connectors.Memory.AzureCognitiveSearch ..Connectors.Memory.AzureAISearch alpha
..Functions.Semantic - Removed - Merged in Core
..Reliability.Basic - Removed - Replaced by .NET Dependency Injection
..Reliability.Polly - Removed - Replaced by .NET Dependency Injection
..TemplateEngine.Basic - Removed - Merged in Core
..Planners.Core ..Planners.OpenAI
Planners.Handlebars
preview
-- ..Experimental.Agents alpha
-- ..Experimental.Orchestration.Flow v1

Reliability Packages - Replaced by .NET Dependency Injection

The Reliability Basic and Polly packages now can be achieved using the .net dependency injection ConfigureHttpClientDefaults service collection extension to inject the desired resiliency policies to the HttpClient instances.

// Before
var retryConfig = new BasicRetryConfig
{
    MaxRetryCount = 3,
    UseExponentialBackoff = true,
};
retryConfig.RetryableStatusCodes.Add(HttpStatusCode.Unauthorized);
var kernel = new KernelBuilder().WithRetryBasic(retryConfig).Build();
// After
builder.Services.ConfigureHttpClientDefaults(c =>
{
    // Use a standard resiliency policy, augmented to retry on 401 Unauthorized for this example
    c.AddStandardResilienceHandler().Configure(o =>
    {
        o.Retry.ShouldHandle = args => ValueTask.FromResult(args.Outcome.Result?.StatusCode is HttpStatusCode.Unauthorized);
    });
});

Package Removal and Changes Needed

Ensure that if you use any of the packages below you match the latest version that V1 uses:

Package Name Version
Microsoft.Extensions.Configuration 8.0.0
Microsoft.Extensions.Configuration.Binder 8.0.0
Microsoft.Extensions.Configuration.EnvironmentVariables 8.0.0
Microsoft.Extensions.Configuration.Json 8.0.0
Microsoft.Extensions.Configuration.UserSecrets 8.0.0
Microsoft.Extensions.DependencyInjection 8.0.0
Microsoft.Extensions.DependencyInjection.Abstractions 8.0.0
Microsoft.Extensions.Http 8.0.0
Microsoft.Extensions.Http.Resilience 8.0.0
Microsoft.Extensions.Logging 8.0.0
Microsoft.Extensions.Logging.Abstractions 8.0.0
Microsoft.Extensions.Logging.Console 8.0.0

Convention Name Changes

Many of our internal naming conventions were changed to better reflect how the AI community names things. As OpenAI started the massive shift and terms like Prompt, Plugins, Models, RAG were taking shape it was clear that we needed to align with those terms to make it easier for the community to understand use the SDK.

Previous Name V1 Name
Semantic Function Prompt Function
Native Function Method Function
Context Variable Kernel Argument
Request Settings Prompt Execution Settings
Text Completion Text Generation
Image Generation Text to Image
Skill Plugin

Code Name Changes

Following the convetion name changes, many of the code names were also changed to better reflect the new naming conventions. Abbreaviations were also removed to make the code more readable.

Previous Name V1 Name
ContextVariables KernelArguments
ContextVariables.Set KernelArguments.Add
IImageGenerationService ITextToImageService
ITextCompletionService ITextGenerationService
Kernel.CreateSemanticFunction Kernel.CreateFunctionFromPrompt
Kernel.ImportFunctions Kernel.ImportPluginFrom____
Kernel.ImportSemanticFunctionsFromDirectory Kernel.ImportPluginFromPromptDirectory
Kernel.RunAsync Kernel.InvokeAsync
NativeFunction MethodFunction
OpenAIRequestSettings OpenAIPromptExecutionSettings
RequestSettings PromptExecutionSettings
SKException KernelException
SKFunction KernelFunction
SKFunctionMetadata KernelFunctionAttribute
SKJsonSchema KernelJsonSchema
SKParameterMetadata KernelParameterMetadata
SKPluginCollection KernelPluginCollection
SKReturnParameterMetadata KernelReturnParameterMetadata
SemanticFunction PromptFunction
SKContext FunctionResult (output)

Namespace Simplifications

The old namespaces before had a deep hierarchy matching 1:1 the directory names in the projects. This is a common practice but did mean that consumers of the Semantic Kernel packages had to add a lot of different using's in their code. We decided to reduce the number of namespaces in the Semantic Kernel packages so the majority of the functionality is in the main Microsoft.SemanticKernel namespace. See below for more details.

Previous Name V1 Name
Microsoft.SemanticKernel.Orchestration Microsoft.SemanticKernel
Microsoft.SemanticKernel.Connectors.AI.* Microsoft.SemanticKernel.Connectors.*
Microsoft.SemanticKernel.SemanticFunctions Microsoft.SemanticKernel
Microsoft.SemanticKernel.Events Microsoft.SemanticKernel
Microsoft.SemanticKernel.AI.* Microsoft.SemanticKernel.*
Microsoft.SemanticKernel.Connectors.AI.OpenAI.* Microsoft.SemanticKernel.Connectors.OpenAI
Microsoft.SemanticKernel.Connectors.AI.HuggingFace.* Microsoft.SemanticKernel.Connectors.HuggingFace

Kernel

The code to create and use a Kernel instance has been simplified. The IKernel interface has been eliminated as developers should not need to create their own Kernel implementation. The Kernel class represents a collection of services and plugins. The current Kernel instance is available everywhere which is consistent with the design philosophy behind the Semantic Kernel.

  • IKernel interface was changed to Kernel class.

  • Kernel.ImportFunctions was removed and replaced by Kernel.ImportPluginFrom____, where ____ can be Functions, Object, PromptDirectory, Type, Grp or OpenAIAsync, etc.

    // Before
    var textFunctions = kernel.ImportFunctions(new StaticTextPlugin(), "text");
    
    // After
    var textFunctions = kernel.ImportPluginFromObject(new StaticTextPlugin(), "text");
    
  • Kernel.RunAsync was removed and replaced by Kernel.InvokeAsync. Order of parameters shifted, where function is the first.

    // Before
    KernelResult result = kernel.RunAsync(textFunctions["Uppercase"], "Hello World!");
    
    // After
    FunctionResult result = kernel.InvokeAsync(textFunctions["Uppercase"], new() { ["input"] = "Hello World!" });
    
  • Kernel.InvokeAsync now returns a FunctionResult instead of a KernelResult.

  • Kernel.InvokeAsync only targets one function per call as first parameter. Pipelining is not supported, use the Example 60 to achieve a chaining behavior.

    ❌ Not supported

    KernelResult result = await kernel.RunAsync(" Hello World! ",
        textFunctions["TrimStart"],
        textFunctions["TrimEnd"],
        textFunctions["Uppercase"]);
    

    ✔️ One function per call

    var trimStartResult = await kernel.InvokeAsync(textFunctions["TrimStart"], new() { ["input"] = " Hello World! " });
    var trimEndResult = await kernel.InvokeAsync(textFunctions["TrimEnd"], new() { ["input"] = trimStartResult.GetValue<string>() });
    var finalResult = await kernel.InvokeAsync(textFunctions["Uppercase"], new() { ["input"] = trimEndResult.GetValue<string>() });
    

    ✔️ Chaining using plugin Kernel injection

    // Plugin using Kernel injection
    public class MyTextPlugin
    {
        [KernelFunction]
        public async Task<string> Chain(Kernel kernel, string input)
        {
            var trimStartResult = await kernel.InvokeAsync("textFunctions", "TrimStart", new() { ["input"] = input });
            var trimEndResult = await kernel.InvokeAsync("textFunctions", "TrimEnd", new() { ["input"] = trimStartResult.GetValue<string>() });
            var finalResult = await kernel.InvokeAsync("textFunctions", "Uppercase", new() { ["input"] = trimEndResult.GetValue<string>() });
    
            return finalResult.GetValue<string>();
        }
    }
    
    var plugin = kernel.ImportPluginFromObject(new MyTextPlugin(), "textFunctions");
    var finalResult = await kernel.InvokeAsync(plugin["Chain"], new() { ["input"] = " Hello World! "});
    
  • Kernel.InvokeAsync does not accept string as input anymore, use a KernelArguments instance instead. The function now is the first argument and the input argument needs to be provided as a KernelArguments instance.

    // Before
    var result = await kernel.RunAsync("I missed the F1 final race", excuseFunction);
    
    // After
    var result = await kernel.InvokeAsync(excuseFunction, new() { ["input"] = "I missed the F1 final race" });
    
  • Kernel.ImportSemanticFunctionsFromDirectory was removed and replaced by Kernel.ImportPluginFromPromptDirectory.

  • Kernel.CreateSemanticFunction was removed and replaced by Kernel.CreateFunctionFromPrompt.

    • Arguments: OpenAIRequestSettings is now OpenAIPromptExecutionSettings

Context Variables

ContextVariables was redefined asKernelArguments and is now a dictionary, where the key is the name of the argument and the value is the value of the argument. Methods like Set and Get were removed and the common dictionary Add or the indexer [] to set and get values should be used instead.

// Before
var variables = new ContextVariables("Today is: ");
variables.Set("day", DateTimeOffset.Now.ToString("dddd", CultureInfo.CurrentCulture));

// After
var arguments = new KernelArguments() {
  ["input"] = "Today is: ",
  ["day"] = DateTimeOffset.Now.ToString("dddd", CultureInfo.CurrentCulture)
};

// Initialize directly or use the dictionary indexer below
arguments["day"] = DateTimeOffset.Now.ToString("dddd", CultureInfo.CurrentCulture);

Kernel Builder

Many changes were made to our KernelBuilder to make it more intuitive and easier to use, as well as to make it simpler and more aligned with the .NET builders approach.

  • Creating a KernelBuilder can now be only created using the Kernel.CreateBuilder() method.

    This change make it simpler and easier to use the KernelBuilder in any code-base ensureing one main way of using the builder instead of multiple ways that adds complexity and maintenance overhead.

    // Before
    IKernel kernel = new KernelBuilder().Build();
    
    // After
    var builder = Kernel.CreateBuilder().Build();
    
  • KernelBuilder.With... was renamed to KernelBuilder.Add...

    • WithOpenAIChatCompletionService was renamed to AddOpenAIChatCompletionService
    • WithAIService<ITextCompletion>
  • KernelBuilder.WithLoggerFactory is not more used, instead use dependency injection approach to add the logger factory.

    IKernelBuilder builder = Kernel.CreateBuilder();
    builder.Services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information));
    
  • WithAIService<T> Dependency Injection

    Previously the KernelBuilder had a method WithAIService<T> that was removed and a new ServiceCollection Services property is exposed to allow the developer to add services to the dependency injection container. i.e.:

    builder.Services.AddSingleton<ITextGenerationService>()
    

Kernel Result

As the Kernel became just a container for the plugins and now executes just one function there was not more need to have a KernelResult entity and all function invocations from Kernel now return a FunctionResult.

SKContext

After a lot of discussions and feedback internally and from the community, to simplify the API and make it more intuitive, the SKContext concept was dilluted in different entities: KernelArguments for function inputs and FunctionResult for function outputs.

With the important decision to make Kernel a required argument of a function calling, the SKContext was removed and the KernelArguments and FunctionResult were introduced.

KernelArguments is a dictionary that holds the input arguments for the function invocation that were previously held in the SKContext.Variables property.

FunctionResult is the output of the Kernel.InvokeAsync method and holds the result of the function invocation that was previously held in the SKContext.Result property.

New Plugin Abstractions

  • KernelPlugin Entity: Before V1 there was no concept of a plugin centric entity. This changed in V1 and for any function you add to a Kernel you will get a Plugin that it belongs to.

Plugins Immutability

Plugins are created by default as immutable by our out-of-the-box DefaultKernelPlugin implementation, which means that they cannot be modified or changed after creation.

Also attempting to import the plugins that share the same name in the kernel will give you a key violation exception.

The addition of the KernelPlugin abstraction allows dynamic implementations that may support mutability and we provided an example on how to implement a mutable plugin in the Example 69.

Combining multiple plugins into one

Attempting to create a plugin from directory and adding Method functions afterwards for the same plugin will not work unless you use another approach like creating both plugins separately and then combining them into a single plugin iterating over its functions to aggregate into the final plugin using kernel.ImportPluginFromFunctions("myAggregatePlugin", myAggregatedFunctions) extension.

Usage of Experimental Attribute Feature.

This features was introduced to mark some functionalities in V1 that we can possibly change or completely remove.

For mode details one the list of current released experimental features check here.

Prompt Configuration Files

Major changes were introduced to the Prompt Configuration files including default and multiple service/model configurations.

Other naming changes to note:

  • completion was renamed to execution_settings
  • input was renamed to input_variables
  • defaultValue was renamed to default
  • parameters was renamed to input_variables
  • Each property name in the execution_settings once matched to the service_id will be used to configure the service/model execution settings. i.e.:
    // The "service1" execution settings will be used to configure the OpenAIChatCompletion service
    Kernel kernel = Kernel.CreateBuilder()
        .AddOpenAIChatCompletion(serviceId: "service1", modelId: "gpt-4")
    

Before

{
  "schema": 1,
  "description": "Given a text input, continue it with additional text.",
  "type": "completion",
  "completion": {
    "max_tokens": 4000,
    "temperature": 0.3,
    "top_p": 0.5,
    "presence_penalty": 0.0,
    "frequency_penalty": 0.0
  },
  "input": {
    "parameters": [
      {
        "name": "input",
        "description": "The text to continue.",
        "defaultValue": ""
      }
    ]
  }
}

After

{
  "schema": 1,
  "description": "Given a text input, continue it with additional text.",
  "execution_settings": {
    "default": {
      "max_tokens": 4000,
      "temperature": 0.3,
      "top_p": 0.5,
      "presence_penalty": 0.0,
      "frequency_penalty": 0.0
    },
    "service1": {
      "model_id": "gpt-4",
      "max_tokens": 200,
      "temperature": 0.2,
      "top_p": 0.0,
      "presence_penalty": 0.0,
      "frequency_penalty": 0.0,
      "stop_sequences": ["Human", "AI"]
    },
    "service2": {
      "model_id": "gpt-3.5_turbo",
      "max_tokens": 256,
      "temperature": 0.3,
      "top_p": 0.0,
      "presence_penalty": 0.0,
      "frequency_penalty": 0.0,
      "stop_sequences": ["Human", "AI"]
    }
  },
  "input_variables": [
    {
      "name": "input",
      "description": "The text to continue.",
      "default": ""
    }
  ]
}