jamiepine/voicebox · error · HTTPException

{e}

Error message

{e}

What it means

Returned as HTTP 400 by POST /generate. The route resolves an engine via _resolve_generation_engine (request.engine, else profile.default_engine, else profile.preset_engine, else 'qwen') and calls profiles.validate_profile_engine(profile, engine). That function raises ValueError for: a preset profile missing preset_engine/preset_voice_id, a preset profile whose preset_engine != engine, a designed profile missing design_prompt, or a cloned profile whose engine is not in CLONING_ENGINES. str(e) is surfaced as the detail.

Source

Thrown at backend/routes/generations.py:75

async def generate_speech(
    data: models.GenerationRequest,
    db: Session = Depends(get_db),
):
    """Generate speech from text using a voice profile."""
    task_manager = get_task_manager()
    generation_id = str(uuid.uuid4())

    profile = await profiles.get_profile(data.profile_id, db)
    if not profile:
        raise HTTPException(status_code=404, detail="Profile not found")

    from ..backends import engine_has_model_sizes

    engine = _resolve_generation_engine(data, profile)
    try:
        profiles.validate_profile_engine(profile, engine)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    model_size = (data.model_size or "1.7B") if engine_has_model_sizes(engine) else None

    text = data.text
    source = "manual"
    if data.personality and getattr(profile, "personality", None):
        try:
            llm_result = await personality.rewrite_as_profile(profile.personality, data.text)
        except ValueError as e:
            raise HTTPException(status_code=400, detail=str(e))
        text = llm_result.text.strip()
        if not text:
            raise HTTPException(status_code=500, detail="LLM produced empty output; nothing to speak.")
        source = "personality_speak"

    generation = await history.create_generation(
        profile_id=data.profile_id,
        text=text,

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Read the detail string — it states the exact mismatch (expected vs requested engine, or missing metadata).
  2. Do not override engine for preset profiles; let _resolve_generation_engine fall back to profile.preset_engine.
  3. For cloned profiles, restrict the engine picker to entries in CLONING_ENGINES for the installed backend.
  4. If metadata is missing, repair the profile (re-set preset_engine/preset_voice_id or design_prompt) via the profile update endpoint.

Example fix

// before: client forces an incompatible engine
post('/generate', { profile_id, text, engine: 'kokoro' });
// after: respect the profile's engine constraints
const engine = profile.voice_type === 'preset' ? profile.preset_engine : undefined;
post('/generate', { profile_id, text, engine });
Defensive patterns

Strategy: try-catch

Validate before calling

# Validate engine compatibility before the request.
from backend.services import profiles as profiles_mod
try:
    profiles_mod.validate_profile_engine(profile, resolved_engine)
except ValueError as e:
    raise BadEngine(str(e))  # surface before the HTTP call

Type guard

def engine_compatible_with_profile(profile, engine: str) -> bool:
    try:
        from backend.services.profiles import validate_profile_engine
        validate_profile_engine(profile, engine)
        return True
    except ValueError:
        return False

Try / catch

try:
    client.post('/generate', json=payload)
except HTTPStatusError as e:
    if e.response.status_code == 400:
        # drop the engine override and retry with the profile's default
        payload.pop('engine', None)
        client.post('/generate', json=payload)
        return
    raise

Prevention

When it happens

Trigger: Sending engine='kokoro' for a preset profile pinned to a different engine; requesting an engine that does not support cloned voices on a cloned profile; a designed profile whose design_prompt was wiped; a preset profile with corrupt/missing preset_engine metadata; explicit engine override that conflicts with the profile type.

Common situations: Frontend lets users pick any engine regardless of profile type; a profile was migrated/edited and its engine metadata was lost; engine name typo (e.g. 'chatterbox-turbo' vs 'chatterbox_turbo'); version skew where a new engine name is not in the installed CLONING_ENGINES list.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/dbbf649aa0c6b9b1. Report an issue: GitHub.