Spec coverage. This chapter is the reference for the Dialog renderer conformance role of OVOS-INTENT-1 §7 over the
.dialogformat of OVOS-INTENT-2 §4.2 (dialog.py), and for the.promptformat of OVOS-INTENT-2 §4.4 (prompt.py, at the end of the chapter). A dialog renderer MUST embed a conformant expander, verify slot-set consistency (§5.5), fill every{name}from caller-supplied values, and MUST NOT emit an unfilled slot (§5.1) —UnfilledSlotis that last MUST.
A .dialog file holds the phrases an assistant may speak for one response.
Rendering a dialog means: pick one phrase, expand its (a|b) / [x]
variety down to a single variant, and fill its {name} slots with values. This
chapter covers the two ways to do it. It implements OVOS-INTENT-2 §4.2.
A .dialog phrase is spoken output, not ASR input, so — unlike the
input-direction roles — it may contain mixed case and punctuation:
# weather.dialog
It is {temperature} degrees.
Right now it's {temperature} degrees out.
(Currently|At the moment) {temperature} degrees.
render() takes an explicit list of phrases and returns one rendered sentence:
from ovos_spec_tools import render
phrases = res.load_dialog("weather", "en-US")
render(phrases, slots={"temperature": 21})
# 'It is 21 degrees.'Slots are filled from the slots dict. The phrase and the (a|b) variant are
chosen at random; pass rng= (anything with a choice method, e.g. a seeded
random.Random) for reproducible output. vocabularies= supplies any
<name> references.
Expansion runs before filling, with slots kept opaque — so a slot value can
never be mis-parsed as grammar. A value of "(a|b)" is filled in literally,
not expanded.
If the chosen phrase has a slot with no value, render() raises
UnfilledSlot — a half-filled phrase must never reach text-to-speech.
render() is fine for one-off use. For a skill that speaks a response
repeatedly, DialogRenderer does better. It is built from a LocaleResources
and a dialog name:
from ovos_spec_tools import DialogRenderer
renderer = DialogRenderer(res, "weather")
renderer.render("en-US", {"temperature": 21})
renderer.render("pt-PT", {"temperature": 22}) # same renderer, another languageIt adds three things over the bare function.
The language is a parameter of render(), not of the constructor — one
renderer serves every language the dialog ships in.
DialogRenderer remembers the phrase it chose last time and avoids picking it
again — tracked per language — so a frequently-spoken response does not
sound mechanical. With two phrases, consecutive renders strictly alternate.
A slot is resolved in a clear order of precedence:
- a value passed to this
render()call; - a default value, set once on the constructor and reused every call;
- a random value from the slot's
.entityvalue set; - otherwise —
UnfilledSlot.
renderer = DialogRenderer(res, "greeting", slots={"assistant": "OVOS"})
renderer.render("en-US")Here {assistant} is filled from the default every time. A {weekday} slot
that the caller does not supply is filled from weekday.entity if the skill
ships one — useful for a slot whose value is a free pick from a known set
rather than a computed value.
render() |
DialogRenderer |
|
|---|---|---|
| Input | an explicit phrase list | a LocaleResources + dialog name |
| Language | decided by the caller | per render() call |
| Repetition avoidance | no | yes, per language |
Default slots / .entity fallback |
no | yes |
Use render() when you already hold the phrases and want one sentence. Use
DialogRenderer for a skill response spoken more than once.
A .prompt is the localized prompt a skill feeds to a language model. Unlike a
.dialog it is not a template — it is plain text — and the whole file,
verbatim, is one prompt. The only special construct is {name} substitution,
and it is conservative: a prompt is free-form text full of code and JSON,
so rendering must never corrupt a brace the author did not write as a slot.
render_prompt() is the stateless function:
from ovos_spec_tools import render_prompt
render_prompt("You are {role}. Answer: {query}", {"role": "concise"})
# 'You are concise. Answer: {query}'A {name} is replaced only when all three OVOS-INTENT-2 §4.4 conditions
hold: it is a well-formed name (§4.4 condition 1), the caller supplied a value
(condition 2), and it is not inside a ``` fenced code block
(condition 3). Everything else stays literal:
- an unfilled slot is left as
{name}— §4.4 "slots are optional", the deliberate opposite of.dialog, where every slot must be filled; - any other
{/}—{},{ }, JSON like{"k": 1}— is untouched; - a
{name}inside a fenced code block is never substituted.
Known gap. OVOS-INTENT-2 §4.4 also requires author-only HTML comments
<!-- … -->to be stripped before a prompt reaches a model (a MUST).render_prompt()does not yet do this — a comment passes through verbatim. Do not rely on comment removal until it is implemented.
PromptRenderer is the resource-backed, multilingual form — built from a
LocaleResources, with the language given per call and optional default slots:
from ovos_spec_tools import PromptRenderer
renderer = PromptRenderer(res, "system", slots={"assistant": "OVOS"})
renderer.render("en-US", {"query": "what time is it"})
renderer.render("pt-PT", {"query": "que horas são"}) # same prompt, another languageIt has no phrase selection, no repetition avoidance, and no .entity fallback
— a prompt is one whole-file body and its slots are optional.
Language matching — the tag logic behind the loader's smart fallback.