Split param processor and tighten Volces frame validation
This commit is contained in:
@@ -440,6 +440,40 @@ func TestVolcesClientVideoSubmitsAndPollsTask(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolcesClientVideoRejectsDuplicateFirstFrameBeforeSubmit(t *testing.T) {
|
||||
var submitted bool
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
submitted = true
|
||||
t.Fatalf("duplicate first_frame request should not be submitted upstream")
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
_, err := (VolcesClient{HTTPClient: server.Client()}).Run(context.Background(), Request{
|
||||
Kind: "videos.generations",
|
||||
ModelType: "image_to_video",
|
||||
Model: "豆包Seedance",
|
||||
Body: map[string]any{
|
||||
"model": "豆包Seedance",
|
||||
"content": []any{
|
||||
map[string]any{"type": "text", "text": "animate it"},
|
||||
map[string]any{"type": "image_url", "role": "first_frame", "image_url": map[string]any{"url": "https://example.com/first.png"}},
|
||||
map[string]any{"type": "image_url", "role": "first_frame", "image_url": map[string]any{"url": "https://example.com/second.png"}},
|
||||
},
|
||||
},
|
||||
Candidate: store.RuntimeModelCandidate{
|
||||
BaseURL: server.URL,
|
||||
ProviderModelName: "doubao-seedance-1-5-pro-251215",
|
||||
Credentials: map[string]any{"apiKey": "volces-key"},
|
||||
},
|
||||
})
|
||||
if err == nil || ErrorCode(err) != "invalid_parameter" {
|
||||
t.Fatalf("expected local invalid_parameter error, got %v", err)
|
||||
}
|
||||
if submitted {
|
||||
t.Fatal("request was submitted upstream")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolcesVideoBodyAllowsOnlyTaskPayloadFields(t *testing.T) {
|
||||
body := volcesVideoBody(Request{
|
||||
Kind: "videos.generations",
|
||||
|
||||
@@ -76,6 +76,9 @@ func (c VolcesClient) runVideo(ctx context.Context, request Request, apiKey stri
|
||||
upstreamTaskID := strings.TrimSpace(request.RemoteTaskID)
|
||||
if upstreamTaskID == "" {
|
||||
body := volcesVideoBody(request)
|
||||
if err := validateVolcesVideoTaskBody(body); err != nil {
|
||||
return Response{}, err
|
||||
}
|
||||
submitResult, requestID, err := c.postJSON(ctx, request, request.Candidate.BaseURL, "/contents/generations/tasks", apiKey, body)
|
||||
submitRequestID = requestID
|
||||
if err != nil {
|
||||
@@ -297,6 +300,39 @@ func volcesVideoTaskBody(body map[string]any, content []map[string]any) map[stri
|
||||
return out
|
||||
}
|
||||
|
||||
func validateVolcesVideoTaskBody(body map[string]any) error {
|
||||
firstFrameCount := 0
|
||||
lastFrameCount := 0
|
||||
for _, item := range contentItems(body["content"]) {
|
||||
if stringFromAny(item["type"]) != "image_url" {
|
||||
continue
|
||||
}
|
||||
switch stringFromAny(item["role"]) {
|
||||
case "first_frame":
|
||||
firstFrameCount++
|
||||
case "last_frame":
|
||||
lastFrameCount++
|
||||
}
|
||||
}
|
||||
if firstFrameCount > 1 {
|
||||
return &ClientError{
|
||||
Code: "invalid_parameter",
|
||||
Message: fmt.Sprintf("content contains %d first_frame image items; expected at most one first frame image content", firstFrameCount),
|
||||
StatusCode: 400,
|
||||
Retryable: false,
|
||||
}
|
||||
}
|
||||
if lastFrameCount > 1 {
|
||||
return &ClientError{
|
||||
Code: "invalid_parameter",
|
||||
Message: fmt.Sprintf("content contains %d last_frame image items; expected at most one last frame image content", lastFrameCount),
|
||||
StatusCode: 400,
|
||||
Retryable: false,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addVolcesVideoTaskParams(out map[string]any, body map[string]any) {
|
||||
copyVolcesStringParam(out, "callback_url", body, "callback_url", "callbackUrl")
|
||||
copyVolcesBoolParam(out, "return_last_frame", body, "return_last_frame", "returnLastFrame")
|
||||
|
||||
Reference in New Issue
Block a user