Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net: Handlebars syntax example #4646

Closed
Closed
Changes from 12 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ed85e5f
Handlebars syntax example - rough and not fully working. Needs help …
joslat Jan 16, 2024
3c6e0bd
the code :)
joslat Jan 16, 2024
6b35190
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Jan 17, 2024
944842a
Update to the "new style" Xunit test inheriting from BaseTest ;)
joslat Jan 17, 2024
a0e3655
and Console becomes this ;)
joslat Jan 17, 2024
48d1af8
two Console.WriteLine missed...
joslat Jan 17, 2024
145ebcd
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Jan 19, 2024
abc0e78
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Jan 22, 2024
b46c444
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Jan 27, 2024
9319abe
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Jan 28, 2024
cfbec24
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Jan 31, 2024
148b26f
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Feb 2, 2024
e058e29
typhos fixed.
joslat Feb 2, 2024
3e7676d
some formatting improvements
joslat Feb 2, 2024
b5e3b9a
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Feb 2, 2024
4918320
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Feb 4, 2024
74c5b26
Update dotnet/samples/KernelSyntaxExamples/Example77_HandlebarsPrompt…
joslat Feb 6, 2024
020dea9
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Feb 6, 2024
6b055af
some typhos, added comments and corrections
joslat Feb 6, 2024
6744342
improvements and refactorings
joslat Feb 6, 2024
f96895e
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Feb 6, 2024
11c1c69
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
markwallace-microsoft Feb 9, 2024
c0813b9
Update dotnet/samples/KernelSyntaxExamples/Example77_HandlebarsPrompt…
joslat Feb 10, 2024
e09cc59
Update dotnet/samples/KernelSyntaxExamples/Example77_HandlebarsPrompt…
joslat Feb 10, 2024
8ddb28f
Update dotnet/samples/KernelSyntaxExamples/Example77_HandlebarsPrompt…
joslat Feb 10, 2024
a7b1ae7
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Feb 10, 2024
54695dc
initial batch of improvements.
joslat Feb 10, 2024
99dcc25
some typhos
joslat Feb 10, 2024
03156cc
Handlebars planner & template examples separated into methods.
joslat Feb 10, 2024
5000dbc
minor corrections
joslat Feb 12, 2024
be88e76
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Feb 13, 2024
d5a3e26
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Feb 16, 2024
2b10ca7
sample added by request of @teresaqhoang to showcase function calling…
joslat Feb 16, 2024
8862756
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
teresaqhoang Feb 21, 2024
9574852
Merge branch 'main' into joslat-HandlebarsTemplateSyntaxExample
joslat Mar 3, 2024
efe485d
minor
joslat Mar 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Threading.Tasks;
using Examples;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;
using Xunit;
using Xunit.Abstractions;

namespace KernelSyntaxExamples;

// This example shows how to use Handlebars different syntax options like:
joslat marked this conversation as resolved.
Show resolved Hide resolved
// - set (works)
// - function calling (works)
// - loops (each) - not working at the moment, but tries to do so over a complex object generated by one of the functions: a JSON array-
// - array - not working at the moment - to accumulate the results of the loop in an array
joslat marked this conversation as resolved.
Show resolved Hide resolved
// - conditionals (works)
// - concatenation (works)

