#1109·voicebox

GenerationRequest.engine defaults to "qwen", silently overriding the profile's own engine

Author: ariellerCreated Sep 16, 2026Updated Sep 16, 2026

Summary

GenerationRequest.engine defaults to the string "qwen" rather than None. Because the resolution chain is data.engine or profile.default_engine or ..., a client that simply omits engine silently overrides the profile's own engine and gets Qwen instead.

There is no error and no warning. The generation succeeds, the audio is fine, and it is in the wrong voice engine.

Where

backend/models.py:

engine: Optional[str] = Field(default="qwen", pattern="^(qwen|qwen_custom_voice|luxtts|chatterbox|chatterbox_turbo|tada|kokoro)$")

backend/routes/generations.py:

def _resolve_generation_engine(data: models.GenerationRequest, profile) -> str:
    return data.engine or getattr(profile, "default_engine", None) or getattr(profile, "preset_engine", None) or "qwen"

The field is typed Optional[str] and the chain is written to fall through to the profile — the fallback is already spelled out, including "qwen" as the final rung. The default value is what prevents any of it being reached.

Impact

A profile's default_engine is the natural place to configure a voice, and it is what the UI sets. Any API consumer that does not know to send engine explicitly gets a different engine from the one the profile names, and the difference is audible rather than reported: we had an integration rendering every reply through base Qwen for days while the profile named chatterbox_turbo, and only found it by reading /history and noticing engine=qwen on every row.

It also has a sharper failure for preset profiles. validate_profile_engine refuses an engine a preset profile does not own, so a client omitting the field against a preset profile whose preset_engine is set but whose default_engine is not gets a 400, rather than the engine the profile plainly names.

Suggested fix

-    engine: Optional[str] = Field(default="qwen", pattern="...")
+    engine: Optional[str] = Field(default=None, pattern="...")

_resolve_generation_engine already ends in or "qwen", so the behaviour for a profile with no engine of its own is unchanged, and the profile's own setting starts being honoured for everyone else.