Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
 into planner
  • Loading branch information
adrianwyatt committed Apr 26, 2023
2 parents a213a1f + 544b2a6 commit 91bf5d7
Show file tree
Hide file tree
Showing 7 changed files with 859 additions and 9 deletions.
22 changes: 22 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,28 @@
"cwd": "${workspaceFolder}/dotnet/src/SemanticKernel.UnitTests/"
}
},
{
"label": "Test - Extensions (Code Coverage)",
"command": "dotnet",
"type": "process",
"args": [
"test",
"--collect",
"XPlat Code Coverage;Format=lcov",
"--filter",
"${input:filter}",
"Extensions.UnitTests.csproj"
],
"problemMatcher": "$msCompile",
"group": "test",
"presentation": {
"reveal": "always",
"panel": "shared"
},
"options": {
"cwd": "${workspaceFolder}/dotnet/src/Extensions/Extensions.UnitTests/"
}
},
{
"label": "Test - Semantic-Kernel Integration (Code Coverage)",
"command": "dotnet",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup>

Expand Down
91 changes: 91 additions & 0 deletions dotnet/src/IntegrationTests/Planning/PlanTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,40 @@ public async Task CanExecuteRunPlanSimpleManualStateAsync(string input, string g
Assert.Equal($"Sent email to: {email}. Body: {expectedBody}".Trim(), plan.State.ToString());
}

[Theory]
[InlineData(null, "Write a poem or joke and send it in an e-mail to Kai.", null)]
[InlineData("", "Write a poem or joke and send it in an e-mail to Kai.", "")]
[InlineData("Hello World!", "Write a poem or joke and send it in an e-mail to Kai.", "some_email@email.com")]
public async Task CanExecuteRunPlanSimpleManualStateNoVariableAsync(string input, string goal, string email)
{
// Arrange
IKernel target = this.InitializeKernel();
var emailSkill = target.ImportSkill(new EmailSkillFake());

// Create the input mapping from parent (plan) plan state to child plan (sendEmailPlan) state.
var cv = new ContextVariables();
cv.Set("email_address", string.Empty);
var sendEmailPlan = new Plan(emailSkill["SendEmailAsync"])
{
NamedParameters = cv,
};

var plan = new Plan(goal);
plan.AddSteps(sendEmailPlan);
plan.State.Set("email_address", email); // manually prepare the state

// Act
var result = await target.StepAsync(input, plan);

// Assert
var expectedBody = string.IsNullOrEmpty(input) ? goal : input;
Assert.Single(result.Steps);
Assert.Equal(1, result.NextStepIndex);
Assert.False(result.HasNextStep);
Assert.Equal(goal, plan.Description);
Assert.Equal($"Sent email to: {email}. Body: {expectedBody}".Trim(), plan.State.ToString());
}

[Theory]
[InlineData(null, "Write a poem or joke and send it in an e-mail to Kai.", null)]
[InlineData("", "Write a poem or joke and send it in an e-mail to Kai.", "")]
Expand Down Expand Up @@ -314,6 +348,63 @@ public async Task CanExecuteRunSequentialAsync(string goal, string inputToSummar
Assert.True(expectedBody.Length < result.Result.Length);
}

[Theory]
[InlineData("Summarize an input, translate to french, and e-mail to Kai", "This is a story about a dog.", "French", "Kai", "Kai@example.com")]
public async Task CanExecuteRunSequentialOnDeserializedPlanAsync(string goal, string inputToSummarize, string inputLanguage, string inputName,
string expectedEmail)
{
// Arrange
IKernel target = this.InitializeKernel();
var summarizeSkill = TestHelpers.GetSkills(target, "SummarizeSkill");
var writerSkill = TestHelpers.GetSkills(target, "WriterSkill");
var emailSkill = target.ImportSkill(new EmailSkillFake());

var expectedBody = $"Sent email to: {expectedEmail}. Body:".Trim();

var summarizePlan = new Plan(summarizeSkill["Summarize"]);

var cv = new ContextVariables();
cv.Set("language", inputLanguage);
var outputs = new ContextVariables();
outputs.Set("TRANSLATED_SUMMARY", string.Empty);

var translatePlan = new Plan(writerSkill["Translate"])
{
NamedParameters = cv,
NamedOutputs = outputs,
};

cv = new ContextVariables();
cv.Update(inputName);
outputs = new ContextVariables();
outputs.Set("TheEmailFromState", string.Empty);
var getEmailPlan = new Plan(emailSkill["GetEmailAddressAsync"])
{
NamedParameters = cv,
NamedOutputs = outputs,
};

cv = new ContextVariables();
cv.Set("email_address", "$TheEmailFromState");
cv.Set("input", "$TRANSLATED_SUMMARY");
var sendEmailPlan = new Plan(emailSkill["SendEmailAsync"])
{
NamedParameters = cv
};

var plan = new Plan(goal);
plan.AddSteps(summarizePlan, translatePlan, getEmailPlan, sendEmailPlan);

// Act
var serializedPlan = plan.ToJson();
var deserializedPlan = Plan.FromJson(serializedPlan, target.CreateNewContext());
var result = await target.RunAsync(inputToSummarize, deserializedPlan);

// Assert
Assert.Contains(expectedBody, result.Result, StringComparison.OrdinalIgnoreCase);
Assert.True(expectedBody.Length < result.Result.Length);
}

[Theory]
[InlineData("Summarize an input, translate to french, and e-mail to Kai", "This is a story about a dog.", "French", "kai@email.com")]
public async Task CanExecuteRunSequentialFunctionsAsync(string goal, string inputToSummarize, string inputLanguage, string expectedEmail)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.SemanticKernel.Orchestration;
using Xunit;

namespace SemanticKernel.UnitTests.Orchestration;

/// <summary>
/// Unit tests of <see cref="ContextVariablesConverter"/>.
/// </summary>
public class ContextVariablesConverterTests
{
[Fact]
public void ReadFromJsonSucceeds()
{
// Arrange
string json = /*lang=json,strict*/ @"[{""Key"":""a"", ""Value"":""b""}]";
var options = new JsonSerializerOptions();
options.Converters.Add(new ContextVariablesConverter());

// Act
var result = JsonSerializer.Deserialize<ContextVariables>(json, options);

// Assert
Assert.Equal("b", result!["a"]);
Assert.Equal(string.Empty, result!["INPUT"]);
}

[Fact]
public void ReadFromJsonWrongTypeThrows()
{
// Arrange
string json = /*lang=json,strict*/ @"[{""Key"":""a"", ""Value"":""b""}]";
var options = new JsonSerializerOptions();
options.Converters.Add(new ContextVariablesConverter());

// Act and Assert
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Dictionary<string, string>>(json, options));
}

[Fact]
public void ReadFromJsonSucceedsWithInput()
{
// Arrange
string json = /*lang=json,strict*/ @"[{""Key"":""INPUT"", ""Value"":""c""}, {""Key"":""a"", ""Value"":""b""}]";
var options = new JsonSerializerOptions();
options.Converters.Add(new ContextVariablesConverter());

// Act
var result = JsonSerializer.Deserialize<ContextVariables>(json, options);

// Assert
Assert.Equal("b", result!["a"]);
Assert.Equal("c", result!["INPUT"]);
}

// input value
// params key/value
[Theory]
[InlineData(null, new[] { "a", "b" }, new[]
{
/*lang=json,strict*/ @"{""Key"":""INPUT"",""Value"":""""}", /*lang=json,strict*/ @"{""Key"":""a"",""Value"":""b""}"
})]
[InlineData("", new[] { "a", "b" }, new[]
{
/*lang=json,strict*/ @"{""Key"":""INPUT"",""Value"":""""}", /*lang=json,strict*/ @"{""Key"":""a"",""Value"":""b""}"
})]
[InlineData("c", new[] { "a", "b" }, new[]
{
/*lang=json,strict*/ @"{""Key"":""INPUT"",""Value"":""c""}", /*lang=json,strict*/ @"{""Key"":""a"",""Value"":""b""}"
})]
[InlineData("c", new[] { "a", "b", "d", "e" }, new[]
{
/*lang=json,strict*/ @"{""Key"":""INPUT"",""Value"":""c""}", /*lang=json,strict*/ @"{""Key"":""a"",""Value"":""b""}", /*lang=json,strict*/
@"{""Key"":""d"",""Value"":""e""}"
})]
public void WriteToJsonSuceeds(string inputValue, IList<string> contextToSet, IList<string> expectedJson)
{
// Arrange
var options = new JsonSerializerOptions();
options.Converters.Add(new ContextVariablesConverter());
var context = new ContextVariables();
if (inputValue != null)
{
context.Update(inputValue);
}

for (int i = 0; i < contextToSet.Count; i += 2)
{
context.Set(contextToSet[i], contextToSet[i + 1]);
}

// Act
string result = JsonSerializer.Serialize(context, options);

// Assert
foreach (string key in expectedJson)
{
Assert.Contains(key, result, StringComparison.Ordinal);
}
}

[Fact]
public void WriteToJsonSucceedsAfterClearing()
{
// Arrange
var options = new JsonSerializerOptions();
options.Converters.Add(new ContextVariablesConverter());
var context = new ContextVariables();
context.Set("a", "b");
context.Set("INPUT", "c");
context.Set("d", "e");
context.Set("f", "ThingToBeCleared");
context.Set("f", null);
context.Set("g", string.Empty);

// Act
string result = JsonSerializer.Serialize(context, options);

// Assert
Assert.Contains( /*lang=json,strict*/ @"{""Key"":""INPUT"",""Value"":""c""}", result, StringComparison.Ordinal);
Assert.Contains( /*lang=json,strict*/ @"{""Key"":""a"",""Value"":""b""}", result, StringComparison.Ordinal);
Assert.Contains( /*lang=json,strict*/ @"{""Key"":""d"",""Value"":""e""}", result, StringComparison.Ordinal);
Assert.DoesNotContain(@"""Key"":""f""", result, StringComparison.Ordinal);
Assert.Contains( /*lang=json,strict*/ @"{""Key"":""g"",""Value"":""""}", result, StringComparison.Ordinal);
}

// Error Tests
[Fact]
public void ReadFromJsonReturnsNullWithNull()
{
// Arrange
string json = /*lang=json,strict*/ @"null";
var options = new JsonSerializerOptions();
options.Converters.Add(new ContextVariablesConverter());

// Act
var result = JsonSerializer.Deserialize<ContextVariables>(json, options);

// Assert
Assert.Null(result);
}

[Fact]
public void ReadFromJsonReturnsDefaultWithEmpty()
{
// Arrange
string json = /*lang=json,strict*/ @"[]";
var options = new JsonSerializerOptions();
options.Converters.Add(new ContextVariablesConverter());

// Act
var result = JsonSerializer.Deserialize<ContextVariables>(json, options);

// Assert
Assert.NotNull(result);
Assert.Equal(string.Empty, result!["INPUT"]);
}

[Fact]
public void ReadFromJsonThrowsWithInvalidJson()
{
// Arrange
string json = /*lang=json,strict*/ @"[{""Key"":""a"", ""Value"":""b""";
var options = new JsonSerializerOptions();
options.Converters.Add(new ContextVariablesConverter());

// Act & Assert
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ContextVariables>(json, options));
}

[Fact]
public void ReadFromJsonThrowsWithInvalidJson2()
{
// Arrange
string json = /*lang=json,strict*/ @"[{""Keys"":""a"", ""Value"":""b""}]";
var options = new JsonSerializerOptions();
options.Converters.Add(new ContextVariablesConverter());

// Act & Assert
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<ContextVariables>(json, options));
}
}

0 comments on commit 91bf5d7

Please sign in to comment.