api.py 的坑:同时传首尾帧和参考图,references 会静默吃掉 keyframes
背景:用户问"H3 怎么把首尾帧和人物设定图一次性都给模型",对照 DiffSynth-Studio 官方
MiniMaxH3Pipeline.__call__ 源码查清楚了——不是我们代码的锅,是这个 pipeline 本身的设计:
keyframes(FL2VA,首尾帧)和 references(Ref2VA,参考图)在打包序列这一层是互斥的,
MiniMax H3Unit_PackedSequenceBuilder 里 if ref_blocks is not None 走 ref2va 分支,
否则才用 keyframes——只要 references 非空,keyframes 会被直接忽略,不报错,静默丢弃。
官方倒是有个 H3-Context-IR(真实存在,查到了官方 API 文档),专门负责编排"首帧/尾帧/身份参考图/
参考视频各自扮演什么角色"这种复杂多模态输入,但官方原话明确写了"不包含在开源发布里"——只有
调用 MiniMax 付费云端 API 才能用,本地部署这条路走不通。
更麻烦的:我们自己的 api.py 没有任何防护,而且已经真的踩过
worker() 里无条件把 keyframes(来自 first_frame_url/last_frame_url)和 references
(来自 reference_image_urls/reference_video_urls)一起传给 pipe(),GenReq 这个
pydantic 模型也完全没有互斥校验——用户完全可以同时填首尾帧 URL 和参考图 URL 提交,系统照单全收,
但底层会按 references 非空走 ref2va 打包,首尾帧完全不起作用,没有任何报错或警告。
查了今天真实提交的任务 ed3663dcda7f:请求里 reference_image_urls 是空数组 []。
prompt 文本里写的"女子、捕快面部由@图片3、@图片5锁定"这类 @图片N 写法是照抄官方
Context-IR 的约定,但我们本地这条链路根本不认识这个约定——那两张"设定图"从来没真的作为
图片数据挂载给模型,纯粹是 prompt 里的文字,模型只是把这几个字当普通文字理解,没有对应的
图片输入。以后写 prompt 不要用这种 @图片N 的写法指望它会自动关联图片,除非真的把对应
URL 填进 reference_image_urls。
要做的事
- 给
GenReq加一条model_validator:如果(first_frame_url or last_frame_url)和(reference_image_urls or reference_video_urls)同时非空,直接 422 拒绝,错误信息写清楚 "FL2VA 首尾帧和 Ref2VA 参考图/参考视频不能同时传,pipeline 层会静默丢弃 keyframes"—— 参考ROWS_LIMIT那条校验的写法(api.py里已有先例),不是新模式。 - h3-console 前端也要同步:现在"首尾帧生视频"(fl2va tab)和"多参考图生视频"(ref2va tab) 是两个独立表单,UI 层面本来就不会让用户在同一次提交里同时填两种,这条主要是防后端被绕过 (比如有人直接调 API 不走 console),前端不用大改,但可以在 fl2va tab 的 hint 里加一句 "这个模式不支持额外挂参考图做身份锁定,要锁身份得改用 Ref2VA 模式",减少以后再犯同样的 prompt 写法误解。
- 给用户的实际建议(不是代码改动,是使用方式):像"延长视频+保持人物身份"这种场景,
本地开源版做不到"首尾帧强约束 + 身份参考图"同时要,只能二选一改用纯 Ref2VA:不设
first_frame_url/last_frame_url,把"原视频最后一帧截图 + 女子设定图 + 捕快设定图" 都塞进reference_image_urls(最多 9 张),需要运镜连贯性的话再加reference_video_urls(2026-08-20 新加的字段,还没在真实 GPU 上跑过一次,见verify-video-reference-on-gpu.md)。 代价是失去"第一帧/最后一帧精确锁定"这个强约束,换成"多张参考图做柔性引导",不是等价替换。
优先级
不着急——这是个"用错了会静默出错"的边界情况,不是现在正在发生的故障。但比起线上突然出现
"明明传了首尾帧却不起作用"的困惑,加一条清晰的 422 报错成本很低(半小时内能写完,参考
ROWS_LIMIT 校验抄一遍就行),建议跟 quality-tuning-plan.md 里的实测一起顺手做掉,
不用单独排期。