fix(keling): 统一 Omni 模型别名与生产绑定 #22

Merged
easyai merged 1 commits from codex/fix-keling-canonical-model-aliases into main 2026-07-22 10:23:18 +08:00
6 changed files with 56 additions and 19 deletions
Showing only changes of commit d37fb3ea60 - Show all commits
+16 -1
View File
@@ -1070,11 +1070,15 @@ func (s *Server) createTask(kind string, compatible bool) http.Handler {
return
}
}
model := requestModelName(body)
requestedModel := requestModelName(body)
model := canonicalTaskModelName(kind, requestedModel)
if model == "" {
writeError(w, http.StatusBadRequest, "model is required")
return
}
if model != requestedModel {
body["model"] = model
}
if !apiKeyScopeAllowed(user, kind) {
writeError(w, http.StatusForbidden, "api key scope does not allow this capability")
return
@@ -1402,6 +1406,17 @@ func requestModelName(body map[string]any) string {
return modelNameFromValue(body["model"])
}
func canonicalTaskModelName(kind string, model string) string {
model = strings.TrimSpace(model)
if kind != "videos.generations" {
return model
}
if canonical, ok := canonicalKlingOmniModel(model); ok {
return canonical
}
return model
}
func modelNameFromValue(value any) string {
switch typed := value.(type) {
case string:
@@ -269,7 +269,7 @@ func normalizeKelingOmniRequest(input map[string]any) (map[string]any, *kelingCo
if sound != "on" && sound != "off" {
return nil, newKelingCompatError(http.StatusBadRequest, 1201, "sound must be on or off")
}
if model == "kling-o1" && sound == "on" {
if model == klingO1Model && sound == "on" {
return nil, newKelingCompatError(http.StatusBadRequest, 1201, "kling-video-o1 does not support generated audio; sound must be off")
}
@@ -347,7 +347,7 @@ func normalizeKelingOmniRequest(input map[string]any) (map[string]any, *kelingCo
if duration < 3 || duration > maxDuration {
return nil, newKelingCompatError(http.StatusBadRequest, 1201, fmt.Sprintf("duration for %s must be an integer between 3 and %d seconds", requestedModel, maxDuration))
}
if model == "kling-o1" && (len(images) == 0 || hasFirstFrame) && duration != 5 && duration != 10 {
if model == klingO1Model && (len(images) == 0 || hasFirstFrame) && duration != 5 && duration != 10 {
return nil, newKelingCompatError(http.StatusBadRequest, 1201, "kling-video-o1 text-to-video and first-frame generation only support 5 or 10 seconds")
}
}
@@ -532,14 +532,14 @@ func normalizeKelingMultiPrompts(value any) ([]any, int, *kelingCompatError) {
}
func kelingCompatModel(value string) (string, int, bool) {
switch strings.ToLower(strings.TrimSpace(value)) {
case "kling-video-o1", "kling-o1":
return "kling-o1", 10, true
case "kling-v3-omni", "kling-3.0-omni":
return "kling-3.0-omni", 15, true
default:
model, ok := canonicalKlingOmniModel(value)
if !ok {
return "", 0, false
}
if model == klingO1Model {
return model, 10, true
}
return model, 15, true
}
func kelingCompatObjectList(value any, field string) ([]map[string]any, *kelingCompatError) {
@@ -27,7 +27,7 @@ func TestNormalizeKelingOmniRequestMapsOfficialFields(t *testing.T) {
if err != nil {
t.Fatalf("normalize Kling request: %v", err)
}
if normalized["model"] != "kling-3.0-omni" ||
if normalized["model"] != "kling-v3-omni" ||
normalized["modelType"] != "omni_video" ||
normalized["resolution"] != "1080p" ||
normalized["aspect_ratio"] != "9:16" ||
@@ -49,6 +49,24 @@ func TestNormalizeKelingOmniRequestMapsOfficialFields(t *testing.T) {
}
}
func TestCanonicalTaskModelNameNormalizesKelingOmniAliases(t *testing.T) {
tests := map[string]string{
"kling-o1": "kling-video-o1",
"kling-video-o1": "kling-video-o1",
"kling-3.0-omni": "kling-v3-omni",
"kling-3-omni": "kling-v3-omni",
"kling-v3-omni": "kling-v3-omni",
}
for input, expected := range tests {
if got := canonicalTaskModelName("videos.generations", input); got != expected {
t.Fatalf("canonicalTaskModelName(%q) = %q, want %q", input, got, expected)
}
}
if got := canonicalTaskModelName("chat.completions", "kling-o1"); got != "kling-o1" {
t.Fatalf("non-video model must not be rewritten, got %q", got)
}
}
func TestNormalizeKelingOmniRequestBuildsMultiShotMedia(t *testing.T) {
normalized, err := normalizeKelingOmniRequest(map[string]any{
"model_name": "kling-3.0-omni",
@@ -70,7 +88,7 @@ func TestNormalizeKelingOmniRequestBuildsMultiShotMedia(t *testing.T) {
if err != nil {
t.Fatalf("normalize multi-shot request: %v", err)
}
if normalized["model"] != "kling-3.0-omni" || normalized["duration"] != 5 || normalized["multi_shot"] != true || normalized["shot_type"] != "customize" {
if normalized["model"] != "kling-v3-omni" || normalized["duration"] != 5 || normalized["multi_shot"] != true || normalized["shot_type"] != "customize" {
t.Fatalf("unexpected multi-shot fields: %+v", normalized)
}
content, _ := normalized["content"].([]any)
@@ -100,9 +100,9 @@ func TestKelingOmniCompatibleHTTPFlow(t *testing.T) {
_, err = db.CreatePlatformModel(ctx, store.CreatePlatformModelInput{
PlatformID: platform.ID,
CanonicalModelKey: "keling:kling-video-o1",
ModelName: "kling-o1",
ModelName: "kling-video-o1",
ProviderModelName: "kling-video-o1",
ModelAlias: "kling-o1",
ModelAlias: "",
ModelType: store.StringList{"omni_video", "video_generate"},
DisplayName: "Kling O1 Compatible Test",
Capabilities: map[string]any{
+5 -1
View File
@@ -695,7 +695,11 @@ func klingV2Status(status string) string {
}
func klingV2ProviderModel(pathModel string) (string, bool) {
switch strings.ToLower(strings.TrimSpace(pathModel)) {
return canonicalKlingOmniModel(pathModel)
}
func canonicalKlingOmniModel(value string) (string, bool) {
switch strings.ToLower(strings.TrimSpace(value)) {
case "kling-o1", "kling-video-o1":
return klingO1Model, true
case "kling-v3-omni", "kling-3.0-omni", "kling-3-omni":
+5 -5
View File
@@ -10,12 +10,12 @@ export GATEWAY_API_KEY="<EasyAI Gateway API Key>"
## 模型与参数映射
| 请求 `model_name` | 网关模型别名 | TranStreams 原生 `model_name` | 时长范围 |
| 请求 `model_name` | 网关候选模型 | 可灵原生 `model_name` | 时长范围 |
| --- | --- | --- | --- |
| `kling-video-o1``kling-o1` | `kling-o1` | `kling-video-o1` | 310 秒 |
| `kling-v3-omni``kling-3.0-omni` | `kling-3.0-omni` | `kling-v3-omni` | 315 秒 |
| `kling-video-o1``kling-o1` | `kling-video-o1` | `kling-video-o1` | 310 秒 |
| `kling-v3-omni``kling-3.0-omni``kling-3-omni` | `kling-v3-omni` | `kling-v3-omni` | 315 秒 |
网关别名用于候选匹配,原生模型名用于发往 TranStreams 的 Kling Omni 请求;两类名称不会混用
上述旧别名会在入口处自动归一为可灵原生模型名,候选匹配不依赖平台模型额外配置别名
`kling-video-o1` 的纯文生视频和首帧生视频只接受 5 或 10 秒;3–10 秒中的其他整数需要使用普通参考图等支持该时长的 Omni 输入。`kling-v3-omni` 接受 315 秒。
@@ -109,7 +109,7 @@ curl -sS -X POST "$GATEWAY_PUBLIC_API_BASE/videos/generations" \
"resolution": "1080p",
"aspect_ratio": "9:16",
"duration": 5,
"audio": true,
"audio": false,
"watermark": false,
"runMode": "real"
}'