forked from microsoft/semantic-kernel
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
.Net: Handlebars prompt template support (microsoft#3392)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
- Loading branch information
1 parent
cc6dd60
commit 86a2e3f
Showing
10 changed files
with
462 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
dotnet/samples/KernelSyntaxExamples/Example64_MultiplePromptTemplates.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System; | ||
using System.Threading.Tasks; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Orchestration; | ||
using Microsoft.SemanticKernel.TemplateEngine; | ||
using Microsoft.SemanticKernel.TemplateEngine.Basic; | ||
using Microsoft.SemanticKernel.TemplateEngine.Handlebars; | ||
using RepoUtils; | ||
|
||
/** | ||
* This example shows how to use multiple prompt template formats. | ||
*/ | ||
// ReSharper disable once InconsistentNaming | ||
public static class Example64_MultiplePromptTemplates | ||
{ | ||
/// <summary> | ||
/// Show how to combine multiple prompt template factories. | ||
/// </summary> | ||
public static async Task RunAsync() | ||
{ | ||
Console.WriteLine("======== Example64_MultiplePromptTemplates ========"); | ||
|
||
string apiKey = TestConfiguration.AzureOpenAI.ApiKey; | ||
string chatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName; | ||
string endpoint = TestConfiguration.AzureOpenAI.Endpoint; | ||
|
||
if (apiKey == null || chatDeploymentName == null || endpoint == null) | ||
{ | ||
Console.WriteLine("Azure endpoint, apiKey, or deploymentName not found. Skipping example."); | ||
return; | ||
} | ||
|
||
IKernel kernel = new KernelBuilder() | ||
.WithLoggerFactory(ConsoleLogger.LoggerFactory) | ||
.WithAzureOpenAIChatCompletionService( | ||
deploymentName: chatDeploymentName, | ||
endpoint: endpoint, | ||
serviceId: "AzureOpenAIChat", | ||
apiKey: apiKey) | ||
.Build(); | ||
|
||
var promptTemplateFactory = new AggregatorPromptTemplateFactory( | ||
new BasicPromptTemplateFactory(), | ||
new HandlebarsPromptTemplateFactory()); | ||
|
||
var skPrompt = "Hello AI, my name is {{$name}}. What is the origin of my name?"; | ||
var handlebarsPrompt = "Hello AI, my name is {{name}}. What is the origin of my name?"; | ||
|
||
await RunSemanticFunctionAsync(kernel, skPrompt, "semantic-kernel", promptTemplateFactory); | ||
await RunSemanticFunctionAsync(kernel, handlebarsPrompt, "handlebars", promptTemplateFactory); | ||
} | ||
|
||
public static async Task RunSemanticFunctionAsync(IKernel kernel, string prompt, string templateFormat, IPromptTemplateFactory promptTemplateFactory) | ||
{ | ||
Console.WriteLine($"======== {templateFormat} : {prompt} ========"); | ||
|
||
var skfunction = kernel.CreateSemanticFunction( | ||
promptTemplate: prompt, | ||
functionName: "MyFunction", | ||
promptTemplateConfig: new PromptTemplateConfig() | ||
{ | ||
TemplateFormat = templateFormat | ||
}, | ||
promptTemplateFactory: promptTemplateFactory | ||
); | ||
|
||
var variables = new ContextVariables() | ||
{ | ||
{ "name", "Bob" } | ||
}; | ||
|
||
var result = await kernel.RunAsync(skfunction, variables); | ||
Console.WriteLine(result.GetValue<string>()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
...ns/Extensions.UnitTests/TemplateEngine/Handlebars/HandlebarsPromptTemplateFactoryTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Microsoft.SemanticKernel.Diagnostics; | ||
using Microsoft.SemanticKernel.TemplateEngine; | ||
using Microsoft.SemanticKernel.TemplateEngine.Handlebars; | ||
using Xunit; | ||
|
||
namespace SemanticKernel.Extensions.UnitTests.TemplateEngine.Handlebars; | ||
|
||
public sealed class HandlebarsPromptTemplateFactoryTests | ||
{ | ||
[Fact] | ||
public void ItCreatesHandlebarsPromptTemplate() | ||
{ | ||
// Arrange | ||
var templateString = "{{input}}"; | ||
var target = new HandlebarsPromptTemplateFactory(); | ||
|
||
// Act | ||
var result = target.Create(templateString, new PromptTemplateConfig() { TemplateFormat = HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat }); | ||
|
||
// Assert | ||
Assert.NotNull(result); | ||
Assert.True(result is HandlebarsPromptTemplate); | ||
} | ||
|
||
[Fact] | ||
public void ItThrowsExceptionForUnknowPromptTemplateFormat() | ||
{ | ||
// Arrange | ||
var templateString = "{{input}}"; | ||
var target = new HandlebarsPromptTemplateFactory(); | ||
|
||
// Act | ||
// Assert | ||
Assert.Throws<SKException>(() => target.Create(templateString, new PromptTemplateConfig() { TemplateFormat = "unknown-format" })); | ||
} | ||
} |
164 changes: 164 additions & 0 deletions
164
...xtensions/Extensions.UnitTests/TemplateEngine/Handlebars/HandlebarsPromptTemplateTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System; | ||
using System.ComponentModel; | ||
using System.Threading.Tasks; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Orchestration; | ||
using Microsoft.SemanticKernel.Services; | ||
using Microsoft.SemanticKernel.TemplateEngine; | ||
using Microsoft.SemanticKernel.TemplateEngine.Handlebars; | ||
using Moq; | ||
using SemanticKernel.Extensions.UnitTests.XunitHelpers; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
using static Microsoft.SemanticKernel.TemplateEngine.PromptTemplateConfig; | ||
|
||
namespace SemanticKernel.Extensions.UnitTests.TemplateEngine.Handlebars; | ||
|
||
public sealed class HandlebarsPromptTemplateTests | ||
{ | ||
private readonly HandlebarsPromptTemplateFactory _factory; | ||
private readonly IKernel _kernel; | ||
private readonly ContextVariables _variables; | ||
private readonly Mock<IReadOnlyFunctionCollection> _functions; | ||
private readonly ITestOutputHelper _logger; | ||
private readonly Mock<IFunctionRunner> _functionRunner; | ||
private readonly Mock<IAIServiceProvider> _serviceProvider; | ||
private readonly Mock<IAIServiceSelector> _serviceSelector; | ||
|
||
public HandlebarsPromptTemplateTests(ITestOutputHelper testOutputHelper) | ||
{ | ||
this._logger = testOutputHelper; | ||
this._factory = new HandlebarsPromptTemplateFactory(TestConsoleLogger.LoggerFactory); | ||
this._kernel = new KernelBuilder().Build(); | ||
this._variables = new ContextVariables(Guid.NewGuid().ToString("X")); | ||
|
||
this._functions = new Mock<IReadOnlyFunctionCollection>(); | ||
this._functionRunner = new Mock<IFunctionRunner>(); | ||
this._serviceProvider = new Mock<IAIServiceProvider>(); | ||
this._serviceSelector = new Mock<IAIServiceSelector>(); | ||
} | ||
|
||
[Fact] | ||
public async Task ItRendersVariablesAsync() | ||
{ | ||
// Arrange | ||
this._variables.Set("bar", "Bar"); | ||
var template = "Foo {{bar}}"; | ||
var target = (HandlebarsPromptTemplate)this._factory.Create(template, new PromptTemplateConfig() { TemplateFormat = HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat }); | ||
var context = this._kernel.CreateNewContext(this._variables); | ||
|
||
// Act | ||
var prompt = await target.RenderAsync(context); | ||
|
||
// Assert | ||
Assert.Equal("Foo Bar", prompt); | ||
} | ||
|
||
[Fact] | ||
public async Task ItRendersFunctionsAsync() | ||
{ | ||
// Arrange | ||
this._kernel.ImportFunctions(new Foo(), "Foo"); | ||
var template = "Foo {{Foo_Bar}}"; | ||
var target = (HandlebarsPromptTemplate)this._factory.Create(template, new PromptTemplateConfig() { TemplateFormat = HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat }); | ||
var context = this._kernel.CreateNewContext(this._variables); | ||
|
||
// Act | ||
var prompt = await target.RenderAsync(context); | ||
|
||
// Assert | ||
Assert.Equal("Foo Bar", prompt); | ||
} | ||
|
||
[Fact] | ||
public async Task ItRendersAsyncFunctionsAsync() | ||
{ | ||
// Arrange | ||
this._kernel.ImportFunctions(new Foo(), "Foo"); | ||
var template = "Foo {{Foo_Bar}} {{Foo_Baz}}"; | ||
var target = (HandlebarsPromptTemplate)this._factory.Create(template, new PromptTemplateConfig() { TemplateFormat = HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat }); | ||
var context = this._kernel.CreateNewContext(this._variables); | ||
|
||
// Act | ||
var prompt = await target.RenderAsync(context); | ||
|
||
// Assert | ||
Assert.Equal("Foo Bar Baz", prompt); | ||
} | ||
|
||
[Fact] | ||
public void ItReturnsParameters() | ||
{ | ||
// Arrange | ||
var promptTemplateConfig = new PromptTemplateConfig() | ||
{ | ||
TemplateFormat = HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat | ||
}; | ||
promptTemplateConfig.Input.Parameters.Add(new InputParameter() | ||
{ | ||
Name = "bar", | ||
Description = "Bar", | ||
DefaultValue = "Bar" | ||
}); | ||
promptTemplateConfig.Input.Parameters.Add(new InputParameter() | ||
{ | ||
Name = "baz", | ||
Description = "Baz", | ||
DefaultValue = "Baz" | ||
}); | ||
var template = "Foo {{Bar}} {{Baz}}"; | ||
var target = (HandlebarsPromptTemplate)this._factory.Create(template, promptTemplateConfig); | ||
|
||
// Act | ||
var parameters = target.Parameters; | ||
|
||
// Assert | ||
Assert.Equal(2, parameters.Count); | ||
} | ||
|
||
[Fact] | ||
public async Task ItUsesDefaultValuesAsync() | ||
{ | ||
// Arrange | ||
var promptTemplateConfig = new PromptTemplateConfig() | ||
{ | ||
TemplateFormat = HandlebarsPromptTemplateFactory.HandlebarsTemplateFormat | ||
}; | ||
promptTemplateConfig.Input.Parameters.Add(new InputParameter() | ||
{ | ||
Name = "Bar", | ||
Description = "Bar", | ||
DefaultValue = "Bar" | ||
}); | ||
promptTemplateConfig.Input.Parameters.Add(new InputParameter() | ||
{ | ||
Name = "Baz", | ||
Description = "Baz", | ||
DefaultValue = "Baz" | ||
}); | ||
var template = "Foo {{Bar}} {{Baz}}"; | ||
var target = (HandlebarsPromptTemplate)this._factory.Create(template, promptTemplateConfig); | ||
var context = this._kernel.CreateNewContext(this._variables); | ||
|
||
// Act | ||
var prompt = await target.RenderAsync(context); | ||
|
||
// Assert | ||
Assert.Equal("Foo Bar Baz", prompt); | ||
} | ||
|
||
private sealed class Foo | ||
{ | ||
[SKFunction, Description("Return Bar")] | ||
public string Bar() => "Bar"; | ||
|
||
[SKFunction, Description("Return Baz")] | ||
public async Task<string> BazAsync() | ||
{ | ||
await Task.Delay(1000); | ||
return await Task.FromResult("Baz"); | ||
} | ||
} | ||
} |
Oops, something went wrong.