{"record":{"id":"5effa318e0045592","repo":"harry0703/MoneyPrinterTurbo","slug":"qwen-returned-empty-choices","errorCode":null,"errorMessage":"[qwen] returned empty choices","messagePattern":"\\[qwen\\] returned empty choices","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"app/services/llm.py","lineNumber":128,"sourceCode":"    except (KeyError, TypeError, AttributeError):\n        return getattr(value, key, None)\n\n\ndef _extract_qwen_generation_text(response) -> str:\n    \"\"\"\n    从 DashScope Generation 响应中提取文本。\n\n    Qwen 使用 `messages` 调用时返回的是 chat 结构：\n    `output.choices[0].message.content`；旧 completion 形态才会返回\n    `output.text`。这里两个路径都兼容，避免 `output.text` 为 None 时\n    继续 `.replace()` 触发不可诊断的 AttributeError。\n    \"\"\"\n    output = _get_response_field(response, \"output\")\n    choices = _get_response_field(output, \"choices\") if output else None\n    if choices is not None:\n        if not choices:\n            logger.warning(\"Qwen returned an empty choices list\")\n            raise ValueError(\"[qwen] returned empty choices\")\n\n        first_choice = choices[0]\n        message = _get_response_field(first_choice, \"message\")\n        content = _get_response_field(message, \"content\") if message else None\n        if content is not None:\n            return _normalize_text_response(content, \"qwen\")\n\n    text = _get_response_field(output, \"text\") if output else None\n    return _normalize_text_response(text, \"qwen\")\n\n\ndef _generate_response(prompt: str, app_config=None) -> str:\n    try:\n        # WebUI 在视频生成期间允许用户准备下一条文案。调用方可以传入提交瞬间\n        # 的配置快照，确保模型请求重试期间不会因为后台任务结束并应用新配置，\n        # 而切换到另一个 Provider、Base URL 或模型。\n        runtime_app_config = app_config if app_config is not None else config.app\n        llm_provider = str(","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/services/llm.py#L110-L146","documentation":"Qwen-specific guard in _extract_qwen_generation_text: the DashScope Generation response's output.choices exists as a key but is an empty list (a warning is logged before raising). Distinguished from output.text fallback: an explicitly empty choices list means the chat-form response carries no completions, so falling through to output.text would mask the failure.","triggerScenarios":"dashscope.Generation.call with messages returns output={'choices': []} — DashScope does this on content-policy blocks or certain error codes that still produce a response object.","commonSituations":"Chinese-content policy interception silently emptying choices, DashScope API version drift, invalid model names that yield an empty chat response instead of an error status, or rate-limit soft failures.","solutions":["Log the full response including status_code and code/message fields — DashScope embeds the real reason there (e.g. DataInspectionFailed)","Adjust the prompt to avoid content-policy triggers","Verify the model name resolves (a wrong model often yields degenerate output instead of a clear error)","Retry once with backoff for rate-limit-related empty responses"],"exampleFix":"# before\nresponse = dashscope.Generation.call(model=model_name, messages=[...])\n# silently empty output -> downstream None errors\n\n# after\nresponse = dashscope.Generation.call(model=model_name, messages=[...])\nif response.status_code != \"200\":\n    raise RuntimeError(f\"dashscope error: {response.code} {response.message}\")\ntext = _extract_qwen_generation_text(response, \"qwen\")","handlingStrategy":"try-catch","validationCode":"if response is None or response.output is None:\n    raise ValueError(\"dashscope returned no output\")\nif getattr(response, \"status_code\", 200) != 200:\n    raise ValueError(f\"dashscope error: {getattr(response, 'code', '')} {getattr(response, 'message', '')}\")","typeGuard":"def qwen_has_chat_output(response) -> bool:\n    output = getattr(response, \"output\", None) or {}\n    choices = output.get(\"choices\", None) if isinstance(output, dict) else None\n    if choices is not None:\n        return bool(choices) and bool((choices[0].get(\"message\") or {}).get(\"content\"))\n    return bool(output.get(\"text\"))","tryCatchPattern":"try:\n    text = _extract_qwen_generation_text(response, \"qwen\")\nexcept ValueError as e:\n    if \"empty choices\" in str(e):\n        raise ValueError(f\"qwen blocked request: code={getattr(response, 'code', '?')}\")","preventionTips":["Always inspect DashScope status_code/code/message before parsing output","Keep prompts clear of content-policy triggers for Chinese providers"],"tags":["qwen","dashscope","llm","response-validation"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}