{"record":{"id":"cdf4fa1b1213a899","repo":"ATH-MaaS/Pixelle-Video","slug":"invalid-response-format-missing-narrations-key","errorCode":null,"errorMessage":"Invalid response format: missing 'narrations' key","messagePattern":"Invalid response format: missing 'narrations' key","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"pixelle_video/utils/content_generators.py","lineNumber":138,"sourceCode":"        topic=topic,\n        n_storyboard=n_scenes,\n        min_words=min_words,\n        max_words=max_words\n    )\n    \n    response = await llm_service(\n        prompt=prompt,\n        temperature=0.8,\n        max_tokens=2000\n    )\n    \n    logger.debug(f\"LLM response: {response[:200]}...\")\n    \n    # Parse JSON\n    result = _parse_json(response)\n    \n    if \"narrations\" not in result:\n        raise ValueError(\"Invalid response format: missing 'narrations' key\")\n    \n    narrations = result[\"narrations\"]\n    \n    # Validate count\n    if len(narrations) > n_scenes:\n        logger.warning(f\"Got {len(narrations)} narrations, taking first {n_scenes}\")\n        narrations = narrations[:n_scenes]\n    elif len(narrations) < n_scenes:\n        raise ValueError(f\"Expected {n_scenes} narrations, got only {len(narrations)}\")\n    \n    logger.info(f\"Generated {len(narrations)} narrations successfully\")\n    return narrations\n\n\nasync def generate_narrations_from_content(\n    llm_service,\n    content: str,\n    n_scenes: int = 5,","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/ATH-MaaS/Pixelle-Video/blob/848b054e4fae40dabc62ec58e960b573e83793ac/pixelle_video/utils/content_generators.py#L120-L156","documentation":"generate_narrations_from_topic parses the LLM response as JSON and requires a top-level \"narrations\" key; ValueError is raised when the model's output doesn't match the expected schema. This guards against LLMs returning prose, wrapped markdown, or differently named keys.","triggerScenarios":"Calling generate_narration/generate_content which routes here when the LLM reply, after _parse_json, is a dict without \"narrations\" — e.g. the model returned {\"scenes\": [...]}, a bare array, or explanation text instead of the requested JSON schema.","commonSituations":"Weak/non-instruct models ignoring the JSON schema; temperature too high producing prose; prompt template edited so the schema instruction is lost; model wrapping JSON in markdown fences that _parse_json strips only partially.","solutions":["Check the logged 'LLM response' debug line to see the actual shape returned","Strengthen the prompt to demand exactly {\"narrations\": [...]} with n_scenes items","Lower temperature / use a model that reliably follows JSON instructions","Add few-shot examples of the expected JSON in the prompt"],"exampleFix":"// model returned {\"scenes\": [\"a\",\"b\"]}\n// before\nnarrations = result[\"scenes\"]\n// after (fix the prompt, or normalize)\nif \"narrations\" not in result and \"scenes\" in result:\n    result = {\"narrations\": result[\"scenes\"]}","handlingStrategy":"validation","validationCode":"def is_valid_narration_result(result, n_scenes: int) -> bool:\n    return (\n        isinstance(result, dict)\n        and isinstance(result.get('narrations'), list)\n        and len(result['narrations']) == n_scenes\n        and all(isinstance(n, str) and n.strip() for n in result['narrations'])\n    )","typeGuard":"def has_narrations(x) -> bool:\n    return isinstance(x, dict) and isinstance(x.get('narrations'), list)","tryCatchPattern":"try:\n    narrations = await generate_narrations_from_topic(topic, n_scenes=n)\nexcept ValueError as e:\n    if 'missing' in str(e):\n        narrations = await generate_narrations_from_topic(topic, n_scenes=n, retry_with_stricter_schema=True)\n    else:\n        raise","preventionTips":["Use the model's JSON/structured-output mode when available","Include an exact-schema example in the prompt","Log raw LLM responses for post-mortem on schema drift"],"tags":["llm","schema-validation","json-parsing","narration"],"backgroundTag":"llm-response-schema-mismatch","analyzedSha":"848b054e4fae40dabc62ec58e960b573e83793ac","analyzedAt":"2026-08-30T03:24:41.468Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}