{"record":{"id":"a6c7f11df80bab4c","repo":"harry0703/MoneyPrinterTurbo","slug":"video-terms-must-be-a-string-or-a-list-of-strings","errorCode":null,"errorMessage":"video_terms must be a string or a list of strings.","messagePattern":"video_terms must be a string or a list of strings\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"app/services/task.py","lineNumber":308,"sourceCode":"    logger.info(\"\\n\\n## generating video terms\")\n    video_terms = params.video_terms\n    if not video_terms:\n        # 开启素材按文案顺序匹配后，关键词本身也必须按脚本叙事顺序生成；\n        # 否则后续即使顺序下载和顺序拼接，也只能复用一组全局主题词，\n        # 无法改善“后面内容的画面提前出现”的问题。\n        video_terms = llm.generate_terms(\n            video_subject=params.video_subject,\n            video_script=video_script,\n            amount=8 if params.match_materials_to_script else 5,\n            match_script_order=params.match_materials_to_script,\n        )\n    else:\n        if isinstance(video_terms, str):\n            video_terms = [term.strip() for term in re.split(r\"[,，]\", video_terms)]\n        elif isinstance(video_terms, list):\n            video_terms = [term.strip() for term in video_terms]\n        else:\n            raise ValueError(\"video_terms must be a string or a list of strings.\")\n\n        logger.debug(f\"video terms: {utils.to_json(video_terms)}\")\n\n    if not video_terms:\n        _mark_task_failed(\n            task_id,\n            \"terms\",\n            \"failed to generate video search terms\",\n        )\n        return None\n\n    # 可选的 TwelveLabs Marengo 语义重排：未启用时返回原顺序，无任何副作用。\n    # 顺序匹配模式下关键词顺序本身就是脚本叙事顺序，必须保持原样，故跳过。\n    if not params.match_materials_to_script:\n        video_terms = twelvelabs.rerank_terms_by_subject(\n            video_subject=params.video_subject,\n            search_terms=video_terms,\n        )","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/app/services/task.py#L290-L326","documentation":"Raised in the task script pipeline when params.video_terms is neither a str nor a list. The value may be a comma or Chinese-comma separated string (split into terms) or a list (entries stripped), but any other type (None from an omitted-yet-present JSON key, a number, a dict) fails fast with this ValueError rather than silently producing no search terms.","triggerScenarios":"Task creation called with video_terms null, a number, or an object in the JSON payload; a client form serializing an empty input as null instead of omitting the field; script callers passing a sentinel value like 0.","commonSituations":"Frontend sends null for an optional field it should omit; API consumers building payloads dynamically and defaulting to None; schema drift where a client assumed an object wrapper.","solutions":["Fix the caller: send video_terms as a string or a list of strings, or omit the field entirely so LLM generation is used.","If the value comes from user input, coerce before task creation and treat null as omitted.","Add request-schema validation (pydantic or jsonschema) at the API boundary so this fails with a 422 naming the field, not a mid-task ValueError.","Inspect the task params JSON on disk to see what was actually stored."],"exampleFix":"# before\ntask_params = {\"video_subject\": \"cats\", \"video_terms\": None}\n# after (omit the key to use LLM generation, or pass a valid type)\ntask_params = {\"video_subject\": \"cats\"}\n# or: {\"video_subject\": \"cats\", \"video_terms\": [\"cats\", \"kittens\"]}","handlingStrategy":"type-guard","validationCode":"# validate at the API boundary before task creation\ndef validate_video_terms(value: object) -> None:\n    if isinstance(value, str):\n        return\n    if isinstance(value, list) and all(isinstance(t, str) for t in value):\n        return\n    raise HTTPException(422, \"video_terms must be a string or list of strings\")","typeGuard":"def is_valid_video_terms(value: object) -> bool:\n    if isinstance(value, str):\n        return True\n    if isinstance(value, list):\n        return all(isinstance(term, str) for term in value)\n    return False","tryCatchPattern":"try:\n    terms = get_video_terms(params)\nexcept ValueError as exc:\n    if \"video_terms must be\" in str(exc):\n        fail_task_with_user_visible_reason(params, str(exc))\n    raise","preventionTips":["Clients should omit video_terms when unused instead of sending null.","Servers should validate the task-params schema at submission so bad types never reach the async pipeline."],"tags":["validation","task-params","api","type-error"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}