Introduction: As LLM applications grow in complexity, managing prompts becomes a significant engineering challenge. Hard-coded prompts scattered across your codebase make iteration difficult, A/B testing impossible, and debugging a nightmare. Prompt template management solves this by treating prompts as first-class configuration—versioned, validated, and dynamically rendered. A good template system separates prompt logic from application code, enables safe variable injection, supports composition of complex prompts from reusable components, and provides tooling for testing and iteration. This guide covers practical approaches to prompt template management: from simple string formatting to sophisticated template engines, version control strategies, validation pipelines, and production deployment patterns that make your LLM applications maintainable and evolvable.

Basic Template System
from dataclasses import dataclass, field
from typing import Any, Optional, List, Dict, Callable
from enum import Enum
import re
import json
class TemplateFormat(Enum):
"""Supported template formats."""
FSTRING = "fstring"
JINJA2 = "jinja2"
MUSTACHE = "mustache"
@dataclass
class PromptTemplate:
"""A prompt template with metadata."""
name: str
template: str
format: TemplateFormat = TemplateFormat.FSTRING
variables: list[str] = field(default_factory=list)
description: str = ""
version: str = "1.0.0"
metadata: dict = field(default_factory=dict)
def __post_init__(self):
if not self.variables:
self.variables = self._extract_variables()
def _extract_variables(self) -> list[str]:
"""Extract variable names from template."""
if self.format == TemplateFormat.FSTRING:
return re.findall(r'\{(\w+)\}', self.template)
elif self.format == TemplateFormat.JINJA2:
return re.findall(r'\{\{\s*(\w+)\s*\}\}', self.template)
elif self.format == TemplateFormat.MUSTACHE:
return re.findall(r'\{\{\s*(\w+)\s*\}\}', self.template)
return []
class TemplateRenderer:
"""Render prompt templates."""
def render(
self,
template: PromptTemplate,
variables: dict
) -> str:
"""Render template with variables."""
if template.format == TemplateFormat.FSTRING:
return self._render_fstring(template.template, variables)
elif template.format == TemplateFormat.JINJA2:
return self._render_jinja2(template.template, variables)
elif template.format == TemplateFormat.MUSTACHE:
return self._render_mustache(template.template, variables)
raise ValueError(f"Unknown format: {template.format}")
def _render_fstring(self, template: str, variables: dict) -> str:
"""Render f-string style template."""
return template.format(**variables)
def _render_jinja2(self, template: str, variables: dict) -> str:
"""Render Jinja2 template."""
from jinja2 import Template
jinja_template = Template(template)
return jinja_template.render(**variables)
def _render_mustache(self, template: str, variables: dict) -> str:
"""Render Mustache template."""
import chevron
return chevron.render(template, variables)
class TemplateValidator:
"""Validate templates and variables."""
def validate_template(self, template: PromptTemplate) -> tuple[bool, list[str]]:
"""Validate template syntax."""
errors = []
# Check for balanced braces
if not self._check_balanced_braces(template.template):
errors.append("Unbalanced braces in template")
# Check variable extraction
try:
variables = template._extract_variables()
except Exception as e:
errors.append(f"Variable extraction failed: {e}")
# Try rendering with dummy values
try:
dummy_vars = {v: f"<{v}>" for v in template.variables}
renderer = TemplateRenderer()
renderer.render(template, dummy_vars)
except Exception as e:
errors.append(f"Template rendering failed: {e}")
return len(errors) == 0, errors
def validate_variables(
self,
template: PromptTemplate,
variables: dict
) -> tuple[bool, list[str]]:
"""Validate variables against template."""
errors = []
# Check for missing variables
for var in template.variables:
if var not in variables:
errors.append(f"Missing variable: {var}")
# Check for extra variables
for var in variables:
if var not in template.variables:
errors.append(f"Unexpected variable: {var}")
return len(errors) == 0, errors
def _check_balanced_braces(self, text: str) -> bool:
"""Check for balanced braces."""
count = 0
for char in text:
if char == '{':
count += 1
elif char == '}':
count -= 1
if count < 0:
return False
return count == 0
class SimpleTemplateEngine:
"""Simple template engine for prompts."""
def __init__(self):
self.templates: dict[str, PromptTemplate] = {}
self.renderer = TemplateRenderer()
self.validator = TemplateValidator()
def register(self, template: PromptTemplate):
"""Register a template."""
valid, errors = self.validator.validate_template(template)
if not valid:
raise ValueError(f"Invalid template: {errors}")
self.templates[template.name] = template
def render(self, name: str, variables: dict) -> str:
"""Render a registered template."""
if name not in self.templates:
raise KeyError(f"Template not found: {name}")
template = self.templates[name]
# Validate variables
valid, errors = self.validator.validate_variables(template, variables)
if not valid:
raise ValueError(f"Invalid variables: {errors}")
return self.renderer.render(template, variables)
def get_template(self, name: str) -> PromptTemplate:
"""Get a template by name."""
return self.templates.get(name)
def list_templates(self) -> list[str]:
"""List all registered templates."""
return list(self.templates.keys())
Advanced Template Features
from dataclasses import dataclass, field
from typing import Any, Optional, List, Callable
import json
@dataclass
class ConditionalBlock:
"""A conditional block in a template."""
condition: str
content: str
else_content: str = ""
@dataclass
class LoopBlock:
"""A loop block in a template."""
variable: str
item_name: str
content: str
class AdvancedTemplateEngine:
"""Advanced template engine with conditionals and loops."""
def __init__(self):
self.templates: dict[str, str] = {}
self.partials: dict[str, str] = {}
self.filters: dict[str, Callable] = {}
self._register_default_filters()
def _register_default_filters(self):
"""Register default filters."""
self.filters["upper"] = str.upper
self.filters["lower"] = str.lower
self.filters["title"] = str.title
self.filters["strip"] = str.strip
self.filters["json"] = json.dumps
self.filters["length"] = len
self.filters["first"] = lambda x: x[0] if x else ""
self.filters["last"] = lambda x: x[-1] if x else ""
self.filters["join"] = lambda x, sep=", ": sep.join(str(i) for i in x)
def register_filter(self, name: str, func: Callable):
"""Register a custom filter."""
self.filters[name] = func
def register_partial(self, name: str, template: str):
"""Register a partial template."""
self.partials[name] = template
def render(self, template: str, context: dict) -> str:
"""Render template with context."""
# Process includes/partials
template = self._process_partials(template)
# Process conditionals
template = self._process_conditionals(template, context)
# Process loops
template = self._process_loops(template, context)
# Process filters
template = self._process_filters(template, context)
# Process variables
template = self._process_variables(template, context)
return template
def _process_partials(self, template: str) -> str:
"""Process partial includes."""
import re
pattern = r'\{\%\s*include\s+"(\w+)"\s*\%\}'
def replace(match):
name = match.group(1)
return self.partials.get(name, "")
return re.sub(pattern, replace, template)
def _process_conditionals(self, template: str, context: dict) -> str:
"""Process conditional blocks."""
import re
# Pattern for if/else/endif
pattern = r'\{\%\s*if\s+(\w+)\s*\%\}([\s\S]*?)(?:\{\%\s*else\s*\%\}([\s\S]*?))?\{\%\s*endif\s*\%\}'
def replace(match):
var = match.group(1)
if_content = match.group(2)
else_content = match.group(3) or ""
if context.get(var):
return if_content
else:
return else_content
return re.sub(pattern, replace, template)
def _process_loops(self, template: str, context: dict) -> str:
"""Process loop blocks."""
import re
# Pattern for for loops
pattern = r'\{\%\s*for\s+(\w+)\s+in\s+(\w+)\s*\%\}([\s\S]*?)\{\%\s*endfor\s*\%\}'
def replace(match):
item_name = match.group(1)
list_name = match.group(2)
content = match.group(3)
items = context.get(list_name, [])
result = []
for i, item in enumerate(items):
item_context = {
item_name: item,
"loop": {
"index": i,
"first": i == 0,
"last": i == len(items) - 1
}
}
rendered = self._process_variables(content, {**context, **item_context})
result.append(rendered)
return "".join(result)
return re.sub(pattern, replace, template)
def _process_filters(self, template: str, context: dict) -> str:
"""Process filter expressions."""
import re
# Pattern for variable|filter
pattern = r'\{\{\s*(\w+)\s*\|\s*(\w+)(?:\(([^)]*)\))?\s*\}\}'
def replace(match):
var = match.group(1)
filter_name = match.group(2)
args = match.group(3)
value = context.get(var, "")
filter_func = self.filters.get(filter_name)
if filter_func:
if args:
return str(filter_func(value, args))
return str(filter_func(value))
return str(value)
return re.sub(pattern, replace, template)
def _process_variables(self, template: str, context: dict) -> str:
"""Process simple variable substitution."""
import re
# Pattern for {{ variable }}
pattern = r'\{\{\s*(\w+(?:\.\w+)*)\s*\}\}'
def replace(match):
var_path = match.group(1)
value = self._get_nested(context, var_path)
return str(value) if value is not None else ""
return re.sub(pattern, replace, template)
def _get_nested(self, obj: dict, path: str) -> Any:
"""Get nested value from dict."""
parts = path.split(".")
current = obj
for part in parts:
if isinstance(current, dict):
current = current.get(part)
else:
return None
return current
class ComposableTemplate:
"""Template that can be composed from parts."""
def __init__(self, name: str):
self.name = name
self.parts: list[tuple[str, str]] = [] # (type, content)
def add_system(self, content: str) -> 'ComposableTemplate':
"""Add system message part."""
self.parts.append(("system", content))
return self
def add_context(self, content: str) -> 'ComposableTemplate':
"""Add context part."""
self.parts.append(("context", content))
return self
def add_examples(self, content: str) -> 'ComposableTemplate':
"""Add examples part."""
self.parts.append(("examples", content))
return self
def add_instruction(self, content: str) -> 'ComposableTemplate':
"""Add instruction part."""
self.parts.append(("instruction", content))
return self
def add_input(self, content: str) -> 'ComposableTemplate':
"""Add input part."""
self.parts.append(("input", content))
return self
def add_output_format(self, content: str) -> 'ComposableTemplate':
"""Add output format part."""
self.parts.append(("output_format", content))
return self
def build(self) -> str:
"""Build the complete template."""
sections = []
for part_type, content in self.parts:
if part_type == "system":
sections.append(content)
elif part_type == "context":
sections.append(f"Context:\n{content}")
elif part_type == "examples":
sections.append(f"Examples:\n{content}")
elif part_type == "instruction":
sections.append(f"Instructions:\n{content}")
elif part_type == "input":
sections.append(f"Input:\n{content}")
elif part_type == "output_format":
sections.append(f"Output Format:\n{content}")
return "\n\n".join(sections)
def to_messages(self, variables: dict) -> list[dict]:
"""Convert to chat messages format."""
messages = []
user_parts = []
for part_type, content in self.parts:
# Render variables
rendered = content.format(**variables) if variables else content
if part_type == "system":
messages.append({"role": "system", "content": rendered})
else:
user_parts.append(rendered)
if user_parts:
messages.append({"role": "user", "content": "\n\n".join(user_parts)})
return messages
Template Storage and Versioning
from dataclasses import dataclass, field
from typing import Any, Optional, List, Dict
from datetime import datetime
import json
import hashlib
@dataclass
class TemplateVersion:
"""A versioned template."""
template: PromptTemplate
version: str
created_at: datetime
created_by: str
changelog: str = ""
hash: str = ""
def __post_init__(self):
if not self.hash:
self.hash = self._compute_hash()
def _compute_hash(self) -> str:
"""Compute hash of template content."""
content = self.template.template.encode()
return hashlib.sha256(content).hexdigest()[:12]
class TemplateStore:
"""Store and manage templates."""
def __init__(self, storage_path: str = None):
self.storage_path = storage_path
self.templates: dict[str, list[TemplateVersion]] = {}
def save(
self,
template: PromptTemplate,
created_by: str = "system",
changelog: str = ""
) -> TemplateVersion:
"""Save a new version of a template."""
# Get current versions
versions = self.templates.get(template.name, [])
# Determine new version
if versions:
last_version = versions[-1].version
new_version = self._increment_version(last_version)
else:
new_version = "1.0.0"
# Create version
version = TemplateVersion(
template=template,
version=new_version,
created_at=datetime.now(),
created_by=created_by,
changelog=changelog
)
# Store
if template.name not in self.templates:
self.templates[template.name] = []
self.templates[template.name].append(version)
# Persist if storage path set
if self.storage_path:
self._persist()
return version
def get(
self,
name: str,
version: str = None
) -> Optional[PromptTemplate]:
"""Get a template by name and optional version."""
versions = self.templates.get(name, [])
if not versions:
return None
if version:
for v in versions:
if v.version == version:
return v.template
return None
# Return latest
return versions[-1].template
def get_version(
self,
name: str,
version: str
) -> Optional[TemplateVersion]:
"""Get a specific version."""
versions = self.templates.get(name, [])
for v in versions:
if v.version == version:
return v
return None
def list_versions(self, name: str) -> list[TemplateVersion]:
"""List all versions of a template."""
return self.templates.get(name, [])
def rollback(self, name: str, version: str) -> Optional[TemplateVersion]:
"""Rollback to a previous version."""
target = self.get_version(name, version)
if not target:
return None
# Create new version with old content
return self.save(
target.template,
created_by="rollback",
changelog=f"Rollback to version {version}"
)
def _increment_version(self, version: str) -> str:
"""Increment version number."""
parts = version.split(".")
parts[-1] = str(int(parts[-1]) + 1)
return ".".join(parts)
def _persist(self):
"""Persist templates to storage."""
data = {}
for name, versions in self.templates.items():
data[name] = [
{
"template": {
"name": v.template.name,
"template": v.template.template,
"format": v.template.format.value,
"variables": v.template.variables,
"description": v.template.description
},
"version": v.version,
"created_at": v.created_at.isoformat(),
"created_by": v.created_by,
"changelog": v.changelog,
"hash": v.hash
}
for v in versions
]
with open(self.storage_path, 'w') as f:
json.dump(data, f, indent=2)
def load(self):
"""Load templates from storage."""
if not self.storage_path:
return
try:
with open(self.storage_path, 'r') as f:
data = json.load(f)
except FileNotFoundError:
return
for name, versions in data.items():
self.templates[name] = [
TemplateVersion(
template=PromptTemplate(
name=v["template"]["name"],
template=v["template"]["template"],
format=TemplateFormat(v["template"]["format"]),
variables=v["template"]["variables"],
description=v["template"]["description"]
),
version=v["version"],
created_at=datetime.fromisoformat(v["created_at"]),
created_by=v["created_by"],
changelog=v["changelog"],
hash=v["hash"]
)
for v in versions
]
class GitTemplateStore:
"""Store templates in Git repository."""
def __init__(self, repo_path: str):
self.repo_path = repo_path
self.templates_dir = f"{repo_path}/prompts"
def save(self, template: PromptTemplate, message: str = ""):
"""Save template to Git."""
import os
import subprocess
# Ensure directory exists
os.makedirs(self.templates_dir, exist_ok=True)
# Write template file
file_path = f"{self.templates_dir}/{template.name}.json"
data = {
"name": template.name,
"template": template.template,
"format": template.format.value,
"variables": template.variables,
"description": template.description,
"version": template.version,
"metadata": template.metadata
}
with open(file_path, 'w') as f:
json.dump(data, f, indent=2)
# Git add and commit
subprocess.run(["git", "add", file_path], cwd=self.repo_path)
commit_message = message or f"Update template: {template.name}"
subprocess.run(["git", "commit", "-m", commit_message], cwd=self.repo_path)
def get(self, name: str, ref: str = "HEAD") -> Optional[PromptTemplate]:
"""Get template at specific Git ref."""
import subprocess
file_path = f"prompts/{name}.json"
try:
result = subprocess.run(
["git", "show", f"{ref}:{file_path}"],
cwd=self.repo_path,
capture_output=True,
text=True
)
if result.returncode != 0:
return None
data = json.loads(result.stdout)
return PromptTemplate(
name=data["name"],
template=data["template"],
format=TemplateFormat(data["format"]),
variables=data["variables"],
description=data.get("description", ""),
version=data.get("version", "1.0.0"),
metadata=data.get("metadata", {})
)
except Exception:
return None
def get_history(self, name: str) -> list[dict]:
"""Get commit history for template."""
import subprocess
file_path = f"prompts/{name}.json"
result = subprocess.run(
["git", "log", "--pretty=format:%H|%an|%ad|%s", "--", file_path],
cwd=self.repo_path,
capture_output=True,
text=True
)
history = []
for line in result.stdout.split("\n"):
if line:
parts = line.split("|")
history.append({
"commit": parts[0],
"author": parts[1],
"date": parts[2],
"message": parts[3]
})
return history
Template Testing and Validation
from dataclasses import dataclass
from typing import Any, Optional, List, Callable
import re
@dataclass
class TestCase:
"""A test case for a template."""
name: str
variables: dict
expected_contains: list[str] = field(default_factory=list)
expected_not_contains: list[str] = field(default_factory=list)
expected_length_min: int = 0
expected_length_max: int = 0
@dataclass
class TestResult:
"""Result of a template test."""
test_name: str
passed: bool
rendered: str
errors: list[str] = field(default_factory=list)
class TemplateTestRunner:
"""Run tests on templates."""
def __init__(self, engine: SimpleTemplateEngine):
self.engine = engine
def run_test(
self,
template_name: str,
test_case: TestCase
) -> TestResult:
"""Run a single test case."""
errors = []
try:
rendered = self.engine.render(template_name, test_case.variables)
except Exception as e:
return TestResult(
test_name=test_case.name,
passed=False,
rendered="",
errors=[f"Render failed: {e}"]
)
# Check expected contains
for expected in test_case.expected_contains:
if expected not in rendered:
errors.append(f"Expected to contain: {expected}")
# Check expected not contains
for not_expected in test_case.expected_not_contains:
if not_expected in rendered:
errors.append(f"Expected NOT to contain: {not_expected}")
# Check length
if test_case.expected_length_min > 0:
if len(rendered) < test_case.expected_length_min:
errors.append(f"Length {len(rendered)} < min {test_case.expected_length_min}")
if test_case.expected_length_max > 0:
if len(rendered) > test_case.expected_length_max:
errors.append(f"Length {len(rendered)} > max {test_case.expected_length_max}")
return TestResult(
test_name=test_case.name,
passed=len(errors) == 0,
rendered=rendered,
errors=errors
)
def run_suite(
self,
template_name: str,
test_cases: list[TestCase]
) -> list[TestResult]:
"""Run a suite of tests."""
return [
self.run_test(template_name, tc)
for tc in test_cases
]
class PromptLinter:
"""Lint prompts for common issues."""
def __init__(self):
self.rules: list[Callable] = []
self._register_default_rules()
def _register_default_rules(self):
"""Register default linting rules."""
self.rules.append(self._check_length)
self.rules.append(self._check_clarity)
self.rules.append(self._check_injection_risk)
self.rules.append(self._check_variable_usage)
def lint(self, template: PromptTemplate) -> list[dict]:
"""Lint a template."""
issues = []
for rule in self.rules:
rule_issues = rule(template)
issues.extend(rule_issues)
return issues
def _check_length(self, template: PromptTemplate) -> list[dict]:
"""Check template length."""
issues = []
if len(template.template) > 10000:
issues.append({
"rule": "length",
"severity": "warning",
"message": "Template is very long (>10000 chars)"
})
if len(template.template) < 10:
issues.append({
"rule": "length",
"severity": "warning",
"message": "Template is very short (<10 chars)"
})
return issues
def _check_clarity(self, template: PromptTemplate) -> list[dict]:
"""Check for clarity issues."""
issues = []
# Check for vague instructions
vague_words = ["maybe", "perhaps", "might", "could possibly"]
for word in vague_words:
if word in template.template.lower():
issues.append({
"rule": "clarity",
"severity": "info",
"message": f"Contains vague word: '{word}'"
})
return issues
def _check_injection_risk(self, template: PromptTemplate) -> list[dict]:
"""Check for injection risks."""
issues = []
# Check for unescaped user input markers
if "{user_input}" in template.template or "{input}" in template.template:
issues.append({
"rule": "injection",
"severity": "warning",
"message": "Contains direct user input variable - ensure proper sanitization"
})
return issues
def _check_variable_usage(self, template: PromptTemplate) -> list[dict]:
"""Check variable usage."""
issues = []
# Check for unused declared variables
declared = set(template.variables)
used = set(re.findall(r'\{(\w+)\}', template.template))
unused = declared - used
for var in unused:
issues.append({
"rule": "variables",
"severity": "warning",
"message": f"Declared but unused variable: {var}"
})
undeclared = used - declared
for var in undeclared:
issues.append({
"rule": "variables",
"severity": "error",
"message": f"Used but undeclared variable: {var}"
})
return issues
class PromptSanitizer:
"""Sanitize variables before injection."""
def __init__(self):
self.sanitizers: dict[str, Callable] = {}
def register(self, var_name: str, sanitizer: Callable):
"""Register sanitizer for variable."""
self.sanitizers[var_name] = sanitizer
def sanitize(self, variables: dict) -> dict:
"""Sanitize all variables."""
result = {}
for key, value in variables.items():
if key in self.sanitizers:
result[key] = self.sanitizers[key](value)
else:
result[key] = self._default_sanitize(value)
return result
def _default_sanitize(self, value: Any) -> Any:
"""Default sanitization."""
if isinstance(value, str):
# Remove potential injection patterns
value = value.replace("{{", "{ {")
value = value.replace("}}", "} }")
value = value.replace("{%", "{ %")
value = value.replace("%}", "% }")
return value
Production Template Service
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional, List, Dict, Any
app = FastAPI()
class TemplateCreate(BaseModel):
name: str
template: str
format: str = "fstring"
description: str = ""
variables: Optional[List[str]] = None
class RenderRequest(BaseModel):
template_name: str
variables: Dict[str, Any]
version: Optional[str] = None
class TestRequest(BaseModel):
template_name: str
test_cases: List[dict]
# Initialize components
store = TemplateStore()
engine = SimpleTemplateEngine()
linter = PromptLinter()
sanitizer = PromptSanitizer()
@app.post("/v1/templates")
async def create_template(request: TemplateCreate) -> dict:
"""Create a new template."""
template = PromptTemplate(
name=request.name,
template=request.template,
format=TemplateFormat(request.format),
variables=request.variables or [],
description=request.description
)
# Lint template
issues = linter.lint(template)
errors = [i for i in issues if i["severity"] == "error"]
if errors:
raise HTTPException(status_code=400, detail={"errors": errors})
# Save to store
version = store.save(template)
# Register with engine
engine.register(template)
return {
"name": template.name,
"version": version.version,
"hash": version.hash,
"warnings": [i for i in issues if i["severity"] != "error"]
}
@app.get("/v1/templates/{name}")
async def get_template(name: str, version: str = None) -> dict:
"""Get a template."""
template = store.get(name, version)
if not template:
raise HTTPException(status_code=404, detail="Template not found")
return {
"name": template.name,
"template": template.template,
"format": template.format.value,
"variables": template.variables,
"description": template.description,
"version": template.version
}
@app.get("/v1/templates/{name}/versions")
async def list_versions(name: str) -> list[dict]:
"""List all versions of a template."""
versions = store.list_versions(name)
return [
{
"version": v.version,
"created_at": v.created_at.isoformat(),
"created_by": v.created_by,
"changelog": v.changelog,
"hash": v.hash
}
for v in versions
]
@app.post("/v1/render")
async def render_template(request: RenderRequest) -> dict:
"""Render a template with variables."""
# Get template
template = store.get(request.template_name, request.version)
if not template:
raise HTTPException(status_code=404, detail="Template not found")
# Sanitize variables
safe_vars = sanitizer.sanitize(request.variables)
# Render
try:
renderer = TemplateRenderer()
rendered = renderer.render(template, safe_vars)
return {
"rendered": rendered,
"template_name": request.template_name,
"version": template.version
}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@app.post("/v1/test")
async def test_template(request: TestRequest) -> dict:
"""Run tests on a template."""
# Get template
template = store.get(request.template_name)
if not template:
raise HTTPException(status_code=404, detail="Template not found")
# Register with engine
engine.register(template)
# Run tests
runner = TemplateTestRunner(engine)
test_cases = [
TestCase(
name=tc.get("name", f"test_{i}"),
variables=tc.get("variables", {}),
expected_contains=tc.get("expected_contains", []),
expected_not_contains=tc.get("expected_not_contains", [])
)
for i, tc in enumerate(request.test_cases)
]
results = runner.run_suite(request.template_name, test_cases)
return {
"template_name": request.template_name,
"total": len(results),
"passed": sum(1 for r in results if r.passed),
"failed": sum(1 for r in results if not r.passed),
"results": [
{
"name": r.test_name,
"passed": r.passed,
"errors": r.errors
}
for r in results
]
}
@app.post("/v1/lint")
async def lint_template(request: TemplateCreate) -> dict:
"""Lint a template."""
template = PromptTemplate(
name=request.name,
template=request.template,
format=TemplateFormat(request.format),
variables=request.variables or [],
description=request.description
)
issues = linter.lint(template)
return {
"template_name": request.name,
"issues": issues,
"error_count": sum(1 for i in issues if i["severity"] == "error"),
"warning_count": sum(1 for i in issues if i["severity"] == "warning")
}
@app.get("/health")
async def health():
return {"status": "healthy"}
References
- LangChain Prompts: https://python.langchain.com/docs/modules/model_io/prompts/
- Jinja2: https://jinja.palletsprojects.com/
- Guidance: https://github.com/guidance-ai/guidance
- PromptLayer: https://promptlayer.com/
- Humanloop: https://humanloop.com/
Conclusion
Prompt template management transforms ad-hoc prompt engineering into systematic software engineering. Start with a simple template system that separates prompts from code—even basic f-string formatting with variable validation is a significant improvement over hard-coded strings. As your needs grow, add features incrementally: conditionals and loops for dynamic prompts, partials for reusable components, filters for data transformation. Version control is essential—store templates in Git or a dedicated template store so you can track changes, rollback failures, and understand prompt evolution over time. Testing catches regressions before they reach production: define test cases with expected outputs and run them on every change. Linting catches common issues like injection risks and unused variables. For production systems, build a template service that handles rendering, validation, and versioning through a clean API. The key insight is that prompts are code—they deserve the same engineering rigor as any other part of your system. Well-managed templates make iteration faster, debugging easier, and your LLM applications more reliable.
Discover more from Code, Cloud & Context
Subscribe to get the latest posts sent to your email.