promptci
Prompt versioning with CI/CD regression gates — for production LLM engineering.
Version, diff, test, and deploy prompts with quality gates, schema migration, PII scrubbing, and full observability.
pip install promptci
Why promptci?
In 2026, prompt changes are the most common cause of silent LLM regressions. Teams edit prompts in Google Docs, paste them into code, and ship — with zero version control, no diff visibility, and no quality gates. When output quality degrades, nobody knows which prompt change caused it.
promptci brings software engineering discipline to prompt management.
Quickstart
from promptci import (
PromptRegistry, PromptVersion, PromptStatus,
LengthGate, VariableGate, InjectionRiskGate, RegressionGatePipeline,
)
# Create and register a versioned prompt
registry = PromptRegistry()
prompt_v1 = PromptVersion(
name="summarize",
version="1.0.0",
content="Summarize the following text: {{text}}. Be concise.",
tags=["summarization"],
status=PromptStatus.ACTIVE,
)
registry.register(prompt_v1)
# Render with variable substitution
rendered = registry.render("summarize", {"text": "The quick brown fox..."})
print(rendered)
# Run CI gates before deploying v2
pipeline = (
RegressionGatePipeline()
.add_gate(LengthGate(min_chars=20, max_chars=5000))
.add_gate(VariableGate(required=["text"]))
.add_gate(InjectionRiskGate())
)
report = pipeline.run(prompt_v1)
print(f"Gates passed: {report.overall_passed}")
Built-in Regression Gates
| Gate | Description |
|---|---|
LengthGate |
Min/max character length check |
VariableGate |
Required {{variable}} presence |
InjectionRiskGate |
Blocks injection patterns (ignore previous instructions, etc.) |
KeywordCoverageGate |
Required keywords coverage fraction |
Prompt Diff
from promptci import diff_prompts, diff_to_unified
diff = diff_prompts(prompt_v1, prompt_v2)
print(diff.summary()) # ScoreDiff summary
print(diff.changed) # True / False
print(diff.char_delta) # Character delta
print(diff_to_unified(prompt_v1, prompt_v2)) # Unified diff string
Advanced Features
Caching (LRU + TTL + SHA-256)
from promptci.advanced import PromptCache
cache = PromptCache(max_size=1000, ttl=600)
memoized_render = cache.memoize(registry.render)
rendered = memoized_render("summarize", "1.0.0", {"text": "hello"})
print(cache.stats())
Prompt Pipeline
from promptci.advanced import PromptPipeline
pipeline = (
PromptPipeline()
.map("strip_trailing", lambda p: p.model_copy(update={"content": p.content.strip()}))
.filter("active_only", lambda p: p.status == PromptStatus.ACTIVE)
.with_retry("strip_trailing", retries=2)
)
result = pipeline.run(prompt_v1)
print(pipeline.audit_log)
Declarative Validation + Schema Evolution
from promptci.advanced import PromptValidator, PromptRule, SchemaEvolver
validator = (
PromptValidator()
.add_rule(PromptRule("non_empty", lambda p: len(p.content) > 0, "Content required"))
.add_rule(PromptRule("has_tags", lambda p: len(p.tags) > 0, "At least one tag required"))
)
violations = validator.validate(prompt_v1)
evolver = SchemaEvolver()
evolver.register_migration("1.0.0", "2.0.0", lambda p: p.model_copy(update={"description": "v2 prompt"}))
prompt_v2 = evolver.migrate(prompt_v1, "2.0.0")
PII Scrubbing
from promptci.advanced import PIIScrubber
scrubber = PIIScrubber()
clean = scrubber.scrub("Contact: john@example.com, SSN: 123-45-6789")
# → "Contact: [EMAIL], SSN: [SSN]"
Async Batch Registration
from promptci.advanced import abatch_register, batch_register
import asyncio
asyncio.run(abatch_register(prompt_list, registry.register))
batch_register(prompt_list, registry.register, max_workers=8)
Rate Limiter (sync + async)
from promptci.advanced import RateLimiter
limiter = RateLimiter(rate=50, capacity=50)
if limiter.acquire():
rendered = registry.render("summarize", {"text": "..."})
Observability
from promptci.advanced import PromptProfiler, DriftDetector, CIReportExporter
profiler = PromptProfiler()
profiled_render = profiler.profile(registry.render)
profiled_render("summarize", {"text": "hi"})
print(profiler.report())
detector = DriftDetector(threshold=0.05)
detector.set_baseline(report_v1)
drifts = detector.detect(report_v2)
exporter = CIReportExporter(report)
print(exporter.to_json())
print(exporter.to_csv())
print(exporter.to_markdown())
Streaming
from promptci.advanced import stream_versions, versions_to_ndjson
for pv in stream_versions(prompt_list):
print(pv.name, pv.version)
for line in versions_to_ndjson(prompt_list):
print(line)
Regression Tracking
from promptci.advanced import PromptRegressionTracker, ScoreTrend
tracker = PromptRegressionTracker(window=20)
tracker.record(report_v1)
tracker.record(report_v2)
print(tracker.trend()) # "improving" / "declining" / "stable"
print(tracker.latest_regression())
trend = ScoreTrend(window=10)
for score in [0.7, 0.8, 0.9]:
trend.record(score)
print(trend.trend(), trend.volatility())
Audit Log + Cost Ledger + Model Router
from promptci.advanced import AuditLog, CostLedger, ModelRouter
log = AuditLog()
log.log("deploy", {"name": "summarize", "version": "2.0.0"})
ledger = CostLedger()
ledger.record("summarize", "2.0.0", tokens=500, cost_usd=0.01)
print(ledger.summary())
router = ModelRouter(cheap_threshold=200)
model = router.route(prompt_v1) # "gpt-4o-mini" or "gpt-4o"
Persistence
registry.save("registry.json")
registry.load("registry.json")
Installation
pip install promptci
Python 3.8+ · No external dependencies (stdlib + pydantic)
License
MIT
Metadata
Release files for promptci 1.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| promptci-1.1.3.tar.gz | 18.6 kB | Without provenance | Details for promptci-1.1.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| promptci-1.1.3-py3-none-any.whl | Python 3 | none | any | Details for promptci-1.1.3-py3-none-any.whl |
Total release size: 34.3 kB
Release files / promptci-1.1.3.tar.gz
| Download URL | promptci-1.1.3.tar.gz |
|---|---|
| Size | 18.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
466c713a2df27732cd30bb35bcc4546e9fa0e7612e30b9ecb78c54646d386438
|
|
BLAKE2b-256 checksum How to use checksums |
903f391688305ffe49fb9cdfe8bec9f7cbb84821a26f47ef6444333533d6f842
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / promptci-1.1.3-py3-none-any.whl
| Download URL | promptci-1.1.3-py3-none-any.whl |
|---|---|
| Size | 15.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3a251c25590dd9aee2478f525083da3f01ee3eed705f7cbcd00b3836f174ae28
|
|
BLAKE2b-256 checksum How to use checksums |
676208bcc9b987ff1eb88908fe1b8af2eb30cc324efd9a31a20e11d26fe08c50
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Security for promptci 1.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.