fix(keling): 统一 Omni 模型别名与生产绑定 (#22)
ci / verify (push) Successful in 12m40s
release-ci / verify-tag (push) Successful in 14m2s
ci / verify (push) Successful in 12m40s
release-ci / verify-tag (push) Successful in 14m2s
线上真实任务验证发现官方兼容接口的模型别名与生产绑定不一致;统一使用原生模型名匹配候选,同时保留通用接口旧别名兼容。
This commit was merged in pull request #22.
This commit is contained in:
@@ -1070,11 +1070,15 @@ func (s *Server) createTask(kind string, compatible bool) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model := requestModelName(body)
|
requestedModel := requestModelName(body)
|
||||||
|
model := canonicalTaskModelName(kind, requestedModel)
|
||||||
if model == "" {
|
if model == "" {
|
||||||
writeError(w, http.StatusBadRequest, "model is required")
|
writeError(w, http.StatusBadRequest, "model is required")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if model != requestedModel {
|
||||||
|
body["model"] = model
|
||||||
|
}
|
||||||
if !apiKeyScopeAllowed(user, kind) {
|
if !apiKeyScopeAllowed(user, kind) {
|
||||||
writeError(w, http.StatusForbidden, "api key scope does not allow this capability")
|
writeError(w, http.StatusForbidden, "api key scope does not allow this capability")
|
||||||
return
|
return
|
||||||
@@ -1402,6 +1406,17 @@ func requestModelName(body map[string]any) string {
|
|||||||
return modelNameFromValue(body["model"])
|
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 {
|
func modelNameFromValue(value any) string {
|
||||||
switch typed := value.(type) {
|
switch typed := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ func normalizeKelingOmniRequest(input map[string]any) (map[string]any, *kelingCo
|
|||||||
if sound != "on" && sound != "off" {
|
if sound != "on" && sound != "off" {
|
||||||
return nil, newKelingCompatError(http.StatusBadRequest, 1201, "sound must be on or 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")
|
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 {
|
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))
|
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")
|
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) {
|
func kelingCompatModel(value string) (string, int, bool) {
|
||||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
model, ok := canonicalKlingOmniModel(value)
|
||||||
case "kling-video-o1", "kling-o1":
|
if !ok {
|
||||||
return "kling-o1", 10, true
|
|
||||||
case "kling-v3-omni", "kling-3.0-omni":
|
|
||||||
return "kling-3.0-omni", 15, true
|
|
||||||
default:
|
|
||||||
return "", 0, false
|
return "", 0, false
|
||||||
}
|
}
|
||||||
|
if model == klingO1Model {
|
||||||
|
return model, 10, true
|
||||||
|
}
|
||||||
|
return model, 15, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func kelingCompatObjectList(value any, field string) ([]map[string]any, *kelingCompatError) {
|
func kelingCompatObjectList(value any, field string) ([]map[string]any, *kelingCompatError) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func TestNormalizeKelingOmniRequestMapsOfficialFields(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("normalize Kling request: %v", err)
|
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["modelType"] != "omni_video" ||
|
||||||
normalized["resolution"] != "1080p" ||
|
normalized["resolution"] != "1080p" ||
|
||||||
normalized["aspect_ratio"] != "9:16" ||
|
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) {
|
func TestNormalizeKelingOmniRequestBuildsMultiShotMedia(t *testing.T) {
|
||||||
normalized, err := normalizeKelingOmniRequest(map[string]any{
|
normalized, err := normalizeKelingOmniRequest(map[string]any{
|
||||||
"model_name": "kling-3.0-omni",
|
"model_name": "kling-3.0-omni",
|
||||||
@@ -70,7 +88,7 @@ func TestNormalizeKelingOmniRequestBuildsMultiShotMedia(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("normalize multi-shot request: %v", err)
|
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)
|
t.Fatalf("unexpected multi-shot fields: %+v", normalized)
|
||||||
}
|
}
|
||||||
content, _ := normalized["content"].([]any)
|
content, _ := normalized["content"].([]any)
|
||||||
|
|||||||
@@ -100,9 +100,9 @@ func TestKelingOmniCompatibleHTTPFlow(t *testing.T) {
|
|||||||
_, err = db.CreatePlatformModel(ctx, store.CreatePlatformModelInput{
|
_, err = db.CreatePlatformModel(ctx, store.CreatePlatformModelInput{
|
||||||
PlatformID: platform.ID,
|
PlatformID: platform.ID,
|
||||||
CanonicalModelKey: "keling:kling-video-o1",
|
CanonicalModelKey: "keling:kling-video-o1",
|
||||||
ModelName: "kling-o1",
|
ModelName: "kling-video-o1",
|
||||||
ProviderModelName: "kling-video-o1",
|
ProviderModelName: "kling-video-o1",
|
||||||
ModelAlias: "kling-o1",
|
ModelAlias: "",
|
||||||
ModelType: store.StringList{"omni_video", "video_generate"},
|
ModelType: store.StringList{"omni_video", "video_generate"},
|
||||||
DisplayName: "Kling O1 Compatible Test",
|
DisplayName: "Kling O1 Compatible Test",
|
||||||
Capabilities: map[string]any{
|
Capabilities: map[string]any{
|
||||||
|
|||||||
@@ -695,7 +695,11 @@ func klingV2Status(status string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func klingV2ProviderModel(pathModel string) (string, bool) {
|
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":
|
case "kling-o1", "kling-video-o1":
|
||||||
return klingO1Model, true
|
return klingO1Model, true
|
||||||
case "kling-v3-omni", "kling-3.0-omni", "kling-3-omni":
|
case "kling-v3-omni", "kling-3.0-omni", "kling-3-omni":
|
||||||
|
|||||||
@@ -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` | 3–10 秒 |
|
| `kling-video-o1`、`kling-o1` | `kling-video-o1` | `kling-video-o1` | 3–10 秒 |
|
||||||
| `kling-v3-omni`、`kling-3.0-omni` | `kling-3.0-omni` | `kling-v3-omni` | 3–15 秒 |
|
| `kling-v3-omni`、`kling-3.0-omni`、`kling-3-omni` | `kling-v3-omni` | `kling-v3-omni` | 3–15 秒 |
|
||||||
|
|
||||||
网关别名用于候选匹配,原生模型名用于发往 TranStreams 的 Kling Omni 请求;两类名称不会混用。
|
上述旧别名会在入口处自动归一为可灵原生模型名,候选匹配不依赖平台模型额外配置别名。
|
||||||
|
|
||||||
`kling-video-o1` 的纯文生视频和首帧生视频只接受 5 或 10 秒;3–10 秒中的其他整数需要使用普通参考图等支持该时长的 Omni 输入。`kling-v3-omni` 接受 3–15 秒。
|
`kling-video-o1` 的纯文生视频和首帧生视频只接受 5 或 10 秒;3–10 秒中的其他整数需要使用普通参考图等支持该时长的 Omni 输入。`kling-v3-omni` 接受 3–15 秒。
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ curl -sS -X POST "$GATEWAY_PUBLIC_API_BASE/videos/generations" \
|
|||||||
"resolution": "1080p",
|
"resolution": "1080p",
|
||||||
"aspect_ratio": "9:16",
|
"aspect_ratio": "9:16",
|
||||||
"duration": 5,
|
"duration": 5,
|
||||||
"audio": true,
|
"audio": false,
|
||||||
"watermark": false,
|
"watermark": false,
|
||||||
"runMode": "real"
|
"runMode": "real"
|
||||||
}'
|
}'
|
||||||
|
|||||||
Reference in New Issue
Block a user