fix(models): 模型目录 - 保留人工能力并按声明发送视频分辨率

This commit is contained in:
ddCat committed 2026-08-17 12:56:51 +08:00
1 parent 050a95c1d5
commit c2352fd9b6
11 files changed
+218 -48

No files matched your search

+15 -4
View File
@@ -67,6 +67,7 @@ type VideoCapabilityConfig struct {
type VideoReferenceConfig struct {
PromptMaxChars int `json:"promptMaxChars"`
MinImages int `json:"minImages"`
MaxImages int `json:"maxImages"`
MaxImageBytes int64 `json:"maxImageBytes"`
MaxVideos int `json:"maxVideos"`
@@ -154,7 +155,7 @@ func defaultImageSizeValues() []string {
func DefaultModelCapabilityConfigForModel(protocol string, modelName string) *ModelCapabilityConfig {
video := &VideoCapabilityConfig{
References: VideoReferenceConfig{PromptMaxChars: 1000, MaxImages: 9, MaxImageBytes: 30 * 1024 * 1024, MaxVideos: 0, MaxVideoBytes: 0, MaxVideoDuration: 0, MaxAudios: 0, MaxAudioBytes: 0, MaxAudioDuration: 0},
References: VideoReferenceConfig{PromptMaxChars: 1000, MinImages: 0, MaxImages: 9, MaxImageBytes: 30 * 1024 * 1024, MaxVideos: 0, MaxVideoBytes: 0, MaxVideoDuration: 0, MaxAudios: 0, MaxAudioBytes: 0, MaxAudioDuration: 0},
Duration: VideoDurationConfig{Selection: "range", Min: 1, Max: 15, Step: 1, Default: 6},
Ratios: []string{"16:9", "9:16", "1:1", "4:3", "3:4", "21:9"},
DefaultRatio: "16:9",
@@ -277,11 +278,14 @@ func validateVideoCapabilityConfig(value *VideoCapabilityConfig) error {
if value.References.PromptMaxChars < 1 || value.References.PromptMaxChars > 1000000 {
return BadAuthRequest("提示词最大字符数必须在 1-1000000 之间")
}
for name, number := range map[string]int{"最大图片引用数": value.References.MaxImages, "最大视频引用数": value.References.MaxVideos, "最大音频引用数": value.References.MaxAudios} {
for name, number := range map[string]int{"最少图片引用数": value.References.MinImages, "最大图片引用数": value.References.MaxImages, "最大视频引用数": value.References.MaxVideos, "最大音频引用数": value.References.MaxAudios} {
if number < 0 || number > 100 {
return BadAuthRequest(name + "必须在 0-100 之间")
}
}
if value.References.MinImages > value.References.MaxImages {
return BadAuthRequest("最少图片引用数不能超过最大图片引用数")
}
if value.References.MaxImageBytes < 0 || value.References.MaxVideoBytes < 0 || value.References.MaxAudioBytes < 0 || value.References.MaxVideoDuration < 0 || value.References.MaxAudioDuration < 0 {
return BadAuthRequest("引用素材限制不能小于 0")
}
@@ -291,8 +295,12 @@ func validateVideoCapabilityConfig(value *VideoCapabilityConfig) error {
if len(value.Ratios) == 0 || strings.TrimSpace(value.DefaultRatio) == "" || !containsCapabilityString(value.Ratios, value.DefaultRatio) {
return BadAuthRequest("请至少配置一个画面比例,并选择默认比例")
}
if len(value.Resolutions) == 0 || strings.TrimSpace(value.DefaultResolution) == "" || !containsCapabilityString(value.Resolutions, value.DefaultResolution) {
return BadAuthRequest("请至少配置一个输出分辨率,并选择默认分辨率")
if len(value.Resolutions) == 0 {
if strings.TrimSpace(value.DefaultResolution) != "" {
return BadAuthRequest("未配置输出分辨率时不能设置默认分辨率")
}
} else if strings.TrimSpace(value.DefaultResolution) == "" || !containsCapabilityString(value.Resolutions, value.DefaultResolution) {
return BadAuthRequest("默认输出分辨率必须属于支持值")
}
if len(value.Operations) == 0 || strings.TrimSpace(value.DefaultOperation) == "" || !containsCapabilityString(value.Operations, value.DefaultOperation) {
return BadAuthRequest("请至少配置一个生成模式,并选择默认模式")
@@ -377,6 +385,9 @@ func validateVideoTask(profile *VideoCapabilityConfig, input canvasGenerationInp
if len(input.ReferenceImages) > profile.References.MaxImages || len(input.ReferenceVideos) > profile.References.MaxVideos || len(input.ReferenceAudios) > profile.References.MaxAudios {
return BadAuthRequest("参考素材数量超过当前模型限制")
}
if len(input.ReferenceImages) < profile.References.MinImages {
return BadAuthRequest(fmt.Sprintf("当前视频模型至少需要 %d 张参考图", profile.References.MinImages))
}
for _, media := range input.ReferenceImages {
if profile.References.MaxImageBytes > 0 && media.Bytes > profile.References.MaxImageBytes {
return BadAuthRequest("参考图片文件超过当前模型大小限制")
@@ -116,3 +116,32 @@ func TestValidateVideoTaskIgnoresGlobalResolutionWhenCatalogDeclaresNone(t *test
t.Fatalf("validateVideoTask() error = %v", err)
}
}
func TestNormalizeVideoCapabilityAllowsOmittedResolution(t *testing.T) {
profile := DefaultModelCapabilityConfigForModel("newapi-channel-2", "endpoint-video").Video
profile.Resolutions = nil
profile.DefaultResolution = ""
result, err := NormalizeModelCapabilityConfig("video", "newapi-channel-2", &ModelCapabilityConfig{Version: 1, Video: profile})
if err != nil {
t.Fatalf("NormalizeModelCapabilityConfig() error = %v", err)
}
if result.Video == nil || len(result.Video.Resolutions) != 0 || result.Video.DefaultResolution != "" {
t.Fatalf("normalized video resolution = %#v", result.Video)
}
}
func TestValidateVideoTaskRequiresDeclaredMinimumImages(t *testing.T) {
profile := DefaultModelCapabilityConfigForModel("newapi-channel-2", "image-required-video").Video
profile.References.MinImages = 1
profile.References.MaxImages = 2
profile.Operations = []string{"image_to_video"}
profile.DefaultOperation = "image_to_video"
err := validateVideoTask(profile, canvasGenerationInput{
Config: providerConfig{Model: "image-required-video", VideoSeconds: "6", Size: "16:9", VQuality: "720"},
})
if err == nil || !strings.Contains(err.Error(), "至少需要 1 张参考图") {
t.Fatalf("validateVideoTask() error = %v", err)
}
}
+4 -1
View File
@@ -1973,7 +1973,10 @@ func newAPIChannel2VideoRequestBody(input canvasGenerationInput) (newAPIVideoReq
seconds = 6
}
ratio := normalizeNewAPIChannel2Ratio(input.Config.Size, modelName)
resolution := normalizeNewAPIChannel2Resolution(input.Config.VQuality, modelName)
resolution := videoResolutionNameRequest(input.VideoCapability, input.Config.VQuality)
if modelName == "grok-video-1.5-1080p" {
resolution = "1080p"
}
body := newAPIVideoRequest{
Model: input.Config.Model,
Prompt: strings.TrimSpace(input.Prompt),
@@ -19,7 +19,7 @@ type newAPIVideoRequest struct {
Prompt string `json:"prompt"`
Seconds string `json:"seconds"`
AspectRatio string `json:"aspect_ratio"`
Resolution string `json:"resolution"`
Resolution string `json:"resolution,omitempty"`
GenerateAudio *bool `json:"generate_audio,omitempty"`
ImageURLs []string `json:"image_urls,omitempty"`
VideoURLs []string `json:"video_urls,omitempty"`
+49 -1
View File
@@ -1278,9 +1278,12 @@ func TestRunNewAPIChannel2VideoTaskDownloadsTemporaryResult(t *testing.T) {
}))
defer server.Close()
profile := DefaultModelCapabilityConfigForModel("newapi-channel-2", "grok-image-video").Video
profile.Resolutions = []string{"720p"}
profile.DefaultResolution = "720p"
result, err := runVideoTask(context.Background(), canvasGenerationInput{
Prompt: "make it move",
Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "grok-image-video", InterfaceType: "newapi-channel-2", VideoSeconds: "15", Size: "720x1280", VQuality: "high"},
Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "grok-image-video", InterfaceType: "newapi-channel-2", VideoSeconds: "15", Size: "720x1280", VQuality: "720"},
ReferenceImages: []providerMedia{
{ID: "image-1", DataURL: testReferenceImageDataURL},
{ID: "image-2", DataURL: testReferenceImageDataURL},
@@ -1288,6 +1291,7 @@ func TestRunNewAPIChannel2VideoTaskDownloadsTemporaryResult(t *testing.T) {
ReferenceVideos: []providerMedia{{ID: "video-1", URL: server.URL + "/reference.mp4"}},
ReferenceAudios: []providerMedia{{ID: "audio-1", URL: server.URL + "/reference.mp3"}},
Metadata: map[string]interface{}{"videoEditOperation": "image_to_video"},
VideoCapability: profile,
})
if err != nil {
t.Fatalf("runVideoTask() error = %v", err)
@@ -1354,6 +1358,50 @@ func TestNewAPIChannel2SingleImageModelsRequireOneReference(t *testing.T) {
}
}
func TestNewAPIChannel2SendsOnlyDeclaredResolution(t *testing.T) {
tests := []struct {
name string
model string
quality string
resolutions []string
want string
}{
{name: "catalog omits resolution", model: "endpoint-video", quality: "720"},
{name: "declared 2K alias", model: "declared-video", quality: "2K", resolutions: []string{"1440p"}, want: "1440p"},
{name: "fixed grok 1080p model", model: "grok-video-1.5-1080p", quality: "720", want: "1080p"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
profile := DefaultModelCapabilityConfigForModel("newapi-channel-2", test.model).Video
profile.Resolutions = test.resolutions
profile.DefaultResolution = ""
input := canvasGenerationInput{
Config: providerConfig{Model: test.model, VideoSeconds: "6", Size: "16:9", VQuality: test.quality},
VideoCapability: profile,
}
if strings.HasPrefix(test.model, "grok-video-1.5") {
input.ReferenceImages = []providerMedia{{ID: "image-1", DataURL: testReferenceImageDataURL}}
}
body, err := newAPIChannel2VideoRequestBody(input)
if err != nil {
t.Fatalf("newAPIChannel2VideoRequestBody() error = %v", err)
}
if body.Resolution != test.want {
t.Fatalf("resolution = %q, want %q", body.Resolution, test.want)
}
mapped, err := requestAsMap(body)
if err != nil {
t.Fatalf("requestAsMap() error = %v", err)
}
_, hasResolution := mapped["resolution"]
if hasResolution != (test.want != "") {
t.Fatalf("resolution presence = %v, want %v; body = %#v", hasResolution, test.want != "", mapped)
}
})
}
}
func TestNewAPIChannel2RejectsAudioWithoutReferenceVideo(t *testing.T) {
_, err := newAPIChannel2VideoRequestBody(canvasGenerationInput{
Config: providerConfig{Model: "grok-image-video", VideoSeconds: "6"},