Prompt Library in Python: Templates, Storage, and Tests

How to build a Python prompt library using typed templates, variable substitution, and output tests. Keep approved prompt decisions and lessons in fenbs alongside the code.

Updated 4 min read

A Python prompt library stores your AI prompts as typed templates with named variables, tests that verify inputs before a call goes out, and a structured record of what each prompt is meant to do. Keeping prompts in code makes them versionable and testable. A tool like fenbs complements the code layer by keeping the approved decisions and the lessons each revision taught, readable by any connected AI, not just the Python process that runs the library.

The steps below build a small library from scratch. The goal is a pattern you can extend, not a framework. Each section adds one layer: template definition, storage, input validation, and output testing.

A basic template structure

Start with a dataclass that holds the prompt text, its required variable names, and a short description of what it is for. Python's string.Template class provides safe substitution with $variable placeholders and handles missing variables gracefully without raising unexpected errors by default.

prompt_template.py
from dataclasses import dataclass, field
from string import Template


@dataclass
class PromptTemplate:
    name: str
    description: str
    template: str
    required_vars: list[str] = field(default_factory=list)

    def render(self, **kwargs: str) -> str:
        missing = [v for v in self.required_vars if v not in kwargs]
        if missing:
            raise ValueError(f"Missing required variables: {missing}")
        return Template(self.template).safe_substitute(**kwargs)

    def variables(self) -> list[str]:
        """Return all $variable names found in the template text."""
        import re
        return re.findall(r'\$([A-Za-z_][A-Za-z0-9_]*)', self.template)

The safe_substitute method leaves unknown $variable references unchanged rather than raising an error. If you want strict checking, use substitute instead and handle the KeyError it raises when a variable is not supplied. Use safe_substitute when templates may evolve and you want backward compatibility; use substitute when you need to guarantee all variables are filled.

Storing and loading templates

For a small library, a module-level dictionary is the simplest storage. For a library that needs to be edited without redeploying code, a JSON file works well. Both approaches are shown below.

prompt_store.py
from prompt_template import PromptTemplate

LIBRARY: dict[str, PromptTemplate] = {
    "summarize": PromptTemplate(
        name="Summarize",
        description="Summarize content for a specific audience.",
        template="Summarize the following for $audience in $max_words words or fewer:\n\n$content",
        required_vars=["audience", "max_words", "content"],
    ),
    "review": PromptTemplate(
        name="Review",
        description="Review a document against stated criteria.",
        template=(
            "Review the following $document_type against these criteria: $criteria\n\n"
            "$content\n\n"
            "For each criterion, say whether the document meets it and why."
        ),
        required_vars=["document_type", "criteria", "content"],
    ),
    "gap_finder": PromptTemplate(
        name="Gap finder",
        description="Find unanswered questions in a document.",
        template=(
            "Read the following document.\n"
            "List $count questions most likely to come from a reviewer who has not seen the source material.\n"
            "For each, say whether the document answers it, answers it partially, or leaves it open.\n\n"
            "$content"
        ),
        required_vars=["count", "content"],
    ),
}


def get(name: str) -> PromptTemplate:
    if name not in LIBRARY:
        raise KeyError(f"No prompt named '{name}'. Available: {list(LIBRARY)}")
    return LIBRARY[name]

Testing prompt inputs

Input tests confirm that the render method raises the right errors and produces the expected structure. These tests run without making any API calls, which makes them fast and cheap to run on every commit.

test_prompts.py
import pytest
from prompt_store import get


def test_summarize_raises_on_missing_vars():
    t = get("summarize")
    with pytest.raises(ValueError, match="Missing required variables"):
        t.render(audience="a lawyer")  # missing max_words and content


def test_summarize_renders_with_all_vars():
    t = get("summarize")
    result = t.render(audience="a lawyer", max_words="100", content="Sample text here.")
    assert "lawyer" in result
    assert "100" in result
    assert "Sample text here." in result


def test_get_raises_for_unknown_name():
    with pytest.raises(KeyError):
        get("this_does_not_exist")


