Skip to content
Prompting Press v0.2

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.

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.

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).

summary.yaml
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 # opaque to the library — the caller interprets it

This 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.

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).

guides_variants_construct_inline.rs
//! 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(())
}

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.

guides_variants_select.rs
//! 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".

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.

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").

guides_variants_discover.rs
//! 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(())
}

Passing a name that is not declared returns/raises/throws a structured error with code "unknown_variant" (shown per language above) — bad selections surface immediately.

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.

docs current as of 0.2.0