Updated on 17th November 2023 to reflect the latest changes in the Semantic Kernel APIs:
- All the Azure OpenAI extension methods have been renamed from Azure to AzureOpenAI. For example,
WithAzureChatCompletionService()
is nowWithAzureOpenAIChatCompletionService()
.
In the previous post, we learned how to create a semantic function plugin for Semantic Kernel. A semantic function is just a prompt, however, by including it in a function inside a plugin, we made it easier to reuse and test it.
In this post, we’re going to explore another type of plugins: native functions. As the name suggests, these plugins are native to the platform you’re using, so they can be written in C#. Python or Java. Since they support native code, they can perform more than just executing a prompt; we can virtually execute any operation which is supported by the platform.
Creating a native plugin
For this sample, we’re going to build a plugin which is able to retrieve the information about the United States population in a given year. This might be a peculiar scenario, but there’s a reason why we’re using it: a platform called DataUSA provides a set of free APIs that we can use to retrieve various data about the United States, like the population number, the number of universities, etc. The APIs are free to use and they don’t require any authentication, so they’re perfect for our sample.
These APIs behave like any other REST APIs. As such, this is the type of operation that requires using native APIs: we need to perform a GET request to a given URL, then we need to parse the response and extract the information we need. We can’t perform this operation just with a prompt.
The first step to create the native function is the same as we have seen in the previous post: we create a folder, called Plugins and, inside it, we create a subfolder for our plugin. We’re going to call it UnitedStatesPlugin. Inside this folder, we’re going to add a new class, which will host our function, called UnitedStatesPlugin.cs. This is how the project looks like:
Inside the class, we can define one or more functions, which will be exposed as Semantic Kernel functions. From a code perspective, these functions are just normal C# functions, so we can use any C# feature we want. The only requirement is that we must decorate the function with the [SKFunction]
attribute, which is defined in the Microsoft.SemanticKernel
namespace. This way, Semantic Kernel will know that the plugin exposes a function, whose name will match the name of the method. Let’s take a look at the following sample:
|
|
As you can see, the function is just a normal C# method, which returns a string
(using a Task
, since the method is asynchronous). The only difference is that it’s decorated with the SKFunction
attribute. This attribute accepts a parameter, which is the description of the function. We have also added a [Description]
attribute also to the year
parameter. These two attributes replace the information that, in a semantic function, we were storing in the config.json file. We’ll come back to the importance of providing this information when we’re going to introduce the planner.
The rest of the code is easy to understand: we use the HttpClient
class to perform a GET request to the DataUSA API. By using the GetFromJsonAsync<T>()
method, we can deserialize the response into a C# class, which maps the content of the JSON response (you can see it here. Finally, we filter the resulting collection to extract only the data for the year we’re interested in and we return the result.
Testing the plugin
Now that we have created the plugin, we can test it. To do so, we need to add it to the Semantic Kernel configuration. First, we create a new instance of the kernel as usual:
|
|
Then, we can add the plugin to the kernel, this time using the ImportFunctions()
method:
|
|
As parameters, we supply first a new instance of the class we have previously created, then we provide a name for the plugin.
Finally, we can retrieve the function defined in the plugin in the same way we did for the semantic function plugin:
|
|
The first parameter is the name of the plugin, the second one is the name of the method declared in the UnitedStatesPlugin
class.
Finally, we execute the plugin in the usual way: we create a ContextVariables
collection with the input, then we call RunAsync()
on the kernel passing the collection and the function as parameter:
|
|
In this case, we are supplying as input 2015, so we’re expecting to get back the population number of United States in 2015. If we did everything correct, this is exactly the information we’re going to get back:
|
|
Wrapping up
In this post, we have seen another type of plugins: native ones. Compared to the semantic functions, they are slightly more complex to define, since we need to create a real class with real code. On the other side, however, they are more powerful, since they can perform any operation we want, not just executing a prompt.
In the next post, we’re going to explore the last type of plugins: OpenAI plugins. Stay tuned!
In the meantime, you can find the full source code of the sample on GitHub, in the SemanticKernel.NativeFunction project.