promptlibretto
promptlibretto is a library for authoring declarative model registries: portable prompt blueprints made of sections, selectable items, runtime state, and an explicit assembly_order.
You can build a registry in Python, hydrate it into a final prompt whenever your app needs one, and optionally run that prompt against a provider with response validation. Registries can also be exported to JSON and imported later when you want portable storage or user-authored behavior.
Install
pip install promptlibretto
pip install "promptlibretto[ollama]"
Core Sections
A registry is organized into named sections. Each section owns authored items,
and assembly_order decides which selected item fields become part of the
final prompt.
Assembly tokens use section.field, where field is read from the currently
selected item in that section. They are not raw JSON paths.
Common sections:
| Section | Use it for |
|---|---|
base_context |
Stable scene, task, product, or domain context |
personas |
Who the model should be for this run |
sentiment |
Tone, attitude, stance, or emotional style |
static_injections |
Optional authored blocks you can turn on and off |
runtime_injections |
Per-call facts, alerts, or context passed by your app |
output_prompt_directions |
Output rules: length, format, constraints |
groups |
Reusable lists of examples or directives attached to items |
prompt_endings |
Final instruction text placed at the end of the prompt |
The main Python building blocks are:
Registry- the whole prompt blueprintSection- one named bucket of authored itemsRegistryState/SectionState- per-call choices from that blueprintRoute- an optional named override for assembly, generation, or defaults- Item helpers like
ContextItem,Persona,Sentiment,Group,StaticInjection,RuntimeInjection,OutputDirection, andPromptEnding
Quick Start: Make a Registry
A registry is the model's prompt blueprint. Build it with Python objects, then pass runtime state to select which authored pieces should be assembled for a specific call.
from promptlibretto import (
Engine,
MockProvider,
OutputDirection,
Persona,
Registry,
RegistryState,
Section,
SectionState,
)
rules = Section(
id="output_prompt_directions",
required=True,
items=[
OutputDirection(id="rules", text="Reply briefly."),
],
)
personas = Section(
id="personas",
required=True,
items=[
Persona(id="shy", context="You are nervous about speaking."),
Persona(id="bold", context="You are direct and confident."),
],
)
registry = Registry(
title="Tiny Assistant",
assembly_order=[
"output_prompt_directions",
"personas.context",
],
sections={
rules.id: rules,
personas.id: personas,
},
)
engine = Engine(registry, provider=MockProvider())
# Select one specific authored persona by item ID.
state = RegistryState(sections={
"personas": SectionState(selected="bold"),
})
prompt = engine.hydrate(state)
print(prompt)
Output:
Reply briefly.
You are direct and confident.
You can also let a section choose randomly at hydration time. This keeps the
registry stable while making each call choose one persona from the authored
personas section:
prompt = engine.hydrate({
"personas": {"section_random": True},
})
print(prompt)
Runtime State
The registry is the authored blueprint; runtime state is the per-call set of choices you make from that blueprint. This is what lets one registry produce different prompts without editing the registry itself.
Here is a complete example with a registry that has three sections, then a runtime state that chooses one item from each section and fills a template variable:
from promptlibretto import (
ContextItem,
Engine,
Persona,
Registry,
RegistryState,
Section,
SectionState,
Sentiment,
)
registry = Registry(
title="Tiny Assistant",
assembly_order=[
"base_context.text",
"personas.context",
"sentiment.context",
],
sections={
"base_context": Section(
id="base_context",
items=[ContextItem(id="scene", text="You are in {place}.")],
template_vars=["place"],
),
"personas": Section(
id="personas",
items=[Persona(id="bold", context="You are direct and confident.")],
),
"sentiment": Section(
id="sentiment",
items=[Sentiment(id="warm", context="Use a warm tone.")],
),
},
)
state = RegistryState(sections={
"base_context": SectionState(
selected="scene",
template_vars={"place": "the kitchen"},
),
"personas": SectionState(selected="bold"),
"sentiment": SectionState(selected="warm"),
})
engine = Engine(registry)
prompt = engine.hydrate(state)
print(prompt)
Output:
You are in the kitchen.
You are direct and confident.
Use a warm tone.
State is section-scoped: each top-level key matches a registry section, and the nested value describes what to select or fill in for this one run.
For app code, the dict form is often the most convenient Python form. It has the same shape as serialized state, so it is also easy to receive from an API, save, or replay later:
prompt = engine.hydrate({
"base_context": {
"selected": "scene",
"template_vars": {"place": "the kitchen"},
},
"personas": {"selected": "bold"},
"sentiment": {"selected": "warm"},
})
Export and Rebuild a Registry
Registries are portable by design. After authoring one in Python, export it as JSON-safe data:
data = registry.to_dict(wrap=True)
That exported data is plain JSON-compatible structure:
{
"registry": {
"version": 2,
"title": "Tiny Assistant",
"description": "",
"assembly_order": [
"output_prompt_directions",
"personas.context"
],
"personas": {
"required": true,
"items": [
{
"id": "shy",
"context": "You are nervous about speaking."
},
{
"id": "bold",
"context": "You are direct and confident."
}
]
},
"output_prompt_directions": {
"required": true,
"items": [
{
"id": "rules",
"text": "Reply briefly."
}
]
}
}
}
Later, load that JSON back into a Registry:
registry = Registry.from_dict(data)
engine = Engine(registry)
Registry.from_dict() accepts wrapped JSON ({"registry": {...}}) or a bare
registry dict. The JSON form is useful for storing registries in a repo,
shipping them with an app, or loading user-authored model behavior at runtime.
Assembly Order
assembly_order is a list of render tokens:
Assembly tokens use section.field, where field is read from the currently
selected item in that section. They are not raw JSON paths.
sectionrenders the selected item's primary field.section.fieldrenders a named field on the selected item.section.scalerenders the selected item'sScale.section.groupsrenders groups attached to the selected item.section.groups[group_id]renders one attached group.groups[group_id]renders a top-level reusable group.section[item_id]renders one specific item by ID regardless of selection.section[item_id].fieldrenders one field of a specific item by ID.injectionsrenders active runtime injections.
Example:
[
"output_prompt_directions",
"base_context.text",
"personas.context",
"personas.groups",
"sentiment.context",
"sentiment.scale",
"groups[normal_examples]",
"prompt_endings"
]
Groups
Group is the reusable prompt-snippet container. Attach groups to items with the item's groups field, then render them through assembly tokens.
{
"groups": {
"required": false,
"items": [
{
"id": "warm_examples",
"pre_context": "Tone examples:",
"items": ["That sounds good.", "I'm glad to help."]
}
]
},
"sentiment": {
"required": true,
"items": [
{
"id": "warm",
"context": "Use a warm tone.",
"groups": ["warm_examples"]
}
]
}
}
Array modes live in SectionState.array_modes and support all, none, index:N, indices:A,B, and random:K.
Dynamic Text
Dynamic items can declare template_vars, template_defaults, and conditional fragments.
{
"id": "scene",
"text": "You are in {place}.",
"template_vars": ["place", "weather"],
"template_defaults": {"place": "the room"},
"fragments": [
{
"id": "weather",
"condition": "weather",
"text": "The weather is {weather}."
}
]
}
State values win over template_defaults. A fragment with condition renders only when that section's template variable is non-empty.
Scale
Any selected item with a scale can render through section.scale.
{
"id": "sarcastic",
"context": "Use dry sarcasm.",
"scale": {
"label": "Intensity",
"scale_descriptor": "sarcasm",
"min_value": 1,
"max_value": 10,
"default_value": 5,
"template": "{label}: {value}/{max_value} - {scale_descriptor}."
}
}
The rendered value comes from SectionState.slider, SectionState.slider_random, or the scale's default_value.
Engine
prompt = eng.hydrate(state)
result = await eng.run(state)
async for chunk in eng.stream(state):
...
Engine.run() hydrates the prompt, calls the provider, cleans output, validates it with OutputPolicy, and retries up to generation.retries.
Built-in providers:
MockProviderOllamaProvider
Development
pip install "promptlibretto[dev]"
pytest -q
Release files for promptlibretto 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| promptlibretto-0.8.0.tar.gz | 60.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| promptlibretto-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 135.2 kB
Release files / promptlibretto-0.8.0.tar.gz
| Download URL | promptlibretto-0.8.0.tar.gz |
|---|---|
| Size | 60.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1b077cf08302277547f7dcf083a743d94bf96ba148ed7c373c0f818ca6343973
|
|
BLAKE2b-256 checksum How to use checksums |
207694ec483da957295bea682ffd6588a0255ec8421553486448662abe69ca45
|
| 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 26, 2026.
Transparency logRelease files / promptlibretto-0.8.0-py3-none-any.whl
| Download URL | promptlibretto-0.8.0-py3-none-any.whl |
|---|---|
| Size | 74.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
29c86b90d64b7a5e9d119fc71c585c30333d88acef66330d68881703f7473db0
|
|
BLAKE2b-256 checksum How to use checksums |
f9f5a3ff0687e78a79cd580c73910b14133099f08f8fc42fd717e33d8fbb2c70
|
| 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 26, 2026.
Transparency log