diff --git a/dotnet/samples/Concepts/Functions/MethodFunctions_Advanced.cs b/dotnet/samples/Concepts/Functions/MethodFunctions_Advanced.cs
index 6583e2dee7e2..7bc3f2bc02cf 100644
--- a/dotnet/samples/Concepts/Functions/MethodFunctions_Advanced.cs
+++ b/dotnet/samples/Concepts/Functions/MethodFunctions_Advanced.cs
@@ -2,27 +2,28 @@
using System.ComponentModel;
using System.Globalization;
+using System.Reflection;
using System.Text.Json;
using Microsoft.SemanticKernel;
namespace Functions;
-// This example shows different ways how to define and execute method functions using custom and primitive types.
+///
+/// These samples show advanced usage of method functions.
+///
public class MethodFunctions_Advanced(ITestOutputHelper output) : BaseTest(output)
{
- #region Method Functions Chaining
-
///
/// This example executes Function1, which in turn executes Function2.
///
[Fact]
- public async Task MethodFunctionsChainingAsync()
+ public async Task MethodFunctionsChaining()
{
Console.WriteLine("Running Method Function Chaining example...");
var kernel = new Kernel();
- var functions = kernel.ImportPluginFromType();
+ var functions = kernel.ImportPluginFromType();
var customType = await kernel.InvokeAsync(functions["Function1"]);
@@ -31,11 +32,28 @@ public async Task MethodFunctionsChainingAsync()
}
///
- /// Plugin example with two method functions, where one function is called from another.
+ /// This example shows how to access the custom attribute the underlying method wrapped by Kernel Function is annotated with.
///
- private sealed class FunctionsChainingPlugin
+ [Fact]
+ public async Task AccessUnderlyingMethodAttributes()
+ {
+ // Import the plugin containing the method with the InvocationSettingsAttribute custom attribute
+ var kernel = new Kernel();
+
+ var functions = kernel.ImportPluginFromType();
+
+ // Get the kernel function wrapping the method with the InvocationSettingsAttribute
+ var kernelFunction = functions[nameof(Plugin.FunctionWithInvocationSettingsAttribute)];
+
+ // Access the custom attribute the underlying method is annotated with
+ var invocationSettingsAttribute = kernelFunction.UnderlyingMethod!.GetCustomAttribute();
+
+ Console.WriteLine($"Priority: {invocationSettingsAttribute?.Priority}");
+ }
+
+ private sealed class Plugin
{
- private const string PluginName = nameof(FunctionsChainingPlugin);
+ private const string PluginName = nameof(Plugin);
[KernelFunction]
public async Task Function1Async(Kernel kernel)
@@ -59,11 +77,12 @@ public static MyCustomType Function2()
Text = "From Function2"
};
}
- }
-
- #endregion
- #region Custom Type
+ [KernelFunction, InvocationSettingsAttribute(priority: Priority.High)]
+ public static void FunctionWithInvocationSettingsAttribute()
+ {
+ }
+ }
///
/// In order to use custom types, should be specified,
@@ -110,5 +129,20 @@ private sealed class MyCustomTypeConverter : TypeConverter
}
}
- #endregion
+ [AttributeUsage(AttributeTargets.Method)]
+ private sealed class InvocationSettingsAttribute : Attribute
+ {
+ public InvocationSettingsAttribute(Priority priority = Priority.Normal)
+ {
+ this.Priority = priority;
+ }
+
+ public Priority Priority { get; }
+ }
+
+ private enum Priority
+ {
+ Normal,
+ High,
+ }
}
diff --git a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs
index a0a425aca1ec..a77a0f782340 100644
--- a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs
+++ b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs
@@ -8,6 +8,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Metrics;
using System.Linq;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
@@ -98,6 +99,15 @@ public abstract class KernelFunction
///
public IReadOnlyDictionary? ExecutionSettings { get; }
+ ///
+ /// Gets the underlying that this function might be wrapping.
+ ///
+ ///
+ /// Provides additional metadata on the function and its signature. Implementations not wrapping .NET methods may return null.
+ ///
+ [Experimental("SKEXP0001")]
+ public MethodInfo? UnderlyingMethod { get; internal init; }
+
///
/// Initializes a new instance of the class.
///
diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs
index 6a8594b3ff0c..49991ad39c5a 100644
--- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs
+++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs
@@ -141,6 +141,7 @@ public static KernelFunction Create(
MethodDetails methodDetails = GetMethodDetails(options?.FunctionName, method, target);
var result = new KernelFunctionFromMethod(
+ method,
methodDetails.Function,
methodDetails.Name,
options?.Description ?? methodDetails.Description,
@@ -181,6 +182,7 @@ public static KernelFunction Create(
MethodDetails methodDetails = GetMethodDetails(options?.FunctionName, method, jsonSerializerOptions, target);
var result = new KernelFunctionFromMethod(
+ method,
methodDetails.Function,
methodDetails.Name,
options?.Description ?? methodDetails.Description,
@@ -275,6 +277,7 @@ public static KernelFunctionMetadata CreateMetadata(
MethodDetails methodDetails = GetMethodDetails(options?.FunctionName, method, null);
var result = new KernelFunctionFromMethod(
+ method,
methodDetails.Function,
methodDetails.Name,
options?.Description ?? methodDetails.Description,
@@ -307,6 +310,7 @@ public static KernelFunctionMetadata CreateMetadata(
MethodDetails methodDetails = GetMethodDetails(options?.FunctionName, method, jsonSerializerOptions, target: null);
var result = new KernelFunctionFromMethod(
+ method,
methodDetails.Function,
methodDetails.Name,
options?.Description ?? methodDetails.Description,
@@ -382,6 +386,7 @@ public override KernelFunction Clone(string pluginName)
if (base.JsonSerializerOptions is not null)
{
return new KernelFunctionFromMethod(
+ this.UnderlyingMethod!,
this._function,
this.Name,
pluginName,
@@ -397,6 +402,7 @@ public override KernelFunction Clone(string pluginName)
KernelFunctionFromMethod Clone()
{
return new KernelFunctionFromMethod(
+ this.UnderlyingMethod!,
this._function,
this.Name,
pluginName,
@@ -424,17 +430,19 @@ private record struct MethodDetails(string Name, string Description, Implementat
[RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")]
[RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")]
private KernelFunctionFromMethod(
+ MethodInfo method,
ImplementationFunc implementationFunc,
string functionName,
string description,
IReadOnlyList parameters,
KernelReturnParameterMetadata returnParameter,
ReadOnlyDictionary? additionalMetadata = null) :
- this(implementationFunc, functionName, null, description, parameters, returnParameter, additionalMetadata)
+ this(method, implementationFunc, functionName, null, description, parameters, returnParameter, additionalMetadata)
{
}
private KernelFunctionFromMethod(
+ MethodInfo method,
ImplementationFunc implementationFunc,
string functionName,
string description,
@@ -442,13 +450,14 @@ private KernelFunctionFromMethod(
KernelReturnParameterMetadata returnParameter,
JsonSerializerOptions jsonSerializerOptions,
ReadOnlyDictionary? additionalMetadata = null) :
- this(implementationFunc, functionName, null, description, parameters, returnParameter, jsonSerializerOptions, additionalMetadata)
+ this(method, implementationFunc, functionName, null, description, parameters, returnParameter, jsonSerializerOptions, additionalMetadata)
{
}
[RequiresUnreferencedCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")]
[RequiresDynamicCode("Uses reflection to handle various aspects of the function creation and invocation, making it incompatible with AOT scenarios.")]
private KernelFunctionFromMethod(
+ MethodInfo method,
ImplementationFunc implementationFunc,
string functionName,
string? pluginName,
@@ -461,9 +470,11 @@ private KernelFunctionFromMethod(
Verify.ValidFunctionName(functionName);
this._function = implementationFunc;
+ this.UnderlyingMethod = method;
}
private KernelFunctionFromMethod(
+ MethodInfo method,
ImplementationFunc implementationFunc,
string functionName,
string? pluginName,
@@ -477,6 +488,7 @@ private KernelFunctionFromMethod(
Verify.ValidFunctionName(functionName);
this._function = implementationFunc;
+ this.UnderlyingMethod = method;
}
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "This method is AOT save.")]
diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromMethodTests2.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromMethodTests2.cs
index 66264fe6bb35..f2800220d8a1 100644
--- a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromMethodTests2.cs
+++ b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionFromMethodTests2.cs
@@ -288,6 +288,24 @@ public void ItMakesProvidedExtensionPropertiesAvailableViaMetadataWhenConstructe
Assert.Equal("value1", func.Metadata.AdditionalProperties["key1"]);
}
+ [Fact]
+ public void ItShouldExposeUnderlyingMethod()
+ {
+ // Arrange
+ var target = new LocalExamplePlugin();
+
+ var methodInfo = target.GetType().GetMethod(nameof(LocalExamplePlugin.FunctionWithCustomAttribute))!;
+
+ var kernelFunction = KernelFunctionFactory.CreateFromMethod(methodInfo, target);
+
+ // Assert
+ Assert.NotNull(kernelFunction.UnderlyingMethod);
+
+ Assert.Equal(methodInfo, kernelFunction.UnderlyingMethod);
+
+ Assert.NotNull(kernelFunction.UnderlyingMethod.GetCustomAttribute());
+ }
+
private interface IExampleService;
private sealed class ExampleService : IExampleService;
@@ -466,6 +484,11 @@ public string WithPrimitives(
{
return string.Empty;
}
+
+ [KernelFunction, CustomAttribute]
+ public void FunctionWithCustomAttribute()
+ {
+ }
}
private sealed class GenericPlugin
@@ -479,4 +502,9 @@ private sealed class GenericPlugin
[KernelFunction]
public Task GetValue3Async(T input) => Task.FromResult(input);
}
+
+ [AttributeUsage(AttributeTargets.Method)]
+ private sealed class CustomAttribute : Attribute
+ {
+ }
}