RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Fredoline Eruo
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /How-to
  5. /How to build a custom prompt template library
HOW-TO · SUP

How to build a custom prompt template library

intermediate·25 min·By Fredoline Eruo
Target environment
Ubuntu 24.04 · Ollama 0.4.x
PREREQUISITES

Python environment, LLM access

What this does

A prompt template library provides versioned, reusable, and composable prompt definitions across an organization. This guide builds a Python library with string interpolation, template inheritance, schema validation, and versioning for production prompt management.

Steps

Step 1: Set up the project structure

mkdir prompt_library && cd prompt_library
pip install jinja2 pydantic
touch __init__.py prompt_templates.py template_manager.py

Step 2: Define the PromptTemplate class

from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
import json

@dataclass
class PromptTemplate:
    """Represents a versioned prompt template with inheritance support."""
    name: str
    template: str
    version: str = "1.0.0"
    variables: List[str] = field(default_factory=list)
    parent_name: Optional[str] = None
    schema: Optional[Dict] = None

    def render(self, **kwargs) -> str:
        """Render the template by substituting variables using Jinja2-style syntax."""
        from string import Template
        import re

        rendered = self.template
        # Extract all {{variable}} patterns
        found_vars = set(re.findall(r'\{\{(\w+)\}\}', self.template))

        for var in found_vars:
            if var not in kwargs:
                raise ValueError(
                    f"Missing required variable '{var}' in template '{self.name}'. "
                    f"Expected: {list(found_vars)}"
                )
            rendered = rendered.replace(f"{{{{{var}}}}}", str(kwargs[var]))

        # Handle any remaining {{variable}} placeholders
        unrendered = re.findall(r'\{\{(\w+)\}\}', rendered)
        if unrendered:
            raise ValueError(f"Unrendered variables: {unrendered}")

        return rendered

    def validate(self, data: Dict) -> bool:
        """Validate rendered output or input data against JSON schema."""
        if not self.schema:
            return True
        required = self.schema.get("required", [])
        for key in required:
            if key not in data:
                raise ValueError(f"Schema validation failed: missing required field '{key}'")
        return True

Step 3: Implement the PromptLibrary manager

class PromptLibrary:
    """Central registry for managing prompt templates with versioning."""

    def __init__(self):
        self._templates: Dict[str, List[PromptTemplate]] = {}
        self._latest: Dict[str, PromptTemplate] = {}

    def register(self, template: PromptTemplate):
        """Register a template. Raises ValueError if name+version already exists."""
        if template.name not in self._templates:
            self._templates[template.name] = []
        existing_versions = [t.version for t in self._templates[template.name]]
        if template.version in existing_versions:
            raise ValueError(f"Template {template.name} v{template.version} already registered.")
        self._templates[template.name].append(template)
        self._templates[template.name].sort(key=lambda t: t.version)
        self._latest[template.name] = self._templates[template.name][-1]

    def get(self, name: str, version: Optional[str] = None) -> PromptTemplate:
        """Retrieve a template by name and optional version."""
        if name not in self._templates:
            raise KeyError(f"Template '{name}' not found.")
        if version:
            for t in self._templates[name]:
                if t.version == version:
                    return t
            raise KeyError(f"Version {version} not found for template '{name}'.")
        return self._latest[name]

    def render(self, name: str, variables: Dict, version: Optional[str] = None) -> Dict[str, Any]:
        """Render a template with variables and return metadata."""
        template = self.get(name, version)
        rendered = template.render(**variables)
        return {
            "rendered_prompt": rendered,
            "template_name": template.name,
            "version": template.version,
            "variables_provided": list(variables.keys())
        }

    def list_templates(self) -> List[Dict]:
        """List all templates with their versions."""
        return [
            {"name": name, "versions": [t.version for t in versions]}
            for name, versions in self._templates.items()
        ]

Step 4: Register and use templates

# Create the library instance
library = PromptLibrary()

# Register a document analysis template
doc_analysis = PromptTemplate(
    name="document_analysis",
    version="1.0.0",
    template=(
        "You are an expert document analyst.\n"
        "Analyze the following document named {{filename}} ({{document_type}}).\n\n"
        "Content:\n{{content}}\n\n"
        "Provide: summary, key entities, and classification."
    ),
    variables=["filename", "document_type", "content"],
    schema={"required": ["filename", "content"]}
)
library.register(doc_analysis)

# Register an extended version with new variables
doc_analysis_v2 = PromptTemplate(
    name="document_analysis",
    version="2.0.0",
    template=(
        "You are an expert document analyst specializing in {{domain}}.\n"
        "Analyze the following document named {{filename}} ({{document_type}}).\n\n"
        "Content:\n{{content}}\n\n"
        "Confidence threshold: {{confidence_threshold}}\n"
        "Provide: summary, key entities, classification, and risk assessment."
    ),
    variables=["filename", "document_type", "content", "domain", "confidence_threshold"],
    parent_name="document_analysis",
    schema={"required": ["filename", "content", "domain"]}
)
library.register(doc_analysis_v2)

# Register a customer support template
support_template = PromptTemplate(
    name="customer_support",
    version="1.0.0",
    template=(
        "Customer ID: {{customer_id}}\n"
        "Issue: {{issue_description}}\n"
        "Priority: {{priority}}\n"
        "Response format: {{response_format}}\n"
        "{{additional_context}}"
    ),
    variables=["customer_id", "issue_description", "priority", "response_format", "additional_context"]
)
library.register(support_template)

# Render a template
result = library.render(
    "document_analysis",
    variables={
        "filename": "Q4_report.pdf",
        "document_type": "financial report",
        "content": "Revenue increased by 15% year-over-year..."
    }
)

print(result)

Step 5: Verify expected output

Expected output from print(result):

{
    "rendered_prompt": "You are an expert document analyst.\nAnalyze the following document named Q4_report.pdf (financial report).\n\nContent:\nRevenue increased by 15% year-over-year...\n\nProvide: summary, key entities, and classification.",
    "template_name": "document_analysis",
    "version": "1.0.0",
    "variables_provided": ["filename", "document_type", "content"]
}

Verify: rendered_prompt contains all substituted variables (no {{...}} remaining), template_name matches the registered name, version is the latest registered version.

To retrieve a specific version:

v1 = library.get("document_analysis", version="1.0.0")
v2 = library.get("document_analysis", version="2.0.0")
print(v1.version, v2.version)  # 1.0.0 2.0.0

Verification

Common failures

  1. Double curly braces left in output. If render() receives fewer variables than the template has placeholders, unmatched {{var}} strings remain in the output. Always validate variable completeness before calling render().

  2. Version collision on re-registration. Attempting to register the same name+version twice raises an error. Use a version bump strategy: increment the minor version (1.0.0 -> 1.1.0) when updating a template.

  3. No inheritance resolution between parent templates. The parent_name field is stored but not automatically merged in render(). Implement inheritance by fetching the parent template and prepending its content in the render() method for templates with a non-null parent_name.

  • Version mismatch - The installed package or runtime differs from the command shown; check the version first and rerun the smallest verification command.
  • Local environment drift - Another service, virtual environment, model, or path is being used; print the active binary path and configuration before changing the guide steps.

Related guides

  • Build a Document Analysis Agent Pipeline - Use the prompt library to standardize extraction and summarization prompts across document types in the pipeline.
  • Implement Few-Shot Example Selection Dynamically - Extend template rendering to inject dynamically selected few-shot examples as a variable, building on the library's variable interpolation system.
← All how-to guidesCourses →