{"record":{"id":"af8c09a6a0e56523","repo":"jamiepine/voicebox","slug":"model-model-size-is-not-downloaded-yet-use-ge","errorCode":null,"errorMessage":"Model ${model_size} is not downloaded yet. Use /generate to trigger a download.","messagePattern":"Model (.+?) is not downloaded yet\\. Use /generate to trigger a download\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/backends/__init__.py","lineNumber":552,"sourceCode":"        await backend.load_model(model_size)\n    else:\n        await backend.load_model()\n\n\nasync def ensure_model_cached_or_raise(engine: str, model_size: str = \"default\") -> None:\n    \"\"\"Check if a model is cached, raise HTTPException if not. Used by streaming endpoint.\"\"\"\n    from fastapi import HTTPException\n\n    backend = get_tts_backend_for_engine(engine)\n    cfg = None\n    for c in get_tts_model_configs():\n        if c.engine == engine and c.model_size == model_size:\n            cfg = c\n            break\n\n    if engine in (\"qwen\", \"qwen_custom_voice\", \"tada\"):\n        if not backend._is_model_cached(model_size):\n            raise HTTPException(\n                status_code=400,\n                detail=f\"Model {model_size} is not downloaded yet. Use /generate to trigger a download.\",\n            )\n    else:\n        if not backend._is_model_cached():\n            display = cfg.display_name if cfg else engine\n            raise HTTPException(\n                status_code=400,\n                detail=f\"{display} model is not downloaded yet. Use /generate to trigger a download.\",\n            )\n\n\ndef unload_model_by_config(config: ModelConfig) -> bool:\n    \"\"\"Unload a model given its config. Returns True if it was loaded, False otherwise.\"\"\"\n    from . import get_tts_backend_for_engine\n    from ..services import tts, transcribe, llm as llm_service\n\n    if config.engine == \"whisper\":","sourceCodeStart":534,"sourceCodeEnd":570,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/backends/__init__.py#L534-L570","documentation":"Raised as an HTTPException(status_code=400) by ensure_model_cached_or_raise() when a streaming endpoint is called for a qwen / qwen_custom_voice / tada engine whose requested model_size is not on disk. These engines support multiple model sizes, so _is_model_cached(model_size) is called with the specific size. The detail tells the client to use /generate first, which triggers the download.","triggerScenarios":"The streaming TTS endpoint receives engine in {qwen, qwen_custom_voice, tada} with a model_size that was never downloaded. _is_model_cached(model_size) returns False, raising 400.","commonSituations":"Fresh install where only the default model was pre-fetched. A client requests a non-default size (e.g. 0.6B vs 1.7B) that the user never downloaded via /generate. Models directory was cleared/moved after install.","solutions":["Call /generate (non-streaming) once for the target engine+model_size to trigger the download before streaming.","Pre-download required model sizes during setup/onboarding via the model-management UI.","Verify the models cache directory exists and is writable, and that _is_model_cached correctly detects files.","Return the available model sizes in the error detail so the client can pick one already cached."],"exampleFix":"# before\nraise HTTPException(status_code=400, detail=f\"Model {model_size} is not downloaded yet. Use /generate to trigger a download.\")\n# after\navailable = backend.list_cached_sizes() if hasattr(backend, 'list_cached_sizes') else []\nraise HTTPException(\n    status_code=400,\n    detail=f\"Model {model_size} is not downloaded yet. Use /generate to trigger a download. Cached: {available}\",\n)","handlingStrategy":"validation","validationCode":"from backend.backends import get_tts_backend_for_engine\n\ndef assert_model_ready(engine: str, model_size: str = 'default') -> None:\n    backend = get_tts_backend_for_engine(engine)\n    if engine in ('qwen', 'qwen_custom_voice', 'tada'):\n        ready = backend._is_model_cached(model_size)\n    else:\n        ready = backend._is_model_cached()\n    if not ready:\n        raise RuntimeError(f'{engine}/{model_size} not cached; call /generate first')","typeGuard":null,"tryCatchPattern":"from fastapi import HTTPException\ntry:\n    await ensure_model_cached_or_raise(engine, model_size)\nexcept HTTPException as e:\n    if e.status_code == 400:\n        # Trigger a download, or return a helpful 409 to the client.\n        await load_engine_model(engine, model_size)\n    else:\n        raise","preventionTips":["Run /generate once per engine+model_size before invoking the streaming endpoint.","Pre-download required sizes during onboarding.","Expose cached sizes in the error detail so clients can pick an available one."],"tags":["backend","python","fastapi","tts","model-cache","validation","http-400"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}