Skip to content

Commit

Permalink
Add support for non-string arguments
Browse files Browse the repository at this point in the history
TypeDescriptors are used to both implicitly support a large number of types implicitly and also to support an extensibility mechanism.  It's the same mechanism used by UI frameworks like WinForms as well as ASP.NET MVC.
  • Loading branch information
stephentoub committed May 25, 2023
1 parent 69c1ffe commit b1dec86
Show file tree
Hide file tree
Showing 35 changed files with 496 additions and 264 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<!-- IMPORT NUGET PACKAGE SHARED PROPERTIES -->
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/InternalUtilities.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/src/InternalUtilities.props" />

<PropertyGroup>
<!-- NuGet Package Settings -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<!-- IMPORT NUGET PACKAGE SHARED PROPERTIES -->
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/InternalUtilities.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/src/InternalUtilities.props" />

<PropertyGroup>
<!-- NuGet Package Settings -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/InternalUtilities.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/src/InternalUtilities.props" />

<PropertyGroup>
<!-- NuGet Package Settings -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/InternalUtilities.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/src/InternalUtilities.props" />

<PropertyGroup>
<!-- NuGet Package Settings -->
Expand Down
5 changes: 0 additions & 5 deletions dotnet/src/InternalUtilities/InternalUtilities.props

This file was deleted.

5 changes: 5 additions & 0 deletions dotnet/src/InternalUtilities/src/InternalUtilities.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<ItemGroup>
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/src/**/*.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
</Project>
File renamed without changes.
17 changes: 17 additions & 0 deletions dotnet/src/InternalUtilities/test/AssertExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using Xunit;

namespace SemanticKernel.UnitTests;

internal static class AssertExtensions
{
/// <summary>Asserts that an exception is an <see cref="ArgumentOutOfRangeException"/> with the specified values.</summary>
public static void AssertIsArgumentOutOfRange(Exception? e, string expectedParamName, string expectedActualValue)
{
ArgumentOutOfRangeException aoore = Assert.IsType<ArgumentOutOfRangeException>(e);
Assert.Equal(expectedActualValue, aoore.ActualValue);
Assert.Equal(expectedParamName, aoore.ParamName);
}
}
34 changes: 34 additions & 0 deletions dotnet/src/InternalUtilities/test/FunctionHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SemanticKernel.UnitTests;

/// <summary>Test helpers for working with native functions.</summary>
internal static class FunctionHelpers
{
/// <summary>
/// Invokes a function on a skill instance via the kernel.
/// </summary>
public static Task<SKContext> CallViaKernel(
object skillInstance,
string methodName,
params (string Name, string Value)[] variables)
{
var kernel = Kernel.Builder.Build();

IDictionary<string, ISKFunction> funcs = kernel.ImportSkill(skillInstance);

SKContext context = kernel.CreateNewContext();
foreach ((string Name, string Value) pair in variables)
{
context.Variables.Set(pair.Name, pair.Value);
}

return funcs[methodName].InvokeAsync(context);
}
}
5 changes: 5 additions & 0 deletions dotnet/src/InternalUtilities/test/TestInternalUtilities.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<ItemGroup>
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/test/**/*.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/InternalUtilities.props" />
<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/src/InternalUtilities.props" />

<PropertyGroup>
<!-- NuGet Package Settings -->
Expand Down
55 changes: 17 additions & 38 deletions dotnet/src/SemanticKernel.UnitTests/CoreSkills/MathSkillTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.CoreSkills;
Expand Down Expand Up @@ -36,16 +35,16 @@ public void ItCanBeImported()
[InlineData("-10", "10", "0")]
[InlineData("-192", "13", "-179")]
[InlineData("-192", "-13", "-205")]
public async Task AddAsyncWhenValidParametersShouldSucceedAsync(string initialValue, string amount, string expectedResult)
public async Task AddWhenValidParametersShouldSucceedAsync(string initialValue, string amount, string expectedResult)
{
// Arrange
var target = new MathSkill();

// Act
string result = await target.AddAsync(initialValue, amount);
var context = await FunctionHelpers.CallViaKernel(target, "Add", ("input", initialValue), ("amount", amount));

// Assert
Assert.Equal(expectedResult, result);
Assert.Equal(expectedResult, context.Variables.Input);
}

