Skip to content

Commit

Permalink
.Net: Add missing XxFromFunctions members (#4549)
Browse files Browse the repository at this point in the history
Following the same pattern used by FromType and FromObject
  • Loading branch information
stephentoub authored Jan 11, 2024
1 parent b7defcf commit 0772d01
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static async Task RunAsync()
Kernel kernel = builder.Build();

// Add a plugin with some helper functions we want to allow the model to utilize.
kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("HelperFunctions", new[]
kernel.ImportPluginFromFunctions("HelperFunctions", new[]
{
kernel.CreateFunctionFromMethod(() => DateTime.UtcNow.ToString("R"), "GetCurrentUtcTime", "Retrieves the current time in UTC."),
kernel.CreateFunctionFromMethod((string cityName) =>
Expand All @@ -44,7 +44,7 @@ public static async Task RunAsync()
"Tel Aviv" => "80 and sunny",
_ => "31 and snowing",
}, "Get_Weather_For_City", "Gets the current weather for the specified city"),
}));
});

Console.WriteLine("======== Example 1: Use automated function calling with a non-streaming prompt ========");
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static void InvokeTwoPartTool()
var function = KernelFunctionFactory.CreateFromMethod(() => { }, functionName: "Bogus");

var kernel = new Kernel();
kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("Fake", "Fake functions", new[] { function }));
kernel.ImportPluginFromFunctions("Fake", new[] { function });

//Act
var tool = kernel.GetAssistantTool(TwoPartToolName);
Expand Down
125 changes: 115 additions & 10 deletions dotnet/src/SemanticKernel.Core/KernelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,37 @@ public static KernelPlugin CreatePluginFromObject(this Kernel kernel, object tar
}
#endregion

#region CreatePluginFromFunctions
/// <summary>Creates a plugin that contains the specified functions.</summary>
/// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
/// <param name="pluginName">The name for the plugin.</param>
/// <param name="functions">The initial functions to be available as part of the plugin.</param>
/// <returns>A <see cref="KernelPlugin"/> containing the functions provided in <paramref name="functions"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is an invalid plugin name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="functions"/> contains a null function.</exception>
/// <exception cref="ArgumentException"><paramref name="functions"/> contains two functions with the same name.</exception>
public static KernelPlugin CreatePluginFromFunctions(this Kernel kernel, string pluginName, IEnumerable<KernelFunction>? functions) =>
CreatePluginFromFunctions(kernel, pluginName, description: null, functions);

/// <summary>Creates a plugin that contains the specified functions.</summary>
/// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
/// <param name="pluginName">The name for the plugin.</param>
/// <param name="description">A description of the plugin.</param>
/// <param name="functions">The initial functions to be available as part of the plugin.</param>
/// <returns>A <see cref="KernelPlugin"/> containing the functions provided in <paramref name="functions"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is an invalid plugin name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="functions"/> contains a null function.</exception>
/// <exception cref="ArgumentException"><paramref name="functions"/> contains two functions with the same name.</exception>
public static KernelPlugin CreatePluginFromFunctions(this Kernel kernel, string pluginName, string? description = null, IEnumerable<KernelFunction>? functions = null)
{
Verify.NotNull(kernel);

return KernelPluginFactory.CreateFromFunctions(pluginName, description, functions);
}
#endregion

#region ImportPlugin/AddFromType
/// <summary>Creates a plugin that wraps a new instance of the specified type <typeparamref name="T"/> and imports it into the <paramref name="kernel"/>'s plugin collection.</summary>
/// <typeparam name="T">Specifies the type of the object to wrap.</typeparam>
Expand Down Expand Up @@ -279,6 +310,68 @@ public static KernelPlugin AddFromObject(this ICollection<KernelPlugin> plugins,
return plugin;
}

