diff --git a/dotnet/SK-dotnet.sln.DotSettings b/dotnet/SK-dotnet.sln.DotSettings index 23fc1a6e67826..ed08a71e0f61f 100644 --- a/dotnet/SK-dotnet.sln.DotSettings +++ b/dotnet/SK-dotnet.sln.DotSettings @@ -93,6 +93,7 @@ API CORS DB + GRPC HMAC HTTP IM diff --git a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests3.cs b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests3.cs index 0290433b0570c..231b1636887e4 100644 --- a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests3.cs +++ b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKFunctionTests3.cs @@ -3,14 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Threading; using System.Threading.Tasks; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.SkillDefinition; -using Moq; -using SemanticKernel.UnitTests.XunitHelpers; using Xunit; namespace SemanticKernel.UnitTests.SkillDefinition; @@ -72,12 +68,8 @@ public void ItThrowsForInvalidFunctions() public async Task ItCanImportNativeFunctionsAsync() { // Arrange - var variables = new ContextVariables(); - var skills = new SkillCollection(); - var logger = TestConsoleLogger.Log; - var cancellationToken = default(CancellationToken); - var memory = new Mock(); - var context = new SKContext(variables, memory.Object, skills.ReadOnlySkillCollection, logger, cancellationToken); + var context = Kernel.Builder.Build().CreateNewContext(); + context["done"] = "NO"; // Note: the function doesn't have any SK attributes async Task ExecuteAsync(SKContext contextIn) @@ -90,7 +82,7 @@ async Task ExecuteAsync(SKContext contextIn) } // Act - context["done"] = "NO"; + ISKFunction function = SKFunction.FromNativeFunction( nativeFunction: ExecuteAsync, parameters: null, @@ -98,13 +90,48 @@ async Task ExecuteAsync(SKContext contextIn) skillName: "skillName", functionName: "functionName"); - SKContext result = await function.InvokeAsync(context, cancellationToken: cancellationToken); + SKContext result = await function.InvokeAsync(context); // Assert Assert.Equal("YES", context["canary"]); Assert.Equal("YES", result["canary"]); } + [Fact] + public async Task ItCanImportNativeFunctionsWithExternalReferencesAsync() + { + // Arrange + var context = Kernel.Builder.Build().CreateNewContext(); + + // Note: This is an important edge case that affects the function signature and how delegates + // are handled internally: the function references an external variable and cannot be static. + // This scenario is used for gRPC functions. + string variableOutsideTheFunction = "foo"; + + async Task ExecuteAsync(SKContext contextIn) + { + string referenceToExternalVariable = variableOutsideTheFunction; + contextIn["canary"] = "YES"; + + await Task.Delay(0); + return contextIn; + } + + context["done"] = "NO"; + + // Act. Note: this will throw an exception if SKFunction doesn't handle the function type. + ISKFunction function = SKFunction.FromNativeFunction( + nativeFunction: ExecuteAsync, + description: "description", + skillName: "skillName", + functionName: "functionName"); + + SKContext result = await function.InvokeAsync(context); + + // Assert + Assert.Equal("YES", result["canary"]); + } + private sealed class InvalidSkill { [SKFunction("one")] diff --git a/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs b/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs index dd5a29a9b4467..5a77964e221aa 100644 --- a/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs +++ b/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs @@ -101,7 +101,7 @@ public static ISKFunction FromNativeFunction( IEnumerable? parameters = null, ILogger? log = null) { - MethodDetails methodDetails = GetMethodDetails(nativeFunction.Method, null, false, log); + MethodDetails methodDetails = GetMethodDetails(nativeFunction.Method, nativeFunction.Target, false, log); return new SKFunction( delegateType: methodDetails.Type, @@ -725,7 +725,15 @@ private static (DelegateTypes type, Delegate function, bool hasStringParam) GetD $"Function '{method.Name}' has an invalid signature not supported by the kernel."); } - [SuppressMessage("Maintainability", "CA1508:Avoid dead conditional code", Justification = "Delegate.CreateDelegate result can be null")] + /// + /// Compare a method against the given signature type using Delegate.CreateDelegate. + /// + /// Optional object containing the given method + /// Method to inspect + /// Definition of the delegate, i.e. method signature + /// The delegate to use, if the function returns TRUE, otherwise NULL + /// True if the method to inspect matches the delegate type + [SuppressMessage("Maintainability", "CA1508:Avoid dead conditional code", Justification = "Delegate.CreateDelegate can return NULL")] private static bool EqualMethods( object? instance, MethodInfo userMethod,