Skip to main content

Prompt versioning, output validation, and run logging for LLM pipelines

Project description

promptlock

Prompt versioning, output validation, and run logging for LLM pipelines.

Most LLM engineering pain comes from three places: prompts that drift without anyone noticing, outputs that break downstream logic, and no record of what ran when. promptlock fixes all three — no cloud account, no dashboard, no framework lock-in.

pip install promptlock

Why promptlock

Problem What promptlock gives you
Prompts change silently and break things Version-controlled prompt registry backed by a local YAML file
LLM output shape is unpredictable Output contracts that validate structure, length, and patterns
No record of what ran in production SQLite run logger with filtering, summary stats, and export

Install

pip install promptlock
# or with uv
uv add promptlock

Requires Python 3.11+. Two external dependencies: pyyaml and openpyxl (for Excel export).


Quickstart

from promptlock import PromptRegistry, OutputContract, RunLogger
from promptlock.exceptions import ContractViolation

# 1. Save and load versioned prompts
registry = PromptRegistry("prompts.yaml")
registry.save("summarizer", "v1.0", "Summarize this document: {doc}\nLanguage: {lang}")

template = registry.load("summarizer", version="latest")
rendered = template.render(doc="AI is transforming healthcare.", lang="English")

# 2. Validate the LLM output
contract = OutputContract(
    required_fields=["summary", "keywords"],
    max_length=500,
    min_length=20,
)

llm_output = {"summary": "AI aids diagnostics.", "keywords": ["ai", "health"]}

try:
    contract.validate(llm_output)
    validated = True
    error = None
except ContractViolation as e:
    validated = False
    error = str(e)

# 3. Log the run
logger = RunLogger("runs.db")
logger.log(
    prompt_name="summarizer",
    version="v1.0",
    model="gpt-4o",
    input=rendered,
    output=llm_output,
    validated=validated,
    error=error,
)

# 4. Export runs for analysis
logger.export("runs.csv", format="csv")
logger.export("runs.xlsx", format="excel")

Modules

PromptRegistry

Store and retrieve prompt versions from a local YAML file.

from promptlock import PromptRegistry

registry = PromptRegistry("prompts.yaml")

# save a prompt version
registry.save("classifier", "v1.0", "Classify the following text: {text}")
registry.save("classifier", "v1.1", "Classify this as positive/negative/neutral: {text}")

# load a specific version
template = registry.load("classifier", version="v1.0")

# load the most recent version
template = registry.load("classifier", version="latest")

# list all prompts and versions
registry.list_prompts()
# {'classifier': ['v1.0', 'v1.1']}

# delete a version or all versions
registry.delete("classifier", version="v1.0")
registry.delete("classifier")

PromptTemplate

Render prompt strings with named placeholders.

from promptlock import PromptTemplate

template = PromptTemplate("Translate this to {lang}: {text}")

print(template.variables)
# ['lang', 'text']

rendered = template.render(lang="French", text="Hello world")
# 'Translate this to French: Hello world'

# missing variables raise TemplateRenderError
template.render(lang="French")
# TemplateRenderError: Missing required template variables: ['text']

OutputContract

Define and validate the expected shape of an LLM output.

from promptlock import OutputContract
from promptlock.exceptions import ContractViolation

# validate a JSON output
contract = OutputContract(
    required_fields=["summary", "keywords"],
    max_length=500,
    min_length=20,
)
contract.validate({"summary": "Short summary.", "keywords": ["ai"]})  # passes

# validate a plain string
sentiment_contract = OutputContract(
    allowed_values=["positive", "negative", "neutral"]
)
sentiment_contract.validate("positive")   # passes
sentiment_contract.validate("unknown")    # raises ContractViolation

# validate with regex
contract = OutputContract(regex_patterns=[r"\d{4}"])
contract.validate("Report from 2025")   # passes
contract.validate("No year here")       # raises ContractViolation

Available rules:

Rule Type Description
required_fields list[str] Keys that must exist in a JSON output
max_length int Maximum character length of the output
min_length int Minimum character length of the output
regex_patterns list[str] Patterns the output must match (all must pass)
allowed_values list[str] Output must be one of these exact strings

RunLogger

Log every LLM run to a local SQLite file and export when needed.

from promptlock import RunLogger

logger = RunLogger("runs.db")

logger.log(
    prompt_name="summarizer",
    version="v1.1",
    model="gpt-4o",
    input="Summarize this: ...",
    output={"summary": "AI is evolving.", "keywords": ["ai"]},
    validated=True,
)

# retrieve runs with filters
logger.get_runs(prompt_name="summarizer", validated="failed", limit=10)

# quick summary of pass/fail counts
logger.summary("summarizer")
# {'passed': 42, 'failed': 3, 'not_checked': 5}

Exporting runs

Export logged runs to CSV, JSON, or Excel for analysis or sharing.

# export all runs
logger.export("runs.csv", format="csv")
logger.export("runs.json", format="json")
logger.export("runs.xlsx", format="excel")

# export with filters
logger.export("failed.csv", format="csv", validated="failed")
logger.export("summarizer.xlsx", format="excel", prompt_name="summarizer")

All three formats share the same columns: id, prompt_name, version, model, input, output, validated, error, timestamp.


Exceptions

All exceptions inherit from PromptlockError so you can catch broadly or specifically.

from promptlock.exceptions import (
    PromptlockError,       # base exception
    ContractViolation,     # output failed validation
    PromptNotFound,        # prompt name/version not in registry
    TemplateRenderError,   # missing variable during render
)

Project structure

src/promptlock/
├── __init__.py       # public API
├── registry.py       # PromptRegistry
├── template.py       # PromptTemplate
├── contract.py       # OutputContract
├── logger.py         # RunLogger
└── exceptions.py     # custom exceptions

Contributing

Pull requests are welcome. For major changes, please open an issue first.

git clone https://github.com/NorthCommits/Promptlock
cd Promptlock
uv sync
uv run pytest -v

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

promptlock-0.2.1.tar.gz (7.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

promptlock-0.2.1-py3-none-any.whl (10.1 kB view details)

Uploaded Python 3

File details

Details for the file promptlock-0.2.1.tar.gz.

File metadata

  • Download URL: promptlock-0.2.1.tar.gz
  • Upload date:
  • Size: 7.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for promptlock-0.2.1.tar.gz
Algorithm Hash digest
SHA256 13ca05b82c25ba43e6551a57b1dc55631b5cd03d7c640b4c726723a8882cf462
MD5 3dc51c9212fc02276359da5c997fd702
BLAKE2b-256 237c3b2763a60c7c6d07fdae32205499efb31be91cf4bb1e51db26b20de34ea2

See more details on using hashes here.

File details

Details for the file promptlock-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: promptlock-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 10.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for promptlock-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 73d365095dc37e85d4cfbbd7b59e2e9ae4a790ef80feb5676e6fb4abe8ee4a75
MD5 a7fd383852e7c81f07fc62f3bdc485d4
BLAKE2b-256 6855c3de4df2ecddc7335faaaf2f2f80229d386ac7d2cf7af1db2a69c18f8724

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page