Files
patbond-ai/app/providers/volcengine/capabilities.py
T
lixi 8f1d1bc5f3 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>
2026-09-20 15:12:06 +08:00

123 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""各 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}"
)