Split param processor and tighten Volces frame validation

This commit is contained in:
2026-05-14 00:41:06 +08:00
parent 3225833f96
commit cdf469eccf
9 changed files with 1983 additions and 1538 deletions
@@ -381,6 +381,128 @@ func TestParamProcessorVideoCapabilitiesNormalizeAndFilter(t *testing.T) {
}
}
func TestParamProcessorDowngradesReferenceImagesToFrames(t *testing.T) {
candidate := store.RuntimeModelCandidate{
ModelType: "image_to_video",
Capabilities: map[string]any{
"image_to_video": map[string]any{
"input_first_frame": true,
"input_first_last_frame": true,
"input_reference_generate_single": false,
"input_reference_generate_multiple": false,
},
},
}
body := map[string]any{
"model": "Seedance",
"prompt": "animate it",
"content": []any{
map[string]any{"type": "text", "text": "animate it"},
map[string]any{"type": "image_url", "role": "reference_image", "image_url": map[string]any{"url": "https://example.com/first.png"}},
map[string]any{"type": "image_url", "role": "reference_image", "image_url": map[string]any{"url": "https://example.com/last.png"}},
},
}
result := preprocessRequestWithLog("videos.generations", body, candidate)
if result.Err != nil {
t.Fatalf("two image references should downgrade to first/last frames: %v", result.Err)
}
content := contentItems(result.Body["content"])
if stringFromAny(content[1]["role"]) != "first_frame" || stringFromAny(content[2]["role"]) != "last_frame" {
t.Fatalf("expected first/last frame downgrade, got %+v", content)
}
}
func TestParamProcessorDowngradesSingleReferenceImageToFirstFrame(t *testing.T) {
candidate := store.RuntimeModelCandidate{
ModelType: "image_to_video",
Capabilities: map[string]any{
"image_to_video": map[string]any{
"input_first_frame": true,
"input_first_last_frame": true,
"input_reference_generate_single": false,
"input_reference_generate_multiple": false,
},
},
}
body := map[string]any{
"model": "Seedance",
"prompt": "animate it",
"content": []any{
map[string]any{"type": "text", "text": "animate it"},
map[string]any{"type": "image_url", "role": "reference_image", "image_url": map[string]any{"url": "https://example.com/first.png"}},
},
}
result := preprocessRequestWithLog("videos.generations", body, candidate)
if result.Err != nil {
t.Fatalf("single image reference should downgrade to first frame: %v", result.Err)
}
content := contentItems(result.Body["content"])
if stringFromAny(content[1]["role"]) != "first_frame" {
t.Fatalf("expected first frame downgrade, got %+v", content)
}
}
func TestParamProcessorRejectsUnsafeReferenceImageDowngrade(t *testing.T) {
candidate := store.RuntimeModelCandidate{
ModelType: "image_to_video",
Capabilities: map[string]any{
"image_to_video": map[string]any{
"input_first_frame": true,
"input_first_last_frame": false,
"input_reference_generate_single": false,
"input_reference_generate_multiple": false,
},
},
}
body := map[string]any{
"model": "Seedance",
"prompt": "animate it",
"content": []any{
map[string]any{"type": "text", "text": "animate it"},
map[string]any{"type": "image_url", "role": "reference_image", "image_url": map[string]any{"url": "https://example.com/first.png"}},
map[string]any{"type": "image_url", "role": "reference_image", "image_url": map[string]any{"url": "https://example.com/last.png"}},
},
}
result := preprocessRequestWithLog("videos.generations", body, candidate)
if result.Err == nil {
t.Fatalf("two image references should be rejected when first/last frame is unsupported")
}
if len(result.Log.Changes) == 0 || result.Log.Changes[len(result.Log.Changes)-1].Action != "reject" {
t.Fatalf("expected reject preprocessing log, got %+v", result.Log.Changes)
}
}
func TestParamProcessorRejectsVideoOrAudioReferenceDowngrade(t *testing.T) {
candidate := store.RuntimeModelCandidate{
ModelType: "image_to_video",
Capabilities: map[string]any{
"image_to_video": map[string]any{
"input_first_frame": true,
"input_first_last_frame": true,
"input_reference_generate_single": false,
"input_reference_generate_multiple": false,
},
},
}
body := map[string]any{
"model": "Seedance",
"prompt": "animate it",
"content": []any{
map[string]any{"type": "text", "text": "animate it"},
map[string]any{"type": "image_url", "role": "reference_image", "image_url": map[string]any{"url": "https://example.com/first.png"}},
map[string]any{"type": "video_url", "role": "reference_video", "video_url": map[string]any{"url": "https://example.com/ref.mp4"}},
},
}
result := preprocessRequestWithLog("videos.generations", body, candidate)
if result.Err == nil {
t.Fatalf("video reference should be rejected instead of downgraded")
}
}
func TestParamProcessorDurationRangeRoundsFractionalSecondsUp(t *testing.T) {
body := map[string]any{
"model": "Seedance",