[Theory]
Expand All @@ -57,16 +56,16 @@ public async Task AddAsyncWhenValidParametersShouldSucceedAsync(string initialVa
[InlineData("-1", "10", "-11")]
[InlineData("-10", "10", "-20")]
[InlineData("-192", "13", "-205")]
public async Task SubtractAsyncWhenValidParametersShouldSucceedAsync(string initialValue, string amount, string expectedResult)
public async Task SubtractWhenValidParametersShouldSucceedAsync(string initialValue, string amount, string expectedResult)
{
// Arrange
var target = new MathSkill();

// Act
string result = await target.SubtractAsync(initialValue, amount);
var context = await FunctionHelpers.CallViaKernel(target, "Subtract", ("input", initialValue), ("amount", amount)); // Assert

// Assert
Assert.Equal(expectedResult, result);
Assert.Equal(expectedResult, context.Variables.Input);
}

[Theory]
Expand All @@ -81,21 +80,16 @@ public async Task SubtractAsyncWhenValidParametersShouldSucceedAsync(string init
[InlineData("zero")]
[InlineData("-100 units")]
[InlineData("1 banana")]
public async Task AddAsyncWhenInvalidInitialValueShouldThrowAsync(string initialValue)
public async Task AddWhenInvalidInitialValueShouldThrowAsync(string initialValue)
{
// Arrange
var target = new MathSkill();

// Act
var exception = await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
await target.AddAsync(initialValue, "1");
});
var context = await FunctionHelpers.CallViaKernel(target, "Add", ("input", initialValue), ("amount", "1"));

// Assert
Assert.NotNull(exception);
Assert.Equal(initialValue, exception.ActualValue);
Assert.Equal("initialValueText", exception.ParamName);
AssertExtensions.AssertIsArgumentOutOfRange(context.LastException, "input", initialValue);
}

[Theory]
Expand All @@ -110,21 +104,16 @@ public async Task AddAsyncWhenInvalidInitialValueShouldThrowAsync(string initial
[InlineData("zero")]
[InlineData("-100 units")]
[InlineData("1 banana")]
public async Task AddAsyncWhenInvalidAmountShouldThrowAsync(string amount)
public async Task AddWhenInvalidAmountShouldThrowAsync(string amount)
{
// Arrange
var target = new MathSkill();

// Act
var exception = await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
await target.AddAsync("1", amount);
});
var context = await FunctionHelpers.CallViaKernel(target, "Add", ("input", "1"), ("amount", amount));

// Assert
Assert.NotNull(exception);
Assert.Equal(amount, exception.ActualValue);
Assert.Equal("amount", exception.ParamName);
AssertExtensions.AssertIsArgumentOutOfRange(context.LastException, "amount", amount);
}

[Theory]
Expand All @@ -139,21 +128,16 @@ public async Task AddAsyncWhenInvalidAmountShouldThrowAsync(string amount)
[InlineData("zero")]
[InlineData("-100 units")]
[InlineData("1 banana")]
public async Task SubtractAsyncWhenInvalidInitialValueShouldThrowAsync(string initialValue)
public async Task SubtractWhenInvalidInitialValueShouldThrowAsync(string initialValue)
{
// Arrange
var target = new MathSkill();

// Act
var exception = await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
await target.SubtractAsync(initialValue, "1");
});
var context = await FunctionHelpers.CallViaKernel(target, "Subtract", ("input", initialValue), ("amount", "1"));

// Assert
Assert.NotNull(exception);
Assert.Equal(initialValue, exception.ActualValue);
Assert.Equal("initialValueText", exception.ParamName);
AssertExtensions.AssertIsArgumentOutOfRange(context.LastException, "input", initialValue);
}

[Theory]
Expand All @@ -174,14 +158,9 @@ public async Task SubtractAsyncWhenInvalidAmountShouldThrowAsync(string amount)
var target = new MathSkill();

// Act
var exception = await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
{
await target.SubtractAsync("1", amount);
});
var context = await FunctionHelpers.CallViaKernel(target, "Subtract", ("input", "1"), ("amount", amount));

// Assert
Assert.NotNull(exception);
Assert.Equal(amount, exception.ActualValue);
Assert.Equal("amount", exception.ParamName);
AssertExtensions.AssertIsArgumentOutOfRange(context.LastException, "amount", amount);
}
}
12 changes: 3 additions & 9 deletions dotnet/src/SemanticKernel.UnitTests/CoreSkills/WaitSkillTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.CoreSkills;
Expand Down Expand Up @@ -46,7 +45,7 @@ public async Task ItWaitSecondsWhenValidParametersSucceedAsync(string textSecond
var target = new WaitSkill(waitProviderMock.Object);

// Act
await target.SecondsAsync(textSeconds);
var context = await FunctionHelpers.CallViaKernel(target, "Seconds", ("input", textSeconds));

// Assert
waitProviderMock.Verify(w => w.DelayAsync(It.IsIn(expectedMilliseconds)), Times.Once);
Expand All @@ -71,14 +70,9 @@ public async Task ItWaitSecondsWhenInvalidParametersFailsAsync(string textSecond
var target = new WaitSkill(waitProviderMock.Object);

// Act
var exception = await Assert.ThrowsAsync<ArgumentException>(async () =>
{
await target.SecondsAsync(textSeconds);
});
var context = await FunctionHelpers.CallViaKernel(target, "Seconds", ("input", textSeconds));

// Assert
Assert.NotNull(exception);
Assert.IsType<ArgumentException>(exception);
Assert.Equal("secondsText", exception.ParamName);
AssertExtensions.AssertIsArgumentOutOfRange(context.LastException, "input", textSeconds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<NoWarn>CA2007,VSTHRD111</NoWarn>
</PropertyGroup>

<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/test/TestInternalUtilities.props" />

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
Expand Down
Loading

0 comments on commit b1dec86

Please sign in to comment.