Merge pull request 'fix(kling): 补齐全模态基础视频能力' (#16) from codex/omni-default-video-capabilities into main
ci / verify (push) Successful in 12m8s
release-ci / verify-tag (push) Successful in 9m56s
ci / verify (push) Successful in 12m8s
release-ci / verify-tag (push) Successful in 9m56s
This commit was merged in pull request #16.
This commit is contained in:
@@ -29,6 +29,23 @@ func TestKlingCompatibilitySimulationFlow(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var upgradedBaseModels int
|
||||
if err := db.Pool().QueryRow(ctx, `
|
||||
SELECT count(*)
|
||||
FROM base_model_catalog
|
||||
WHERE provider_key = 'keling'
|
||||
AND provider_model_name IN ('kling-video-o1', 'kling-v3-omni')
|
||||
AND model_type @> '["video_generate","image_to_video","omni_video"]'::jsonb
|
||||
AND capabilities ? 'video_generate'
|
||||
AND capabilities ? 'image_to_video'
|
||||
AND capabilities ? 'omni_video'
|
||||
AND metadata->'rawModel'->'types' @> '["video_generate","image_to_video","omni_video"]'::jsonb`).Scan(&upgradedBaseModels); err != nil {
|
||||
t.Fatalf("read upgraded Kling Omni base model capabilities: %v", err)
|
||||
}
|
||||
if upgradedBaseModels != 2 {
|
||||
t.Fatalf("expected both Kling Omni base models to expose base video capabilities, got %d", upgradedBaseModels)
|
||||
}
|
||||
|
||||
serverCtx, cancelServer := context.WithCancel(ctx)
|
||||
defer cancelServer()
|
||||
server := httptest.NewServer(NewServerWithContext(serverCtx, config.Config{
|
||||
@@ -89,6 +106,55 @@ func TestKlingCompatibilitySimulationFlow(t *testing.T) {
|
||||
"displayName": model,
|
||||
}, http.StatusCreated, nil)
|
||||
}
|
||||
var upgradedPlatformModels int
|
||||
if err := db.Pool().QueryRow(ctx, `
|
||||
SELECT count(*)
|
||||
FROM platform_models
|
||||
WHERE platform_id = $1::uuid
|
||||
AND model_type @> '["video_generate","image_to_video","omni_video"]'::jsonb
|
||||
AND capabilities ? 'video_generate'
|
||||
AND capabilities ? 'image_to_video'
|
||||
AND capabilities ? 'omni_video'`, platform.ID).Scan(&upgradedPlatformModels); err != nil {
|
||||
t.Fatalf("read upgraded Kling Omni platform model capabilities: %v", err)
|
||||
}
|
||||
if upgradedPlatformModels != 2 {
|
||||
t.Fatalf("expected both Kling Omni platform models to expose base video capabilities, got %d", upgradedPlatformModels)
|
||||
}
|
||||
|
||||
assertGenericVideoGeneration := func(name string, model string, image string, expectedModelType string) {
|
||||
t.Helper()
|
||||
t.Run(name, func(t *testing.T) {
|
||||
request := map[string]any{
|
||||
"model": model,
|
||||
"prompt": "通用视频接口模拟任务",
|
||||
"duration": 5,
|
||||
"resolution": "720p",
|
||||
"runMode": "simulation",
|
||||
"simulation": true,
|
||||
"simulationDurationMs": 5,
|
||||
}
|
||||
if image != "" {
|
||||
request["image"] = image
|
||||
}
|
||||
var response struct {
|
||||
Task struct {
|
||||
Status string `json:"status"`
|
||||
ModelType string `json:"modelType"`
|
||||
ResolvedModel string `json:"resolvedModel"`
|
||||
} `json:"task"`
|
||||
}
|
||||
doJSON(t, server.URL, http.MethodPost, "/api/v1/videos/generations", apiKeyResponse.Secret, request, http.StatusAccepted, &response)
|
||||
resolvedModel, resolved := klingV2ProviderModel(response.Task.ResolvedModel)
|
||||
if response.Task.Status != "succeeded" || response.Task.ModelType != expectedModelType || !resolved || resolvedModel != model {
|
||||
t.Fatalf("generic video request without modelType should use inferred capability: %+v", response.Task)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for _, model := range []string{klingO1Model, klingV3OmniModel} {
|
||||
assertGenericVideoGeneration(model+"-text-to-video", model, "", "video_generate")
|
||||
assertGenericVideoGeneration(model+"-image-to-video", model, "https://example.com/first.png", "image_to_video")
|
||||
}
|
||||
|
||||
createV1 := func(model string, duration int, externalID string) string {
|
||||
t.Helper()
|
||||
|
||||
@@ -488,6 +488,8 @@ func modelTypeAliases(value string) []string {
|
||||
return []string{"image_edit"}
|
||||
case "video", "videos.generations":
|
||||
return []string{"video_generate"}
|
||||
case "omni_video":
|
||||
return []string{"video_generate", "image_to_video", "omni_video"}
|
||||
case "song", "music", "song.generations", "music.generations", "music_generate":
|
||||
return []string{"audio_generate"}
|
||||
case "speech", "speech.generations", "tts":
|
||||
|
||||
@@ -9,6 +9,19 @@ func TestNormalizeModelMatchKeyRemovesWhitespace(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeModelTypeListExpandsOmniVideoBaseCapabilities(t *testing.T) {
|
||||
got := normalizeModelTypeList([]string{"omni_video"})
|
||||
want := StringList{"video_generate", "image_to_video", "omni_video"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("omni_video should include text-to-video and image-to-video capabilities: got=%v want=%v", got, want)
|
||||
}
|
||||
for index := range want {
|
||||
if got[index] != want[index] {
|
||||
t.Fatalf("omni_video capability mismatch at %d: got=%v want=%v", index, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskBillingModelIdentityKeepsRequestedModelPrimary(t *testing.T) {
|
||||
identity := taskBillingModelIdentity(GatewayTask{
|
||||
Model: "doubao-5.0 图像编辑",
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
WITH keling_omni_models(provider_model_name, model_type, capabilities) AS (
|
||||
VALUES
|
||||
(
|
||||
'kling-video-o1',
|
||||
'["video_generate","image_to_video","omni_video"]'::jsonb,
|
||||
'{
|
||||
"video_generate": {
|
||||
"output_resolutions": ["720p", "1080p"],
|
||||
"aspect_ratio_allowed": ["16:9", "1:1", "9:16"],
|
||||
"duration_options": [3, 4, 5, 6, 7, 8, 9, 10],
|
||||
"output_audio": false,
|
||||
"input_audio": false,
|
||||
"prompt_length_limit": {
|
||||
"max": 2500,
|
||||
"count_mode": "non_ascii_weighted",
|
||||
"label": "可灵口径"
|
||||
}
|
||||
},
|
||||
"image_to_video": {
|
||||
"output_resolutions": ["720p", "1080p"],
|
||||
"aspect_ratio_allowed": ["16:9", "1:1", "9:16"],
|
||||
"duration_options": [3, 4, 5, 6, 7, 8, 9, 10],
|
||||
"input_first_frame": true,
|
||||
"input_last_frame": false,
|
||||
"input_first_last_frame": true,
|
||||
"input_reference_generate_single": true,
|
||||
"input_reference_generate_multiple": true,
|
||||
"max_images": 7,
|
||||
"max_images_for_last_frame": 2,
|
||||
"support_video_effect_template": false,
|
||||
"output_audio": false,
|
||||
"input_audio": false,
|
||||
"prompt_length_limit": {
|
||||
"max": 2500,
|
||||
"count_mode": "non_ascii_weighted",
|
||||
"label": "可灵口径"
|
||||
}
|
||||
},
|
||||
"omni_video": {
|
||||
"supported_modes": ["text_to_video", "image_reference", "element_reference", "first_last_frame", "video_reference", "video_edit"],
|
||||
"output_resolutions": ["720p", "1080p"],
|
||||
"aspect_ratio_allowed": ["16:9", "1:1", "9:16"],
|
||||
"duration_options": [3, 4, 5, 6, 7, 8, 9, 10],
|
||||
"output_audio": false,
|
||||
"input_audio": false,
|
||||
"max_videos": 1,
|
||||
"max_audios": 0,
|
||||
"max_images": 7,
|
||||
"max_elements": 7,
|
||||
"max_images_and_elements": 7,
|
||||
"limits_with_video": {"max_images_and_elements": 4},
|
||||
"max_images_for_last_frame": 2,
|
||||
"support_instruction_edit": true,
|
||||
"prompt_length_limit": {
|
||||
"max": 2500,
|
||||
"count_mode": "non_ascii_weighted",
|
||||
"label": "可灵口径"
|
||||
}
|
||||
},
|
||||
"originalTypes": ["video_generate", "image_to_video", "omni_video"]
|
||||
}'::jsonb
|
||||
),
|
||||
(
|
||||
'kling-v3-omni',
|
||||
'["video_generate","image_to_video","omni_video"]'::jsonb,
|
||||
'{
|
||||
"video_generate": {
|
||||
"output_resolutions": ["720p", "1080p", "2160p"],
|
||||
"aspect_ratio_allowed": ["16:9", "1:1", "9:16"],
|
||||
"duration_options": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
"output_audio": true,
|
||||
"input_audio": false,
|
||||
"prompt_length_limit": {
|
||||
"max": 2500,
|
||||
"count_mode": "non_ascii_weighted",
|
||||
"label": "可灵口径"
|
||||
}
|
||||
},
|
||||
"image_to_video": {
|
||||
"output_resolutions": ["720p", "1080p", "2160p"],
|
||||
"aspect_ratio_allowed": ["16:9", "1:1", "9:16"],
|
||||
"duration_options": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
"input_first_frame": true,
|
||||
"input_last_frame": false,
|
||||
"input_first_last_frame": true,
|
||||
"input_reference_generate_single": true,
|
||||
"input_reference_generate_multiple": true,
|
||||
"max_images": 7,
|
||||
"max_images_for_last_frame": 2,
|
||||
"support_video_effect_template": false,
|
||||
"output_audio": true,
|
||||
"input_audio": false,
|
||||
"prompt_length_limit": {
|
||||
"max": 2500,
|
||||
"count_mode": "non_ascii_weighted",
|
||||
"label": "可灵口径"
|
||||
}
|
||||
},
|
||||
"omni_video": {
|
||||
"supported_modes": ["text_to_video", "image_reference", "element_reference", "first_last_frame", "video_reference", "video_edit", "multi_shot"],
|
||||
"output_resolutions": ["720p", "1080p", "2160p"],
|
||||
"aspect_ratio_allowed": ["16:9", "1:1", "9:16"],
|
||||
"duration_options": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
"output_audio": true,
|
||||
"input_audio": false,
|
||||
"max_videos": 1,
|
||||
"max_audios": 0,
|
||||
"max_images": 7,
|
||||
"max_elements": 7,
|
||||
"max_images_and_elements": 7,
|
||||
"limits_with_video": {
|
||||
"max_images_and_elements": 4,
|
||||
"duration_options": [3, 4, 5, 6, 7, 8, 9, 10]
|
||||
},
|
||||
"max_images_for_last_frame": 2,
|
||||
"support_instruction_edit": true,
|
||||
"prompt_length_limit": {
|
||||
"max": 2500,
|
||||
"count_mode": "non_ascii_weighted",
|
||||
"label": "可灵口径"
|
||||
}
|
||||
},
|
||||
"originalTypes": ["video_generate", "image_to_video", "omni_video"]
|
||||
}'::jsonb
|
||||
)
|
||||
)
|
||||
UPDATE base_model_catalog model
|
||||
SET model_type = defs.model_type,
|
||||
capabilities = defs.capabilities,
|
||||
metadata = COALESCE(model.metadata, '{}'::jsonb)
|
||||
|| jsonb_build_object(
|
||||
'originalTypes', defs.model_type,
|
||||
'rawModel', COALESCE(model.metadata->'rawModel', '{}'::jsonb)
|
||||
|| jsonb_build_object(
|
||||
'types', defs.model_type,
|
||||
'capabilities', defs.capabilities
|
||||
)
|
||||
),
|
||||
default_snapshot = CASE
|
||||
WHEN COALESCE(model.default_snapshot, '{}'::jsonb) = '{}'::jsonb THEN model.default_snapshot
|
||||
ELSE jsonb_set(
|
||||
jsonb_set(
|
||||
jsonb_set(model.default_snapshot, '{modelType}', defs.model_type, true),
|
||||
'{capabilities}',
|
||||
defs.capabilities,
|
||||
true
|
||||
),
|
||||
'{metadata}',
|
||||
COALESCE(model.default_snapshot->'metadata', '{}'::jsonb)
|
||||
|| jsonb_build_object(
|
||||
'originalTypes', defs.model_type,
|
||||
'rawModel', COALESCE(model.default_snapshot->'metadata'->'rawModel', '{}'::jsonb)
|
||||
|| jsonb_build_object(
|
||||
'types', defs.model_type,
|
||||
'capabilities', defs.capabilities
|
||||
)
|
||||
),
|
||||
true
|
||||
)
|
||||
END,
|
||||
updated_at = now()
|
||||
FROM keling_omni_models defs
|
||||
WHERE model.provider_model_name = defs.provider_model_name
|
||||
AND model.model_type @> '["omni_video"]'::jsonb;
|
||||
|
||||
WITH keling_omni_models(provider_model_name, model_type, capabilities) AS (
|
||||
SELECT DISTINCT ON (model.provider_model_name)
|
||||
model.provider_model_name,
|
||||
model.model_type,
|
||||
model.capabilities
|
||||
FROM base_model_catalog model
|
||||
WHERE model.provider_model_name IN ('kling-video-o1', 'kling-v3-omni')
|
||||
AND model.model_type @> '["omni_video"]'::jsonb
|
||||
ORDER BY model.provider_model_name, (model.provider_key = 'keling') DESC, model.created_at ASC
|
||||
)
|
||||
UPDATE platform_models model
|
||||
SET model_type = defs.model_type,
|
||||
capabilities = defs.capabilities,
|
||||
updated_at = now()
|
||||
FROM keling_omni_models defs
|
||||
WHERE COALESCE(NULLIF(model.provider_model_name, ''), model.model_name) = defs.provider_model_name
|
||||
AND model.model_type @> '["omni_video"]'::jsonb;
|
||||
Reference in New Issue
Block a user