{"record":{"id":"dbbf649aa0c6b9b1","repo":"jamiepine/voicebox","slug":"e","errorCode":null,"errorMessage":"{e}","messagePattern":"\\{e\\}","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/routes/generations.py","lineNumber":75,"sourceCode":"async def generate_speech(\n    data: models.GenerationRequest,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Generate speech from text using a voice profile.\"\"\"\n    task_manager = get_task_manager()\n    generation_id = str(uuid.uuid4())\n\n    profile = await profiles.get_profile(data.profile_id, db)\n    if not profile:\n        raise HTTPException(status_code=404, detail=\"Profile not found\")\n\n    from ..backends import engine_has_model_sizes\n\n    engine = _resolve_generation_engine(data, profile)\n    try:\n        profiles.validate_profile_engine(profile, engine)\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail=str(e))\n\n    model_size = (data.model_size or \"1.7B\") if engine_has_model_sizes(engine) else None\n\n    text = data.text\n    source = \"manual\"\n    if data.personality and getattr(profile, \"personality\", None):\n        try:\n            llm_result = await personality.rewrite_as_profile(profile.personality, data.text)\n        except ValueError as e:\n            raise HTTPException(status_code=400, detail=str(e))\n        text = llm_result.text.strip()\n        if not text:\n            raise HTTPException(status_code=500, detail=\"LLM produced empty output; nothing to speak.\")\n        source = \"personality_speak\"\n\n    generation = await history.create_generation(\n        profile_id=data.profile_id,\n        text=text,","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/generations.py#L57-L93","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the detail string — it states the exact mismatch (expected vs requested engine, or missing metadata).","Do not override engine for preset profiles; let _resolve_generation_engine fall back to profile.preset_engine.","For cloned profiles, restrict the engine picker to entries in CLONING_ENGINES for the installed backend.","If metadata is missing, repair the profile (re-set preset_engine/preset_voice_id or design_prompt) via the profile update endpoint."],"exampleFix":"// before: client forces an incompatible engine\npost('/generate', { profile_id, text, engine: 'kokoro' });\n// after: respect the profile's engine constraints\nconst engine = profile.voice_type === 'preset' ? profile.preset_engine : undefined;\npost('/generate', { profile_id, text, engine });","handlingStrategy":"try-catch","validationCode":"# Validate engine compatibility before the request.\nfrom backend.services import profiles as profiles_mod\ntry:\n    profiles_mod.validate_profile_engine(profile, resolved_engine)\nexcept ValueError as e:\n    raise BadEngine(str(e))  # surface before the HTTP call","typeGuard":"def engine_compatible_with_profile(profile, engine: str) -> bool:\n    try:\n        from backend.services.profiles import validate_profile_engine\n        validate_profile_engine(profile, engine)\n        return True\n    except ValueError:\n        return False","tryCatchPattern":"try:\n    client.post('/generate', json=payload)\nexcept HTTPStatusError as e:\n    if e.response.status_code == 400:\n        # drop the engine override and retry with the profile's default\n        payload.pop('engine', None)\n        client.post('/generate', json=payload)\n        return\n    raise","preventionTips":["Do not override engine for preset profiles; let the backend resolve preset_engine.","Restrict the engine picker to CLONING_ENGINES for cloned profiles.","Keep client and backend versions in sync when engine names change.","Read the detail string — it names the expected vs requested engine."],"tags":["fastapi","http-400","generate","profiles","engine","valueerror","validation","rest"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}