{"record":{"id":"ec52d3b629f8c943","repo":"unslothai/unsloth","slug":"keyframes-and-references-cannot-be-combined-minim","errorCode":null,"errorMessage":"keyframes and references cannot be combined: MiniMax-H3 runs them against different denoiser partitions","messagePattern":"keyframes and references cannot be combined: MiniMax-H3 runs them against different denoiser partitions","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"studio/backend/models/inference.py","lineNumber":3793,"sourceCode":"                if len(item) > 32 * 1024 * 1024:\n                    raise ValueError(\"each reference must be at most 32 MiB (base64)\")\n        return value\n\n    @model_validator(mode = \"after\")\n    def _references_fit_the_models_budget(self) -> \"VideoGenerateRequest\":\n        images = self.reference_images or []\n        videos = self.reference_videos or []\n        audios = self.reference_audios or []\n        total = len(images) + len(videos) + len(audios)\n        if total > 12:\n            raise ValueError(f\"MiniMax-H3 takes at most 12 references in total, got {total}\")\n        # Standalone audio must accompany an image or video reference.\n        if audios and not images and not videos:\n            raise ValueError(\n                \"reference audio needs at least one reference image or video to go with\"\n            )\n        if (images or videos or audios) and (self.first_frame or self.last_frame):\n            raise ValueError(\n                \"keyframes and references cannot be combined: MiniMax-H3 runs them against \"\n                \"different denoiser partitions\"\n            )\n        return self\n\n    @model_validator(mode = \"after\")\n    def _keyframe_canvas_needs_both_axes(self) -> \"VideoGenerateRequest\":\n        # Omit both axes for \"match source\", or provide both for an explicit canvas.\n        # KEYFRAME requests only. There a half-specified canvas is silently discarded:\n        # _resolve_keyframes matches the source aspect whenever either axis is missing, so the\n        # axis that was sent never reaches the render and the API would accept one recipe and\n        # draw another. Without a keyframe the backend deliberately resolves the missing axis\n        # from the family's default preset -- validate_video_request_shape and generate() both\n        # document and implement that -- so applying the rule to every request would reject\n        # half-specified LTX, Wan, Hunyuan and prompt-only H3 calls that have always been valid.\n        if not (self.first_frame or self.last_frame):\n            return self\n        if (self.width is None) != (self.height is None):","sourceCodeStart":3775,"sourceCodeEnd":3811,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/models/inference.py#L3775-L3811","documentation":"Raised by the same model_validator on VideoGenerateRequest (MiniMax-H3). MiniMax-H3 runs first_frame/last_frame keyframes and free-form reference media (reference_images/videos/audios) against different denoiser partitions, so the two conditioning modes are mutually exclusive in one request. The check fires when any reference list is non-empty AND either first_frame or last_frame is set. It is the third check in the validator, after the budget and standalone-audio checks.","triggerScenarios":"POST to the video generation endpoint with e.g. {\"first_frame\": \"...\", \"reference_images\": [\"...\"]}, or {\"last_frame\": \"...\", \"reference_audios\": [\"...\"], \"reference_videos\": [\"...\"]} — any non-empty reference list combined with either keyframe field.","commonSituations":"Migrating from another model family (LTX, Wan, Hunyuan) that permits mixing keyframes with references; a UI that shows keyframe and reference widgets simultaneously and submits both; incrementally adding reference images to an existing keyframe workflow without clearing first_frame/last_frame.","solutions":["Decide the conditioning mode: either keep first_frame/last_frame and drop all reference_* fields, or keep references and drop the keyframes.","If you need both a start anchor and extra references, fold the anchor image into reference_images and remove first_frame.","Audit request-building code for stale keyframe fields left over from a previous model's template."],"exampleFix":"# before\nreq = {\n  \"prompt\": p,\n  \"first_frame\": start_b64,\n  \"reference_images\": [style_b64],\n}\n# after (mode 1: keyframes only)\nreq = {\"prompt\": p, \"first_frame\": start_b64}\n# after (mode 2: references only)\nreq = {\"prompt\": p, \"reference_images\": [start_b64, style_b64]}","handlingStrategy":"validation","validationCode":"def keyframes_xor_references(req: dict) -> bool:\n    has_refs = any(req.get(k) for k in (\"reference_images\", \"reference_videos\", \"reference_audios\"))\n    has_kf = bool(req.get(\"first_frame\") or req.get(\"last_frame\"))\n    return not (has_refs and has_kf)","typeGuard":"function conditioningExclusive(req: Record<string, unknown>): boolean {\n  const refs = ['reference_images','reference_videos','reference_audios'].some(k => Array.isArray(req[k]) && (req[k] as unknown[]).length > 0);\n  const kf = Boolean(req.first_frame || req.last_frame);\n  return !(refs && kf);\n}","tryCatchPattern":"try { await client.generate(req) } catch (e) { if (/cannot be combined/.test(String(e))) { delete req.first_frame; delete req.last_frame; return client.generate(req); } throw e; }","preventionTips":["Make the UI a mode switch: keyframes OR references, never both visible","Strip keyframe fields when a request template is reused for reference-based generation"],"tags":["pydantic","validation","video-generation","keyframes","minimax"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}