Metadata-Version: 2.4
Name: promptfiles
Version: 0.1.0
Summary: LLM prompts as versioned YAML files — git-trackable, renderable, and diffable. Works with OpenAI, Anthropic, LiteLLM, and any LLM client.
Project-URL: Homepage, https://github.com/maheshmakvana/promptfiles
Project-URL: Documentation, https://github.com/maheshmakvana/promptfiles#readme
Project-URL: Repository, https://github.com/maheshmakvana/promptfiles
Project-URL: Bug Tracker, https://github.com/maheshmakvana/promptfiles/issues
Project-URL: Changelog, https://github.com/maheshmakvana/promptfiles/blob/main/CHANGELOG.md
Author: promptfile contributors
License: MIT
License-File: LICENSE
Keywords: agent prompts,ai prompts,anthropic,chat template,generative ai,git,langchain,litellm,llm,llm prompts,openai,prompt,prompt engineering,prompt file,prompt management,prompt registry,prompt store,prompt template,prompt versioning,rag,system prompt,versioning,yaml
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: all
Requires-Dist: jinja2>=3.0; extra == 'all'
Requires-Dist: pydantic>=2.0; extra == 'all'
Requires-Dist: pyyaml>=5.1; extra == 'all'
Provides-Extra: dev
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pydantic>=2.0; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: pyyaml>=5.1; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Provides-Extra: jinja2
Requires-Dist: jinja2>=3.0; extra == 'jinja2'
Provides-Extra: pydantic
Requires-Dist: pydantic>=2.0; extra == 'pydantic'
Provides-Extra: yaml
Requires-Dist: pyyaml>=5.1; extra == 'yaml'
Description-Content-Type: text/markdown

# promptfile

**LLM prompts as versioned YAML files — git-trackable, renderable, and diffable.**  
No servers. No SaaS. No database. Works with OpenAI, Anthropic, LiteLLM, and any string-based API.

