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,