From 9fdfcdc70432e68b66fa3bd778cdafb1f54b1c82 Mon Sep 17 00:00:00 2001 From: Jaylea <17256627+jaylea@user.noreply.gitee.com> Date: Tue, 15 Sep 2026 20:03:01 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(model-capability):=20=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=20-=20=E6=8F=90=E7=A4=BA=E8=AF=8D=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=AD=97=E7=AC=A6=E4=B8=8A=E9=99=90=201000=20?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=B8=BA=208000?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 视频提示词由「输入框文本 + 连线内容 + 技能上下文」合成,实际长度远大于 用户手输内容(技能上下文预算 32000 字符,单个 SKILL.md 可达数万字符)。 原有的 1000 字符默认值会让画布工作流的正常提示词在本地预检阶段直接失败。 将前后端默认值同步提升到 8000: - 后端新增 DefaultVideoPromptMaxChars 常量并在默认能力配置中引用 - 前端新增 DEFAULT_VIDEO_PROMPT_MAX_CHARS 常量并在默认能力配置中引用 - 前后端常量必须保持同值,否则会出现前端放行、后端拒绝的判定分裂 管理员仍可按模型覆盖该值(取值 1-1000000),本次仅调整新建模型的默认值, 已存在的渠道模型不受影响。 补充回归测试 TestDefaultVideoPromptMaxCharsAllowsComposedCanvasPrompt, 锁定默认值与边界行为:2399 字符(画布实际合成量级)与 8000(恰好等于上限) 放行,8001 明确失败并保留「不自动截断」语义。 --- backend/internal/app/model_capability.go | 7 ++++- backend/internal/app/model_capability_test.go | 28 +++++++++++++++++++ web/src/lib/model-capabilities.ts | 11 +++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/backend/internal/app/model_capability.go b/backend/internal/app/model_capability.go index 5eb25973b..a9b0612ab 100644 --- a/backend/internal/app/model_capability.go +++ b/backend/internal/app/model_capability.go @@ -105,6 +105,11 @@ type VideoReferenceConfig struct { MaxAudioDuration int `json:"maxAudioDurationSeconds"` } +// DefaultVideoPromptMaxChars 是普通视频模型提示词字符数的默认上限。 +// 视频提示词由输入框文本、连线内容和技能上下文合成,远长于用户手输内容; +// 默认值过小会把画布工作流正常可用的提示词拦在本地。管理员仍可按模型覆盖。 +const DefaultVideoPromptMaxChars = 8000 + type VideoDurationConfig struct { Selection string `json:"selection"` Min int `json:"min,omitempty"` @@ -206,7 +211,7 @@ func DefaultModelCapabilityConfigForModel(protocol string, modelName string) *Mo streaming := true text := &TextCapabilityConfig{Streaming: &streaming, References: TextReferenceConfig{PromptMaxChars: 32000}} video := &VideoCapabilityConfig{ - References: VideoReferenceConfig{PromptMaxChars: 1000, MinImages: 0, MaxImages: 9, MaxImageBytes: 30 * 1024 * 1024, MaxVideos: 0, MaxVideoBytes: 0, MaxVideoDuration: 0, MaxAudios: 0, MaxAudioBytes: 0, MaxAudioDuration: 0}, + References: VideoReferenceConfig{PromptMaxChars: DefaultVideoPromptMaxChars, MinImages: 0, MaxImages: 9, MaxImageBytes: 30 * 1024 * 1024, MaxVideos: 0, MaxVideoBytes: 0, MaxVideoDuration: 0, MaxAudios: 0, MaxAudioBytes: 0, MaxAudioDuration: 0}, Duration: VideoDurationConfig{Selection: "range", Min: 1, Max: 15, Step: 1, Default: 6}, Ratios: []string{"16:9", "9:16", "1:1", "4:3", "3:4", "21:9"}, DefaultRatio: "16:9", diff --git a/backend/internal/app/model_capability_test.go b/backend/internal/app/model_capability_test.go index 3ee40d7bf..2028dc406 100644 --- a/backend/internal/app/model_capability_test.go +++ b/backend/internal/app/model_capability_test.go @@ -52,6 +52,34 @@ func TestValidateVideoTaskRejectsPromptAboveCapabilityLimit(t *testing.T) { } } +// 视频提示词由输入框文本、连线内容和技能上下文合成,默认上限必须留出足够余量, +// 否则画布工作流会连同线内容一起被拦在本地。这里锁定默认值与边界行为。 +func TestDefaultVideoPromptMaxCharsAllowsComposedCanvasPrompt(t *testing.T) { + profile := DefaultModelCapabilityConfigForModel("seedance-videos-compatible", "sd-2.5").Video + if profile.References.PromptMaxChars != 8000 { + t.Fatalf("视频默认提示词上限 = %d, want 8000", profile.References.PromptMaxChars) + } + + // 画布实际合成出的提示词量级(输入框 + 连线 + 技能上下文)必须通过。 + composed := canvasGenerationInput{Mode: "video", Prompt: strings.Repeat("镜", 2399), Config: providerConfig{Model: "sd-2.5", VideoSeconds: "6", Size: "16:9", VQuality: "720p"}} + if err := validateVideoTask(profile, composed); err != nil { + t.Fatalf("合成提示词 2399 字符被拒绝: %v", err) + } + // 恰好等于上限仍然放行,与前端 `actualChars <= maxChars` 的判定保持一致。 + atLimit := composed + atLimit.Prompt = strings.Repeat("镜", 8000) + if err := validateVideoTask(profile, atLimit); err != nil { + t.Fatalf("提示词 8000 字符被拒绝: %v", err) + } + // 超出上限必须明确失败,并保持“不自动截断”的语义。 + overLimit := composed + overLimit.Prompt = strings.Repeat("镜", 8001) + err := validateVideoTask(profile, overLimit) + if err == nil || !strings.Contains(err.Error(), "最多 8000 个字符") || !strings.Contains(err.Error(), "8001 个字符") { + t.Fatalf("validateVideoTask() error = %v", err) + } +} + func TestValidateTaskCapabilityRejectsWorkflowPromptAboveConfiguredLimit(t *testing.T) { profile := DefaultModelCapabilityConfigForModel("runninghub-workflow-video", "workflow-video") profile.Video.References.PromptMaxChars = 10_000 diff --git a/web/src/lib/model-capabilities.ts b/web/src/lib/model-capabilities.ts index 60958bba3..3fa86df56 100644 --- a/web/src/lib/model-capabilities.ts +++ b/web/src/lib/model-capabilities.ts @@ -24,6 +24,15 @@ export type TextCapabilityConfig = { export type ImageSizeParameter = "none" | "size" | "aspect_ratio"; +/** + * 普通视频模型提示词字符数的默认上限。 + * + * 视频提示词由「输入框文本 + 连线内容 + 技能上下文」合成,远长于用户手输内容, + * 因此不能沿用偏小的默认值把画布工作流拦在本地。必须与后端 + * `DefaultVideoPromptMaxChars` 保持一致,否则前端放行后端拒绝(或反之)。 + */ +export const DEFAULT_VIDEO_PROMPT_MAX_CHARS = 8000; + export type ImageCapabilityConfig = { references: { promptMaxChars: number; @@ -262,7 +271,7 @@ export function defaultModelCapabilityConfig(protocol?: ModelProtocol, model = " }; const video: VideoCapabilityConfig = { references: { - promptMaxChars: 1000, + promptMaxChars: DEFAULT_VIDEO_PROMPT_MAX_CHARS, minImages: 0, maxImages: 9, maxImageBytes: 30 * 1024 * 1024, From a1de6ccc124d93e816baa8b839eb2eb6e4ac9b96 Mon Sep 17 00:00:00 2001 From: Jaylea <17256627+jaylea@user.noreply.gitee.com> Date: Tue, 15 Sep 2026 21:47:04 +0800 Subject: [PATCH 2/2] =?UTF-8?q?test(model-capability):=20=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=A8=A1=E5=9E=8B=20-=20=E8=A1=A5=E5=85=85=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E9=BB=98=E8=AE=A4=E6=8F=90=E7=A4=BA=E8=AF=8D=E4=B8=8A?= =?UTF-8?q?=E9=99=90=E5=9B=9E=E5=BD=92=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #511 只改了后端与前端两处默认值,但没有前端测试锁定该默认值,任何一次 「顺手改回小值」都不会被 CI 拦住。这里补上回归断言: - 默认常量必须为 8000,且各协议默认能力配置都引用它; - 文本/图片默认上限(32000)不受本次调整影响,避免顺带改变判定口径。 同时锁住画布实际合成量级(技能上下文预算 32000)所需的余量。 --- web/test/model-capabilities.test.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/web/test/model-capabilities.test.ts b/web/test/model-capabilities.test.ts index 3e32cfa70..d320615ca 100644 --- a/web/test/model-capabilities.test.ts +++ b/web/test/model-capabilities.test.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import test from "node:test"; // Bun 直接执行 TypeScript 测试时需要保留扩展名;生产 tsconfig 不包含 test/。 -import { defaultModelCapabilityConfig, normalizeVideoValue } from "../src/lib/model-capabilities.ts"; +import { DEFAULT_VIDEO_PROMPT_MAX_CHARS, defaultModelCapabilityConfig, normalizeVideoValue } from "../src/lib/model-capabilities.ts"; test("switching to MiniMax H3 replaces an unsupported 720p value with 768P", () => { const profile = defaultModelCapabilityConfig("minimax-video", "MiniMax-H3").video!; @@ -13,3 +13,21 @@ test("switching to MiniMax H3 replaces an unsupported 720p value with 768P", () resolution: "768P", }); }); + +// 视频提示词由「输入框文本 + 连线内容 + 技能上下文」合成,技能上下文预算为 32000, +// 合成结果远长于用户手输内容。默认上限过小会把正常可用的画布工作流拦在本地预检。 +// 这里锁定默认值本身,避免被改回偏小值(前端放行/后端拒绝的判定必须同源)。 +test("video prompt default allows a composed canvas prompt", () => { + assert.equal(DEFAULT_VIDEO_PROMPT_MAX_CHARS, 8000); + for (const protocol of [undefined, "seedance-videos-compatible", "agnes-video", "volcengine-ark-video"]) { + const profile = defaultModelCapabilityConfig(protocol, "test-model"); + assert.equal(profile.video!.references.promptMaxChars, DEFAULT_VIDEO_PROMPT_MAX_CHARS); + } +}); + +test("raising the video default leaves text and image limits untouched", () => { + // 只放宽视频默认值,避免顺带改变其它能力的判定口径。 + const profile = defaultModelCapabilityConfig("seedance-videos-compatible", "sd-2.5"); + assert.equal(profile.text!.references.promptMaxChars, 32000); + assert.equal(profile.image!.references.promptMaxChars, 32000); +});