/// <summary>Creates a plugin that wraps the specified target object and adds it into the plugin collection.</summary>
/// <param name="plugins">The plugin collection to which the new plugin should be added.</param>
/// <param name="target">The instance of the class to be wrapped.</param>
/// <param name="pluginName">
/// Name of the plugin for function collection and prompt templates. If the value is null, a plugin name is derived from the type of the <paramref name="target"/>.
/// </param>
/// <returns>The same instance as <paramref name="plugins"/>.</returns>
/// <remarks>
/// Public methods that have the <see cref="KernelFunctionFromPrompt"/> attribute will be included in the plugin.
/// </remarks>
public static IKernelBuilderPlugins AddFromObject(this IKernelBuilderPlugins plugins, object target, string? pluginName = null)
{
Verify.NotNull(plugins);

plugins.Services.AddSingleton(serviceProvider => KernelPluginFactory.CreateFromObject(target, pluginName, serviceProvider?.GetService<ILoggerFactory>()));

return plugins;
}
#endregion

#region ImportPlugin/AddFromFunctions
/// <summary>Creates a plugin that contains the specified functions and imports it into the <paramref name="kernel"/>'s plugin collection.</summary>
/// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
/// <param name="pluginName">The name for the plugin.</param>
/// <param name="functions">The initial functions to be available as part of the plugin.</param>
/// <returns>A <see cref="KernelPlugin"/> containing the functions provided in <paramref name="functions"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is an invalid plugin name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="functions"/> contains a null function.</exception>
/// <exception cref="ArgumentException"><paramref name="functions"/> contains two functions with the same name.</exception>
public static KernelPlugin ImportPluginFromFunctions(this Kernel kernel, string pluginName, IEnumerable<KernelFunction>? functions) =>
ImportPluginFromFunctions(kernel, pluginName, description: null, functions);

/// <summary>Creates a plugin that contains the specified functions and imports it into the <paramref name="kernel"/>'s plugin collection.</summary>
/// <param name="kernel">The <see cref="Kernel"/> containing services, plugins, and other state for use throughout the operation.</param>
/// <param name="pluginName">The name for the plugin.</param>
/// <param name="description">A description of the plugin.</param>
/// <param name="functions">The initial functions to be available as part of the plugin.</param>
/// <returns>A <see cref="KernelPlugin"/> containing the functions provided in <paramref name="functions"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is an invalid plugin name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="functions"/> contains a null function.</exception>
/// <exception cref="ArgumentException"><paramref name="functions"/> contains two functions with the same name.</exception>
public static KernelPlugin ImportPluginFromFunctions(this Kernel kernel, string pluginName, string? description = null, IEnumerable<KernelFunction>? functions = null)
{
KernelPlugin plugin = CreatePluginFromFunctions(kernel, pluginName, description, functions);
kernel.Plugins.Add(plugin);
return plugin;
}

/// <summary>Creates a plugin that contains the specified functions and adds it into the plugin collection.</summary>
/// <param name="plugins">The plugin collection to which the new plugin should be added.</param>
/// <param name="pluginName">The name for the plugin.</param>
/// <param name="functions">The initial functions to be available as part of the plugin.</param>
/// <returns>A <see cref="KernelPlugin"/> containing the functions provided in <paramref name="functions"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is an invalid plugin name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="functions"/> contains a null function.</exception>
/// <exception cref="ArgumentException"><paramref name="functions"/> contains two functions with the same name.</exception>
public static KernelPlugin AddFromFunctions(this ICollection<KernelPlugin> plugins, string pluginName, IEnumerable<KernelFunction>? functions) =>
AddFromFunctions(plugins, pluginName, description: null, functions);

