{"id":2441,"date":"2024-04-11T13:03:32","date_gmt":"2024-04-11T20:03:32","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/semantic-kernel\/?p=2441"},"modified":"2024-04-12T16:06:55","modified_gmt":"2024-04-12T23:06:55","slug":"using-handlebars-planner-in-semantic-kernel","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/agent-framework\/using-handlebars-planner-in-semantic-kernel\/","title":{"rendered":"Using Handlebars Planner in Semantic Kernel"},"content":{"rendered":"<p>We are excited to dive into the Handlebars Planner, a powerful tool for creating customized plans.<\/p>\n<p>This comprehensive guide will provide you with a thorough understanding on how to create your own plans using the Handlebars Planner and explain some of the planner options you can customize, such as providing additional information when creating the prompt. This guide provides sample code and instructions on how to instantiate the kernel, import plugins, specify planner options, create and invoke the plan, and handle error scenarios.<\/p>\n<p>The Handlebars Planner uses the <a href=\"https:\/\/handlebarsjs.com\/guide\/\">Handlebars syntax<\/a> to generate a plan, allowing the model to leverage native features like <a href=\"https:\/\/handlebarsjs.com\/guide\/block-helpers.html#simple-iterators\">loops<\/a> and <a href=\"https:\/\/handlebarsjs.com\/guide\/block-helpers.html#conditionals\">conditions<\/a> without additional prompting<\/p>\n<p><strong>Running the Planner and Handling Error Scenarios<\/strong><\/p>\n<ol>\n<li><strong> Instantiate the kernel and import plugins<\/strong><\/li>\n<\/ol>\n<p>Instantiate the kernel and import any plugins you need using the <code class=\"language-cs language-csharp\">Kernel.CreateBuilder()<\/code>method.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var kernel = Kernel.CreateBuilder()\r\n  .AddAzureOpenAIChatCompletion(\r\n    deploymentName: \"gpt-4\",\r\n    endpoint: &lt;Azure OpenAI Endpoint&gt;,\r\n    serviceId: \"AzureOpenAIChat\",\r\n    apiKey: &lt;Azure OpenAI API Key&gt;,\r\n    modelId: \"gpt-4\"\r\n  ).Build();\r\n\r\nawait kernel.ImportPluginFromOpenApiAsync(\r\n  PluginName,\r\n  new Uri(\"https:\/\/&lt;url_to_plugin_manfest&gt;\/openapi.yaml\"));<\/code><code class=\"language-cs language-csharp\"><\/code><\/pre>\n<ol start=\"2\">\n<li><strong> Specify any planner options you want<\/strong><\/li>\n<\/ol>\n<p>Next, specify the options for the HB Planner using the HandlebarsPlannerOptions class. For example, to allow loops in your plan, you can use the following code.<\/p>\n<p>Note: Use gpt-4 or newer models if you want to test with loops. Older models like gpt-35-turbo are less recommended. They do handle loops but are more prone to syntax errors.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var plannerOptions = new HandlebarsPlannerOptions()\r\n    {\r\n        \/\/ When using OpenAI models, we recommend using low values for temperature and top_p to minimize planner hallucinations.\r\n        ExecutionSettings = new OpenAIPromptExecutionSettings()\r\n        {\r\n            Temperature = 0.0,\r\n            TopP = 0.1,\r\n        },\r\n        \/\/ Use gpt-4 or newer models if you want to test with loops.\r\n        \/\/ Older models like gpt-35-turbo are less recommended. They do handle loops but are more prone to syntax errors.\r\n        AllowLoops = ChatDeploymentName.Contains(\"gpt-4\", StringComparison.OrdinalIgnoreCase),\r\n    };<\/code><\/pre>\n<ol start=\"3\">\n<li><strong> Instantiate the planner and create the plan<\/strong><\/li>\n<\/ol>\n<p>With the kernel and planner options configured, you can now instantiate the planner and create a plan.<\/p>\n<p>To create a plan for the goal &#8220;Find a restaurant near me&#8221;, you can use the following code.<\/p>\n<p>Note: Always wrap your<code class=\"language-cs language-csharp\">`CreatePlanAsync`<\/code>call in a try catch block if you want to inspect data about the prompt and LLM response. This follows standard exception patterns found across SK.<\/p>\n<p>If the planner fails to create a plan for any reason, it will throw a<code class=\"language-cs language-csharp\">`PlanCreationException.`<\/code>For certain known failure cases, the exception will contain an inner<code class=\"language-cs language-csharp\">`KernelException`<\/code>with a specified<code class=\"language-cs language-csharp\">`HandlebarsPlannerErrorCode`<\/code>. All<code class=\"language-cs language-csharp\">`PlanCreationExceptions`<\/code>will contain the prompt used to create the plan, as well as the LLM results (the plan proposed by the model).<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">try {\r\n  var planner = new HandlebarsPlanner(plannerOptions);\r\n  var plan = await planner.CreatePlanAsync(kernel, goal);\r\n  var planResult = await plan.InvokeAsync(kernel);\r\n}\r\ncatch (PlanCreationException e)\r\n{\r\n  var errorDetails = e.InnerException?.Message;\r\n  var promptDetails = e.CreatePlanPrompt;\r\n  var modelResults = exception?.ModelResults?.Content; \/\/ Proposed plan\r\n}<\/code><\/pre>\n<ol start=\"4\">\n<li><strong>Invoke the plan<\/strong><\/li>\n<\/ol>\n<p>Finally, you can invoke the plan using the<code class=\"language-cs language-csharp\">`InvokeAsync` <\/code>method.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var planResult = await plan.InvokeAsync(kernel);<\/code><\/pre>\n<p><strong>Planner Options<\/strong><\/p>\n<p>Now that you know how to create and invoke a Handlebars Plan, we\u2019ll talk you through the customizable options available for the Handlebars Planner.<\/p>\n<p>These options allow you to tailor the planner to your specific needs, including the ability to:<\/p>\n<ul>\n<li>Override the default CreatePlan prompt with your own prompt<\/li>\n<li>Provide additional context or rules for the planner<\/li>\n<li>Use predefined arguments when invoking the plan<\/li>\n<\/ul>\n<p>By taking advantage of these options, you can create plans that are more specific to your use case and improve the performance and accuracy of your planner.<\/p>\n<ol>\n<li><strong> CreatePlanPromptHandler<\/strong><\/li>\n<\/ol>\n<p>This option allows you to override the default CreatePlan prompt with your own prompt. This is useful if you want to provide a prompt that is more tailored to your use case or domain. You can even use Handlebars syntax to generate the prompt, allowing you to leverage native features like loops and conditions without additional prompting.<\/p>\n<p><strong>Building a custom prompt<\/strong><\/p>\n<p>When creating your custom prompt, you have access to all of the partials located in the <a href=\"https:\/\/github.com\/microsoft\/semantic-kernel\/tree\/f2e52bd87b2a256364e0ed3604ea06e79f5fc466\/dotnet\/src\/Planners\/Planners.Handlebars\/Handlebars\/CreatePlanPromptPartials\">CreatePlanPromptPartials<\/a> folder. These partials can be referenced in your custom prompt using Handlebars syntax. For any other custom content, be sure to use the ChatHistory syntax so the completion request is formatted correctly before it&#8217;s sent to the model.<\/p>\n<p>For example, let&#8217;s say you want to create a custom prompt that instructs users to create a plan using only the built-in loop helpers. You can create a handlebars file that looks like this:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">{{!-- Example of a custom CreatePlan prompt for the Handlebars Planner.  --}}\r\n{{#message role=\"system\"}}\r\nExplain how to achieve the users goal using the available helpers with a Handlebars .Net template.\r\n{{~\/message}}\r\n\r\n{{&gt; VariableHelpers }}\r\n{{&gt; LoopHelpers }}\r\n{{&gt; UserGoal }}\r\n{{&gt; TipsAndInstructions }}<\/code><\/pre>\n<p>In this example, we have pulled in the built-in\u00a0<code class=\"language-cs language-csharp\">` VariableHelpers`<\/code>,<code class=\"language-cs language-csharp\">` LoopHelpers`<\/code>,<code class=\"language-cs language-csharp\">`UserGoal`<\/code>*, and<code class=\"language-cs language-csharp\">`TipsAndInstructions`<\/code>partials from the CreatePlanPromptPartials folder, and included a custom message introductory block.<\/p>\n<p><strong>Note:<\/strong> that you can inject the user goal manually by referencing the variable<code class=\"language-cs language-csharp\">`{{goal}}`<\/code>.<\/p>\n<p>Using our built-in partials allows you to leverage SK&#8217;s templating and prompt engineering. We recommend always using the<code class=\"language-cs language-csharp\">`{{&gt; UserGoal}}`<\/code>and<code class=\"language-cs language-csharp\">` {{&gt; AdditionalContext }}`<\/code>partials in your custom prompt to ensure all user and domain context is appropriately populated as well as the<code class=\"language-cs language-csharp\">`{{&gt; CustomHelpers}}`<\/code>and<code class=\"language-cs language-csharp\">`{{&gt; VariableHelpers}}`<\/code>partials to populate all kernel function definitions and allow for state manipulation.<\/p>\n<p><strong>Using the CreatePlanPromptHandler option<\/strong><\/p>\n<p><code class=\"language-cs language-csharp\"><\/code><code class=\"language-cs language-csharp\"><\/code><\/p>\n<p>Once you have created your custom prompt, you can provide it as a delegate that returns a string to the<code class=\"language-cs language-csharp\">`CreatePlanPromptHandler`<\/code>option. Make sure to load any additional helpers or partials your custom prompt requires.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var plannerOptions = new HandlebarsPlannerOptions()\r\n\r\n{\r\n    \/\/ Context to be used in the prompt template.\r\n    GetAdditionalPromptContext = () =&gt; \r\n    Task.FromResult(\"If the goal cannot be fully achieved with the provided helpers, call the `\\\\{Plugin.CustomFunctionName}` helper.\"),\r\n};<\/code><\/pre>\n<p>Please note that setting the CreatePlanPromptHandler directly as a string will not work. You must specify a delegate that returns a string or file stream. We configured the option this way to allow use cases such as reading in a custom prompt from a file as shown above.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">\/\/ Invalid\r\nvar plannerOptions = new HandlebarsPlannerOptions()\r\n\r\n{\r\n\r\n    CreatePlanPromptHandler = \"Create a Handlebars template to ...\",\r\n\r\n};\r\n\r\n\/\/ Valid\r\n\r\nvar plannerOptions = new HandlebarsPlannerOptions()\r\n\r\n{\r\n\r\n    CreatePlanPromptHandler = () =&gt; \"Create a Handlebars template to ...\",\r\n\r\n};<\/code><\/pre>\n<p>You can run an E2E sample using a custom prompt at Example65_HandlebarsPlanner.<a href=\"https:\/\/github.com\/microsoft\/semantic-kernel\/blob\/f2e52bd87b2a256364e0ed3604ea06e79f5fc466\/dotnet\/samples\/KernelSyntaxExamples\/Example65_HandlebarsPlanner.cs#L434C17-L434C55\">RunOverrideCreatePlanPromptSampleAsync<\/a>. This sample loads a custom prompt from the <a href=\"https:\/\/github.com\/microsoft\/semantic-kernel\/blob\/f2e52bd87b2a256364e0ed3604ea06e79f5fc466\/dotnet\/samples\/KernelSyntaxExamples\/Resources\/65-prompt-override.handlebars#L4\">65-prompt-override.handlebars<\/a> file, which mixes the partial referencing described above as well as custom content.<\/p>\n<p>With this option in place, the Handlebars Planner will use your custom prompt instead of the default prompt when creating plans.<\/p>\n<ol start=\"2\">\n<li><strong> GetAdditionalPromptContext<\/strong><\/li>\n<\/ol>\n<p>If you want to provide additional context or rules to the planner that can help improve its performance and accuracy, you can use the<code class=\"language-cs language-csharp\">`GetAdditionalPromptContext`<\/code>option.<\/p>\n<p>This option allows you to pass in any domain knowledge or specific content that might be helpful for the model to fulfill the goal. This context should just hold static information that&#8217;s the same for every request. It should not include or define any variables (use KernelArguments when invoking the planner instead).<\/p>\n<p>Like the<code class=\"language-cs language-csharp\">`CreatePlanPromptHandler`<\/code>option, this option expects a delegate that returns a string.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var plannerOptions = new HandlebarsPlannerOptions()\r\n{\r\n    \/\/ Context to be used in the prompt template.\r\n    GetAdditionalPromptContext = () =&gt; Task.FromResult(\"If the goal cannot be fully achieved with the provided helpers, call the `\\\\{Plugin.CustomFunctionName}` helper.\"),\r\n};\r\n<\/code><\/pre>\n<p>Again, assigning a string directly won&#8217;t work.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">\/\/ Invalid\r\nvar plannerOptions = new HandlebarsPlannerOptions()\r\n{\r\n    \/\/ Context to be used in the prompt template.\r\n    GetAdditionalPromptContext = \"If the goal cannot be fully achieved with the provided helpers, call the `\\\\{Plugin.CustomFunctionName}` helper.\",\r\n};<\/code><\/pre>\n<p>Please note that this option expects an asynchronous return type to accommodate handlers that need to fetch data. If your handler doesn&#8217;t require asynchronous functionality, you can simply wrap the result in a Task.FromResult, as demonstrated in the example above.<\/p>\n<ol start=\"3\">\n<li><strong> Using Predefined Arguments<\/strong><\/li>\n<\/ol>\n<p>If you want to run a Handlebars plan with a specific set of argument values, you can define an initial set of<code class=\"language-cs language-csharp\">`PlanCreationExceptions`<\/code>to pass into the Planner.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var initialArguments = new KernelArguments()\r\n{\r\n    { \"greetings\", new List&lt;string&gt;(){ \"hey\", \"bye\" } },\r\n    { \"someNumber\", 1 },\r\n    { \"person\", new Dictionary&lt;string, string&gt;()\r\n      {\r\n          {\"name\", \"John Doe\" },\r\n          { \"language\", \"Italian\" },\r\n      }\r\n    }\r\n };\r\n\r\nvar planner = new HandlebarsPlanner(plannerOptions);\r\n<\/code><\/pre>\n<p>It\u2019s important to note that if you use a set of predefined arguments when creating the plan, you must also pass in these arguments when invoking the plan.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var plan = await planner.CreatePlanAsync(kernel, goal, initialArguments);\r\n\r\nvar planResult = await plan.InvokeAsync(kernel, initialArguments);<\/code><\/pre>\n<p>You can run the plan with a different set of argument values if all the required parameters are present and their values are typed correctly.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var newArgs = new KernelArguments()\r\n{\r\n  { \"greetings\", new List&lt;string&gt;(){ \"hola\", \"adi\u00f3s\", \"hasta pronto\", \"hasta luego\" } },\r\n  { \"someNumber\", 19 },\r\n  { \"person\", new Dictionary&lt;string, string&gt;()\r\n    {\r\n      {\"name\", \"Jane Doe\" },\r\n      { \"language\", \"Spanish\" },\r\n    }\r\n  }\r\n};\r\n\r\nvar planResult = await plan.InvokeAsync(kernel, newArgs);\r\n<\/code><\/pre>\n<p><strong>Templatizing a HandlebarsPlan<\/strong><\/p>\n<p>If you want to save a Handlebars plan for later use or share it with others, you can templatize it by serializing it into a string.<\/p>\n<p>This way, you can load the plan into a new HandlebarsPlan object and run it at a later time. In this section, we&#8217;ll show you two options for templatizing a Handlebars plan and provide some examples of how to use them.<\/p>\n<p>Option 1: Serializing the entire Plan object<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var plan = await planner.CreatePlanAsync(kernel, goal, initialContext);\r\n\r\n\/\/ Serialize plan and save offline\r\n\r\nvar savedPlan = JsonSerializer.Serialize(plan);\r\n\r\n\/\/ Load plan and deserialize into a HandlebarsPlan object\r\n\r\nvar deserializedPlan = JsonSerializer.Deserialize&lt;HandlebarsPlan&gt;(savedPlan);\r\n\r\n\/\/ Remember to pass in arguments if you used any pre-defined variables when creating the plan.\r\n\r\nvar result = await deserializedPlan.InvokeAsync(kernel, initialContext);\r\n<\/code><\/pre>\n<p>Option 2: Saving only the plan template<\/p>\n<ul>\n<li>Option 1 is preferred as you retain all the creation states (i.e., CreatePlan prompt) of the plan. But if you don&#8217;t need that, this option will save on storage.<\/li>\n<\/ul>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">var plan = await planner.CreatePlanAsync(kernel, goal, initialContext);\r\n\r\n\/\/ You can also just save the plan template and load it into a plan object at a later time\r\n\r\n\/\/ Save this string somewhere offline\r\n\r\nvar planTemplate = plan.ToString();\r\n\r\n\/\/ Load plan and deserialize into a HandlebarsPlan object\r\n\r\nvar savedPlan = new HandlebarsPlan(planTemplate );\r\n\r\n\/\/ Be sure to include any necessary arguments if you used any pre-defined variables when creating the plan.\r\n\r\nvar result = await savedPlan.InvokeAsync(kernel, initialContext);<\/code><\/pre>\n<p>So there you have it, the Handlebars Planner! It&#8217;s a cool tool that lets you create customized plans using the Handlebars syntax and native features like loops and conditions to make plans more specific to your use case.<\/p>\n<p>Have fun exploring the Handlebars Planner and all its possibilities!<\/p>\n<p><strong>Dive Deeper<\/strong><\/p>\n<p>We recommend checking out this article for information on the Planners <a href=\"https:\/\/www.developerscantina.com\/p\/semantic-kernel-new-planners\/\">here<\/a>.<\/p>\n<p>We\u2019re always interested in hearing from you. If you have feedback, questions or want to discuss further, feel free to reach out to us and the community on the <a href=\"https:\/\/github.com\/microsoft\/semantic-kernel\/discussions\/categories\/general\" target=\"_blank\" rel=\"noopener\">Semantic Kernel GitHub Discussion Channel<\/a>! We would also love your support, if you\u2019ve enjoyed using Semantic Kernel, give us a star on\u00a0<a href=\"https:\/\/github.com\/microsoft\/semantic-kernel\" target=\"_blank\" rel=\"noopener\">GitHub<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We are excited to dive into the Handlebars Planner, a powerful tool for creating customized plans. This comprehensive guide will provide you with a thorough understanding on how to create your own plans using the Handlebars Planner and explain some of the planner options you can customize, such as providing additional information when creating the [&hellip;]<\/p>\n","protected":false},"author":149071,"featured_media":2365,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[46,1],"tags":[48,52,64,63,9],"class_list":["post-2441","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-learning","category-semantic-kernel","tag-ai","tag-handlebars","tag-handlebars-planner","tag-microsoft-semantic-kernel","tag-semantic-kernel"],"acf":[],"blog_post_summary":"<p>We are excited to dive into the Handlebars Planner, a powerful tool for creating customized plans. This comprehensive guide will provide you with a thorough understanding on how to create your own plans using the Handlebars Planner and explain some of the planner options you can customize, such as providing additional information when creating the [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/2441","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/users\/149071"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/comments?post=2441"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/2441\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media\/2365"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media?parent=2441"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/categories?post=2441"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/tags?post=2441"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}