// In order to create a Prompt Function that fully benefits from the Handlebars syntax power.
// The example also shows how to use the HandlebarsPlanner to generate a plan (and persist it) which was used to generate the initial Handlebar template.
// The example also shows how to create two prompt functions and a plugin to group them together.
public class Example77_HandlebarsPromptSyntax : BaseTest
{
[Fact]

public async Task RunAsync()
{
joslat marked this conversation as resolved.
Show resolved Hide resolved
this.WriteLine("======== LLMPrompts ========");
joslat marked this conversation as resolved.
Show resolved Hide resolved

string openAIModelId = TestConfiguration.OpenAI.ChatModelId;
string openAIApiKey = TestConfiguration.OpenAI.ApiKey;

if (openAIApiKey == null)
joslat marked this conversation as resolved.
Show resolved Hide resolved
{
this.WriteLine("OpenAI credentials not found. Skipping example.");
return;
}

Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: openAIModelId,
apiKey: openAIApiKey)
.Build();


KernelFunction kernelFunctionGenerateProductNames =
KernelFunctionFactory.CreateFromPrompt(
"Given the company description, generate five different product names in name and function " +
"that match the company description. " +
"Ensure that they match the company and are aligned to it. " +
"Think of products this company would really make and also have market potential. " +
"Be original and do not make too long names or use more than 3-4 words for them." +
"Also, the product name should be catchy and easy to remember. " +
// JSON or NOT JSON, that is the question...
"Output the product names and short descriptions as a bullet point list. " +
//"Output the product names in a JSON array inside a JSON object named products. " +
//"On them use the name and description as keys." +
//"Ensure the JSON is well formed and is valid" +
joslat marked this conversation as resolved.
Show resolved Hide resolved
"The company description: {{$input}}",
functionName: "GenerateProductNames",
description: "Generate five product names to match a company.");

KernelFunction kernelFunctionGenerateProductDescription =
KernelFunctionFactory.CreateFromPrompt(
"Given a product name and initial description, generate a description which is compelling, " +
"engaging and stunning. Also add at the end how would you approach to develop, create it," +
"with Semantic Kernel." +
"Think of marketing terms and use positive words but do not lie and oversell. " +
"Be original and do not make a too long description or use more than 2 paragraphs for it." +
"Also, the product description should be catchy and easy to remember. " +
"Output the description folled by the development approach preceeded by Development approach: " +
"The product name and description: {{$input}}",
functionName: "GenerateProductCompellingDescription",
description: "Generate a compelling product description for a product name and initial description.");

KernelPlugin productMagicianPlugin =
KernelPluginFactory.CreateFromFunctions(
"productMagician",
"Helps create a set of products for a company and descriptions for them.",
new[] {
kernelFunctionGenerateProductNames,
kernelFunctionGenerateProductDescription
});

kernel.Plugins.Add(productMagicianPlugin);
joslat marked this conversation as resolved.
Show resolved Hide resolved

string companyDescription = "The company is a startup that is building new AI solutions for the market. using Generative AI and AI orchestration novel techonlogies. The company is an expert on this recently launched SDK (Software Development Toolkit) named Semantic Kernel. Semantic Kernel or SK, enables AI Orchestration with .NET which is production ready, enterprise ready and cloud ready." +
joslat marked this conversation as resolved.
Show resolved Hide resolved
"Also it is able to self plan and execute complex tasks and use the power of AI agents which" +
"enables to divide-and-conquer complex problems between different entitites that specialize in " +
"concrete tasks like for example project management, coding and creating tests as well as other" +
" agents can be responsible for executing the tests and assessing the code delivered and iterate" +
" - this means creating feedback loops until the quality levels are met. The company is thinking of using AI Agent programming on coding, writing and project planning, and anything where AI Agents" +
" can be applied and revolutionize a process or market niche.";

//////////////////////////////////////////////////////////////////////////////////////////////////////
//// Testing a the 5 product name generation
//var productsResult =
// await kernel.InvokeAsync(kernelFunctionGenerateProductNames,
// new() {
// { "input", companyDescription }
// });
//This.WriteLine($"Result: {productsResult}");

//////////////////////////////////////////////////////////////////////////////////////////////////////
// Testing the product description generation
//string ProductDescription = "Product name: Skynet SDK Product description:A powerful .NET SDK destined to empower developers with advanced AI orchestration capabilities, capable of handling complex task automation.";
//var productDescriptionResult =
// await kernel.InvokeAsync(kernelFunctionGenerateProductDescription,
// new() {
// { "input", ProductDescription }
// });
//This.WriteLine($"Result: {productDescriptionResult}");

// Using the planner to generate a plan for the user
string userPrompt =
"Using as input the following company description:" +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question for clarity - are you asking the model to first generate an array of products with unique names and descriptions and then iterate over that array to make the descriptions more engaging?

The model tends to work best when given a well-defined set of instruction and then a definition of what the expected output should be.

"Using as input the following company description:" +
            "---" +
            " {{input}}" +
            "---" +
            "I want to generate five product names and engaging descriptions for a company." +
            "Please provide your output as a JSON array of products, where each product contains a name and description." +
            "for example:" +
            "---" +
            "[\r\n    {\r\n        \"name\": \"SmartCode SK\",\r\n        \"description\": \"An AI solution that utilizes AI agent programming to automate code writing and assess the quality of code, reducing the need for manual review and increasing code efficiency\"\r\n    },\r\n    {\r\n        \"name\": \"ProjectMind SK\",\r\n        \"description\": \"An AI-powered project management tool that utilizes Semantic Kernel to automate project planning, task scheduling, and enable more effective communication within teams\"\r\n    },\r\n    {\r\n        \"name\": \"SK WriterPlus\",\r\n        \"description\": \"An AI-driven writing solution that uses AI Agents and Semantic Kernel technology to automate content creation, editing, and proofreading tasks\"\r\n    },\r\n    {\r\n        \"name\": \"SQA Tester SK\",\r\n        \"description\": \"A software product that utilizes AI agents to execute tests, assess the code delivered and iterate. It uses a divide-and-conquer approach to simplify complex testing problems\"\r\n    },\r\n    {\r\n        \"name\": \"SK CloudMaster\",\r\n        \"description\": \"An AI solution that intelligently manages and orchestrates cloud resources using the power of AI agents and Semantic Kernel technology, ready for enterprise and production environments\"\r\n    }\r\n]" +
            "Then, using this array, …{ Rest of your instructions}"

Another note: the model is usually smart enough to know how to translate function outputs between plan steps, so you don't need to explicitly define the example output like this. The HB Planner already has information about the functions, inputs, and outputs in the CreatePlan prompt, so its "magic" comes from being able to select and execute the appropriate functions without much explicit guidance. Play around with it using a simpler prompt and see what it gives you!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way your functions are set up are too complex for this example.

Either:

  1. Have a function that generates the products (with name and description fields) and then a second function that can iterate through those products and produce more detailed descriptions.

i.e.

var products = GenerateNewProducts(string companyDescription) // outputs `[ product1, product2, ...]`

// Either pass in the entire result and have the model handle the products array for you or parse `products` array and loop through each object
GenerateDetailedDescription(product[0]) // outputs "Some compelling description"
  1. Have a function that returns a list of product names (output: a list of strings) and then iterate over that list to generate descriptions for each name (output: a list of products containing name and desc)

"---" +
" {{input}}" +
"---" +
"I want to generate five product names and engaging descriptions for a company." +
"For this, I suggest the following process:" +
"1. Please create first the product names given the company description" +
"2. Then, for each of the 5 provided product names and descriptions, provided as a bullet point list, " +
"generate the compelling, engaging, description" +
"Please while doing this provide all the information as input:" +
"For point 2, concatenate the product name to the rough description." +
"Please use the format Product name: productname Description: description substituting" +
"productname and description by the product names provided on point 1" +
"Finally output all the product names and engaging descriptions preceded by PRODUCT 1: for the first" +
"PRODUCT 2: for the second, and so on.";

//////////////////////////////////////////////////////////////////////////////////////////////////////
// Create the plan
//var planner = new HandlebarsPlanner(new HandlebarsPlannerOptions() { AllowLoops = true });
//var plan = await planner.CreatePlanAsync(kernel, userPrompt);
//var planName = "plan4ProductsGeneration.txt";

//// Print the plan to the console
//This.WriteLine($"Plan: {plan}");

//var serializedPlan = plan.ToString();
//await File.WriteAllTextAsync(planName, serializedPlan);
//string retrievedPlan = await File.ReadAllTextAsync(planName);
//plan = new HandlebarsPlan(serializedPlan);


//////////////////////////////////////////////////////////////////////////////////////////////////////
/// One of the generated plans of the code above:
// Plan: {{!-- Step 1: Save the input company description to a variable --}}
//{{set "companyDescription" input}}

//{{!-- Step 2: Generate product names based on the company description --}}
//{{set "productNames" (productMagician-GenerateProductNames input=companyDescription)}}

//{{!-- Step 3: Loop over the generated product names to generate product descriptions--}}
//{{set "products" (array)}}
//{{#each productNames}}
// {{set "productName" this}}
// {{set "roughDescription" (concat "Product related to " companyName)}}
// {{set "productDescription" (productMagician-GenerateProductCompellingDescription input=roughDescription)}}
// {{set "products" (array.push products (concat "Product name: " productName " Description: " productDescription))}}
//{{/each}}

//{{!-- Step 4: Loop over the final products array to print --}}
//{{#each products}}
// {{set "index" (Add @index 1)}}
// {{json (concat "PRODUCT " index ": " this)}}
//{{/each}}
joslat marked this conversation as resolved.
Show resolved Hide resolved


//////////////////////////////////////////////////////////////////////////////////////////////////////
// We will use one of the generated HandlebarsTemplate plan by the above code, with some modifications,
// to highlight better the HandlebarsTemplate syntax usage.
// And invoke it as a Prompt Function
string handlebarsTemplate = @"
{{!-- example of set with input and function calling with two syntax types --}}
{{set ""companyDescription"" input}}
{{set ""productNames"" (productMagician-GenerateProductNames companyDescription)}}
{{set ""productNames2"" (productMagician-GenerateProductNames input=companyDescription)}}

{{#if generateEngagingDescriptions}}

{{!-- Step 2: Create array for storing final descriptions --}}
{{set ""finalDescriptions"" (array)}}
{{set ""finalDescriptionsV2"" ""- PRODUCTS AND ENGAGING DESCRIPTIONS -""}}

{{!-- Step 3: Iterate over each generated product name --}}
{{#each productNames}}
{{!-- Step 3.1: Concatenating productName to initial company description --}}
{{set ""productDescription"" (concat ""Product Name: "" this.name "" Description: "" this.description)}}

{{!-- Step 3.2: Generate compelling description for each productName --}}
{{set ""compellingDescription"" (productMagician-GenerateProductCompellingDescription productDescription)}}

{{!-- Step 3.3: Concatenate compelling description and product number --}}
{{set ""outputDescription"" (concat ""PRODUCT :"" this.name "" Engaging Description: "" compellingDescription)}}

{{!-- Step 3.4: Add output description to the list --}}
{{set ""finalDescriptions"" (array finalDescriptions outputDescription)}}
{{set ""finalDescriptionsV2"" (concat finalDescriptionsV2 "" -- "" outputDescription)}}

{{/each}}

{{!-- Step 4: Print all product names and compelling descriptions --}}
{{json finalDescriptionsV2}}

{{else}}
{{!-- Example of concatenating text and variables to finally output it with json --}}
{{set ""finalOutput"" (concat ""Description 1: "" productNames "" Description 2: "" productNames2)}}
{{json finalOutput}}
{{/if}}";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of showcase a HB prompt template, these are much too complex.

Example of how you can reduce complexity:

{{!-- Omit the initial variable mapping of inputs (this is required for the planner to reduce error) but as a dev executing their own HB template, you should have full context over how input are maps. --}}
{{set ""productNames"" (productMagician-GenerateJSONProducts input)}}

{{#if generateEngagingDescriptions}} 
{{!-- Since SK doesn't support mutable arrays, you must concatenate each result onto a string. --}}
    {{set ""finalProducts"" """"}}

    {{#each productNames}}
{{!-- Refactor your GenerateJSONProducts function to return just an array of products, then remove the inner {{#each}}} block. It's unnecessarily complex. --}}
        {{set ""productDescription"" (concat ""Product Name: "" this.name "" Description: "" this.description)}}
        {{set ""compellingDescription"" (productMagician-GenerateProductCompellingDescription this)}}
        {{set ""outputProduct"" (concat ""PRODUCT :"" this.name "" Engaging Description: "" compellingDescription)}}

{{!-- Check if you're on the first object; if not, concatenate to end of string.  --}}
        {{#if @first}}{{set ""finalProducts"" outputProduct}}
        {{else}}{{set ""finalProducts"" (concat finalProducts ""\n\n"" outputProduct)}}
        {{/if}}
    {{/each}}
{{!--  OUTPUT The following product descriptions as is, do not modify anything --}}   
    {{json finalProducts}}
{{else}} 
{{!-- Return products as is since the conditional is checking whether descriptions should be regenerated. --}}
    {{json productNames}}
{{/if}}


/// did not work:
/// {{set ""outputDescription"" (concat ""PRODUCT "" (Add @index 1) "": "" compellingDescription)}}
/// Reason: helper add not found
/// Also the array thing doesn't seem to work
/// {{set ""finalDescriptions"" (array)}}
/// what fails on the each loop, when I use it I set generateEngagingDescriptions and make the prompt function output
/// JSON to iterate through it. Technically it is the proper syntax but somehow it fails. some returns from the LLM are
/// absolute hallucinations :/P
joslat marked this conversation as resolved.
Show resolved Hide resolved

var HandlebarsSPromptFunction = kernel.CreateFunctionFromPrompt(
joslat marked this conversation as resolved.
Show resolved Hide resolved
new()
{
Template = handlebarsTemplate,
TemplateFormat = "handlebars"
},
new HandlebarsPromptTemplateFactory()
);

// Invoke prompt
var result = await kernel.InvokeAsync(
HandlebarsSPromptFunction,
new() {
{ "input", companyDescription },
{ "generateEngagingDescriptions", false }
}
);

this.WriteLine($"Result: {result}");
}

public Example77_HandlebarsPromptSyntax(ITestOutputHelper output) : base(output)
{
}
}
Loading