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: New Feature: Add support for adding [KernelFunction] attribute to an interface methods #10323

Open
ruslany opened this issue Jan 28, 2025 · 1 comment
Assignees
Labels
.NET Issue or Pull requests regarding .NET code

Comments

@ruslany
Copy link

ruslany commented Jan 28, 2025


name: Add support for adding [KernelFunction] attribute to an interface methods
about: Need this for testing the AI without invoking the actual plugin code

For testing and prompt engineering purposes I want to define a plugin as an interface and then use a mock class to test the AI prompts and interaction. However it is not possible to add this attribute to interface methods. I am getting an error "Unhandled exception. System.ArgumentException: The sklearn.SubscriptionPlugin instance doesn't implement any [KernelFunction]-attributed methods."

Example code that does not work:

using Microsoft.SemanticKernel;
using System.ComponentModel;

namespace sklearn
{
    [Description("The id and display name of an Azure subscription")]
    public sealed record SubscriptionDescriptor(
        string Id,
        string DisplayName);

    internal interface ISubscriptionPlugin
    {
        [KernelFunction("list_subscriptions")]
        [Description("Lists all Azure subscriptions that user has access to")]
        public Task<IReadOnlyList<SubscriptionDescriptor>> ListSubscriptionsAsync();

        [KernelFunction("get_subscription")]
        [Description("Gets the details of a specific Azure subscription id")]
        public Task<SubscriptionDescriptor?> GetSubscriptionAsync(string id);
    }

    public class SubscriptionPlugin : ISubscriptionPlugin
    {
        private readonly List<SubscriptionDescriptor> _subscriptions = new()
        {
            new SubscriptionDescriptor("31cad2f7-1ec4-4491-b69f-d0360ec2b723", "Subscription 1"),
            new SubscriptionDescriptor("7f07aade-c4d8-416c-a3d9-06c76acacf71", "Subscription 2"),
            new SubscriptionDescriptor("62b38d30-c1ab-4f92-b26f-9fb4c85a01a2", "Subscription 3")
        };

        public Task<IReadOnlyList<SubscriptionDescriptor>> ListSubscriptionsAsync()
        {
            return Task.FromResult<IReadOnlyList<SubscriptionDescriptor>>(_subscriptions);
        }

        public Task<SubscriptionDescriptor?> GetSubscriptionAsync(string id)
        {
            return Task.FromResult(_subscriptions.FirstOrDefault(subscription => subscription.Id == id));
        }
    }
}
@markwallace-microsoft markwallace-microsoft added .NET Issue or Pull requests regarding .NET code triage labels Jan 28, 2025
@github-actions github-actions bot changed the title New Feature: Add support for adding [KernelFunction] attribute to an interface methods .Net: New Feature: Add support for adding [KernelFunction] attribute to an interface methods Jan 28, 2025
@ruslany
Copy link
Author

ruslany commented Jan 29, 2025

Worked around by using this approach:

using System.ComponentModel;

namespace sklearn
{
    [Description("The id and display name of an Azure subscription")]
    public sealed record SubscriptionDescriptor(
        string Id,
        string DisplayName);

    public interface ISubscriptionPlugin
    {
        public Task<IReadOnlyList<SubscriptionDescriptor>> ListSubscriptionsAsync();

        public Task<SubscriptionDescriptor?> GetSubscriptionAsync(string id);
    }

    public class SubscriptionPluginDefinition
    {
        private readonly ISubscriptionPlugin _plugin;

        public class SubscriptionPluginDefinition(ISubscriptionPlugin plugin)
        {
            _plugin = plugin;
        }

        [KernelFunction("list_subscriptions")]
        [Description("Lists all Azure subscriptions that user has access to")]
        public Task<IReadOnlyList<SubscriptionDescriptor>> ListSubscriptionsAsync()
        {
            return _plugin.ListSubscriptionsAsync();
        }

        [KernelFunction("get_subscription")]
        [Description("Gets the details of a specific Azure subscription id")]
        public Task<SubscriptionDescriptor?> GetSubscriptionAsync(string id)
        {
            return _plugin.GetSubscriptionAsync(id);
        }
    }

    public class SubscriptionPlugin : ISubscriptionPlugin
    {
        private readonly List<SubscriptionDescriptor> _subscriptions = new()
        {
            new SubscriptionDescriptor("31cad2f7-1ec4-4491-b69f-d0360ec2b723", "Subscription 1"),
            new SubscriptionDescriptor("7f07aade-c4d8-416c-a3d9-06c76acacf71", "Subscription 2"),
            new SubscriptionDescriptor("62b38d30-c1ab-4f92-b26f-9fb4c85a01a2", "Subscription 3")
        };

        public Task<IReadOnlyList<SubscriptionDescriptor>> ListSubscriptionsAsync()
        {
            return Task.FromResult<IReadOnlyList<SubscriptionDescriptor>>(_subscriptions);
        }

        public Task<SubscriptionDescriptor?> GetSubscriptionAsync(string id)
        {
            return Task.FromResult(_subscriptions.FirstOrDefault(subscription => subscription.Id == id));
        }
    }
}

Then instantiate this and add to the kernel

serviceCollection.AddScoped<ISubscriptionPlugin, SubscriptionPlugin>();
serviceCollection.AddScoped<SubscriptionPluginDefinition>();

var subscriptionPluginDefinition = sp.GetRequiredService<SubscriptionPluginDefinition>();
kernelBuilder.Plugins.AddFromObject(subscriptionPluginDefinition, "SubscriptionPlugin");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
.NET Issue or Pull requests regarding .NET code
Projects
None yet
Development

No branches or pull requests

4 participants