[![PyPI version](https://badge.fury.io/py/promptfile.svg)](https://pypi.org/project/promptfile/)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

---

## Why promptfile?

Most teams manage prompts as hardcoded strings, littered through source files. When a prompt changes, there's no diff, no version history, no review process — just a mystery regression.

`promptfile` treats your prompts like code: plain YAML files, committed to git, reviewed in PRs, and rendered at runtime.

| Problem | Without promptfile | With promptfile |
|---------|-------------------|-----------------|
| Prompt lives in source code | Hard to find & change | Separate `.prompt.yaml` file |
| No version history | Git doesn't know what changed | Full git diff, blame, history |
| Variables scattered everywhere | `f"Summarise {article}"` strings | `{{article}}` in YAML |
| Can't review prompt changes | Buried in code diffs | Clean YAML diff in PR |
| Works only with one LLM SDK | Tied to one client | Plain dict output, any SDK |

---

## Install

```bash
# Core install
pip install promptfile

# With YAML parsing (recommended — you almost always need this)
pip install "promptfile[yaml]"

# Everything
pip install "promptfile[all]"
```

---

## Quick Start

**1. Write a prompt file** (`prompts/summarise.prompt.yaml`):

```yaml
name: summarise
version: "1.0.0"
description: Summarise an article in a given tone.

system: You are a professional editor. Summarise text clearly and accurately.
user: "Summarise the following article in a {{tone}} tone:\n\n{{article}}"
```

**2. Load and render it in Python:**

```python
from promptfile import load

prompt = load("prompts/summarise.prompt.yaml")

# Render variables
rendered = prompt.render(
    tone="concise",
    article="Today's top story is about AI...",
)

# Use with any LLM client
messages = rendered.to_messages()
# → [{'role': 'system', 'content': '...'}, {'role': 'user', 'content': '...'}]
```

**3. Pass to your LLM:**

```python
# OpenAI
import openai
client = openai.OpenAI()
response = client.chat.completions.create(
    model="gpt-4o",
    messages=rendered.to_messages(),
)

# Anthropic
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-opus-4-6",
    max_tokens=1024,
    system=rendered.messages[0].content,
    messages=rendered.to_messages()[1:],
)

# LiteLLM
import litellm
response = litellm.completion(
    model="gpt-4o",
    messages=rendered.to_messages(),
)
```

That's it. Your prompt is now a versioned, reviewable, git-trackable artifact.

---

## YAML Formats

### Shorthand (quick authoring)

```yaml
name: my-prompt
version: "1.0.0"
description: Optional human-readable description.

system: You are a helpful assistant.
user: "Answer this question: {{question}}"
```

### Full messages list (multi-turn / precise control)

```yaml
name: customer-support
version: "2.1.0"
description: Customer support agent for SaaS product.

messages:
  - role: system
    content: |
      You are a friendly customer support agent for Acme Corp.
      Always be empathetic and solution-focused.
  - role: user
    content: "{{customer_message}}"
```

### With metadata (model hints, tags, defaults)

```yaml
name: code-review
version: "1.3.0"
description: Reviews a code diff and suggests improvements.

system: You are an expert software engineer.
user: "Review this {{language}} code:\n\n{{code}}"

# Metadata: anything extra is stored in prompt.metadata
model: gpt-4o
temperature: 0.2
tags:
  - engineering
  - code-quality

defaults:
  language: Python
```

---

## All Features

### `load(path)` — Load a single prompt file

```python
from promptfile import load

prompt = load("prompts/summarise.prompt.yaml")

print(prompt.name)        # "summarise"
print(prompt.version)     # "1.0.0"
print(prompt.variables)   # {"tone", "article"}
print(prompt.metadata)    # {"model": "gpt-4o", ...}
```

### `load_dir(directory)` — Load all prompts in a folder

```python
from promptfile import load_dir

prompts = load_dir("prompts/", recursive=True)
# → {"summarise": Prompt(...), "code-review": Prompt(...), ...}

summarise = prompts["summarise"]
```

### `render(prompt, **kwargs)` — Fill template variables

```python
from promptfile import load, render

prompt = load("prompts/summarise.prompt.yaml")

rendered = render(prompt, tone="formal", article="The article text...")

# Or call directly on the prompt object:
rendered = prompt.render(tone="formal", article="The article text...")
```

Missing variables raise a clear `KeyError`:
```
KeyError: Prompt 'summarise' requires variables: ['article', 'tone']
Provided: []
```

### `registry` — Central prompt store

```python
from promptfile import registry

# Load at startup
registry.load_dir("prompts/")

# Access anywhere in your app
summarise = registry.get("summarise")
rendered = summarise.render(tone="concise", article="...")

# List all registered prompts
print(registry.list())
# → ["code-review", "customer-support", "summarise"]
```

### `diff_prompts(old, new)` — Human-readable prompt diff

```python
from promptfile import load, diff_prompts

old = load("prompts/v1/summarise.prompt.yaml")
new = load("prompts/v2/summarise.prompt.yaml")

print(diff_prompts(old, new, color=True))
```

```diff
--- summarise v1.0.0
+++ summarise v2.0.0
@@ -3,4 +3,4 @@
   - role: user
-    Summarise the following article in a {{tone}} tone:
+    Summarise the following article in a {{tone}} tone. Be {{length}}.
```

### `validate(prompt)` — Catch authoring mistakes

```python
from promptfile import load
from promptfile._validate import validate

prompt = load("prompts/my-prompt.yaml")
result = validate(prompt)

if not result.valid:
    for error in result.errors:
        print(f"ERROR: {error}")
for warning in result.warnings:
    print(f"WARN:  {warning}")
```

---

## CLI

```bash
# Show a prompt
promptfile show prompts/summarise.prompt.yaml

# Validate all prompts in a directory
promptfile validate prompts/ --recursive

# Diff two versions
promptfile diff prompts/v1/summarise.yaml prompts/v2/summarise.yaml

# List all prompts
promptfile list prompts/ --recursive

# Render a prompt with variables (for manual testing)
promptfile render prompts/summarise.prompt.yaml \
    --var tone=concise \
    --var article="The quick brown fox."
```

---

## Git Integration

Because prompt files are plain YAML, they integrate naturally with git:

```bash
# See what changed in your prompts
git diff HEAD~1 prompts/

# Blame a specific line
git blame prompts/summarise.prompt.yaml

# Review prompt changes in a PR — it's just YAML
```

**Recommended project layout:**

```
my-project/
├── prompts/
│   ├── summarise.prompt.yaml
│   ├── code-review.prompt.yaml
│   ├── customer-support.prompt.yaml
│   └── v2/
│       └── summarise.prompt.yaml   ← in-progress update
├── src/
│   └── ...
└── tests/
    └── test_prompts.py
```

---

## Testing Prompts

Use `promptfile` with [genassert](https://pypi.org/project/genassert/) for semantic prompt testing:

```python
import pytest
from promptfile import load
from genassert import assert_intent, assert_no_hallucination

PRODUCT_FACTS = [
    "The price is $49/month",
    "There is a 14-day free trial",
]

def test_support_prompt():
    prompt = load("prompts/customer-support.prompt.yaml")
    rendered = prompt.render(customer_message="How much does it cost?")

    # Call your LLM here...
    response = my_llm(rendered.to_messages())

    assert_intent(response, "pricing information")
    assert_no_hallucination(response, PRODUCT_FACTS)
```

---

## CI Integration

```yaml
# .github/workflows/prompts.yml
name: Validate Prompts

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install "promptfile[yaml]"
      - run: promptfile validate prompts/ --recursive
```

---

## Framework Compatibility

`promptfile` outputs plain Python dicts — it works with every LLM client:

```python
messages = rendered.to_messages()
# → [{"role": "system", "content": "..."}, {"role": "user", "content": "..."}]

# OpenAI
openai_client.chat.completions.create(model="gpt-4o", messages=messages)

# Anthropic
anthropic_client.messages.create(model="claude-opus-4-6", messages=messages[1:], system=messages[0]["content"])

# LiteLLM
litellm.completion(model="gpt-4o", messages=messages)

# LangChain
from langchain_core.messages import SystemMessage, HumanMessage
lc_messages = [SystemMessage(m["content"]) if m["role"] == "system" else HumanMessage(m["content"]) for m in messages]
```

---

## Configuration

No configuration files needed. Optional environment variables:

| Variable | Default | Description |
|----------|---------|-------------|
| `PROMPTFILE_DIR` | — | Default prompts directory for `registry.load_dir()` |

---

## License

MIT © promptfile contributors

---

## Related Projects

- [genassert](https://pypi.org/project/genassert/) — pytest-native semantic testing for LLM apps
- [PyYAML](https://pyyaml.org/) — YAML parsing
- [LiteLLM](https://github.com/BerriAI/litellm) — unified LLM client

---

*promptfile is the missing piece between your prompts and your version control.*  
*Stop managing prompts as strings. Start managing them as files.*
