LLTSharp 1.6.0
dotnet add package LLTSharp --version 1.6.0
NuGet\Install-Package LLTSharp -Version 1.6.0
<PackageReference Include="LLTSharp" Version="1.6.0" />
<PackageVersion Include="LLTSharp" Version="1.6.0" />
<PackageReference Include="LLTSharp" />
paket add LLTSharp --version 1.6.0
#r "nuget: LLTSharp, 1.6.0"
#:package LLTSharp@1.6.0
#addin nuget:?package=LLTSharp&version=1.6.0
#tool nuget:?package=LLTSharp&version=1.6.0
LLTSharp
A flexible and expressive template engine for Large Language Model (LLM) prompts and structured message generation in C#.
LLT is designed to make prompt engineering and content generation as powerful and maintainable as regular C# code.
✨ Key Features
- Razor-inspired DSL with
@if,@foreach, expressions, metadata, and inline variables - Full expression evaluator — arithmetic, logic, ternary, null-coalescing (
??), property checks (?:), safe navigation (?.), array/object literals, and method calls - Message-oriented syntax for LLM role structures (system, user, assistant, tool)
- Powerful metadata filtering — select the best template by language, model, or custom qualifiers
- Metadata fallback schemes — hierarchical language fallback (
en-US→en→ sibling → default) - Composable templates — reuse and render nested templates inside others, with optional context override (
@render 'name' with ctx) - Extensible function system — built-in functions plus custom ones registered from C#
- Deterministic formatting — indentation is normalized during parsing, inner spacing is preserved, output is predictable
- Library-driven workflow — import templates from assemblies, files, or strings
- Body parsing — parse template bodies without the
@templatewrapper viaParseTextTemplate/ParseMessagesTemplate
📦 Installation
Install the package from NuGet:
dotnet add package LLTSharp
Or via the Package Manager Console:
Install-Package LLTSharp
💡 Example
var parser = new LLTParser();
var templateStr = """
@template GreetingTemplate
{
Greetings, @name!
@if age > 18
{
You are an adult.
}
else
{
You are too young!
}
Have a nice day.
}
""";
var template = parser.Parse(templateStr).First();
var adult = new { name = "Andrew", age = 20 };
var young = new { name = "Alice", age = 15 };
Console.WriteLine(template.Render(adult));
Console.WriteLine(template.Render(young));
Output:
Greetings, Andrew!
You are an adult.
Have a nice day.
Greetings, Alice!
You are too young!
Have a nice day.
🧩 Template Library Usage
Multiple templates can be stored, versioned, and retrieved by language or model ID:
var lib = new TemplateLibrary();
lib.ImportFromString("""
@template greeting
{
@metadata { lang: 'en' }
Hello!
}
@template greeting
{
@metadata { lang: 'en', model: 'gpt-4' }
Hello GPT-4!
}
@template greeting
{
@metadata { lang: 'es' }
Hola!
}
""");
var template = lib.Retrieve("greeting", new LanguageMetadata("en"), new TargetModelMetadata("gpt-4"));
Console.WriteLine(template.Render()); // Hello GPT-4!
Templates are resolved by metadata specificity, similar to CSS selector priority. If no template matches exactly, use fallback retrieval:
// en-US → en → any sibling of the same language root → default language
lib.SetLanguageFallbackScheme(new HierarchicalLanguageFallbackScheme("en"));
var fallback = lib.RetrieveWithFallback("greeting", new LanguageMetadata("en-US"));
💬 Message Templates for LLM Chats
LLT supports special @messages syntax for structured chat prompts:
@messages template ChatBot
{
@metadata { language: 'en', version: 1 }
@system message {
You are a helpful assistant.
Here is your instructions:
@foreach instruction in instructions {
Instruction: @instruction
}
}
@foreach name in names {
@message {
@role 'user'
Hello, I am @name!
}
}
}
Rendered result: a sequence of chat messages with proper roles (system, user, assistant, tool).
Roles can be declared statically (@system message, @user message, @assistant message, @tool message)
or dynamically via @message { @role <expression> ... }.
🧠 Language Reference
Expressions
Literals
| Literal | Examples |
|---|---|
| Numbers | 42, 3.14, -7 |
| Strings | 'single', "double" |
| Booleans | true, false |
| Null | null |
| Arrays | [1, 2, 3], ['a', ctx?.value], [] (trailing commas allowed) |
| Objects | { name: 'Fish', qty: 5 }, { 'key with spaces': 1, [expr]: 2 } |
Access
| Syntax | Meaning |
|---|---|
ctx |
The root of the rendering context |
name |
Property of the root context (same as ctx.name) |
?name |
Safe property access — returns null instead of throwing when missing |
a.b / a.b.c |
Property access (chained) |
a?.b |
Safe navigation — null if a is null or lacks b |
a[expr] |
Index access (arrays, dictionaries, strings) |
a?[expr] |
Safe index access |
a.method(args) |
Method call (any public .NET method) |
a?.method(args) |
Safe method call |
func(args) |
Global template function (see Functions) |
Unary operators
| Operator | Meaning | Example |
|---|---|---|
- |
Negation | @(-x) |
! |
Logical NOT | @(!flag) |
# |
Length (string / array / dictionary) | @(#name), @if #items > 0 |
+ |
No-op (kept for symmetry) | @(+x) |
Binary operators (by precedence, high → low)
| Precedence | Operators | Meaning |
|---|---|---|
| 1 | * / % |
Multiplication, division, modulus |
| 2 | + - |
Addition, subtraction, string concatenation |
| 3 | < <= > >= |
Relational |
| 4 | ?: |
Has operator — checks whether the left operand has the right property |
| 5 | == != |
Equality |
| 6 | && |
Logical AND |
| 7 | \|\| |
Logical OR |
| 8 | ?? |
Null-coalescing — right operand when the left is null or missing |
| 9 | ? : |
Ternary conditional |
Null-coalescing, Has and Safe navigation
@template t {
1: @(?value ?? 'No value')
2: @(ctx ?: 'value' ? value ?? 'Null' : 'No value')
}
| Render context | Output |
|---|---|
new { value = "Hello" } |
1: Hello / 2: Hello |
new { value = (string?)null } |
1: No value / 2: Null — the property exists but is null |
new { } |
1: No value / 2: No value — the property is missing |
??treats missing andnullthe same way.?:(has) distinguishes them: it only checks existence, so it works great as the condition when you need different fallbacks fornullvs. missing:
@(user ?: 'name' ? user.name : 'Anonymous')
@(user?.name ?? 'Anonymous') @/ Safe navigation + coalescing
@-statements in plain text accept simple expressions (unary + member access).
Wrap binary expressions in parentheses: @(a + b) — inside @if, @while etc. full expressions are allowed.
Formatting
An expression can be followed by : and a format string:
Price: @price:'0.00'
Statements
| Statement | Syntax | Description |
|---|---|---|
| If | @if cond { ... } |
Conditional block |
| Else | else { ... }, else if cond { ... } or @else { ... } |
Optional @ before else is allowed |
| Foreach | @foreach item in items { ... } |
Iteration; loop variable is scoped to the block |
| While | @while cond { ... } |
Conditional loop |
| Let | @let x = expr |
Declares a new variable (lexically scoped) |
| Assign | @x = expr |
Assigns to an existing variable |
| Render | @render 'name', @render 'name' with expr |
Renders another template, optionally with a new context |
| Output | @expr, @(expr), @expr:'format' |
Prints an expression value |
| Comment | @/ line comment |
C#-style line comment — to the end of the line |
| Comment | @* block comment *@ |
Razor-style block comment |
Comments are skipped by the parser and can be placed wherever whitespace is allowed, including inside template bodies.
Escapes: @@ renders a literal @, {{ renders {, }} renders }.
Multi-line raw text can be wrapped in five backticks to avoid escaping:
@template code_sample
{
`````
@if isNotParsed { this is raw text, not a statement }
`````
}
Metadata
Attach metadata to a template with the @metadata block (only constant values are allowed):
@template greeting
{
@metadata { lang: 'en', model: 'gpt-4', version: 2 }
Hello!
}
Built-in metadata keys (parsed by the corresponding factories):
| Key | Metadata type |
|---|---|
lang |
LanguageMetadata |
model |
TargetModelMetadata |
model_family |
TargetModelFamilyMetadata |
version |
VersionMetadata |
Pass factories to Parse so the keys are recognized; unknown keys become AdditionalMetadata:
var parser = new LLTParser();
var templates = parser.Parse(src, new MetadataFactory[]
{
new LanguageMetadataFactory(),
new TargetModelMetadataFactory(),
new VersionMetadataFactory(),
});
Custom metadata
public class MyMetadata : IMetadata
{
public MyMetadata(string value) => Value = value;
public string Value { get; }
}
public class MyMetadataFactory : MetadataFactory
{
public override bool TryCreateMetadata(string key, TemplateDataAccessor value, out IMetadata metadata)
{
if (key == "my_key")
{
metadata = new MyMetadata(value.ToString());
return true;
}
metadata = null;
return false;
}
}
Fallback schemes
lib.SetLanguageFallbackScheme(new HierarchicalLanguageFallbackScheme("en"))— walks up the language hierarchy (en-US→en), then tries siblings, then the default language.lib.SetLanguageFallbackScheme(new MajorLanguageFallbackScheme())— falls back to the major language group.lib.SetFallbackScheme(typeof(LanguageMetadata), scheme)— register a customMetadataFallbackScheme<T>for any metadata type.
Retrieval API: Retrieve, TryRetrieve, TryRetrieveBest, RetrieveWithFallback, RetrieveAll —
each with optional identifier and metadata arguments.
🔧 Functions
Built-in functions (TemplateFunctions.All):
| Function | Description | Example |
|---|---|---|
type(x) |
Type name of the value (string, number, boolean, object, array, null, ...) |
@type(item) |
length(x) |
Length of a string, array or dictionary | @length(items) |
strcat(a, b, ...) |
Concatenates values into a string | @strcat('Hi, ', name) |
substr(s, start, len) |
Substring | @substr(name, 0, 3) |
Register custom functions and pass them to Render:
var functions = new TemplateFunctionSet(includeDefault: true,
new TemplateFunction("shout", (self, args) => args[0].ToString().ToUpperInvariant()));
var rendered = template.Render(new { name = "Andrew" }, functions);
Hello, @shout(name)! @/ → Hello, ANDREW!
🧾 Formatting & Scoping Rules
- Leading indentation is normalized during parsing: every line loses up to
depth × TabSizeleading whitespace, wheredepthis the block nesting level. You can write templates with comfortable indentation — the common base indent is stripped and the output stays clean - Inner whitespace, line breaks and blank lines are preserved as written
- Leading/trailing blank lines of blocks are trimmed; indentation before complex statements
(
@if,@foreach,@while) is removed - Lines containing only non-rendering constructs —
@/and@* *@comments,@letdeclarations, variable assignments — are removed entirely: the surrounding line breaks are trimmed so they leave no blank lines in the output TabSize(default4) onLLTParsercontrols how many columns one indent level takes during refinement@letvariables are lexically scoped- Loop variables do not leak outside their block
- Nested
@ifand@foreachblocks behave predictably, matching C#‑like logical semantics
🏗️ Architecture Overview
- LLTParser – parses source text into ASTs (
TemplateNode,TemplateExpressionNode, etc.); supports full-file parsing (Parse) and body-only parsing (ParseTextTemplate,ParseMessagesTemplate) - Template – runtime object capable of rendering with dynamic context (
TextTemplate,MessagesTemplate) - TemplateLibrary – registry and loader for collections of templates with filtering and fallbacks
- Metadata System – extensible mechanism for attaching attributes such as language, model, or custom version
- Data Accessors – reflection / dictionary / array wrappers (
DataAccessorFactory) withPropertiesToLowerCase,KeysToLowerCaseandSnapshotoptions - Function System –
TemplateFunctionSet/TemplateFunctionfor extending template logic - ChatMessage Model – unified representation for structured message templates (
system,user,assistant,tool)
🔖 License
MIT License © 2025
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.