Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net: Import plugins with mixed prompt and native functions (avoiding creating a plugin for each function) #8851

Open
markwallace-microsoft opened this issue Sep 17, 2024 Discussed in #8818 · 0 comments
Assignees
Labels
kernel.core .NET Issue or Pull requests regarding .NET code sk team issue A tag to denote issues that where created by the Semantic Kernel team (i.e., not the community)

Comments

@markwallace-microsoft
Copy link
Member

Discussed in #8818

Originally posted by gsubiran September 16, 2024
Hey! I've been working with SK for some time, and I'm having a bit of trouble handling the import of plugin functions. Here's what I've encountered:

When you want to import a plugin with prompt functions (formerly known as semantic functions), you can use:

kernel.ImportPluginFromPromptDirectory("Plugins/SomeSpecificPlugin", "PluginName");

On the other hand, if you want to import a plugin with native functions, a simple way to do that is:

kernel.ImportPluginFromType<Plugins.MyNativeFunctions>("PluginName");

That’s pretty straightforward. However, the problem arises when you want to combine native and prompt functions in the same plugin. I found that there is no easy way to do this. Additionally, if you want to separate native functions into different classes, you end up creating a new plugin for each native function due to the same issue.

I’d appreciate some advice if I’m missing something here, but I believe the goal of plugins should be to group related native and semantic functions.

My Approach
I created a class called CustomKernelExtensions to make it easier to import both native and prompt functions into the same plugin by calling:

kernel.CustomImportPlugin("PluginName");

(For simplicity, assume CustomKernelExtensions.cs is placed in the "Plugins" directory, and the plugin name must match the specific plugin folder name.)

Here’s the code for the CustomKernelExtensions class:

internal static class CustomKernelExtensions
{
    private static IEnumerable<KernelFunction> CreatePromptFunctions(Kernel kernel, string pluginName)
    {
        var scanDirectory = Path.Combine(Environment.CurrentDirectory, nameof(Plugins), pluginName);

        // Creating plugin from directory (but not importing it into the kernel)
        return kernel.CreatePluginFromPromptDirectory(scanDirectory);
    }

    private static IEnumerable<KernelFunction> CreateNativeFunctions(Kernel kernel, string pluginName)
    {
        List<KernelFunction> result = new List<KernelFunction>();

        string namespaceToGetPlugins = typeof(CustomKernelExtensions).Namespace!;
        namespaceToGetPlugins = $"{namespaceToGetPlugins}.{pluginName}";

        foreach (Type type in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.Namespace != null && x.Namespace.StartsWith(namespaceToGetPlugins)))
        {
            var containsAnyNativeFunction = type.GetMethods().Any(m => m.GetCustomAttributes(typeof(KernelFunctionAttribute), false).Length > 0);

            if (containsAnyNativeFunction)
            {
                string functionName = type.FullName!.Replace(type.Assembly!.GetName().Name!, string.Empty).Trim('.').Replace($"{nameof(Plugins)}.", string.Empty);

                // Creating an instance, ensuring to inject dependencies
                var instance = ActivatorUtilities.CreateInstance(kernel.Services, type);

                // Creating plugin from object (but not importing it into the kernel)
                result.AddRange(kernel.CreatePluginFromObject(instance, type.Name!));
            }
        }

        return result;
    }

    internal static void CustomImportPlugin(this Kernel kernel, string pluginName)
    {
        if (string.IsNullOrWhiteSpace(pluginName))
            throw new ArgumentNullException(nameof(pluginName), "You must provide a plugin name to import.");

        // Merging all kernel functions (native and prompt) into the same plugin
        kernel.ImportPluginFromFunctions(pluginName,
            CreatePromptFunctions(kernel, pluginName)
            .Concat(CreateNativeFunctions(kernel, pluginName)));
    }
}

Does anyone have any feedback on this approach? I’m wondering if I’m overcomplicating things. Is there a simpler way to achieve the same result? I would appreciate any insights or suggestions!

@markwallace-microsoft markwallace-microsoft added .NET Issue or Pull requests regarding .NET code kernel.core labels Sep 17, 2024
@markwallace-microsoft markwallace-microsoft self-assigned this Sep 17, 2024
@github-actions github-actions bot changed the title Import plugins with mixed prompt and native functions (avoiding creating a plugin for each function) .Net: Import plugins with mixed prompt and native functions (avoiding creating a plugin for each function) Sep 17, 2024
@evchaki evchaki added the sk team issue A tag to denote issues that where created by the Semantic Kernel team (i.e., not the community) label Sep 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kernel.core .NET Issue or Pull requests regarding .NET code sk team issue A tag to denote issues that where created by the Semantic Kernel team (i.e., not the community)
Projects
Status: Backlog: Planned
Development

No branches or pull requests

2 participants