def test_gap_finder_contains_count_and_content():
    t = get("gap_finder")
    result = t.render(count="5", content="A short brief.")
    assert "5" in result
    assert "A short brief." in result

These tests cover the mechanics of the library, not the quality of the AI's response. They verify that the right text reaches the model, that missing inputs are caught early, and that the library behaves consistently as it grows. That is the appropriate job for unit tests here.

Testing prompt outputs

Output tests call the AI and check that the response meets structural requirements: sections that should appear, claims that should not appear, and format rules. These tests cost money to run and depend on the model's behavior, so run them less frequently than input tests.

  • Check for required sections. If a prompt should return a numbered list, assert that the response contains at least one numbered item.
  • Check that variable content appears. If you passed "lawyer" as the audience, confirm the response is pitched at a non-technical reader.
  • Check for hallucination markers. If the prompt said "use only the content above," assert that phrases from your test content appear in the output.
  • Store a sample output alongside the test. When a model update changes behavior, the stored sample shows what the output used to look like.

When to update a template

Update a template when an output test fails consistently, when the task it serves changes, or when a team member finds a better instruction through trial and error. Keep a brief comment in the code noting what changed and why.

The code comment captures the technical change. The reasoning behind it, the problem it was solving, and whether it actually worked are harder to express in a comment and easy to lose. Recording those as lessons in fenbs keeps them beside the project context, readable by any AI working on the same codebase.

A related question: when should you retire a template rather than update it? Retire it when the task it was written for no longer exists, when the new version is incompatible with the old signature, or when the old version caused a problem you want to document rather than overwrite. Mark retired templates clearly rather than deleting them. The record of what did not work is often as useful as the record of what does.

How fenbs helps

Record what each prompt revision taught the team

fenbs keeps Lessons learned alongside your project tasks and decisions. When a prompt update solves a problem, add a lesson: what the old version did wrong, what changed, and what to check if the problem returns. Any connected AI reads lessons before similar work. Create a free account to build that record alongside your Python library.

Create a free account

Free for one person · No credit card · Works with Claude, ChatGPT, Cursor and more

How to do this with fenbs

The Python library handles code-level concerns: structure, validation, and testing. fenbs handles the team-level concerns: which prompt is currently approved, why it is shaped the way it is, and what earlier versions taught. Together they cover the full lifecycle of a prompt, from the very first commit to the lesson that shapes the next revision.

  1. Create a free account. Add a project for the application that uses the prompt library.
  2. Go to Skills and prompts in your project. Add each template as an entry with its name, the current approved text, and the decisions behind any non-obvious instructions.
  3. Add a Lesson each time a template revision solves a real problem. Note what failed, what changed, and what the output check confirmed. The next developer who maintains the library can read that history before making changes.
  4. Connect your AI apps at Integrations. A connected assistant can retrieve the current approved prompt text and the lessons attached to it, so code reviews and prompt reviews work from the same record.

Questions people ask.

How do I store prompts in Python?

A module-level dictionary mapping names to dataclasses works well for small libraries. Each dataclass holds the prompt text as a template string, a list of required variable names, and a description. For larger libraries, store the same data in a JSON file and load it at startup.

How do I add variables to a Python prompt template?

Use Python's string.Template class with $variable placeholders. Call safe_substitute with keyword arguments to fill in the values. Validate required variables before calling safe_substitute by checking that all names in your required_vars list appear in the keyword arguments.

How do I test a Python prompt library?

Write two sets of tests: input tests that run without any API calls and check that render raises errors for missing variables and produces the right structure, and output tests that call the AI and check that the response meets structural requirements. Run input tests on every commit; run output tests less frequently because they cost money.

Should I version my Python prompt templates?

Yes. Keep the current approved version in the code and track changes in a commit history. Add a short comment when you update a template to explain what changed and why. For the reasoning and lessons behind each change, a shared record outside the codebase keeps that context available to teammates who may not read git log messages.

More in Prompts and skills for AI work

All Prompts and skills for AI work articles

Start with one thing.

Free for one person · No credit card · Works with Claude, ChatGPT, Cursor and more

There is nothing to set up first. Write one line and you’ve started.