Skip to main content

promptfuse

ci

Local prompt registry with a Langfuse-compatible prompt API. Prompts are stored in a YAML tree, in SQLite, or in SQLite with the YAML tree as the seed. promptfuse does not call Langfuse Cloud and does not send traces.

This document is the contract for the public API.

The prompt record and the fetch, label, cache, compile, and fallback rules match Langfuse prompt management. Where Langfuse leaves a local-store choice open, this document defines it.

Install

Requires Python 3.10+.

pip install promptfuse

Client

from promptfuse import Promptfuse

client = Promptfuse(
    yaml_dir="prompts",       # optional YAML store
    sqlite_path="prompts.db", # optional SQLite store
    snapshot_path=None,       # optional disk cache of successful reads
    cache_ttl_seconds=60,     # default; 0 disables the memory cache
)

At least one of yaml_dir and sqlite_path is required. See Stores for which one is read and written.

prompt = client.get_prompt("deep-agent/system")
text = prompt.compile(persona="analyst")

client.create_prompt(
    name="deep-agent/system",
    type="text",
    prompt="You are a {{persona}} research assistant.",
    labels=["production"],
    config={"model": "gpt-4.1", "temperature": 0.2},
    tags=["deep-agent"],
    commit_message="initial system prompt",
)

client.update_prompt(
    name="deep-agent/system",
    version=1,
    new_labels=["staging"],
)

get_prompt follows GET /api/public/v2/prompts/{name}. create_prompt and update_prompt follow the Langfuse Python SDK methods of the same names.

Prompt client

get_prompt, create_prompt, and a fallback all return a prompt client.

Attribute Meaning
name Prompt name. Slashes are folders (deep-agent/system).
version Integer. Store versions start at 1.
type "text" or "chat". Fixed for a name after the first version.
prompt Text prompt: str. Chat prompt: list of messages.
config JSON object versioned with this prompt. {} when omitted.
labels Labels currently pointing at this version.
tags String list. [] when omitted.
commit_message Optional string.
is_fallback True only for a client built from the fallback argument.
variables {{name}} placeholders found in the prompt, in order.

A text prompt's prompt is a string. A chat prompt's prompt is a list of either of:

{"type": "message", "role": "system", "content": "You are a {{persona}}."}
{"type": "placeholder", "name": "chat_history"}

role is any string. promptfuse does not restrict it to system, user, or assistant.

config is an arbitrary JSON object. Typical keys are model, temperature, max_tokens, response_format, tools, and tool_choice. promptfuse stores and returns config. It does not call a model.

Fetching

From How label resolution works:

Call Result
get_prompt(name) Version labeled production.
get_prompt(name, label="staging") Version labeled staging.
get_prompt(name, version=3) Version 3, whatever its labels are.
get_prompt(name, label=..., version=...) InvalidPromptRequest. label and version are mutually exclusive.

A missing name, a missing label, or a missing version raises PromptNotFound. There is no silent fallback from staging to production or latest.

label defaults to "production" only when version is omitted. type defaults to "text". Passing type="chat" for a text prompt, or the reverse, raises PromptNotFound. The default type does not coerce a chat prompt into text.

latest is a real label. It always points at the highest version of that name. Callers fetch it with label="latest". They cannot assign it.

cache_ttl_seconds overrides the client default for that call. fallback is used only as described in Fallback.

Creating a version

create_prompt appends a version. It does not edit an existing version.

  • The first version of a name is 1. The next is max(version) + 1.
  • type must match every existing version of that name. A change raises InvalidPromptRequest.
  • labels moves each listed label onto the new version and off any older version. Omitted labels stay where they are.
  • latest always moves to the new version. An entry of "latest" in labels raises InvalidPromptRequest.
  • config, tags, and commit_message belong to this version.
client.create_prompt(
    name="movie-critic",
    type="text",
    prompt="As a {{criticLevel}} movie critic, do you like {{movie}}?",
    labels=["production"],
)

The same call with type="chat" takes a message list:

client.create_prompt(
    name="movie-critic-chat",
    type="chat",
    prompt=[
        {"role": "system", "content": "You are a {{criticLevel}} movie critic."},
        {"type": "placeholder", "name": "chat_history"},
        {"role": "user", "content": "What should I watch next?"},
    ],
    labels=["production"],
)

A chat item with role and content is stored as {"type": "message", "role": ..., "content": ...}. type may be omitted on input. A placeholder must be {"type": "placeholder", "name": "..."} and must not include role or content.

