Skip to main content

Engine

Struct Engine 

Source
pub struct Engine { /* private fields */ }
Expand description

The core NLG engine. Holds a language implementation, template registry, and immutable configuration. All per-render mutable state lives in Session, which callers pass into render methods.

Implementations§

Source§

impl Engine

Source

pub fn new(language: impl Language + 'static) -> Self

Create a new engine with the given language implementation.

Source

pub fn language_preference(self, lang: impl Into<String>) -> Self

Set the BCP-47 language code that variant selection should prefer. When templates are registered with Engine::register_template_with_language, the engine picks variants whose language matches this preference; if none match, it falls back to language-untagged variants, then to any registered variant.

Source

pub fn set_language_preference(&mut self, lang: impl Into<String>)

Update the BCP-47 language code used to prefer language-tagged variants without rebuilding the engine or dropping registered templates.

Source

pub fn style_preference(self, style: impl Into<String>) -> Self

Set the free-form style tag that variant selection should prefer. Style tags are application-defined strings such as "executive", "technical", or "customer". The fallback chain mirrors language: preferred style first, then unstyled variants, then any registered variant for the already-selected language bucket.

Source

pub fn set_style_preference(&mut self, style: impl Into<String>)

Update the style tag used to prefer style-tagged variants without rebuilding the engine or dropping registered templates.

Source

pub fn strictness(self, strictness: Strictness) -> Self

Set the strictness mode for missing slot handling.

Source

pub fn variation(self, variation: Variation) -> Self

Set the variation strategy for template selection.

Source

pub fn salience_thresholds(self, thresholds: SalienceThresholds) -> Self

Set the thresholds for automatic salience derivation from context.

Source

pub fn style_profile(self, profile: StyleProfile) -> Self

Apply a StyleProfile — a declarative voice configuration that biases the engine’s existing rendering decisions toward a target register without breaking determinism. Setting the neutral profile (StyleProfile::neutral()) is byte-for-byte equivalent to never calling this method.

Source

pub fn current_style_profile(&self) -> &StyleProfile

Read the currently-applied StyleProfile. Returns the neutral profile if none was explicitly set.

Source

pub fn refine(self, config: RefineConfig) -> Self

Apply a RefineConfig — opts the engine into the retrospective refine pass on DocumentPlan::render. RefineConfig::off() (the default) is a no-op; the render path produces byte-identical output to its non-refined form when the config is off.

Source

pub fn current_refine_config(&self) -> &RefineConfig

Read the currently-applied RefineConfig.

Source

pub fn register_entity(&mut self, descriptor: EntityDescriptor)

Register an entity descriptor for referring-expression generation (REG). When the engine produces a Full form reference via the {name|refer} pipe, it consults registered entities — if the target shares its type with other registered entities, the Dale & Reiter incremental algorithm selects the shortest set of distinguishing attributes to include as premodifiers.

Entities not registered here still render with just type + name.

§Example
use prosaic_core::{Context, Engine, EntityDescriptor, Value};
use prosaic_grammar_en::English;

let mut engine = Engine::new(English::new());
engine.register_entity(
    EntityDescriptor::new("UserService", "class")
        .with_attribute("layer", "domain"),
);
engine.register_entity(
    EntityDescriptor::new("AuthService", "class")
        .with_attribute("layer", "infra"),
);

engine.register_template("t", "{name|refer} was modified").unwrap();
let mut ctx = Context::new();
ctx.insert("entity_type", Value::String("class".into()));
ctx.insert("name", Value::String("UserService".into()));

let mut session = prosaic_core::Session::new();
assert_eq!(
    engine.render(&mut session, "t", &ctx).unwrap(),
    "The domain class UserService was modified."
);
Source

pub fn attribute_preference(self, order: Vec<String>) -> Self

Set the preferred attribute walking order for REG. Attributes earlier in the list are tried first; unknown attributes are ignored. Attributes not mentioned here still participate but fall to the end, in the order they were registered on the target entity.

Typical use: prefer semantic attributes (“layer”, “scope”) over physical ones (“color”) when disambiguating code entities.

Source

pub fn reg_algorithm(self, algo: RegAlgorithm) -> Self

Select the REG algorithm used when rendering Full-form references via {name|refer}.

The default is RegAlgorithm::DaleReiter, which selects distinguishing unary attributes only. Use RegAlgorithm::GraphBased to enable the Krahmer 2003 greedy algorithm, which also considers labeled relations registered via EntityDescriptor::with_relation.

Source

pub fn reference_time(self, unix_secs: i64) -> Self

Override the engine’s “now” reference for relative-time rendering. Useful for tests and for rendering a report “as of” a specific point in time. The value is seconds since Unix epoch.

If not set, the engine reads SystemTime::now() on each call to the {timestamp|relative} pipe.

§Example
use prosaic_core::{Context, Engine, Value};
use prosaic_grammar_en::English;

let now = 1_700_000_000;
let mut engine = Engine::new(English::new()).reference_time(now);
engine.register_template("t", "The change landed {ts|relative}").unwrap();

let mut ctx = Context::new();
ctx.insert("ts", Value::Number(now - 86400 - 3600));
let mut session = prosaic_core::Session::new();
assert_eq!(engine.render(&mut session, "t", &ctx).unwrap(), "The change landed yesterday.");
Source

pub fn register_partial( &mut self, name: &str, source: &str, ) -> Result<(), ProsaicError>

Register a reusable template fragment under name. Inside any template, {>name} expands inline to the partial’s content at render time. Partials share the same template syntax as the rest of the engine — slots, pipes, conditionals, and nested partials all work inside a partial.

Example:

engine.register_partial(
    "impact_tail",
    "{?consumer_count}, affecting {consumer_count} \
     {consumer_count|pluralize:consumer}{/?}",
)?;
engine.register_template("code.modified", "{name|refer} was modified{>impact_tail}")?;
engine.register_template("code.renamed",  "{name|refer} was renamed to {new_name}{>impact_tail}")?;
Source

pub fn smart_quotes(self, enabled: bool) -> Self

Enable typographic (“smart”) quote substitution on rendered output. Straight " becomes curly \u{201C}/\u{201D}; straight ' becomes \u{2018}/\u{2019}; apostrophes inside words (Alice’s, it’s) become U+2019. Off by default — opt in for human-readable prose output, leave disabled for code-like outputs.

§Example
use prosaic_core::{Context, Engine, Value};
use prosaic_grammar_en::English;

let mut engine = Engine::new(English::new()).smart_quotes(true);
engine.register_template("t", r#"Alice said "hello""#).unwrap();
let mut session = prosaic_core::Session::new();
let out = engine.render(&mut session, "t", Context::new()).unwrap();
assert!(out.contains('\u{201C}'));
assert!(out.contains('\u{201D}'));
Source

pub fn max_sentence_length(self, max_chars: usize) -> Self

Cap the character length of any single rendered sentence. When a sentence exceeds max_chars, the engine splits it at the latest natural boundary (subordinate clauses introduced by “which”, “affecting”, “impacting”, “requiring”; list prefixes like “including”; em-dashes; explicit sentence breaks) and wraps the remainder as a follow-up sentence with a light grammatical fix-up.

If no natural boundary exists inside the budget the sentence passes through unchanged — we never chop mid-word.

§Example
use prosaic_core::{Context, Engine};
use prosaic_grammar_en::English;

let mut engine = Engine::new(English::new()).max_sentence_length(60);
engine.register_template(
    "t",
    "The class UserService was renamed to AccountService, \
     which impacts 6 consumers",
).unwrap();

let mut session = prosaic_core::Session::new();
let out = engine.render(&mut session, "t", Context::new()).unwrap();
assert!(out.contains("This impacts 6 consumers"));
Source

pub fn sentence_rhythm(self, enabled: bool) -> Self

Toggle the sentence-rhythm cadence penalty layered on top of choose-best scoring (Variation::Seeded/Random). Enabled by default — the penalty discourages picking template variants whose length closely matches recently emitted sentences, biasing prose toward higher length variance / burstiness.

Disable this when downstream tooling needs the historical scoring behaviour (word-repetition only) — e.g. golden-output regressions taken before the rhythm pass landed.

Source

pub fn register_antonym(&mut self, negative: &str, positive: &str)

Register a positive-framing antonym for a negative verb phrase. The {phrase|negated} pipe will prefer the registered positive form (e.g. “remained unchanged”) over the default “not {phrase}” fallback (“was not modified”).

Matching is case-insensitive.

Source

pub fn register_synonyms(&mut self, group: &[&str])

Register a group of synonym words for elegant variation. The {word|syn} pipe will look up the input word in the registered groups and pick whichever synonym from the group has appeared least recently in output. Ties break toward registration order.

Example:

engine.register_synonyms(&["consumer", "dependent", "caller"]);
// Template: "{count} {consumer|syn}{count|pluralize:}"
// First render emits "consumer(s)"; next "dependent(s)"; next "caller(s)".
Source

pub fn with_faithfulness_gate(self, threshold: f32) -> Self

Enable a runtime faithfulness gate on every render* call.

When set, each rendered output is scored against its input Context and the selected template’s literal tokens using PARENT-style precision + polarity checking. If the score’s precision falls below threshold OR polarity tokens mismatch between source and output, the render returns ProsaicError::FaithfulnessRejection and the session state is restored as if the render had not occurred.

threshold is typically 1.0 (strict: every content token in the output must be sourced from the context or template literals). Values below 1.0 tolerate a fraction of unentailed tokens — useful when the engine legitimately emits hedged phrasings that introduce words not present in the input (e.g. “approximately”, “likely”).

Default: no gate (all renders pass).

§Example
use prosaic_core::{Context, Engine, ProsaicError, Value};
use prosaic_grammar_en::English;

let mut engine = Engine::new(English::new())
    .with_faithfulness_gate(1.0);

engine.register_template("t", "{name} was modified").unwrap();

let mut ctx = Context::new();
ctx.insert("name", Value::String("UserService".into()));
let mut session = prosaic_core::Session::new();
// "modified" is in the template literal — renders faithfully.
assert!(engine.render(&mut session, "t", &ctx).is_ok());
Source

pub fn language(&self) -> &dyn Language

Get a reference to the language implementation.

Source

pub fn register_template( &mut self, key: &str, source: &str, ) -> Result<(), ProsaicError>

Register a template string under a key with Medium salience. Multiple templates registered under the same key become alternatives for variation at that salience level.

§Example
use prosaic_core::{Context, Engine, Session, Value};
use prosaic_grammar_en::English;

let mut engine = Engine::new(English::new());
engine.register_template(
    "count.items",
    "You have {n} {n|pluralize:item}",
).unwrap();

let mut ctx = Context::new();
ctx.insert("n", Value::Number(3));
let mut session = Session::new();
assert_eq!(engine.render(&mut session, "count.items", &ctx).unwrap(), "You have 3 items.");
Source

pub fn register_template_at( &mut self, key: &str, source: &str, salience: Salience, ) -> Result<(), ProsaicError>

Register a template at a specific salience level. The engine selects templates at the salience matching the rendered event’s magnitude.

Source

pub fn register_template_with_language( &mut self, key: &str, source: &str, language: Option<&str>, ) -> Result<(), ProsaicError>

Register a template variant tagged with a BCP-47 language code. Variants registered with None language are language-agnostic fallbacks. The engine’s Engine::language_preference biases variant selection: when a preference is set and any variant matches it, the engine picks among matching variants only.

Source

pub fn register_template_with_style( &mut self, key: &str, source: &str, style: Option<&str>, ) -> Result<(), ProsaicError>

Register a template variant tagged with a free-form style. Variants registered with None style are unstyled fallbacks. The engine’s Engine::style_preference biases variant selection after language filtering and before salience filtering.

Source

pub fn register_template_with_language_and_style( &mut self, key: &str, source: &str, language: Option<&str>, style: Option<&str>, ) -> Result<(), ProsaicError>

Register a template variant tagged with both language and style.

Source

pub fn register_template_with_language_at( &mut self, key: &str, source: &str, salience: Salience, language: Option<&str>, ) -> Result<(), ProsaicError>

Salience-aware companion to Engine::register_template_with_language.

Source

pub fn register_template_with_style_at( &mut self, key: &str, source: &str, salience: Salience, style: Option<&str>, ) -> Result<(), ProsaicError>

Salience-aware companion to Engine::register_template_with_style.

Source

pub fn register_template_with_language_and_style_at( &mut self, key: &str, source: &str, salience: Salience, language: Option<&str>, style: Option<&str>, ) -> Result<(), ProsaicError>

Register a template variant with explicit salience, language, and style tags.

Source

pub fn register_template_with_schema<T>( &mut self, key: &str, source: &str, ) -> Result<(), ProsaicError>

Register a template and cross-check every slot’s inferred type against the static schema of T, a type that implements IntoContext + HasProsaicSchema.

Slot types inferred from pipe chains (e.g. {count|pluralize:item} implies count: Number) must be compatible with T’s schema. Use this when loading templates dynamically (from JSON, disk, etc.) and you want the same strong guarantees the prosaic_template! macro provides at compile time.

Source

pub fn has_template(&self, key: &str) -> bool

Check whether a template is registered under the given key.

Pure read, no mutation. Useful for callers that want to skip rendering when no template matches (e.g. tracing bridges where not every event type has a registered template).

§Example
use prosaic_core::Engine;
use prosaic_grammar_en::English;

let mut engine = Engine::new(English::new());
engine.register_template("greet", "Hello {name}").unwrap();

assert!(engine.has_template("greet"));
assert!(!engine.has_template("farewell"));
Source

pub fn context_salience(&self, ctx: &Context) -> Salience

Compute the salience for a context using this engine’s thresholds.

Source

pub fn render( &self, session: &mut Session, key: &str, context: impl IntoContext, ) -> Result<String, ProsaicError>

Render a registered template with the given context.

The session tracks discourse state across calls: entity mentions, template history, word frequency. Each call benefits from context established by previous calls. Use session.reset() between unrelated sequences.

Render is transactional: if any step fails (missing slot in Strict mode, unknown pipe, etc.), the discourse state is rolled back to what it was before the call. A caller that catches the error sees no residue from the failed render in subsequent output.

§Example
use prosaic_core::{Context, Engine, Session, Value, Variation};
use prosaic_grammar_en::English;

let mut engine = Engine::new(English::new()).variation(Variation::Fixed);
engine.register_template("greet", "Hello {name}").unwrap();

let mut session = Session::new();
let mut ctx = Context::new();
ctx.insert("name", Value::String("world".into()));
assert_eq!(engine.render(&mut session, "greet", &ctx).unwrap(), "Hello world");
Source

pub fn score_variants( &self, session: &mut Session, key: &str, context: impl IntoContext, ) -> Result<Vec<VariantScore>, ProsaicError>

Score every registered variant for key against context, without committing any render. Returns one VariantScore per variant that matches the context’s salience bucket, with the choose-best score the engine would compute and a selected flag marking which one render() would currently emit.

Useful for template-author diagnostics (“why does variant B always win?”) and vocab-module lints. Does not mutate discourse state — the function snapshots and restores state around candidate rendering.

Source

pub fn render_inline( &self, session: &mut Session, source: &str, context: impl IntoContext, ) -> Result<String, ProsaicError>

Render a one-off template string (not registered) with the given context.

Inline templates are state-isolated: they do not participate in discourse tracking (no connectives, no entity mentions, no list-style cycle advancement, no plural-focus flag changes) and do not consume template-variant / round-robin counters. The only side effect on the real session is that output words are recorded for repetition scoring, and only when the render succeeds — a failed inline render leaves the session exactly as it was before the call.

Implementation: render into a cloned session and discard it. On success, record output words on the caller’s session.

Source

pub fn render_batch( &self, session: &mut Session, events: &[(&str, Context)], ) -> Result<String, ProsaicError>

Render a batch of events as a cohesive paragraph.

Each event is rendered sequentially through render(), which means the discourse system produces natural cross-sentence flow via referring expressions, connectives, template anti-repeat, and list style cycling.

Additionally, consecutive events sharing a template key but with different entities are aggregated by combining subjects: “UserService and AuthService were renamed” instead of two sentences.

§Example — clause reduction across same-entity events
use prosaic_core::{Context, Engine, Value};
use prosaic_grammar_en::English;

let mut engine = Engine::new(English::new());
engine.register_template("renamed", "{name|refer} was renamed").unwrap();
engine.register_template("modified", "{name|refer} was modified").unwrap();
engine.register_template("moved", "{name|refer} was moved").unwrap();

let mut ctx = Context::new();
ctx.insert("entity_type", Value::String("class".into()));
ctx.insert("name", Value::String("UserService".into()));
let events: Vec<(&str, Context)> = vec![
    ("renamed", ctx.clone()),
    ("modified", ctx.clone()),
    ("moved", ctx.clone()),
];

let mut session = prosaic_core::Session::new();
let out = engine.render_batch(&mut session, &events).unwrap();
assert_eq!(
    out,
    "The class UserService was renamed, modified, and moved."
);
Source

pub fn render_batch_with_relations( &self, session: &mut Session, events: &[(&str, Context, Option<RstRelation>)], ) -> Result<String, ProsaicError>

Render a batch of events where each event carries an optional RST relation describing its rhetorical link to the preceding event.

When a relation is present on events[i] (i ≥ 1), the corresponding discourse marker is prepended to that sentence instead of a plain space — e.g. “However, the class Foo was modified.” — and the leading determiner of the rendered sentence is lowercased so the marker’s capitalisation leads naturally.

When all relations are None, this method delegates to Engine::render_batch so aggregation (clause-reduction, subject-aggregation) still applies.

Source

pub fn render_explained( &self, session: &mut Session, key: &str, context: impl IntoContext, ) -> Result<RenderExplanation, ProsaicError>

Render a template and return both the output and a RenderExplanation describing the decisions the engine made.

Functionally equivalent to render() — discourse state advances the same way; any error rolls back the same way — but each step’s diagnostic is also captured. Use this for debugging template behavior: “why did variant B win?”, “was this entity reference a pronoun or short-name?”, “did the length budget split the output?”.

Source

pub fn render_iter<'a>( &'a self, session: &'a mut Session, events: &'a [(&'a str, Context)], ) -> RenderIter<'a>

Iterator form of Engine::render_batch. Yields each sentence (or aggregated run) as it is produced, so callers concerned with time-to-first-sentence can stream output instead of waiting for the full batch. Each .next() call produces one sentence — which may correspond to multiple events (when aggregation or clause reduction fires) — and returns None once the events are exhausted.

Errors are terminal. If any render inside the run fails, the iterator yields Some(Err(_)) exactly once and then returns None on every subsequent call. This keeps semantics predictable even when the failing event sits inside an aggregated, gapped, or same-entity run whose earlier sentences already mutated session state — replaying the run after partial success would compound pronoun / anti-repetition state in unsafe ways. If you need error-skipping behaviour, validate templates and contexts up front (e.g. with Engine::score_variants) rather than relying on the iterator to recover.

Source

pub fn new_session(&self) -> Session

Create a new Session compatible with this engine.

Sugar for Session::new(). Equivalent, but clarifies intent at call sites where a reader might wonder which session type to construct.

§Example
use prosaic_core::{Context, Engine, Value};
use prosaic_grammar_en::English;

let mut engine = Engine::new(English::new());
engine.register_template("hello", "Hello {name}!").unwrap();

let mut session = engine.new_session();
let mut ctx = Context::new();
ctx.insert("name", Value::String("world".into()));
assert_eq!(engine.render(&mut session, "hello", &ctx).unwrap(), "Hello world!");

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.