{"record":{"id":"03281548e5824ee9","repo":"jamiepine/voicebox","slug":"llm-produced-empty-output-nothing-to-speak","errorCode":null,"errorMessage":"LLM produced empty output; nothing to speak.","messagePattern":"LLM produced empty output; nothing to speak\\.","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"backend/routes/generations.py","lineNumber":88,"sourceCode":"\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,\n        language=data.language,\n        audio_path=\"\",\n        duration=0,\n        seed=data.seed,\n        db=db,\n        instruct=data.instruct,\n        generation_id=generation_id,\n        status=\"generating\",\n        engine=engine,\n        model_size=model_size if engine_has_model_sizes(engine) else None,\n        source=source,\n    )\n","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/generations.py#L70-L106","documentation":"Returned as HTTP 500 by POST /generate. After a successful personality rewrite, the route strips the result text and checks `if not text`. If the LLM returned only whitespace or an empty string, there is nothing to send to TTS, so it raises this 500. It is a server-side quality gate: the LLM produced no usable content.","triggerScenarios":"Personality rewrite returns an empty string; the LLM returns only whitespace/punctuation that strips to empty; a model misconfiguration causes the rewrite to yield blank output; prompt injection causes the model to refuse with an empty response.","commonSituations":"LLM provider returning empty completions under load or quota pressure; a personality prompt that over-constrains the model into silence; upstream model returning a refusal that the adapter reduces to empty; whitespace-only output from a misconfigured streaming adapter.","solutions":["Retry the request — transient empty output from the LLM often clears on retry.","Inspect the personality prompt and relax constraints that may force empty/refusal output.","Temporarily disable personality (personality=false) to send the original text to TTS.","If persistent, capture the raw LLM response in the personality service logs to diagnose the empty completion."],"exampleFix":"// before: single attempt, surface 500 to user\nconst r = await post('/generate', { profile_id, text, personality: true });\n// after: retry once, then fall back to raw text\ntry {\n  await post('/generate', { profile_id, text, personality: true });\n} catch (e) {\n  if (e.status === 500) await post('/generate', { profile_id, text, personality: false });\n}","handlingStrategy":"retry","validationCode":"# Pre-flight: confirm the personality rewrite produces non-empty text.\nresult = await personality.rewrite_as_profile(profile.personality, data.text)\nif not (result.text or '').strip():\n    # would 500 — retry once, then fall back to raw text\n    data.personality = False","typeGuard":"def rewrite_is_usable(text: str) -> bool:\n    return bool(text and text.strip())","tryCatchPattern":"try:\n    client.post('/generate', json=payload)\nexcept HTTPStatusError as e:\n    if e.response.status_code == 500 and 'empty output' in e.response.json().get('detail', ''):\n        # retry once, then fall back to raw text without personality\n        try:\n            client.post('/generate', json=payload)\n        except HTTPStatusError:\n            payload['personality'] = False\n            client.post('/generate', json=payload)\n        return\n    raise","preventionTips":["Retry once — transient empty LLM output often clears.","Relax personality prompts that over-constrain the model into silence.","Capture raw LLM responses in logs to diagnose persistent empties.","Offer a personality toggle so users can fall back to raw text."],"tags":["fastapi","http-500","generate","personality","llm","empty-output","rest"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}