From cefff2bbeecb3343c7deb84ef096e76e13b01a3b Mon Sep 17 00:00:00 2001 From: Anthony Puppo Date: Mon, 26 Aug 2024 10:33:35 -0400 Subject: [PATCH] Expose any custom attributes on the underlying function method --- .../NexusRaven_FunctionCalling.cs | 1 + .../GeminiToolCallBehaviorTests.cs | 1 + .../OpenAI/ToolCallBehaviorTests.cs | 1 + .../Extensions/KernelFunctionExtensionTests.cs | 4 +++- .../Functions/KernelFunction.cs | 9 ++++++--- .../Functions/KernelFunctionMetadata.cs | 15 +++++++++++++++ .../Functions/KernelFunctionFactory.cs | 8 ++++++-- .../Functions/KernelFunctionFromMethod.cs | 18 +++++++++++++----- .../KernelFunctionFromMethodOptions.cs | 5 +++++ .../Functions/KernelFunctionFromPrompt.cs | 1 + .../SemanticKernel.Core/KernelExtensions.cs | 8 ++++++-- 11 files changed, 58 insertions(+), 13 deletions(-) diff --git a/dotnet/samples/Concepts/FunctionCalling/NexusRaven_FunctionCalling.cs b/dotnet/samples/Concepts/FunctionCalling/NexusRaven_FunctionCalling.cs index d11b6948eaea..c599558cad2d 100644 --- a/dotnet/samples/Concepts/FunctionCalling/NexusRaven_FunctionCalling.cs +++ b/dotnet/samples/Concepts/FunctionCalling/NexusRaven_FunctionCalling.cs @@ -127,6 +127,7 @@ private static KernelPlugin ImportFunctions(Kernel kernel) (string cityName) => "12°C\nWind: 11 KMPH\nHumidity: 48%\nMostly cloudy", "GetWeatherForCity", "Gets the current weather for the specified city", + [], new List { new("cityName") { Description = "The city name", ParameterType = string.Empty.GetType() } diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/GeminiToolCallBehaviorTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/GeminiToolCallBehaviorTests.cs index 958f2ad27082..a4a0bc4b875d 100644 --- a/dotnet/src/Connectors/Connectors.Google.UnitTests/GeminiToolCallBehaviorTests.cs +++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/GeminiToolCallBehaviorTests.cs @@ -200,6 +200,7 @@ private static KernelPlugin GetTestPlugin() (string parameter1, string parameter2) => "Result1", "MyFunction", "Test Function", + [], [new KernelParameterMetadata("parameter1"), new KernelParameterMetadata("parameter2")], new KernelReturnParameterMetadata { ParameterType = typeof(string), Description = "Function Result" }); diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/ToolCallBehaviorTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/ToolCallBehaviorTests.cs index d39480ebfe8d..e65515dbfd38 100644 --- a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/ToolCallBehaviorTests.cs +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/ToolCallBehaviorTests.cs @@ -228,6 +228,7 @@ private KernelPlugin GetTestPlugin() (string parameter1, string parameter2) => "Result1", "MyFunction", "Test Function", + [], [new KernelParameterMetadata("parameter1"), new KernelParameterMetadata("parameter2")], new KernelReturnParameterMetadata { ParameterType = typeof(string), Description = "Function Result" }); diff --git a/dotnet/src/Experimental/Agents.UnitTests/Extensions/KernelFunctionExtensionTests.cs b/dotnet/src/Experimental/Agents.UnitTests/Extensions/KernelFunctionExtensionTests.cs index b69aead79981..a7efe9a07dfa 100644 --- a/dotnet/src/Experimental/Agents.UnitTests/Extensions/KernelFunctionExtensionTests.cs +++ b/dotnet/src/Experimental/Agents.UnitTests/Extensions/KernelFunctionExtensionTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Collections.Generic; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Experimental.Agents; @@ -33,8 +34,9 @@ public static void GetToolModelFromFunction() var requiredParam = new KernelParameterMetadata("required") { IsRequired = true }; var optionalParam = new KernelParameterMetadata("optional"); + var attributes = new List(); var parameters = new List { requiredParam, optionalParam }; - var function = KernelFunctionFactory.CreateFromMethod(() => true, ToolName, FunctionDescription, parameters); + var function = KernelFunctionFactory.CreateFromMethod(() => true, ToolName, FunctionDescription, attributes, parameters); var toolModel = function.ToToolModel(PluginName); var properties = toolModel.Function?.Parameters.Properties; diff --git a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs index b838d7b30261..8fa7fccc2090 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs @@ -96,14 +96,15 @@ public abstract class KernelFunction /// /// A name of the function to use as its . /// The description of the function to use as its . + /// The attributes on the function. /// The metadata describing the parameters to the function. /// The metadata describing the return parameter of the function. /// /// The to use with the function. These will apply unless they've been /// overridden by settings passed into the invocation of the function. /// - internal KernelFunction(string name, string description, IReadOnlyList parameters, KernelReturnParameterMetadata? returnParameter = null, Dictionary? executionSettings = null) - : this(name, null, description, parameters, returnParameter, executionSettings) + internal KernelFunction(string name, string description, IReadOnlyList attributes, IReadOnlyList parameters, KernelReturnParameterMetadata? returnParameter = null, Dictionary? executionSettings = null) + : this(name, null, description, attributes, parameters, returnParameter, executionSettings) { } @@ -113,6 +114,7 @@ internal KernelFunction(string name, string description, IReadOnlyListA name of the function to use as its . /// The name of the plugin this function instance has been added to. /// The description of the function to use as its . + /// The attributes on the function. /// The metadata describing the parameters to the function. /// The metadata describing the return parameter of the function. /// @@ -120,7 +122,7 @@ internal KernelFunction(string name, string description, IReadOnlyList /// Properties/metadata associated with the function itself rather than its parameters and return type. - internal KernelFunction(string name, string? pluginName, string description, IReadOnlyList parameters, KernelReturnParameterMetadata? returnParameter = null, Dictionary? executionSettings = null, ReadOnlyDictionary? additionalMetadata = null) + internal KernelFunction(string name, string? pluginName, string description, IReadOnlyList attributes, IReadOnlyList parameters, KernelReturnParameterMetadata? returnParameter = null, Dictionary? executionSettings = null, ReadOnlyDictionary? additionalMetadata = null) { Verify.NotNull(name); Verify.ParametersUniqueness(parameters); @@ -129,6 +131,7 @@ internal KernelFunction(string name, string? pluginName, string description, IRe { PluginName = pluginName, Description = description, + Attributes = attributes, Parameters = parameters, ReturnParameter = returnParameter ?? KernelReturnParameterMetadata.Empty, AdditionalProperties = additionalMetadata ?? KernelFunctionMetadata.s_emptyDictionary, diff --git a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunctionMetadata.cs b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunctionMetadata.cs index cae651f74fea..c6da38f492cc 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunctionMetadata.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunctionMetadata.cs @@ -16,6 +16,8 @@ public sealed class KernelFunctionMetadata private string _name = string.Empty; /// The description of the function. private string _description = string.Empty; + /// The function's attributes. + private IReadOnlyList _attributes = []; /// The function's parameters. private IReadOnlyList _parameters = []; /// The function's return parameter. @@ -46,6 +48,7 @@ public KernelFunctionMetadata(KernelFunctionMetadata metadata) this.Name = metadata.Name; this.PluginName = metadata.PluginName; this.Description = metadata.Description; + this.Attributes = metadata.Attributes; this.Parameters = metadata.Parameters; this.ReturnParameter = metadata.ReturnParameter; this.AdditionalProperties = metadata.AdditionalProperties; @@ -74,6 +77,18 @@ public string Description init => this._description = value ?? string.Empty; } + /// Gets the attributes on the function. + /// If the function has no attributes, the returned list will be empty. + public IReadOnlyList Attributes + { + get => this._attributes; + init + { + Verify.NotNull(value); + this._attributes = value; + } + } + /// Gets the metadata for the parameters to the function. /// If the function has no parameters, the returned list will be empty. public IReadOnlyList Parameters diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFactory.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFactory.cs index f6f0a805f4a6..0ba9d5afc701 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFactory.cs +++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFactory.cs @@ -23,6 +23,7 @@ public static class KernelFunctionFactory /// The method to be represented via the created . /// The name to use for the function. If null, it will default to one derived from the method represented by . /// The description to use for the function. If null, it will default to one derived from the method represented by , if possible (e.g. via a on the method). + /// Optional function attributes. If null, it will default to those derived from the method represented by . /// Optional parameter descriptions. If null, it will default to one derived from the method represented by . /// Optional return parameter description. If null, it will default to one derived from the method represented by . /// The to use for logging. If null, no logging will be performed. @@ -31,10 +32,11 @@ public static KernelFunction CreateFromMethod( Delegate method, string? functionName = null, string? description = null, + IEnumerable? attributes = null, IEnumerable? parameters = null, KernelReturnParameterMetadata? returnParameter = null, ILoggerFactory? loggerFactory = null) => - CreateFromMethod(method.Method, method.Target, functionName, description, parameters, returnParameter, loggerFactory); + CreateFromMethod(method.Method, method.Target, functionName, description, attributes, parameters, returnParameter, loggerFactory); /// /// Creates a instance for a method, specified via a delegate. @@ -55,6 +57,7 @@ public static KernelFunction CreateFromMethod( /// The target object for the if it represents an instance method. This should be null if and only if is a static method. /// The name to use for the function. If null, it will default to one derived from the method represented by . /// The description to use for the function. If null, it will default to one derived from the method represented by , if possible (e.g. via a on the method). + /// Optional function attributes. If null, it will default to those derived from the method represented by . /// Optional parameter descriptions. If null, it will default to ones derived from the method represented by . /// Optional return parameter description. If null, it will default to one derived from the method represented by . /// The to use for logging. If null, no logging will be performed. @@ -64,10 +67,11 @@ public static KernelFunction CreateFromMethod( object? target = null, string? functionName = null, string? description = null, + IEnumerable? attributes = null, IEnumerable? parameters = null, KernelReturnParameterMetadata? returnParameter = null, ILoggerFactory? loggerFactory = null) => - KernelFunctionFromMethod.Create(method, target, functionName, description, parameters, returnParameter, loggerFactory); + KernelFunctionFromMethod.Create(method, target, functionName, description, attributes, parameters, returnParameter, loggerFactory); /// /// Creates a instance for a method, specified via an instance diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs index c851e6a99501..105ecb7d2500 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs +++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs @@ -37,6 +37,7 @@ internal sealed partial class KernelFunctionFromMethod : KernelFunction /// The target object for the if it represents an instance method. This should be null if and only if is a static method. /// The name to use for the function. If null, it will default to one derived from the method represented by . /// The description to use for the function. If null, it will default to one derived from the method represented by , if possible (e.g. via a on the method). + /// Optional function attributes. If null, it will default to those derived from the method represented by . /// Optional parameter descriptions. If null, it will default to one derived from the method represented by . /// Optional return parameter description. If null, it will default to one derived from the method represented by . /// The to use for logging. If null, no logging will be performed. @@ -46,6 +47,7 @@ public static KernelFunction Create( object? target = null, string? functionName = null, string? description = null, + IEnumerable? attributes = null, IEnumerable? parameters = null, KernelReturnParameterMetadata? returnParameter = null, ILoggerFactory? loggerFactory = null) @@ -57,6 +59,7 @@ public static KernelFunction Create( { FunctionName = functionName, Description = description, + Attributes = attributes, Parameters = parameters, ReturnParameter = returnParameter, LoggerFactory = loggerFactory @@ -87,6 +90,7 @@ public static KernelFunction Create( methodDetails.Function, methodDetails.Name, options?.Description ?? methodDetails.Description, + options?.Attributes?.ToList() ?? methodDetails.Attributes, options?.Parameters?.ToList() ?? methodDetails.Parameters, options?.ReturnParameter ?? methodDetails.ReturnParameter, options?.AdditionalMetadata); @@ -160,6 +164,7 @@ public override KernelFunction Clone(string pluginName) this.Name, pluginName, this.Description, + this.Metadata.Attributes, this.Metadata.Parameters, this.Metadata.ReturnParameter, this.Metadata.AdditionalProperties); @@ -175,16 +180,17 @@ private delegate ValueTask ImplementationFunc( private static readonly object[] s_cancellationTokenNoneArray = [CancellationToken.None]; private readonly ImplementationFunc _function; - private record struct MethodDetails(string Name, string Description, ImplementationFunc Function, List Parameters, KernelReturnParameterMetadata ReturnParameter); + private record struct MethodDetails(string Name, string Description, ImplementationFunc Function, List Attributes, List Parameters, KernelReturnParameterMetadata ReturnParameter); private KernelFunctionFromMethod( ImplementationFunc implementationFunc, string functionName, string description, + IReadOnlyList attributes, IReadOnlyList parameters, KernelReturnParameterMetadata returnParameter, ReadOnlyDictionary? additionalMetadata = null) : - this(implementationFunc, functionName, null, description, parameters, returnParameter, additionalMetadata) + this(implementationFunc, functionName, null, description, attributes, parameters, returnParameter, additionalMetadata) { } @@ -193,10 +199,11 @@ private KernelFunctionFromMethod( string functionName, string? pluginName, string description, + IReadOnlyList attributes, IReadOnlyList parameters, KernelReturnParameterMetadata returnParameter, ReadOnlyDictionary? additionalMetadata = null) : - base(functionName, pluginName, description, parameters, returnParameter, additionalMetadata: additionalMetadata) + base(functionName, pluginName, description, attributes, parameters, returnParameter, additionalMetadata: additionalMetadata) { Verify.ValidFunctionName(functionName); @@ -281,6 +288,7 @@ ValueTask Function(Kernel kernel, KernelFunction function, Kerne Function = Function, Name = functionName!, Description = method.GetCustomAttribute(inherit: true)?.Description ?? "", + Attributes = [.. Attribute.GetCustomAttributes(method, inherit: true)], Parameters = argParameterViews, ReturnParameter = new KernelReturnParameterMetadata() { @@ -470,7 +478,7 @@ private static bool TryToDeserializeValue(object value, Type targetType, out obj JsonDocument document => document.Deserialize(targetType), JsonNode node => node.Deserialize(targetType), JsonElement element => element.Deserialize(targetType), - // The JSON can be represented by other data types from various libraries. For example, JObject, JToken, and JValue from the Newtonsoft.Json library. + // The JSON can be represented by other data types from various libraries. For example, JObject, JToken, and JValue from the Newtonsoft.Json library. // Since we don't take dependencies on these libraries and don't have access to the types here, // the only way to deserialize those types is to convert them to a string first by calling the 'ToString' method. // Attempting to use the 'JsonSerializer.Serialize' method, instead of calling the 'ToString' directly on those types, can lead to unpredictable outcomes. @@ -739,7 +747,7 @@ private static void ThrowForInvalidSignatureIf([DoesNotReturnIf(true)] bool cond { if (input?.GetType() is Type type && converter.CanConvertFrom(type)) { - // This line performs string to type conversion + // This line performs string to type conversion return converter.ConvertFrom(context: null, culture, input); } diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethodOptions.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethodOptions.cs index c4ea1f55175d..47ffafc37aaa 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethodOptions.cs +++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethodOptions.cs @@ -25,6 +25,11 @@ public sealed class KernelFunctionFromMethodOptions /// public string? Description { get; init; } + /// + /// Optional function attributes. If null, it will default to what is derived from the passed or . + /// + public IEnumerable? Attributes { get; init; } + /// /// Optional parameter descriptions. If null, it will default to one derived from the passed or . /// diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs index a7849ab8d155..7238848c482d 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs +++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs @@ -252,6 +252,7 @@ private KernelFunctionFromPrompt( functionName ?? CreateRandomFunctionName(), pluginName, description ?? string.Empty, + attributes: [], parameters, returnParameter, executionSettings) diff --git a/dotnet/src/SemanticKernel.Core/KernelExtensions.cs b/dotnet/src/SemanticKernel.Core/KernelExtensions.cs index 6a96395cedea..654a43d133f8 100644 --- a/dotnet/src/SemanticKernel.Core/KernelExtensions.cs +++ b/dotnet/src/SemanticKernel.Core/KernelExtensions.cs @@ -26,6 +26,7 @@ public static class KernelExtensions /// The method to be represented via the created . /// The name to use for the function. If null, it will default to one derived from the method represented by . /// The description to use for the function. If null, it will default to one derived from the method represented by , if possible (e.g. via a on the method). + /// Optional function attributes. If null, it will default to those derived from the method represented by . /// Optional parameter descriptions. If null, it will default to one derived from the method represented by . /// Optional return parameter description. If null, it will default to one derived from the method represented by . /// The created for invoking . @@ -34,13 +35,14 @@ public static KernelFunction CreateFunctionFromMethod( Delegate method, string? functionName = null, string? description = null, + IEnumerable? attributes = null, IEnumerable? parameters = null, KernelReturnParameterMetadata? returnParameter = null) { Verify.NotNull(kernel); Verify.NotNull(method); - return KernelFunctionFactory.CreateFromMethod(method.Method, method.Target, functionName, description, parameters, returnParameter, kernel.LoggerFactory); + return KernelFunctionFactory.CreateFromMethod(method.Method, method.Target, functionName, description, attributes, parameters, returnParameter, kernel.LoggerFactory); } /// @@ -52,6 +54,7 @@ public static KernelFunction CreateFunctionFromMethod( /// The target object for the if it represents an instance method. This should be null if and only if is a static method. /// The name to use for the function. If null, it will default to one derived from the method represented by . /// The description to use for the function. If null, it will default to one derived from the method represented by , if possible (e.g. via a on the method). + /// Optional function attributes. If null, it will default to those derived from the method represented by . /// Optional parameter descriptions. If null, it will default to one derived from the method represented by . /// Optional return parameter description. If null, it will default to one derived from the method represented by . /// The created for invoking . @@ -61,13 +64,14 @@ public static KernelFunction CreateFunctionFromMethod( object? target = null, string? functionName = null, string? description = null, + IEnumerable? attributes = null, IEnumerable? parameters = null, KernelReturnParameterMetadata? returnParameter = null) { Verify.NotNull(kernel); Verify.NotNull(method); - return KernelFunctionFactory.CreateFromMethod(method, target, functionName, description, parameters, returnParameter, kernel.LoggerFactory); + return KernelFunctionFactory.CreateFromMethod(method, target, functionName, description, attributes, parameters, returnParameter, kernel.LoggerFactory); } #endregion