Variants
A variant is a named alternative body for a prompt that shares the same declared variables,
role, and output_model. Variants are the mechanism for parallel, coexisting wordings of the same
prompt (concise, formal, an experiment arm) — the caller selects one by name at render time.
This guide covers declaring variants in a prompt document and selecting one at render. To add
a variant to an already-constructed Prompt at runtime, see
Deriving a prompt.
The default arm
Section titled “The default arm”Every prompt has an implicit default arm: the root body. It is surfaced under the reserved
variant name "default", and it renders when no variant is selected
(variant = None / omitted). A variant literally named default is rejected at construction — that
name is reserved for the root body.
Declaring variants in the document
Section titled “Declaring variants in the document”Add a variants map to the prompt definition. Each entry has its own body and may carry an
opaque per-variant metadata map. Variants share the prompt’s variables, role, and
output_model — a variant that tries to redefine those is rejected (it differs only in
body + metadata).
name: summaryrole: userbody: "Summarise the following article in {{ max_words }} words:\n\n{{ article }}"variables: article: type: string trusted: false max_words: type: integer trusted: truevariants: concise: body: "In one sentence, summarise: {{ article }}" structured: body: "Summarise {{ article }} as a title, three bullets, and a one-line conclusion." metadata: group: experiment-q4 # opaque to the library — the caller interprets it{ "name": "summary", "role": "user", "body": "Summarise the following article in {{ max_words }} words:\n\n{{ article }}", "variables": { "article": { "type": "string", "trusted": false }, "max_words": { "type": "integer", "trusted": true } }, "variants": { "concise": { "body": "In one sentence, summarise: {{ article }}" }, "structured": { "body": "Summarise {{ article }} as a title, three bullets, and a one-line conclusion.", "metadata": { "group": "experiment-q4" } } }}name = "summary"role = "user"body = "Summarise the following article in {{ max_words }} words:\n\n{{ article }}"
[variables.article]type = "string"trusted = false
[variables.max_words]type = "integer"trusted = true
[variants.concise]body = "In one sentence, summarise: {{ article }}"
[variants.structured]body = "Summarise {{ article }} as a title, three bullets, and a one-line conclusion."metadata = { group = "experiment-q4" } # opaque to the library — the caller interprets itThis prompt has three arms: the default (the root body), concise, and structured. All three see
the same article / max_words variables — the agreement check covers every arm, and a variant
body that referenced an undeclared variable would fail at construction.
Constructing inline from a shape object
Section titled “Constructing inline from a shape object”The same three-arm prompt can be built directly from a typed shape object — no text to parse. The
variants map mirrors the document form one-to-one, and construction runs the same validation
(agreement over every arm, reserved-name check).
//! Constructing a three-arm prompt inline from a typed shape object — the//! `variants` map mirrors the document form one-to-one and runs the same//! validation as `Prompt::from_yaml`. Standalone://! `cargo run --example guides_variants_construct_inline`.
use prompting_press::{Prompt, PromptDefinition};
fn main() -> Result<(), Box<dyn std::error::Error>> { let def: PromptDefinition = serde_json::from_value(serde_json::json!({ "name": "summary", "role": "user", "body": "Summarise the following article in {{ max_words }} words:\n\n{{ article }}", "variables": { "article": { "type": "string", "trusted": false }, "max_words": { "type": "integer", "trusted": true } }, "variants": { "concise": { "body": "In one sentence, summarise: {{ article }}" }, "structured": { "body": "Summarise {{ article }} as a title, three bullets, and a one-line conclusion.", "metadata": { "group": "experiment-q4" } } } }))?;
let summary = Prompt::new(def)?; // same validation as Prompt::from_yaml assert!(summary.variants().contains_key("concise")); // true Ok(())}"""Constructing a three-arm prompt inline from a shape object.
The ``variants`` map mirrors the document form one-to-one and construction runsthe same validation as ``Prompt.from_yaml``. Standalone program; run it directlyor under the example test harness."""
from __future__ import annotations
from prompting_press import Prompt
def main() -> None: summary = Prompt( { "name": "summary", "role": "user", "body": "Summarise the following article in {{ max_words }} words:\n\n{{ article }}", "variables": { "article": {"type": "string", "trusted": False}, "max_words": {"type": "integer", "trusted": True}, }, "variants": { "concise": {"body": "In one sentence, summarise: {{ article }}"}, "structured": { "body": "Summarise {{ article }} as a title, three bullets, and a one-line conclusion.", "metadata": {"group": "experiment-q4"}, }, }, } ) # same validation as Prompt.from_yaml
assert sorted(summary.variants) == ["concise", "structured"]
if __name__ == "__main__": main()/** * Constructing a three-arm prompt inline from a typed shape object — the * `variants` map mirrors the document form one-to-one and construction runs the * same validation as `Prompt.fromYaml`. Standalone program. */
import assert from "node:assert/strict";import { Prompt, type PromptDefinition } from "prompting-press";
const definition: PromptDefinition = { name: "summary", role: "user", body: "Summarise the following article in {{ max_words }} words:\n\n{{ article }}", variables: { article: { type: "string", trusted: false }, max_words: { type: "integer", trusted: true }, }, variants: { concise: { body: "In one sentence, summarise: {{ article }}" }, structured: { body: "Summarise {{ article }} as a title, three bullets, and a one-line conclusion.", metadata: { group: "experiment-q4" }, }, },};
const summary = new Prompt(definition); // same validation as Prompt.fromYaml
assert.deepEqual(Object.keys(summary.variants ?? {}).sort(), ["concise", "structured"]);Selecting a variant at render
Section titled “Selecting a variant at render”Pass the variant name at render; omit it (or pass None / no variant) for the default arm. The
resolved name comes back on RenderResult.variant, and RenderResult.text is that arm’s rendered
body — selecting a different variant produces a different text.
//! Selecting a variant at render: omit the name for the default (root body),//! pass a name for that arm. The resolved name comes back on//! `RenderResult.variant` and the text is that arm's rendered body. Standalone://! `cargo run --example guides_variants_select`.
use garde::Validate;use prompting_press::{GuardConfig, Prompt};use serde::Serialize;use std::fs;
#[derive(Serialize, Validate)]struct SummaryVars { #[garde(length(min = 1))] article: String, #[garde(range(min = 1))] max_words: i64,}
fn main() -> Result<(), Box<dyn std::error::Error>> { let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/examples"); let summary = Prompt::from_yaml(&fs::read_to_string(format!("{dir}/summary_select.yaml"))?)?; let vars = SummaryVars { article: "The Nile floods yearly.".into(), max_words: 20, };
let def = summary.render(&vars, None, &GuardConfig::default(), false)?; // default (root body) let concise = summary.render(&vars, Some("concise"), &GuardConfig::default(), false)?;
assert_eq!(def.variant, "default"); assert_eq!( def.text, "Summarise the following article in 20 words:\n\nThe Nile floods yearly." ); assert_eq!(concise.variant, "concise"); assert_eq!( concise.text, "In one sentence, summarise: The Nile floods yearly." ); Ok(())}An unknown variant name returns Err(ConsumerError::Kernel(..)) with code "unknown_variant".
"""Selecting a variant at render.
Omit the name (or pass ``variant=None``) for the default arm; pass a name forthat arm. The resolved name comes back on ``RenderResult.variant`` and the textis that arm's rendered body. Standalone program."""
from __future__ import annotations
from pathlib import Path
from prompting_press import Promptfrom pydantic import BaseModel, Field
# The caller reads the definition; the library does no file I/O itself.# Resolve the file next to this program (a real app uses its own path)._HERE = Path(__file__).parent
class SummaryVars(BaseModel): article: str = Field(min_length=1) max_words: int = Field(ge=1)
def main() -> None: summary = Prompt.from_yaml((_HERE / "summary.yaml").read_text()) data = {"article": "The Nile floods yearly.", "max_words": 20}
default = summary.render(SummaryVars, data=data) # default (root body) concise = summary.render(SummaryVars, data=data, variant="concise")
assert default.variant == "default" assert ( default.text == "Summarise the following article in 20 words:\n\nThe Nile floods yearly." ) assert concise.variant == "concise" assert concise.text == "In one sentence, summarise: The Nile floods yearly."
if __name__ == "__main__": main()An unknown variant name raises PromptRenderError with code "unknown_variant".
/** * Selecting a variant at render: omit the name for the default (root body), * pass `{ variant }` for that arm. The resolved name comes back on * `RenderResult.variant` and the text is that arm's rendered body. Standalone. */
import assert from "node:assert/strict";import { readFileSync } from "node:fs";import { fileURLToPath } from "node:url";import { Prompt } from "prompting-press";import { z } from "zod";
// The caller reads the definition; the library does no file I/O itself.// Resolve the file next to this program (a real app uses its own path).const defFile = (name: string) => fileURLToPath(new URL(name, import.meta.url));
const SummaryVars = z.object({ article: z.string().min(1), max_words: z.number().int().min(1),});
const summary = Prompt.fromYaml(readFileSync(defFile("summary.yaml"), "utf8"));const data = { article: "The Nile floods yearly.", max_words: 20 };
const def = summary.render(SummaryVars, data); // default (root body)const concise = summary.render(SummaryVars, data, { variant: "concise" });
assert.equal(def.variant, "default");assert.equal( def.text, "Summarise the following article in 20 words:\n\nThe Nile floods yearly.",);assert.equal(concise.variant, "concise");assert.equal(concise.text, "In one sentence, summarise: The Nile floods yearly.");An unknown variant name throws PromptRenderError with code "unknown_variant".
The variant name is a plain string resolved at render time — the set of variants is whatever the prompt document declares, so it is not a compile-time enum. The names are readable from the prompt object, and an unknown name fails loudly, never silently.
Discovering the available variants
Section titled “Discovering the available variants”The variants accessor returns the declared variant map; read its keys to see what is selectable
(the default arm is always available and is not listed there — it is the root body, name "default").
//! Discovering the selectable variants: `variants()` returns the declared//! variant map; read its keys (the default arm is not listed — it is the root//! body, name `"default"`). Standalone://! `cargo run --example guides_variants_discover`.
use prompting_press::Prompt;use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> { let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/examples"); let summary = Prompt::from_yaml(&fs::read_to_string(format!("{dir}/summary.yaml"))?)?;
let mut keys = summary.variants().keys().collect::<Vec<_>>(); // ["concise", "structured"] keys.sort(); assert_eq!(keys, ["concise", "structured"]); assert!(summary.variants().contains_key("concise")); // true Ok(())}"""Discovering the selectable variants.
The ``variants`` accessor returns the declared variant map; read its keys to seewhat is selectable (the default arm is not listed — it is the root body, name``"default"``). Standalone program."""
from __future__ import annotations
from pathlib import Path
from prompting_press import Prompt
# The caller reads the definition; the library does no file I/O itself.# Resolve the file next to this program (a real app uses its own path)._HERE = Path(__file__).parent
def main() -> None: summary = Prompt.from_yaml((_HERE / "summary.yaml").read_text())
assert sorted(summary.variants) == ["concise", "structured"] assert "concise" in summary.variants # True
if __name__ == "__main__": main()/** * Discovering the selectable variants: `variants` is the declared variant map; * read its keys to see what is selectable (the default arm is not listed — it is * the root body, name `"default"`). Standalone program. */
import assert from "node:assert/strict";import { readFileSync } from "node:fs";import { fileURLToPath } from "node:url";import { Prompt } from "prompting-press";
// The caller reads the definition; the library does no file I/O itself.// Resolve the file next to this program (a real app uses its own path).const defFile = (name: string) => fileURLToPath(new URL(name, import.meta.url));
const summary = Prompt.fromYaml(readFileSync(defFile("summary.yaml"), "utf8"));const variants = summary.variants ?? {};
assert.deepEqual(Object.keys(variants).sort(), ["concise", "structured"]);assert.ok("concise" in variants); // truePassing a name that is not declared returns/raises/throws a structured error with code
"unknown_variant" (shown per language above) — bad selections surface immediately.
Per-variant metadata
Section titled “Per-variant metadata”Each variant can carry an opaque metadata map (weights, group labels) for the caller’s selection
logic — the library stores and echoes it but never acts on it. See
Metadata for how per-variant and prompt-level metadata
work and how to read them back.
- Deriving a prompt — add or change a variant on an
already-constructed
Prompt(immutable copy-with-overlay). - Template features — what each variant body may use.
docs current as of 0.2.0