feat: 完善模型请求适配与输出限制
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package clients
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
)
|
||||
|
||||
func TestVolcesClientSupportsDeyunEnvelope(t *testing.T) {
|
||||
var submitted bool
|
||||
var polled bool
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") != "Bearer deyun-secret" {
|
||||
t.Fatalf("unexpected authorization header: %q", r.Header.Get("Authorization"))
|
||||
}
|
||||
switch r.Method + " " + r.URL.Path {
|
||||
case "POST /c39/api/v3/video/tasks":
|
||||
submitted = true
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"code": 0,
|
||||
"request_id": "deyun-submit-request",
|
||||
"data": map[string]any{"id": "deyun-task-1"},
|
||||
})
|
||||
case "GET /c39/api/v3/video/tasks/deyun-task-1":
|
||||
polled = true
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"code": 0,
|
||||
"request_id": "deyun-poll-request",
|
||||
"data": map[string]any{
|
||||
"id": "deyun-task-1",
|
||||
"status": "succeeded",
|
||||
"created_at": 123,
|
||||
"content": map[string]any{
|
||||
"video_url": "https://example.com/deyun.mp4",
|
||||
},
|
||||
},
|
||||
})
|
||||
default:
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
response, err := (VolcesClient{HTTPClient: server.Client()}).Run(context.Background(), Request{
|
||||
Kind: "videos.generations",
|
||||
ModelType: "video_generate",
|
||||
Model: "deyun-seedance-2.0-canary",
|
||||
Body: map[string]any{
|
||||
"prompt": "A red cube rotates on a white table",
|
||||
"resolution": "480p",
|
||||
"ratio": "16:9",
|
||||
"duration": 4,
|
||||
"generate_audio": false,
|
||||
},
|
||||
Candidate: store.RuntimeModelCandidate{
|
||||
BaseURL: server.URL + "/c39/api/v3",
|
||||
ProviderModelName: "doubao-seedance-2-0",
|
||||
Credentials: map[string]any{"apiKey": "deyun-secret"},
|
||||
PlatformConfig: map[string]any{
|
||||
"volcesPollIntervalMs": 100,
|
||||
"volcesPollTimeoutSeconds": 1,
|
||||
"volcesVideoTaskPath": "/video/tasks",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("run deyun-compatible video task: %v", err)
|
||||
}
|
||||
if !submitted || !polled {
|
||||
t.Fatalf("expected submit and poll, submitted=%v polled=%v", submitted, polled)
|
||||
}
|
||||
if response.RequestID != "deyun-poll-request" {
|
||||
t.Fatalf("unexpected request id: %s", response.RequestID)
|
||||
}
|
||||
data, _ := response.Result["data"].([]any)
|
||||
item, _ := data[0].(map[string]any)
|
||||
if response.Result["upstream_task_id"] != "deyun-task-1" || item["url"] != "https://example.com/deyun.mp4" {
|
||||
t.Fatalf("unexpected response: %+v", response.Result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeVolcesCompatibleResultPreservesNativeResponse(t *testing.T) {
|
||||
native := map[string]any{"id": "native-task", "status": "queued"}
|
||||
got, requestID, err := normalizeVolcesCompatibleResult(native)
|
||||
if err != nil {
|
||||
t.Fatalf("normalize native response: %v", err)
|
||||
}
|
||||
if got["id"] != "native-task" || requestID != "" {
|
||||
t.Fatalf("native response changed unexpectedly: %+v requestID=%q", got, requestID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeVolcesCompatibleResultRejectsBusinessError(t *testing.T) {
|
||||
_, requestID, err := normalizeVolcesCompatibleResult(map[string]any{
|
||||
"code": 1004,
|
||||
"message": "Authorization is expired",
|
||||
"request_id": "deyun-error-request",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected business error")
|
||||
}
|
||||
if requestID != "deyun-error-request" || ErrorCode(err) != "volces_1004" {
|
||||
t.Fatalf("unexpected error metadata requestID=%q code=%q err=%v", requestID, ErrorCode(err), err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Authorization is expired") {
|
||||
t.Fatalf("unexpected error message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeVolcesCompatibleResultRejectsHTTP200ErrorObject(t *testing.T) {
|
||||
_, _, err := normalizeVolcesCompatibleResult(map[string]any{
|
||||
"error": map[string]any{
|
||||
"code": "ModelNotOpen",
|
||||
"message": "model service is not activated",
|
||||
"type": "Not Found",
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected HTTP 200 error object to fail")
|
||||
}
|
||||
if ErrorCode(err) != "ModelNotOpen" || !strings.Contains(err.Error(), "not activated") {
|
||||
t.Fatalf("unexpected error: code=%q err=%v", ErrorCode(err), err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user