👉 Try the demo: https://github.com/LM-Kit/lm-kit-net-samples/tree/main/console_net/prompt_templates
Prompt Templates with Logic for C# .NET Applications
🎯 Purpose of the Demo
The Prompt Templates demo shows how to use LM-Kit.NET's PromptTemplate engine to build dynamic, reusable prompts with variable substitution, conditionals, loops, filters, scoping, and custom helpers. It demonstrates a parse-once, render-many pattern that separates prompt structure from runtime data.
👥 Who Should Use This Demo
- Application developers who build prompts from configuration, user input, or database records and need a clean separation between template structure and runtime values.
- Agent framework builders who configure system prompts dynamically based on available tools, user roles, or domain context.
- Platform teams who maintain prompt libraries and want versioned, testable templates instead of string concatenation.
- Localization engineers who swap languages, personas, or constraints at runtime without rewriting prompt logic.
🚀 What Problem It Solves
Hard-coded string concatenation for prompts is fragile, hard to test, and mixes logic with content. PromptTemplate solves this by providing a lightweight template language with:
- Variables for injecting runtime values
- Conditionals (
if/else/unless) for toggling prompt sections - Loops (
each) for listing tools, constraints, or examples - Filters (
trim,upper,truncate,json, etc.) for transforming values inline - Scoping (
with) for cleanly accessing nested object properties - Custom helpers for injecting computed values like dates or formatted strings
Templates compile to an AST once and render many times with different contexts, making them efficient for high-throughput systems.
💻 Demo Application Overview
The demo is a console application with two parts:
- Template Feature Showcase (no model required): walks through each template construct, printing the template source and rendered output side by side.
- Dynamic Chat: asks the user to configure an assistant (domain, language, verbosity) and builds a system prompt from a template at runtime. The assistant then runs as a live multi-turn chat.
✨ Key Features
- 9 interactive examples covering variables, filters, defaults, conditionals, loops, scoping, custom helpers, variable introspection, and alternative syntaxes
- Real-world agent prompt combining all constructs into a single production-quality system prompt
- Live multi-turn chat where the system prompt is generated dynamically from user input via a template
- Three syntax styles: Mustache (
{{var}}), Dollar (${var}), and Percent (%var%) - Filter chaining:
{{name|trim|upper|truncate:20}} - Inline defaults:
{{language:English}}
🏗️ Architecture
┌───────────────────────────────────────────────────────┐
│ Prompt Template Pipeline │
├───────────────────────────────────────────────────────┤
│ │
│ Template String ──► Parse() ──► Compiled AST │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ Render(ctx1) Render(ctx2) Render │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ "prompt A" "prompt B" "prompt C"│
│ │
│ Parse once, render many ─ efficient for production │
└───────────────────────────────────────────────────────┘
⚙️ Getting Started
📋 Prerequisites
- .NET 8.0 or later
- LM-Kit.NET SDK
- 4 to 18 GB VRAM (depending on model choice, only needed for Part 2)
📥 Download the Project
▶️ Running the Application
cd console_net/prompt_templates
dotnet run
The template showcase runs immediately (no model needed). When you reach Part 2, select a model to start the live chat.
💡 Example Usage
Basic Variable Substitution
var template = PromptTemplate.Parse("You are {{role}}. Help with {{topic}}.");
string prompt = template.Render(new PromptTemplateContext
{
["role"] = "a senior C# developer",
["topic"] = "async programming"
});
// "You are a senior C# developer. Help with async programming."
Conditionals and Loops for Agent Prompts
var template = PromptTemplate.Parse(@"You are {{persona}}.
{{#if tools}}Available tools:
{{#each tools}}- {{name}}: {{description}}
{{/each}}{{/if}}
Always respond in {{language:English}}.");
string prompt = template.Render(new PromptTemplateContext
{
["persona"] = "Aria",
["tools"] = new[]
{
new { name = "web_search", description = "Search the internet" },
new { name = "calculator", description = "Math operations" }
}
});
Filters and Defaults
var template = PromptTemplate.Parse(
"Welcome, {{name|trim|capitalize}}! Role: {{role:user}}."
);
string result = template.Render(new PromptTemplateContext
{
["name"] = " alice "
});
// "Welcome, Alice! Role: user."
🔧 Troubleshooting
| Issue | Solution |
|---|---|
PromptTemplateException on parse |
Check for unclosed {{ tags or mismatched block constructs ({{#if}} without {{/if}}) |
| Missing variable renders blank | This is default behavior. Use StrictVariables = true to throw instead, or provide inline defaults with {{var:fallback}} |
| Filters not applying | Verify pipe syntax with no spaces: {{name\|upper}} not {{name \| upper}} |
🚀 Extend the Demo
- Register custom global filters via
PromptTemplateFilters.Register()for domain-specific transformations - Use
TryParsefor user-supplied templates that may contain syntax errors - Combine with
AgentBuilderto build fully dynamic agent configurations - Use variable introspection (
template.Variables) to auto-generate UI forms for template parameters
📚 What to Read Next
- Build Dynamic Prompts with Templates: step-by-step how-to guide for prompt templates
- Prompt Engineering: core prompting techniques
- Prompt Templates: glossary entry explaining the concept
- Create an AI Agent with Tools: use templates to configure agent instructions dynamically