Software Architect / Microsoft MVP (AI) and Technical Author

AI, C#, Native Functions, OpenAI, Plugins, Prompt Engineering, Semantic Kernel

Semantic Kernel: Implementing Native Functions and Plugins

Native Functions and Plugins are two of the key features of Semantic Kernel.

Native Functions allow you to introduce your own custom code and combine this with LLM and SLM integrations during your AI agent development.

Native functions can be developed to perform many tasks.  This might be connecting to a backend API, databases or 3rd party web service.

In this blog post you will learn how to implement a Native Function using Semantic Kernel.

You will also learn about:

  • concept of plugins
  • the architecture of a plugin, prompts, and native functions
  • how to organise plugins and native functions within your solution

 

A walkthrough, code, and a video demo is included.

~

Plugins

In earlier blog posts, we saw how prompts can be used to implement inline prompt functions and how to load prompts from the filesystem.  If you haven’t checked those out, I recommend doing so before reading on as those will give you a grounding on what is about to be discussed.

One way to augment AI agent capability with new features is to create a Plugin.

Creating a Plugin lets you encapsulate multiple prompts and native functions into a single reusable component.

(above: booking plugin with multiple native functions)

 

We saw in an earlier blog post how Semantic Kernel can instruct an LLM as to the parameters to expect and how to handle prompts when working with Prompt Functions.

The process is similar when creating a Plugin that contains Native Functions.  You semantically describe what each Native Function does and to help describe the plugin to the agent.

Doing this lets your AI agent know what the capabilities of a plugin and its functions are.

Information for the plugin definition includes:

  • name of the plugin
  • names of the functions
  • descriptions of the functions
  • parameters of the functions
  • schema definitions for parameters (including complex types)

 

After creating one or many functions, these can be encapsulated within a plugin class that can be imported into the kernel.  The plugin class is regular class that you create.

Good practice is to place this within a /Plugins directory in your solution.

You can also add subdirectories within the /Plugins directory to help you further organise additional plugins, e.g.:

  • ~/Plugins/Calendar/CalendarPlugin.cs
  • ~/Plugins/Bookings/BookingPlugin.cs
  • ~/Plugins/Complaints/ComplaintsPlugin.cs

 

We can see an example of this here:

~

An Example: Anatomy of a Plugin and Native Function

Imagine we have a customer service agent. It has the following 3 capabilities:

  • Fetch the next available booking date
  • Make a booking
  • File a complaint

 

We can create a native function to fetch the next available booking date.

This can be encapsulated within a BookingPlugin :

public sealed class BookingPlugin
{
  // a list of available dates the user can book
  private List<DateTime> _available = new List<DateTime>
  {
   // add dates for the next 9 days
   DateTime.Now.AddDays(1), DateTime.Now.AddDays(2), DateTime.Now.AddDays(3),

   DateTime.Now.AddDays(4), DateTime.Now.AddDays(5), DateTime.Now.AddDays(6),

   DateTime.Now.AddDays(7), DateTime.Now.AddDays(8),    DateTime.Now.AddDays(9),
  };

[KernelFunction, Description("Determines the next available date for a   booking")]
  public DateTime GetNextAvailableDate([Description("The date to start  searching from")] DateTime startDate)
  {
    // get the next available date
    return _available.FirstOrDefault(d => d > startDate);
  }
}

 

This plugin contains the Native Function GetNextAvailableDate .

The purpose of this Native Function is to get the next available date from a list of available dates that is stored in the variable _available.

We can see this is just a regular C# method. Decorating it with the KernelFunction, Description annotations lets Semantic Kernel and an integrated LLM know the method is available for discovery and use by the kernel.

Further information for the parameter startDate is provided by setting the Description annotation.

Again, this provides the kernel and integrated LLM/SLM with more guidance on the purpose of the parameter within the Native Function.

~

Using the Plugin with Semantic Kernel

If the BookingPlugin has been added to the solution (see above), it can be tested directly within Semantic Kernel by using the following code:

var builder = Kernel.CreateBuilder()
                    .AddOpenAIChatCompletion(modelId, apiKey);

builder.Plugins.AddFromType<BookingPlugin>();

Kernel kernel = builder.Build();
// in a real application, you would get the user input from the chat interface here we are just hardcoding a date

DateTime startDate = DateTime.Now.AddDays(2);

// test the booking plugin directly rather than inferring intent using a // Semantic Kernel Planner with Function Calling

// this is useful for testing the plugin in isolation
DateTime nextAvailable = await kernel.InvokeAsync<DateTime>(
                        "BookingPlugin", "GetNextAvailableDate", new()
                        {
                          { "startDate", startDate }
                        });


// print the result
Console.WriteLine($"The next available date is {nextAvailable}");

In the above example, the OpenAI model and completions endpoint are added to the kernel.  The plugin is added to the list of plugins by using the following line:

  builder.Plugins.AddFromType<BookingPlugin>();

The following lines execute the plugin method, passing in the startDate to search from for a available appointment date:

 DateTime nextAvailable = await kernel.InvokeAsync<DateTime>(
                          "BookingPlugin", "GetNextAvailableDate", new()
                          {
                            { "startDate", startDate }
                          });

  // print the result
  Console.WriteLine($"The next available date is {nextAvailable}");

 

Upon running the application, the console application will generate a startDate (current date + 2days) then send this directly to the BookingPlugin.

The next available date is then displayed.

Whilst you can invoke a plugin function directly this is not advised. The AI should be decide which functions to call. If you need explicit control over what functions are called, you should use standard methods in your codebase instead of plugins.

~

Plugins and Native Functions in the Real World

In a real-world application, the Planner (more on this in a future blog) component within Semantic Kernel and language model, examines any available plugins that have been added to the kernel.

Then, using the Native Function KernelFunction and method Description attributes, an attempt is made to detect the most suitable plugin/function/prompt to satisfy the user’s request.

 

Semantic Kernel, when integrated with an LLM and native functions, makes it possible to handle this type of scenario -without having to write additional logic.

 

All of this is done automatically and one of the main selling points of Semantic Kernel.

This means that as a developer, you can create a variety of plugins that contain many functions, then have Semantic Kernel + an LLM/SLM (cloud or local) automatically create a solution in real-time to satisfy user request.

This might be one you have not considered during the original design, coding, and subsequent deployment.

For example, imagine you have the following available plugins:

Each has several native functions and you might have developed existing logic such as:

  1. Fetch the next available date
  2. Make a booking in the calendar from the users preferred date

.. but have not yet anticipated that a user may want to make a booking then file a complaint:

Semantic Kernel, when integrated with an LLM and native functions, makes it possible to handle this type of scenario -without having to write additional logic.

The kernel and language model can automatically select the relevant code for the task at hand.

This is one key features and benefits of the Semantic Kernel SDK in my opinion.

More on Planners in a future blog post.

~

Video Demo

The following 6 minutes demo shows a Plugin and Native Function in action.  In this demo you will see the Native Function being called directly.  A parameter is passed from the console application.

~

Summary

In this blog post, you have learned about the concept of Plugins in Semantic Kernel.

You learned how to create a Native Function and how to make this discoverable and useable by the kernel / OpenAI language model in your own code.

You learned about method and parameter attribute descriptors and how these are used to semantically described their purpose to Semantic Kernel and the language model.  We saw how to manually consume the Native Function in a console application.

In the next blog post, you will learn more about the importance of Function Calling.

We’ll also take a closer look at how Semantic Kernel automates what can be a complex process, and how this can automatically create a “plan” using one or many native functions to execute user tasks.

~

Further Reading and Resources

You can learn more about Semantic Kernel and Prompt Engineering here:

 

Enjoy what you’ve read, have questions about this content, or would like to see another topic? Drop me a note below.

You can also schedule a call using my Calendly link to discuss consulting and development services.

JOIN MY EXCLUSIVE EMAIL LIST
Get the latest content and code from the blog posts!
I respect your privacy. No spam. Ever.

Leave a Reply