Creating native functions for AI to call

In the how to create prompts section, we showed how you could create a prompt that retrieves a user's intent, but what do you do once you have the intent? In most cases, you want to perform some sort of task based on the intent. For example, if the user wants to send an email, you'll need to make the necessary API calls to actually send an email.

Automating tasks like these are the primary purpose of AI apps. In this section, we'll show how you can create a simple native function that can perform a task LLMs cannot do easily on their own: arithmetic.

If you want to see the final solution to this article, you can check out the following samples in the public documentation repository. Use the link to the previous solution if you want to follow along.

Language Link to previous solution Link to final solution
C# Open example in GitHub Open solution in GitHub
Java - Open example in GitHub
Python Open solution in GitHub Open solution in GitHub

Why should you create functions for your AI?

Large language models are great at generating text, but there are several tasks they cannot perform on their own. These include, but are not limited to:

  • Retrieve data from external data sources
  • Knowing what time it is
  • Performing complex math
  • Completing tasks in the real world
  • Memorizing and recalling information

Augmenting AI with native functions

Thankfully, these tasks can already be completed by computers using native code. With native functions, you can author these features as functions that can later be called by the kernel. This allows you to combine the power of large language models with the power of native code.

For example, if you simply asked a large language model What is the square root of 634?, it would likely return back a number that is close to the square root of 634, but not the exact answer. This is because large language models are trained to predict the next word in a sequence, not to perform math.

Giving your agent the ability to perform math

To solve this problem, we'll demonstrate how to create native functions that can perform arithmetic based on a user's intent. At the end of this section you will have the following supported functions that your AI can call.

  • Sqrt – Takes the square root of a number
  • Add – Adds two numbers together
  • Subtract – Subtracts two numbers
  • Multiply – Multiplies two numbers
  • Divide – Divides two numbers

Finding a home for your native functions

We recommend that you create a new folder for your plugins at the root of your project. We recommend putting this folder at the root of your project and calling it Plugins.

Since we're giving our kernel the ability to perform math, we'll create a new plugin called MathPlugin. To do this, we'll create a MathPlugin folder along with a file to store all its native functions. Depending on the language you're using, you'll create either a C#, Python or Java file.

Plugins
│
└─── MathPlugin.cs

Creating your native functions

Open up the MathPlugin.cs, Math.py or MathPlugin.java file you created earlier and follow the instructions below to create the Sqrt function. This function will take a single number as an input and return the square root of that number.

Defining the class for your plugin

All native functions must be defined as public methods of a class that represents your plugin. To begin, create a class called Math in your math plugin file.

using System.ComponentModel;
using Microsoft.SemanticKernel;

namespace Plugins;

public sealed class MathPlugin
{

Use the KernelFunction decorator to define a native function

Now that you have a class for your plugin, you can add the Sqrt function. To make sure Semantic Kernel knows this is a native function, use the KernelFunction decorator above your new method. This decorator will tell the kernel that this method is a native function and will automatically register it with the kernel when the plugin is loaded.

[KernelFunction, Description("Take the square root of a number")]
public static double Sqrt(
    [Description("The number to take a square root of")] double number1
)
{
    return Math.Sqrt(number1);
}

Notice how we've added a description to the function and each of its parameters with the Description attribute. This description will be used by function calling and by planners to automatically create a plan using these functions. In our case, we're telling planner that this function can Take the square root of a number.

Creating the remaining math functions

Now that you've created the Sqrt function, you can create the remaining math functions. To do this, you can copy the Sqrt function and update the code to perform the correct math operation. Below is the entire MathPlugin class with all the functions implemented.

// Copyright (c) Microsoft. All rights reserved.

using System;
using System.ComponentModel;
using Microsoft.SemanticKernel;

namespace Plugins;

public sealed class MathPlugin
{
    [KernelFunction, Description("Take the square root of a number")]
    public static double Sqrt(
        [Description("The number to take a square root of")] double number1
    )
    {
        return Math.Sqrt(number1);
    }

    [KernelFunction, Description("Add two numbers")]
    public static double Add(
        [Description("The first number to add")] double number1,
        [Description("The second number to add")] double number2
    )
    {
        return number1 + number2;
    }

    [KernelFunction, Description("Subtract two numbers")]
    public static double Subtract(
        [Description("The first number to subtract from")] double number1,
        [Description("The second number to subtract away")] double number2
    )
    {
        return number1 - number2;
    }

    [KernelFunction, Description("Multiply two numbers. When increasing by a percentage, don't forget to add 1 to the percentage.")]
    public static double Multiply(
        [Description("The first number to multiply")] double number1,
        [Description("The second number to multiply")] double number2
    )
    {
        return number1 * number2;
    }

    [KernelFunction, Description("Divide two numbers")]
    public static double Divide(
        [Description("The first number to divide from")] double number1,
        [Description("The second number to divide by")] double number2
    )
    {
        return number1 / number2;
    }

    [KernelFunction, Description("Raise a number to a power")]
    public static double Power(
        [Description("The number to raise")] double number1,
        [Description("The power to raise the number to")] double number2
    )
    {
        return Math.Pow(number1, number2);
    }

    [KernelFunction, Description("Take the log of a number")]
    public static double Log(
        [Description("The number to take the log of")] double number1,
        [Description("The base of the log")] double number2
    )
    {
        return Math.Log(number1, number2);
    }

    [KernelFunction, Description("Round a number to the target number of decimal places")]
    public static double Round(
        [Description("The number to round")] double number1,
        [Description("The number of decimal places to round to")] double number2
    )
    {
        return Math.Round(number1, (int)number2);
    }

    [KernelFunction, Description("Take the absolute value of a number")]
    public static double Abs(
        [Description("The number to take the absolute value of")] double number1
    )
    {
        return Math.Abs(number1);
    }

    [KernelFunction, Description("Take the floor of a number")]
    public static double Floor(
        [Description("The number to take the floor of")] double number1
    )
    {
        return Math.Floor(number1);
    }

    [KernelFunction, Description("Take the ceiling of a number")]
    public static double Ceiling(
        [Description("The number to take the ceiling of")] double number1
    )
    {
        return Math.Ceiling(number1);
    }

    [KernelFunction, Description("Take the sine of a number")]
    public static double Sin(
        [Description("The number to take the sine of")] double number1
    )
    {
        return Math.Sin(number1);
    }

    [KernelFunction, Description("Take the cosine of a number")]
    public static double Cos(
        [Description("The number to take the cosine of")] double number1
    )
    {
        return Math.Cos(number1);
    }

    [KernelFunction, Description("Take the tangent of a number")]
    public static double Tan(
        [Description("The number to take the tangent of")] double number1
    )
    {
        return Math.Tan(number1);
    }

    [KernelFunction, Description("Take the arcsine of a number")]
    public static double Asin(
        [Description("The number to take the arcsine of")] double number1
    )
    {
        return Math.Asin(number1);
    }

    [KernelFunction, Description("Take the arccosine of a number")]
    public static double Acos(
        [Description("The number to take the arccosine of")] double number1
    )
    {
        return Math.Acos(number1);
    }

    [KernelFunction, Description("Take the arctangent of a number")]
    public static double Atan(
        [Description("The number to take the arctangent of")] double number1
    )
    {
        return Math.Atan(number1);
    }
}

Running your native function

Now that you've created your first native function, you can import it and run it using the following code. Notice how calling a native function is the same as calling a prompt. This is one of the benefits of using the kernel, both semantic and native functions are treated identically.

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Plugins;

        var builder = Kernel.CreateBuilder()
                            .AddAzureOpenAIChatCompletion(modelId, endpoint, apiKey);
        builder.Plugins.AddFromType<MathPlugin>();
        Kernel kernel = builder.Build();

        // Test the math plugin
        double answer = await kernel.InvokeAsync<double>(
            "MathPlugin", "Sqrt", new()
            {
                { "number1", 12 }
            });
        WriteLine($"The square root of 12 is {answer}.");

The code should output 3.4641016151377544 since it's the square root of 12.

Allow the AI to automatically call your function

Now that you've created your first native function, you can now allow the AI to automatically call it within the C# or Java version of the SDK. To do this, let's go ahead and create a chat loop that will allow us to talk back-and-forth with our agent.

While in the chat loop, we'll configure the OpenAI connection to automatically call any functions that are registered with the kernel. To do this, we'll set the ToolCallBehavior property to ToolCallBehavior.AutoInvokeKernelFunctions on the OpenAIPromptExecutionSettings object.

// Create chat history
ChatHistory history = [];

// Start the conversation
Write("User > ");
string? userInput;
while ((userInput = ReadLine()) != null)
{
    history.AddUserMessage(userInput);

    // Enable auto function calling
    OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
    {
        ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
    };

    // Get the response from the AI
    var result = chatCompletionService.GetStreamingChatMessageContentsAsync(
                        history,
                        executionSettings: openAIPromptExecutionSettings,
                        kernel: kernel);

    // Stream the results
    string fullMessage = "";
    var first = true;
    await foreach (var content in result)
    {
        if (content.Role.HasValue && first)
        {
            Write("Assistant > ");
            first = false;
        }
        Write(content.Content);
        fullMessage += content.Content;
    }
    WriteLine();

    // Add the message from the agent to the chat history
    history.AddAssistantMessage(fullMessage);

    // Get user input again
    Write("User > ");
}

When you run this code, you'll be able to ask the AI to perform math for you. For example, you can ask the AI to Take the square root of 12 and it will return back the correct answer.

Take the next step

Now that you can create a simple native function, you can now learn how to create native functions that accept multiple input parameters. This will be helpful to create functions like addition, multiplication, subtraction, and division which all require multiple inputs.