os_helper.prompts_utils module

Model-Facing Prompt Templates, in Several Languages

The third localization shape the suite needs, and the one that is not translation. Catalog serves interface copy keyed on its English source; Locales serves any text keyed on meaning; Prompts serves the text a model reads, which looks like the second but is consumed like neither.

Why prompts are not gettext

A prompt’s language versions are independently authored, not translated. Measured in standpoint on 2026-10-22, one prompt ran 1345 characters in English, 1532 in French and 1479 in Spanish: nobody translated that: somebody wrote the instruction three times, because what makes a model behave in French is not the French words of the English sentence.

Two consequences follow, and they are the whole reason this module exists rather than a second catalogue:

  • The key is the stable thing. Prompt engineering reworks the wording constantly. Key on the English text, as gettext does, and every tweak to the English orphans the other languages — which then fall back to English silently, exactly where a wrong language is hardest to notice. Key on axis_prompt and the wording is free to move.

  • A 1300-character msgid is not a catalogue entry. It is unreadable in a .po, unusable in a translation tool, and meaningless to a translator who is not a prompt engineer.

So prompts stay keyed on meaning, in YAML, read through Locales — which already parses, validates and falls back. What this class adds is the four things every caller was otherwise writing by hand:

  1. the whole-language view (a caller wants every prompt for one language, not one prompt at a time), cached;

  2. the field shape key.system / key.user, for prompts that come in parts;

  3. language resolution for a requested tag, so fr-CA finds fr;

  4. a placeholder check across languages — the failure specific to this shape, where a translated prompt drops a {field} the code fills and the render raises in production rather than in a test.

Layout

prompts:
  axis_prompt:                  # simple: key -> language -> text
    en: You are naming the four poles…
    fr: Vous nommez les quatre pôles…
  eyeball_critique:             # fielded: key -> field -> language -> text
    system:
      en: You are a strict visual-quality reviewer…
    user:
      en: Examine the image and write…

Usage example

>>> # p = Prompts.from_path("standpoint/locales/i18n.yaml", section="bundle")
>>> # p.text("axis_prompt", "fr")
>>> # p.render("title_template", "fr", domain="LLM")
>>> # p.bundle("fr")["axis_prompt"]

Author

[Warith HARCHAOUI](https://linkedin.com/in/warith-harchaoui/)

class os_helper.prompts_utils.Prompts(locales, section)[source]

Bases: object

One section of a locale file, read as model-facing templates.

Build one with from_path(); the constructor takes an already-parsed Locales for callers that have one.

Parameters:
locales

The parsed file. Reused rather than re-read, so a package that also has a gui section pays for one parse.

Type:

os_helper.Locales

section

Which section holds the prompts. Named by the caller because os-helper has no business knowing that prompts are a thing — it is the member that decides, and members differ (prompts here, bundle there).

Type:

str

bundle(language=None)[source]

Every prompt for one language at once.

This is how prompts are actually consumed: a caller resolves the language once, then reads several templates. Asking key by key means resolving the language again for each, which is both slower and an invitation to resolve it inconsistently.

Parameters:

language (str or None) – Wanted language, resolved through resolve().

Returns:

{key: text} for simple prompts, {key: {field: text}} for fielded ones. Values come back as stored, so a key holding a structure for that language (a nested sub-tree, not a template) survives intact. A key with nothing for this language and nothing for the default is omitted rather than raising: a bundle is a view, and a caller that needs one key should ask for it by name and get the error.

Return type:

dict

property default_language: str

The language served when the request names one we do not have.

classmethod from_path(path, section='prompts')[source]

Read a locale file and take one section of it as prompts.

Parameters:
  • path (str or pathlib.Path) – The locales/i18n.yaml to read. Must live inside the importable package directory, or it is not package data and never reaches the wheel.

  • section (str) – The section holding the templates.

Returns:

A handle. The file is parsed and validated now, by Locales.

Return type:

Prompts

property keys: tuple[str, ...]

Every prompt key in this section, sorted.

property languages: tuple[str, ...]

Every language any prompt in this section is written in, sorted.

Taken from the prompts themselves rather than from meta: a package whose interface is bilingual may well have prompts in three languages, or in one, and the file should not have to lie about either.

locales: Locales
problems()[source]

Everything wrong with this section, as sentences.

Checks, in order:

  • a prompt missing a language that the others have — the reader gets a different language than the one they asked for, which for a model means a different answer;

  • a prompt whose text is empty;

  • a language whose template does not carry the same placeholders as the default — the failure specific to this shape. A French prompt that drops the {criteria} the English one has will raise at render time, in production, on whichever request first asked for French. An extra placeholder is just as bad: it reaches format() with no value and raises there instead.

Returns:

One sentence per fault, empty when the section is sound.

Return type:

list of str

render(key, language=None, *, field=None, **values)[source]

One prompt with its {placeholder} fields filled in.

Templates are data, never code: this formats, it never evaluates. The placeholder names are checked first, so a caller learns what it forgot instead of sending a half-filled instruction to a model — which would not fail, it would just answer the wrong question.

Parameters:
  • key (str) – The semantic key.

  • language (str or None) – Wanted language, resolved through resolve().

  • field (str, optional) – Which part, for a fielded prompt.

  • **values – One value per placeholder in the template.

Returns:

The formatted prompt.

Return type:

str

Raises:

KeyError – If the template needs a placeholder no value was given for.

resolve(language)[source]

The language that will actually be served for a request.

Parameters:

language (str or None) – A language code, possibly regional (fr-CA) or absent.

Returns:

language when prompts exist in it, its base language when only that has them, otherwise default_language.

Return type:

str

section: str
text(key, language=None, *, field=None)[source]

One prompt, in the best available language.

Parameters:
  • key (str) – The semantic key, e.g. axis_prompt.

  • language (str or None) – Wanted language; resolved through resolve() first, so a regional tag or an unknown one never reaches the file.

  • field (str, optional) – For prompts that come in parts, which part — typically "system" or "user". Omit for a single-text prompt.

Returns:

The template, unformatted. Use render() to fill it.

Return type:

str

Raises:

KeyError – If the key (or the field) does not exist, or carries neither the wanted language nor the default one. Raising beats returning the key: a prompt that is silently the string axis_prompt would reach a model and produce confident nonsense.