Skip to main content

Deterministic, content-addressed prompt composition — the Peisinoe core (Python reference implementation)

Project description

Peisinoe

Prompt composition with explicit variants and deterministic identity.

Peisinoe is a Python library for building prompts from reusable, typed parts — text and binary alike. Parts can be combined, selected, and ordered declaratively, so feature flags, A/B variants, and other prompt experiments remain part of the prompt definition rather than being scattered through application code.

Prompts can be authored in Markdown and YAML packages and loaded by name, or constructed directly in Python. Resolving a prompt binds its inputs and produces model-ready messages together with deterministic identities for the selected structure and the exact filled instance. Recorded alongside a model's output, they identify exactly which composition produced it.

The eval layer uses the same structure to enumerate reachable variants, target them, and report coverage. Model execution stays in the application through a small bring-your-own Model interface; the core has zero runtime dependencies.

Why it exists

Prompt iteration often changes both content and the logic that selects or combines it. When that content and logic live inside application code, experiments can require edits across unrelated code paths, variants are harder to exercise systematically, and the exact prompt used for a result may be difficult to reconstruct.

Peisinoe keeps prompt source and composition separate from application flow. Variants remain explicit, reusable parts can be shared, and each resolution carries a content-derived identity.

A taste

Author prompts as files and load them by name — application code loads a package once, then addresses assemblies and units by name:

support.prompt/              # a package (a ".prompt" folder)
├── triage.assembly.yaml       # an assembly: named parts wired to units
├── system/unit.yaml         # a unit: params + sections (Select, Child, …)
└── user_message.md          # a unit: a bare Markdown file *is* a unit
from peisinoe_tools.storage import load

pkg = load("support.prompt")                      # point at the folder once
prompt = pkg["triage"]                            # get the assembly by name
prompt.resolve({"tier": "pro", "question": "…"}).materialize()

The same structures can be constructed directly in Python:

import peisinoe_core as p

support = p.Unit("support", params=("tier",), sections=(
    p.Static("hi", "Hello!"),
    p.Select("policy", on="tier", cases={
        "free": p.Static("f", "Basic help."),
        "pro":  p.Static("pp", "Priority help.", tags=("safety",)),
    }, default="free"),
))
r = support.resolve({"tier": "pro"})
r.structure_hash   # the variant (values excluded) — your attribution key
r.materialize()    # typed, role-tagged Messages of typed Parts

A loaded unit compiles to the same identity as the hand-built one (loaded.full_version() == built.full_version()) — the loader is a compiler.

Target an eval at a variant, then see what you missed:

from peisinoe_tools.evals import EvalSpec, Target, Contains, plan, coverage

spec = EvalSpec("mentions_help", Target(all_of=("safety",)), Contains("help"))
pl = plan(support, [spec])
coverage(pl).branches_uncovered   # ('support > policy=free',)  — the free branch is untested

Beyond text

Prompts aren't only text. A single Markdown file can define a complete multimodal prompt — a scalar hole and a typed binary hole:

---
params:
  instruction:
  image:
    type: blob
---
{{instruction}}

{{image}}
edit = load("image_edit.prompt").unit("edit")
r = edit.resolve({"instruction": "turn the cat into a tiger",
                  "image": p.Blob("image/png", png_bytes)})

A binary hole also takes an ordered list ([img_a, img_b, img_c] — a model's "up to N reference images" is one param), and 1-vs-N values is the same structure_hash: count, order, and content are instance data.

Design model

Peisinoe uses the following model:

  • Prompt as a program, not a string — it branches, and the branch space is enumerable.
  • Identity is content-addressed; names and versions are addressing — identity is derived from the canonical template or resolved representation, not assigned by a label. A version: label is a mutable human pointer (like a git tag); hashes are computed, never written into files.
  • Binary is prompt content, not an attachment — images, PDFs, and external refs fill typed holes and participate in prompt identity and redaction; Ref values also support deferred loading. A runnable image-editing integration is included in examples/integrations/.
  • Target by intent, not position — evals point at tags (what a block is about), not block names (where it is).
  • You ask "what did I cover?", not just "did it pass?" — because branches are explicit, coverage can report which cases an eval plan exercised.
  • Record, don't control — the eval layer scores what a bring-your-own model returns; it never drives the model (so it works unchanged with multimodal output, agents, even diffusion models).

When (not) to use it

Use it when you have prompt variation worth tracking — variants, tiers, A/B flags, evals you want to target and cover, or a need to attribute results to an exact prompt.

It won't ship a provider adapter, route to providers, stream, or score by token log-probabilities (a deliberate boundary — it scores observed output, not model internals). You bring the Model; the eval layer calls it and scores what it returns.

Documentation

  • Tutorial — the whole loop end to end in ~15 minutes: build a branching prompt, see its identity, author it on disk, target evals, and find the branch you missed.
  • Core — composition and the identity model (typed parts, structure_hash / instance_hash, branching, redaction).
  • Storage — author prompts as .prompt folders and load them by name (unit.yaml / *.assembly.yaml, params, binary holes, versions, imports).
  • Evals — targeting by tags, coverage, datasets, scoring.
  • examples/ — runnable scripts: zero-setup library demos (core/, evals/) and real-model integrations (integrations/).

Install (development)

Requires Python 3.11+. Dual-licensed: MIT or Apache-2.0, at your option. Not yet on PyPI — install from a checkout:

python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,storage]"   # ".[storage]" adds the loader's YAML parser (or bring your own)
pytest
ruff check . && mypy peisinoe_core peisinoe_tools

Knowledge flows downward only: peisinoe_core (zero-dependency composition + identity), peisinoe_tools.evals (the eval layer built on it), and peisinoe_tools.storage (an opt-in loader that compiles .prompt folders to the same core objects). The core stays dependency-free; only storage has an optional extra.

The name

Peisinoë is one of the Sirens — the singers whose composed voices drew sailors in. A fitting namesake for a library about composing prompts (and, with luck, a little luring). 🙂


Alpha / reference implementation. The hashing is locked by golden vectors (testvectors/core.json) for cross-language conformance — regenerate after an intentional hashing change and bump HASHSPEC (python -m tests.gen_vectors).

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

peisinoe-0.1.0.tar.gz (181.2 kB view details)

Uploaded Source

Built Distribution

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

peisinoe-0.1.0-py3-none-any.whl (105.3 kB view details)

Uploaded Python 3

File details

Details for the file peisinoe-0.1.0.tar.gz.

File metadata

  • Download URL: peisinoe-0.1.0.tar.gz
  • Upload date:
  • Size: 181.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for peisinoe-0.1.0.tar.gz
Algorithm Hash digest
SHA256 7647c23454e2c59c09f0e3b51d3aeb2527e705ec4e77dcd73ab6b7f78a73d88a
MD5 896b92b9c28a4affd079f9d621f0e957
BLAKE2b-256 4fe0b8bab3be8f8256f5f951bdc8d0a346dead82b574ce05e0a0cc3d5080695d

See more details on using hashes here.

File details

Details for the file peisinoe-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: peisinoe-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 105.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for peisinoe-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1dae4402e05b3c4e1fe0d551d1cd9a682a6dddab75c17ec16edcf37c6f84e70d
MD5 d49abfdabcfb896dc3d2efbb069a5dcf
BLAKE2b-256 973fa9a80ed9788cddcc3aad1e971f03ef6b20ed36c2b6c8ed145e276d608cd8

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