Moving labels

update_prompt(name, version, new_labels) replaces the labels on that version. Prompt text, config, tags, and commit_message stay as stored.

  • Each name in new_labels is removed from every other version of this prompt, then pointed at version.
  • Labels previously on version and absent from new_labels are removed.
  • "latest" in new_labels raises InvalidPromptRequest. latest stays on the highest version.
  • A version that does not exist raises PromptNotFound.

Rollback is this call. Repoint production at the earlier version:

client.update_prompt(name="movie-critic", version=2, new_labels=["production"])

Compile

compile substitutes {{variable}} the way the Langfuse Python SDK's TemplateParser does.

  • The name is the text between {{ and }}, with surrounding whitespace removed. {{ persona }} and {{persona}} are the same variable.
  • A provided value is inserted with str(value). None is inserted as an empty string.
  • A variable with no matching keyword argument is left unchanged, including its original braces.
  • compile does not require every variable to be present, and it does not reject extra keyword arguments. There is no throw_on_incomplete_variables switch.
prompt.compile(criticLevel="expert", movie="Dune 2")
# "As an expert movie critic, do you like Dune 2?"

Text compile(**kwargs) returns a str.

Chat compile(**kwargs) returns a list of messages:

  • A message gets {{variable}} substitution in content. The result is {"role": ..., "content": ...} with no type key.
  • A placeholder whose name is a keyword argument must be a list of messages. Each dict in that list is copied, and its content is compiled when it is a string. A non-dict item, or a value that is not a list, is appended as {"role": "NOT_GIVEN", "content": str(value)}.
  • A placeholder with no keyword argument stays {"type": "placeholder", "name": ...} in the result.

Prompt references are stored and returned as literal text. promptfuse does not expand @@@langfusePrompt:name=...|version=1@@@ or @@@langfusePrompt:name=...|label=production@@@.

get_langchain_prompt is not part of this package.

Cache

The memory cache matches the Langfuse client cache.

  • The key is (name, "label", label) or (name, "version", version). A label and a version pin do not share an entry.
  • Default TTL is 60 seconds.
  • A fresh entry is returned without reading the store.
  • After the TTL, the stale client is returned immediately and the store is read in the background. A failed refresh keeps the stale client.
  • cache_ttl_seconds=0 skips the memory cache and reads the store on every call.

The cache stores the resolved client, so a label move is visible on the next read after the TTL, not at the moment the label changes. Use cache_ttl_seconds=0 when a call must see the store immediately. label="latest" with a TTL of 0 is the usual development setting.

Fallback

fallback matches guaranteed availability.

get_prompt raises if the memory cache has neither a fresh nor a stale client and every store read fails, and fallback was not passed.

fallback is used only in that case. A successful store read ignores fallback. A stale cache hit ignores fallback and does not build a fallback client.

A string fallback returns a text client. A list fallback returns a chat client. The client uses the requested name, version=0, labels=[], config={}, tags=[], commit_message=None, and is_fallback=True. compile still runs on the fallback body.

prompt = client.get_prompt("movie-critic", fallback="Do you like {{movie}}?")
prompt.is_fallback  # True only when the stores and the cache could not answer

Stores

YAML and SQLite store the same prompt record. The memory cache sits in front of both.

Constructor Source of truth Writes
yaml_dir only YAML YAML
sqlite_path only SQLite SQLite
both SQLite, when it can be opened SQLite

A healthy SQLite database that does not contain the prompt raises PromptNotFound. It does not continue into YAML. YAML is consulted when SQLite cannot be opened or a query fails, and when SQLite was not configured.

YAML

prompts/
├── index.yaml
└── deep-agent/
    └── system/
        ├── v1.yaml
        └── v2.yaml

The directory under yaml_dir is the prompt name. deep-agent/system/v2.yaml is version 2 of deep-agent/system. The file stem must be v plus the integer version. Names must not contain .. or escape yaml_dir.

Version files are immutable. A label change edits index.yaml only.

# prompts/deep-agent/system/v1.yaml
name: deep-agent/system
version: 1
type: text
prompt: |
  You are a {{persona}} research assistant.
config:
  model: gpt-4.1
  temperature: 0.2
tags: ["deep-agent", "system"]
commit_message: initial system prompt
created_at: "2026-09-28T00:00:00Z"
updated_at: "2026-09-28T00:00:00Z"

