Skip to main content

Git-native prompt engineering library with Registry/Lab dual-state model

Project description

PromptTree

PromptTree

Git-native prompt engineering library. Version and manage your LLM prompts like code — with a branching registry, Jinja2 templating, AES-256 encryption, and a visual UI.


Install

pip install prompttree          # core library + CLI
pip install "prompttree[ui]"    # with Streamlit UI

Requires Python 3.10+.


Quick Start

prompttree init    # creates .prompttree/ workspace in current directory
import prompttree as pt

engine = pt.PromptTree()

# Save a prompt and label it
# Recommendation (prompttree ui) : Use UI for saving rapid prompt iterations
node = engine.save(
    content="Summarise the following in {{language}}: {{text}}",
    name="Summariser",
    display_name="v1",
    model="gpt-4o",
    temperature=0.7,
    label="prod",
)

# Resolve by label (scoped to the prompt family name)
prompt = engine.get_prompt("Summariser", label="prod", vars={"language": "French", "text": "the quarterly report"})
print(prompt)
# → "Summarise the following in French: the quarterly report"

# Resolve latest node in the family (no label needed)
prompt = engine.get_prompt("Summariser", vars={"language": "Spanish", "text": "the quarterly report"})

# Resolve directly by node ID
prompt = engine.get_prompt(node.id, by="id", vars={...})

Visual UI

Launch a browser-based DAG explorer to create, browse, and branch prompts:

prompttree ui

Opens at http://localhost:8501.

  • Browse all prompt families from the dropdown
  • Click any node in the DAG to inspect its content and metadata
  • Branch directly from any node
  • Assign labels to nodes

New Prompt/Branch

Prompt Iteration


Core Concepts

Registry

The Registry is the versioned store for your prompts. Each prompt is saved as a node — a YAML file containing the content, model settings, and a pointer to its parent node.

Nodes form a DAG (directed acyclic graph): you branch from any existing node to create a new version, preserving the full lineage.

Summariser
├── v1  ←  label: prod
├── v2  (branched from v1)
└── v3  (branched from v1)

All nodes live in .prompttree/registry/nodes/ and are git-tracked — diffs, history, and blame work out of the box.

Labels

Labels are human-readable aliases pointing to a specific node within a prompt family. They are scoped to the family name, so two families can each have their own prod label without conflict.

engine.set_label("Summariser", "prod", v2.id)
engine.set_label("Classifier", "prod", other_node.id)  # independent — no collision

engine.get_prompt("Summariser", label="prod", vars={...})
engine.get_prompt("Classifier", label="prod", vars={...})

Labels are stored in .prompttree/registry/labels.json as a nested map:

{
  "Summariser":  { "prod": "a1b2c3...", "staging": "d4e5f6..." },
  "Classifier":  { "prod": "g7h8i9..." }
}

Resolving Prompts

get_prompt has three resolution modes:

Call Resolves to
get_prompt("My Prompt", label="prod") Node with the prod label in that family
get_prompt("My Prompt") Most recently created node in the family
get_prompt(node_id, by="id") Exact node by ID

Jinja2 Templating

Prompt content supports Jinja2 {{ variable }} syntax. Variables are injected at render time via get_prompt().

node = engine.save("Translate {{text}} to {{language}}.", name="Translator")
prompt = engine.get_prompt("Translator", vars={"text": "hello world", "language": "French"})

API Reference

PromptTree(storage, key)

Parameter Default Description
storage ".prompttree" Path to workspace directory
key None AES-256 decryption key (only needed for encrypted registries)

Methods

# Save & retrieve
engine.save(content, name, display_name, model, temperature, parent_id, tags, label)  RegistryNode
engine.get_prompt(name_or_id, label=None, by=None, vars=None)  str
engine.get_node(node_id)  RegistryNode | None
engine.list_nodes()  list[RegistryNode]
engine.list_names()  list[str]
engine.get_nodes_by_name(name)  list[RegistryNode]

# Labels
engine.get_labels()  dict[str, dict[str, str]]   # {name: {label: node_id}}
engine.set_label(name, label, node_id)

