feat(seedance): 两个已开通模型的限制表与提交前本地预校验(23 测试)
账号实际接入 doubao-seedance-2-5-260628 与 doubao-seedance-2-0-260128,两者限制 差异不小,且同一组参数在一个上合法、在另一个上不合法: | | 2.5 | 2.0 | | 时长 | [4, 30] | [4, 15] | | 分辨率 | 480p/720p/1080p | 480p/720p/1080p/4k | | 图生视频 ratio | **只能 adaptive** | 可指定 | | output_format | mp4/mov | 仅 mp4 | 超范围的请求会被厂商 400 拒绝——那要花一次往返、计入 RPM,并表现为一个失败的 任务。故在 capabilities.py 落一张限制表并在提交前本地预校验,错误信息直接指出 是哪个模型的哪条限制。 两处刻意的设计: - **按模型族前缀匹配**而非完整 ID:尾部是日期版本(260628/260128),会随厂商 小版本更新而变,按前缀匹配使一次升级不必改表。 - **未知模型一律放行**(只记日志):账号新开通一个模型时不该因为这张表没更新 就用不了,否则「开通即可用」会退化成「开通还得改代码发版」。代价只是失去 本地预校验,厂商仍会兜底。 测试 100 → 123:限制表 23 项逐条对应官方文档的一条限制,另在适配器层加 4 项 端到端断言(超范围请求的 handler 直接 assert 不该被调用,确认请求根本没发出)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
"""各 Seedance 模型的参数限制。
|
||||
|
||||
**为什么需要它**:已接入的两个模型限制不同——2.5 时长上限 30 秒但没有 4k,
|
||||
2.0 有 4k 但时长上限 15 秒;2.5 在图生视频时 `ratio` 只能是 `adaptive`。超范围的
|
||||
请求会被厂商以 400 拒绝,那要花一次往返、计入 RPM,并表现为一个失败的任务。
|
||||
在本地先拦下来,错误信息还能具体指出是哪个模型的哪条限制。
|
||||
|
||||
**未知模型一律放行**(只记一条日志):账号新开通一个模型时,不应该因为这张表
|
||||
没更新就用不了——那会让「开通即可用」变成「开通还得改代码发版」。代价只是失去
|
||||
本地预校验,厂商仍会兜底。
|
||||
|
||||
限制取自官方文档,取证见 patbond-doc
|
||||
`docs/development/providers/volcengine-seedance.md` §5。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ModelLimits:
|
||||
"""一个模型族的参数限制。
|
||||
|
||||
:param ratio_must_be_adaptive_with_first_frame: 2.5 在图生视频(首帧/首尾帧)
|
||||
与视频编辑/延长任务中**只支持 `adaptive`**——模型自动保持输出宽高比与
|
||||
首帧图片一致,不接受指定值。2.0 系列则可指定。
|
||||
:param supports_output_format: `output_format`(mp4/mov)仅 2.5 支持。
|
||||
"""
|
||||
|
||||
resolutions: frozenset[str]
|
||||
min_duration_sec: int
|
||||
max_duration_sec: int
|
||||
ratio_must_be_adaptive_with_first_frame: bool
|
||||
supports_output_format: bool
|
||||
|
||||
|
||||
#: Seedance 2.5:时长可到 30 秒,但没有 4k;图生视频时宽高比锁 adaptive。
|
||||
#: 1080p 输出为 10bit + H.265/HEVC,少数播放环境可能不兼容。
|
||||
SEEDANCE_2_5 = ModelLimits(
|
||||
resolutions=frozenset({"480p", "720p", "1080p"}),
|
||||
min_duration_sec=4,
|
||||
max_duration_sec=30,
|
||||
ratio_must_be_adaptive_with_first_frame=True,
|
||||
supports_output_format=True,
|
||||
)
|
||||
|
||||
#: Seedance 2.0 系列:有 4k,但时长上限 15 秒。4k 输出为 10bit + H.265/HEVC。
|
||||
SEEDANCE_2_0 = ModelLimits(
|
||||
resolutions=frozenset({"480p", "720p", "1080p", "4k"}),
|
||||
min_duration_sec=4,
|
||||
max_duration_sec=15,
|
||||
ratio_must_be_adaptive_with_first_frame=False,
|
||||
supports_output_format=False,
|
||||
)
|
||||
|
||||
#: 按模型族前缀匹配,而非精确匹配完整 model ID。
|
||||
#: 完整 ID 形如 `doubao-seedance-2-5-260628`,尾部的日期版本会随厂商更新而变;
|
||||
#: 按前缀匹配使一次模型小版本升级不需要改这张表。
|
||||
_BY_PREFIX: tuple[tuple[str, ModelLimits], ...] = (
|
||||
("doubao-seedance-2-5", SEEDANCE_2_5),
|
||||
("doubao-seedance-2-0", SEEDANCE_2_0),
|
||||
)
|
||||
|
||||
#: `duration` 的特殊值:交给模型在有效范围内自选。不参与范围校验。
|
||||
AUTO_DURATION = -1
|
||||
|
||||
|
||||
def limits_for(model: str) -> ModelLimits | None:
|
||||
"""查模型限制;未知模型返回 None(表示不做本地预校验)。"""
|
||||
for prefix, limits in _BY_PREFIX:
|
||||
if model.startswith(prefix):
|
||||
return limits
|
||||
return None
|
||||
|
||||
|
||||
def validate(
|
||||
model: str,
|
||||
*,
|
||||
resolution: str | None = None,
|
||||
duration_sec: int | None = None,
|
||||
ratio: str | None = None,
|
||||
has_first_frame: bool = False,
|
||||
) -> None:
|
||||
"""按模型限制校验参数,不合法则抛 `ValueError`。
|
||||
|
||||
未知模型直接放行——见模块 docstring。
|
||||
|
||||
:raises ValueError: 参数超出该模型的限制,消息里指明模型与具体限制
|
||||
"""
|
||||
limits = limits_for(model)
|
||||
if limits is None:
|
||||
log.info("模型 %s 无本地限制表,跳过预校验,由厂商兜底", model)
|
||||
return
|
||||
|
||||
if resolution and resolution not in limits.resolutions:
|
||||
raise ValueError(
|
||||
f"模型 {model} 不支持分辨率 {resolution},"
|
||||
f"可选:{sorted(limits.resolutions)}"
|
||||
)
|
||||
|
||||
if duration_sec is not None and duration_sec != AUTO_DURATION:
|
||||
if not limits.min_duration_sec <= duration_sec <= limits.max_duration_sec:
|
||||
raise ValueError(
|
||||
f"模型 {model} 的时长需在 "
|
||||
f"[{limits.min_duration_sec}, {limits.max_duration_sec}] 秒内"
|
||||
f"(或 {AUTO_DURATION} 表示由模型自选),当前 {duration_sec}"
|
||||
)
|
||||
|
||||
if (
|
||||
has_first_frame
|
||||
and limits.ratio_must_be_adaptive_with_first_frame
|
||||
and ratio
|
||||
and ratio != "adaptive"
|
||||
):
|
||||
raise ValueError(
|
||||
f"模型 {model} 在图生视频时只支持 ratio=adaptive"
|
||||
f"(输出宽高比自动与首帧图片一致),当前 {ratio}"
|
||||
)
|
||||
Reference in New Issue
Block a user