name and version in the file must match the path. type is text or chat. For chat, prompt is the message list. config, tags, commit_message, created_at, and updated_at may be omitted.

# prompts/index.yaml
prompts:
  deep-agent/system:
    development: 1
    staging: 1
    production: 1

Index values are integers. A label points at one version. latest must not appear in the index; the highest vN.yaml for that name is latest. A version file does not list its labels.

create_prompt writes vN.yaml and updates the index. update_prompt updates the index only. Neither rewrites an existing vN.yaml.

SQLite

Two tables:

CREATE TABLE prompt_version (
    name TEXT NOT NULL,
    version INTEGER NOT NULL,
    type TEXT NOT NULL,
    prompt TEXT NOT NULL,          -- JSON: a string or a message list
    config TEXT NOT NULL,          -- JSON object
    tags TEXT NOT NULL,            -- JSON array of strings
    commit_message TEXT,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    PRIMARY KEY (name, version)
);

CREATE TABLE prompt_label (
    name TEXT NOT NULL,
    label TEXT NOT NULL,
    version INTEGER NOT NULL,
    PRIMARY KEY (name, label),
    FOREIGN KEY (name, version) REFERENCES prompt_version (name, version)
);

prompt_version rows are inserted, never updated. prompt_label is the only mutable data. latest is a row in prompt_label, moved when a version is inserted.

Using both

import_yaml() copies the YAML tree into SQLite.

  • A YAML version that is not in SQLite is inserted.
  • A YAML version that is already in SQLite with the same body, type, config, tags, and commit_message is left as stored.
  • A YAML version whose stored body differs raises InvalidPromptRequest. Versions stay immutable.
  • Labels in index.yaml replace the SQLite label rows for names present in the index. SQLite versions that exist only in SQLite are kept.
  • latest is recomputed as the highest version after the import.

Promptfuse(..., import_yaml=True) runs import_yaml() once at construction. The default is False. Import overwrites SQLite label pointers for the imported names, including labels that were moved at runtime.

Disk snapshot

snapshot_path is a JSON file of prompt clients from successful store reads. It is a cache. It is not edited by hand and it is not a third source of truth.

On a store outage, get_prompt uses the snapshot entry for that cache key when the memory cache is empty. A missing snapshot entry then uses YAML if SQLite failed and yaml_dir is set. A snapshot hit sets is_fallback to False.

The snapshot is updated after a successful SQLite or YAML read. A fallback client is not written to the snapshot.

Errors

Exception When
PromptNotFound Unknown name, label, or version. type does not match the stored prompt. SQLite is healthy and the row is absent.
InvalidPromptRequest label and version both set. type changes on create_prompt. "latest" is assigned by the caller or written in index.yaml. A version file disagrees with its path. import_yaml() finds a body that differs from SQLite. The YAML path escapes yaml_dir.
PromptStoreError SQLite cannot be opened or a query fails, and snapshot, YAML, and fallback do not produce a client.

PromptNotFound and InvalidPromptRequest are not retried through the seed files when SQLite is healthy.

Out of scope

  • Langfuse Cloud, API keys, tracing, and linking a prompt to a generation
  • Expanding @@@langfusePrompt:...@@@ references
  • get_langchain_prompt
  • Protected labels
  • Postgres and other database engines
  • A network server

References

Release files for promptfuse 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for promptfuse 0.1.0
File Size Uploaded
promptfuse-0.1.0.tar.gz 35.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for promptfuse 0.1.0
File Interpreter ABI Platform
promptfuse-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 59.5 kB

Release files / promptfuse-0.1.0.tar.gz

Download URL promptfuse-0.1.0.tar.gz
Size 35.5 kB
Tags Source
SHA-256 checksum
How to use checksums
01dd47135b24176b64e38aedc3c9920fcd554f68e0915afa063de717d4a6b65d
BLAKE2b-256 checksum
How to use checksums
a0abdd84859ece13ae35ede9c2c0d78aaaa3aedf7475d5181d06a510bfb174c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release files / promptfuse-0.1.0-py3-none-any.whl

Download URL promptfuse-0.1.0-py3-none-any.whl
Size 23.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
27f3c9535b3463dab4cc9ea744496c5028c9f8e76421601b4b3db4d729cbddaf
BLAKE2b-256 checksum
How to use checksums
ffbb6ae04dbd06c4ee000b335fc16fb6682e224a21ce4477f02390d29f4cd324
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page