Skip to content

Voice & language catalog

Every session starts on the runtime’s defaults — English on both legs — and the brain sets the recognizer language and the speaking voice for this caller. This page is the catalog of allowed values, and the one rule that makes them safe to change.

There is no model to choose. Every session is served by vql-stt, a composite router covering English plus 22 Indic languages; it picks the engine underneath from the language, and a brain never names an engine.

vql-stt serves English (en) plus these 22 Indic languages:

CodeLanguageCodeLanguageCodeLanguage
asAssamesebnBengalibrxBodo
doiDogrikokKonkaniguGujarati
hiHindiknKannadaksKashmiri
maiMaithilimlMalayalammrMarathi
mniManipurineNepaliorOdia
paPunjabisaSanskritsatSantali
sdSindhitaTamilteTelugu
urUrdu

These are the members of the Language enum, so a language outside the set is not a value you can send — it is a value you cannot construct. That is the point: the old failure mode was an unserved code falling through to the English recognizer, in a call nobody could tell had gone wrong.

A language change that crosses the English↔Indic boundary is applied at the next end-of-turn, never mid-utterance.

There are no VAD or end-of-turn knobs on the wire. The runtime keeps its own tuned defaults; we widen the surface as we learn, not in advance.

The engine is omnivoice, a voice-cloning model with two personas, and the whole catalog is these two:

VoiceVoice IDPersona
Voice.OMNIVOICE_GAURIomnivoice/gauriFemale
Voice.OMNIVOICE_GAURAVomnivoice/gauravMale

There is no separate model field: the engine is chosen entirely by the voice-id prefix, and omnivoice is the only prefix.

tts.language is not a text tag — it selects which recorded reference clip the voice is cloned from. omnivoice/gauri speaking hi and omnivoice/gauri speaking en are two different recorded speakers.

Both personas have clips for ten of the 23 languages:

hi, en, bn, gu, kn, ml, mr, pa, ta, te

A tts.language outside those ten is rejected by the runtime, not quietly served by the Hindi clip. The list is the roster today, not a promise frozen into the wire contract: it grows as clips are recorded, and the session tells you when you name one it cannot speak. So an Odia call is a configuration you write down:

Config(
stt=SttConfig(language=Language.OR), # understood in Odia
tts=TtsConfig(language=Language.HI), # spoken with the Hindi clip
)

That is a legitimate session, and it is why both legs keep their own language field rather than sharing one. What is not legitimate is arriving at it by accident.

Each half fails on its own, quietly, in a different way:

Half wrongWhat you getHow you find out
tts.languageThe right words in the wrong speaker — Hindi read by the English reference clip sounds like a non-native accentBy ear only. The words are correct, so transcription-based scoring is blind to it
stt.languageWhatever the caller says is transcribed by the English recognizerGarbled transcripts, blamed on the model

The first is the reason this page leads with a rule instead of a menu. A demo shipped with Devanagari read in an English voice for weeks: every test was green, every log line looked right, and every automated score was unchanged, because none of them can hear.

Two rules kill it, and they are checked in two different places:

  1. State both legs or neither. Not that the two agree — that you said both. Changing only the voice touches no language field and is unaffected. Config raises ConfigError on this one at the call site, before anything reaches the socket: it is a property of the request, so nothing needs to be asked.
  2. No silent substitution. A speaking language with no clip is refused, so the Hindi fallback can only be something you asked for. This one comes back from the runtime as a rejected response naming the language — which clips exist is the speech tier’s answer, and it changes as clips are recorded.
  • For every session: the runtime’s own defaults, English on both legs. The agent record holds brain_url and nothing about voice or language — an agent-level language cannot depend on the caller, and lead_qual is the proof: it resolves a language from an enquiry form that does not exist until the session starts.
  • Per caller, or mid-call: await session.configure(Config(...)) from the brain — the only thing in the call that sees this caller. STT applies at the next turn boundary, TTS at the next speech unit, never mid-utterance.
  • From the browser: never. A page can set at most one leg of a pair, which is precisely the failure above.

The lead_qual demo resolves its language per caller and then switches mid-call across eight Indic languages — a worked example of both.