# Bulk operations
engine.delete_family(name)  int     # deletes all nodes + labels for a family
engine.reset()  int                 # wipes the entire registry

# Encryption
engine.lock(key)  int
engine.unlock(key)  int

Branching Versions

Branch from any node by passing parent_id:

v1 = engine.save("Summarise {{text}} briefly.", name="Summariser", label="prod")

v2 = engine.save(
    "Summarise {{text}} briefly. Use bullet points.",
    name="Summariser",
    display_name="v2",
    parent_id=v1.id,
)

# Promote v2 to prod when ready
engine.set_label("Summariser", "prod", v2.id)

Encryption

Encrypt all registry nodes before deploying to CI/production:

prompttree lock --key $PT_KEY      # encrypts all nodes in-place
prompttree unlock --key $PT_KEY    # decrypts back to plaintext

Decryption happens in-memory at render time — the encrypted YAML is never modified:

engine = pt.PromptTree(key="my-secret-key")
prompt = engine.get_prompt("Summariser", label="prod", vars={...})   # decrypts on the fly

Uses AES-256-GCM with a random nonce per encryption. The key is SHA-256 hashed so any string length is accepted.


CLI

prompttree init                        # initialise workspace + update .gitignore
prompttree list                        # list all nodes and labels
prompttree lock --key $PT_KEY          # encrypt registry
prompttree unlock --key $PT_KEY        # decrypt registry
prompttree delete-family <NAME>        # delete all nodes + labels for a prompt family
prompttree delete-family <NAME> --yes  # skip confirmation prompt
prompttree reset                       # wipe the entire registry
prompttree reset --yes                 # skip confirmation prompt
prompttree ui                          # launch the visual UI
prompttree ui --port 8080              # on a custom port

Workspace Layout

.prompttree/
├── registry/
│   ├── nodes/          # one YAML file per prompt node (git-tracked)
│   └── labels.json     # {name: {label: node_id}} map (git-tracked)

.prompttree/.lab/ and .prompttree/.artifacts/ are local-only and automatically added to .gitignore by prompttree init.


Node YAML Format

id: "a1b2c3d4..."
parent_id: null
name: "Summariser"
display_name: "v1"
metadata:
  encrypted: false
  model: "gpt-4o"
  temperature: 0.7
  created_at: "2024-01-01T00:00:00+00:00"
  tags: []
content: "Summarise {{text}} briefly. Use bullet points."

Development

git clone https://github.com/yedhuk/prompttree
cd prompttree
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,ui]"

pytest          # run tests
ruff check prompttree/
mypy prompttree/

License

MIT © Yedhu Krishna

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

prompttree-1.1.3.tar.gz (54.3 kB view details)

Uploaded Source

Built Distribution

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

prompttree-1.1.3-py3-none-any.whl (50.3 kB view details)

Uploaded Python 3

File details

Details for the file prompttree-1.1.3.tar.gz.

File metadata

  • Download URL: prompttree-1.1.3.tar.gz
  • Upload date:
  • Size: 54.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for prompttree-1.1.3.tar.gz
Algorithm Hash digest
SHA256 55b14846326c786e000a8625eba02a058b313c967ceb5f5f9c49e0fed8c101a5
MD5 12085d8e7893fff34dbd3d4a93baa030
BLAKE2b-256 f999ca735f755fbe6c8ed97570288a0a4f27dc03f1040e22b664ce02dfa6e440

See more details on using hashes here.

File details

Details for the file prompttree-1.1.3-py3-none-any.whl.

File metadata

  • Download URL: prompttree-1.1.3-py3-none-any.whl
  • Upload date:
  • Size: 50.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for prompttree-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 25efd0c63030174ef0ce3e1e60b720310f128b63eef428a7c28c733fc1a8e0be
MD5 4135c73d063771ad122b23d813a23d35
BLAKE2b-256 a5b17201006f5629af6968ad6e20a5dcdae3d6a883123258ca163f7cdb1260be

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