Skip to content

Commit

Permalink
Add support for non-static native functions with external references
Browse files Browse the repository at this point in the history
  • Loading branch information
dluc committed May 11, 2023
1 parent ab625af commit 2be4487
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 14 deletions.
1 change: 1 addition & 0 deletions dotnet/SK-dotnet.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CORS/@EntryIndexedValue">CORS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DB/@EntryIndexedValue">DB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GRPC/@EntryIndexedValue">GRPC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HMAC/@EntryIndexedValue">HMAC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HTTP/@EntryIndexedValue">HTTP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IM/@EntryIndexedValue">IM</s:String>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ISemanticTextMemory>();
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<SKContext> ExecuteAsync(SKContext contextIn)
Expand All @@ -90,21 +82,56 @@ async Task<SKContext> ExecuteAsync(SKContext contextIn)
}

// Act
context["done"] = "NO";

ISKFunction function = SKFunction.FromNativeFunction(
nativeFunction: ExecuteAsync,
parameters: null,
description: "description",
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<SKContext> 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")]
Expand Down
12 changes: 10 additions & 2 deletions dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public static ISKFunction FromNativeFunction(
IEnumerable<ParameterView>? 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,
Expand Down Expand Up @@ -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")]
/// <summary>
/// Compare a method against the given signature type using Delegate.CreateDelegate.
/// </summary>
/// <param name="instance">Optional object containing the given method</param>
/// <param name="userMethod">Method to inspect</param>
/// <param name="delegateDefinition">Definition of the delegate, i.e. method signature</param>
/// <param name="result">The delegate to use, if the function returns TRUE, otherwise NULL</param>
/// <returns>True if the method to inspect matches the delegate type</returns>
[SuppressMessage("Maintainability", "CA1508:Avoid dead conditional code", Justification = "Delegate.CreateDelegate can return NULL")]
private static bool EqualMethods(
object? instance,
MethodInfo userMethod,
Expand Down

0 comments on commit 2be4487

Please sign in to comment.