/// <summary>Creates a plugin that contains the specified functions and adds it into the plugin collection.</summary>
/// <param name="plugins">The plugin collection to which the new plugin should be added.</param>
/// <param name="pluginName">The name for the plugin.</param>
Expand All @@ -289,7 +382,7 @@ public static KernelPlugin AddFromObject(this ICollection<KernelPlugin> plugins,
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is an invalid plugin name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="functions"/> contains a null function.</exception>
/// <exception cref="ArgumentException"><paramref name="functions"/> contains two functions with the same name.</exception>
public static KernelPlugin AddFromFunctions(this ICollection<KernelPlugin> plugins, string pluginName, string? description, IEnumerable<KernelFunction>? functions = null)
public static KernelPlugin AddFromFunctions(this ICollection<KernelPlugin> plugins, string pluginName, string? description = null, IEnumerable<KernelFunction>? functions = null)
{
Verify.NotNull(plugins);

Expand All @@ -300,19 +393,31 @@ public static KernelPlugin AddFromFunctions(this ICollection<KernelPlugin> plugi

/// <summary>Creates a plugin that wraps the specified target object and adds it into the plugin collection.</summary>
/// <param name="plugins">The plugin collection to which the new plugin should be added.</param>
/// <param name="target">The instance of the class to be wrapped.</param>
/// <param name="pluginName">
/// Name of the plugin for function collection and prompt templates. If the value is null, a plugin name is derived from the type of the <paramref name="target"/>.
/// </param>
/// <param name="pluginName">The name for the plugin.</param>
/// <param name="functions">The initial functions to be available as part of the plugin.</param>
/// <returns>The same instance as <paramref name="plugins"/>.</returns>
/// <remarks>
/// Public methods that have the <see cref="KernelFunctionFromPrompt"/> attribute will be included in the plugin.
/// </remarks>
public static IKernelBuilderPlugins AddFromObject(this IKernelBuilderPlugins plugins, object target, string? pluginName = null)
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is an invalid plugin name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="functions"/> contains a null function.</exception>
/// <exception cref="ArgumentException"><paramref name="functions"/> contains two functions with the same name.</exception>
public static IKernelBuilderPlugins AddFromFunctions(this IKernelBuilderPlugins plugins, string pluginName, IEnumerable<KernelFunction>? functions) =>
AddFromFunctions(plugins, pluginName, description: null, functions);

/// <summary>Creates a plugin that wraps the specified target object and adds it into the plugin collection.</summary>
/// <param name="plugins">The plugin collection to which the new plugin should be added.</param>
/// <param name="pluginName">The name for the plugin.</param>
/// <param name="description">A description of the plugin.</param>
/// <param name="functions">The initial functions to be available as part of the plugin.</param>
/// <returns>The same instance as <paramref name="plugins"/>.</returns>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="pluginName"/> is an invalid plugin name.</exception>
/// <exception cref="ArgumentNullException"><paramref name="functions"/> contains a null function.</exception>
/// <exception cref="ArgumentException"><paramref name="functions"/> contains two functions with the same name.</exception>
public static IKernelBuilderPlugins AddFromFunctions(this IKernelBuilderPlugins plugins, string pluginName, string? description = null, IEnumerable<KernelFunction>? functions = null)
{
Verify.NotNull(plugins);

plugins.Services.AddSingleton(serviceProvider => KernelPluginFactory.CreateFromObject(target, pluginName, serviceProvider?.GetService<ILoggerFactory>()));
plugins.Services.AddSingleton(KernelPluginFactory.CreateFromFunctions(pluginName, description, functions));

return plugins;
}
Expand Down
159 changes: 159 additions & 0 deletions dotnet/src/SemanticKernel.UnitTests/Functions/KernelExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;
using Xunit;

namespace SemanticKernel.UnitTests.Functions;

public class KernelExtensionsTests
{
[Fact]
public void CreatePluginFromFunctions()
{
Kernel kernel = new();

KernelPlugin plugin = kernel.CreatePluginFromFunctions("coolplugin", new[]
{
kernel.CreateFunctionFromMethod(() => { }, "Function1"),
kernel.CreateFunctionFromMethod(() => { }, "Function2"),
});

Assert.NotNull(plugin);
Assert.Empty(kernel.Plugins);

Assert.Equal("coolplugin", plugin.Name);
Assert.Empty(plugin.Description);
Assert.Equal(2, plugin.FunctionCount);
Assert.True(plugin.Contains("Function1"));
Assert.True(plugin.Contains("Function2"));
}

[Fact]
public void CreateEmptyPluginFromFunctions()
{
Kernel kernel = new();

KernelPlugin plugin = kernel.CreatePluginFromFunctions("coolplugin");

Assert.NotNull(plugin);
Assert.Empty(kernel.Plugins);

Assert.Equal("coolplugin", plugin.Name);
Assert.Empty(plugin.Description);
Assert.Empty(plugin);
Assert.Equal(0, plugin.FunctionCount);
}

[Fact]
public void CreatePluginFromDescriptionAndFunctions()
{
Kernel kernel = new();

KernelPlugin plugin = kernel.CreatePluginFromFunctions("coolplugin", "the description", new[]
{
kernel.CreateFunctionFromMethod(() => { }, "Function1"),
kernel.CreateFunctionFromMethod(() => { }, "Function2"),
});

Assert.NotNull(plugin);
Assert.Empty(kernel.Plugins);

Assert.Equal("coolplugin", plugin.Name);
Assert.Equal("the description", plugin.Description);
Assert.Equal(2, plugin.FunctionCount);
Assert.True(plugin.Contains("Function1"));
Assert.True(plugin.Contains("Function2"));
}

[Fact]
public void ImportPluginFromFunctions()
{
Kernel kernel = new();

kernel.ImportPluginFromFunctions("coolplugin", new[]
{
kernel.CreateFunctionFromMethod(() => { }, "Function1"),
kernel.CreateFunctionFromMethod(() => { }, "Function2"),
});

Assert.Single(kernel.Plugins);

KernelPlugin plugin = kernel.Plugins["coolplugin"];
Assert.Equal("coolplugin", plugin.Name);
Assert.Empty(plugin.Description);
Assert.NotNull(plugin);

Assert.Equal(2, plugin.FunctionCount);
Assert.True(plugin.Contains("Function1"));
Assert.True(plugin.Contains("Function2"));
}

[Fact]
public void ImportPluginFromDescriptionAndFunctions()
{
Kernel kernel = new();

kernel.ImportPluginFromFunctions("coolplugin", "the description", new[]
{
kernel.CreateFunctionFromMethod(() => { }, "Function1"),
kernel.CreateFunctionFromMethod(() => { }, "Function2"),
});

Assert.Single(kernel.Plugins);

KernelPlugin plugin = kernel.Plugins["coolplugin"];
Assert.Equal("coolplugin", plugin.Name);
Assert.Equal("the description", plugin.Description);
Assert.NotNull(plugin);

Assert.Equal(2, plugin.FunctionCount);
Assert.True(plugin.Contains("Function1"));
Assert.True(plugin.Contains("Function2"));
}

[Fact]
public void AddFromFunctions()
{
Kernel kernel = new();

kernel.Plugins.AddFromFunctions("coolplugin", new[]
{
kernel.CreateFunctionFromMethod(() => { }, "Function1"),
kernel.CreateFunctionFromMethod(() => { }, "Function2"),
});

Assert.Single(kernel.Plugins);

KernelPlugin plugin = kernel.Plugins["coolplugin"];
Assert.Equal("coolplugin", plugin.Name);
Assert.Empty(plugin.Description);
Assert.NotNull(plugin);

Assert.Equal(2, plugin.FunctionCount);
Assert.True(plugin.Contains("Function1"));
Assert.True(plugin.Contains("Function2"));
}

[Fact]
public void AddFromDescriptionAndFunctions()
{
Kernel kernel = new();

kernel.Plugins.AddFromFunctions("coolplugin", "the description", new[]
{
kernel.CreateFunctionFromMethod(() => { }, "Function1"),
kernel.CreateFunctionFromMethod(() => { }, "Function2"),
});

Assert.Single(kernel.Plugins);

KernelPlugin plugin = kernel.Plugins["coolplugin"];
Assert.Equal("coolplugin", plugin.Name);
Assert.Equal("the description", plugin.Description);
Assert.NotNull(plugin);

Assert.Equal(2, plugin.FunctionCount);
Assert.True(plugin.Contains("Function1"));
Assert.True(plugin.Contains("Function2"));
}
}
Loading

0 comments on commit 0772d01

Please sign in to comment.