Semantic Kernel Prompt Created: 23 Jan 2026 Updated: 23 Jan 2026

Building Support Agents with Handlebars Templates

In the previous sections, we explored standard templates and the Liquid engine. However, when building high-performance, structured AI applications within the Semantic Kernel ecosystem, the Handlebars Prompt Template Factory is a standout choice. Handlebars offers a perfect balance: it is more powerful than basic templates but often more intuitive for developers coming from web development backgrounds.

Using the Microsoft.SemanticKernel.PromptTemplates.Handlebars namespace, you can transform a static prompt into a living, data-driven conversation engine.

Why Use Handlebars for Prompts?

Handlebars is widely loved for its "logic-less" philosophy, which actually makes prompts safer and cleaner. Its primary advantages include:

  1. Clean List Rendering: Use {{#each}} blocks to iterate over collections without complex C# string building.
  2. Path-Based Access: Easily reach into nested objects (e.g., {{user.Details.Address}}).
  3. Structural Clarity: It encourages moving logic (like calculating discounts) into your C# code, leaving the template to handle the presentation of that data to the AI.

Practical Implementation: TechMart E-Commerce Support

Let’s look at a complete, real-world implementation of a TechMart Support Chat. This assistant uses Handlebars to render a system prompt that includes customer profile data, tiered instructions, and a full order history.

Listing 4.10: Full Implementation of a Handlebars-Powered Support Bot

// See https://aka.ms/new-console-template for more information

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;

var apiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY");

if (string.IsNullOrEmpty(apiKey))
{
Console.WriteLine("Please set the OPEN_AI_KEY environment variable.");
return;
}

var kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
"gpt-4o",
apiKey)
.Build();

// Fix console encoding for emojis
Console.OutputEncoding = System.Text.Encoding.UTF8;

Console.WriteLine("=== πŸ›οΈ TechMart E-Commerce Support Chat ===\n");
Console.WriteLine("Welcome to TechMart Customer Support!");
Console.WriteLine("I'm your AI assistant, ready to help with your orders and questions.\n");

// 1. Customer Profile Setup
Console.Write("Please enter your first name: ");
var firstName = Console.ReadLine() ?? "Guest";

Console.Write("Select your membership tier (Gold/Silver/Standard): ");
var membershipInput = Console.ReadLine() ?? "Standard";

// Normalize membership to proper case
var membership = membershipInput.ToLower() switch
{
"gold" => "Gold",
"silver" => "Silver",
_ => "Standard"
};

Console.WriteLine($"\n✨ Welcome {firstName}! Your membership tier: {membership}");
Console.WriteLine("Type 'exit' to end the conversation.\n");
Console.WriteLine(new string('=', 80) + "\n");

// 2. Define customer data and prepare tier-specific text in C# (Simpler approach)
var totalSpent = membership == "Gold" ? 1250.50 : membership == "Silver" ? 450.00 : 125.00;
var currentDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm");

// Prepare tier instructions in C# to avoid complex Handlebars helpers
var tierInstructions = membership switch
{
"Gold" => "🌟 VIP GOLD MEMBER - Priority: HIGHEST\n- Offer 10% discount code: GOLD10\n- Free express shipping on all orders\n- 24/7 priority support available\n- Tone: Extremely polite and grateful for loyalty",
"Silver" => "✨ VALUED SILVER MEMBER - Priority: HIGH\n- Offer 5% shipping discount: SILVER5\n- Free shipping on orders over $50\n- Priority email support (2 hour reply)\n- Tone: Warm and appreciative",
_ => "πŸ“¦ STANDARD MEMBER - Priority: NORMAL\n- Welcome discount available\n- Standard shipping rates apply\n- Tone: Friendly and helpful"
};

var specialAlerts = "";
if (totalSpent > 1000)
{
specialAlerts += "🎁 Customer spent over $1,000 - Offer loyalty rewards!\n";
}

var customer = new Dictionary<string, object>
{
["FirstName"] = firstName,
["LastName"] = "Customer",
["Email"] = $"{firstName.ToLower()}@email.com",
["Membership"] = membership,
["IsAccountActive"] = true,
["TotalSpent"] = totalSpent,
["JoinedDate"] = "2023-05-15",
["TierInstructions"] = tierInstructions
};

var orders = new List<Dictionary<string, object>>
{
new()
{
["Id"] = "ORD-101", ["Item"] = "Wireless Headphones", ["Status"] = "Delivered", ["Price"] = 120.00,
["DeliveryDate"] = "2026-01-15"
},
new()
{
["Id"] = "ORD-205", ["Item"] = "Mechanical Keyboard", ["Status"] = "In Transit", ["Price"] = 85.50,
["DeliveryDate"] = "2026-01-25"
},
new()
{
["Id"] = "ORD-309", ["Item"] = "USB-C Cable", ["Status"] = "Cancelled", ["Price"] = 15.00,
["DeliveryDate"] = "N/A"
}
};

if (orders.Count > 2)
{
specialAlerts += "πŸ’Ž Frequent buyer - Show extra appreciation!";
}

// 3. Define the Handlebars System Prompt Template (Simplified syntax for Semantic Kernel)
var systemPromptTemplate = """
<message role="system">
You are a professional customer support assistant for TechMart E-Commerce.
Current Date: {{current_date}}
Support Hours: {{support_hours}}

# Customer Profile
- Name: {{user.FirstName}} {{user.LastName}}
- Email: {{user.Email}}
- Membership Tier: {{user.Membership}}
- Account Status: {{#if user.IsAccountActive}}Active{{else}}Inactive{{/if}}
- Total Spent: ${{user.TotalSpent}}
- Member Since: {{user.JoinedDate}}

# Tier Instructions
{{user.TierInstructions}}

# Order History ({{order_history.Count}} orders)
{{#each order_history}}
- Order {{Id}}: {{Item}} - {{Status}} (${{Price}})
{{/each}}

# Special Alerts
{{special_alerts}}

Instructions: Be concise, professional, and tier-appropriate. Help with orders, shipping, returns, and product questions.
</message>
""";

// 4. Configure Handlebars Prompt (Following reference code pattern)
var promptConfig = new PromptTemplateConfig
{
Name = "SupportSystemPrompt",
Template = systemPromptTemplate,
TemplateFormat = "handlebars", // Changed to handlebars
Description = "Handlebars prompt template for customer support with dynamic context",
InputVariables =
[
new() { Name = "user", AllowDangerouslySetContent = true },
new() { Name = "order_history", AllowDangerouslySetContent = true },
new() { Name = "support_hours" },
new() { Name = "current_date" }
]
};

// 5. Initialize Handlebars Factory (Following reference pattern)
var handlebarsFactory = new HandlebarsPromptTemplateFactory();

// 6. Create function from prompt config (Reference code approach)
var promptFunction = kernel.CreateFunctionFromPrompt(promptConfig, handlebarsFactory);

// 7. Prepare kernel arguments
var kernelArguments = new KernelArguments
{
{ "user", customer },
{ "order_history", orders },
{ "support_hours", "9 AM - 5 PM EST" },
{ "current_date", currentDate },
{ "special_alerts", specialAlerts }
};

// 8. Render and display system prompt (Using Handlebars template)
var template = handlebarsFactory.Create(promptConfig);
var systemPrompt = await template.RenderAsync(kernel, kernelArguments);

// Display the rendered system prompt
Console.WriteLine("=== πŸ“‹ RENDERED HANDLEBARS PROMPT (Sent to AI) ===\n");
Console.WriteLine(systemPrompt);
Console.WriteLine("\n" + new string('=', 80) + "\n");

// 8. Initialize Chat History with rendered system prompt
var chat = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory();
history.AddSystemMessage(systemPrompt);

// Add initial greeting
history.AddAssistantMessage($"Hello {firstName}! πŸ‘‹ I'm your TechMart support assistant. How can I help you today?");
Console.WriteLine($"Assistant: Hello {firstName}! πŸ‘‹ I'm your TechMart support assistant. How can I help you today?\n");

// 9. Configure execution settings
var executionSettings = new OpenAIPromptExecutionSettings
{
MaxTokens = 500,
Temperature = 0.7
};

// 10. Main conversation loop
while (true)
{
Console.Write($"{firstName} >>> ");
var userInput = Console.ReadLine();

if (string.IsNullOrWhiteSpace(userInput))
{
continue;
}

if (userInput.ToLower() == "exit")
{
Console.WriteLine("\nAssistant: Thank you for contacting TechMart! Have a great day! πŸ‘‹");
break;
}

// Add user message to history
history.AddUserMessage(userInput);

// Get AI response with streaming
Console.Write("Assistant: ");
string fullMessage = string.Empty;

await foreach (var token in chat.GetStreamingChatMessageContentsAsync(history, executionSettings, kernel))
{
Console.Write(token.Content);
fullMessage += token.Content;
}

Console.WriteLine("\n");

// Add assistant response to history
history.AddAssistantMessage(fullMessage);
}

// 11. Display conversation summary
Console.WriteLine("\n" + new string('=', 80));
Console.WriteLine("=== πŸ“Š Conversation Summary ===\n");
Console.WriteLine($"Total messages: {history.Count}");
Console.WriteLine($"Customer: {customer["FirstName"]} ({customer["Membership"]} Member)");
Console.WriteLine($"Total spent: ${customer["TotalSpent"]}");
Console.WriteLine("\nThank you for using TechMart Support! πŸ›οΈ");

Key Concepts in the Handlebars Factory

1. The Block Helpers (#if and #each)

The power of Handlebars lies in its blocks.

  1. {{#if ...}}: Allows the prompt to conditionally show text (like "Active" vs "Inactive") based on boolean data.
  2. {{#each ...}}: This is the cleaner way to list items. Within the loop, you can access properties directly (e.g., {{Item}}) because the context shifts to the current item in the list.

2. Rendering vs. Invoking

In step 4 of our example, we use template.RenderAsync. This is a crucial debugging step. It allows you to print the Final Rendered Prompt to your console. This visibility ensures that your loops and if-statements are producing the exact text you intend to send to the AI model.

3. Separation of Concerns

Note how the TierInstructions were prepared in C# logic before being passed to the template. This is a "best practice" when using Handlebars: do your heavy calculations and business logic in your C# code, and use the template simply to organize and display that information to the LLM.

Share this lesson: