diff --git a/app/api/external/image/generate/route.ts b/app/api/external/image/generate/route.ts index 2b1f54ceb..af24466f3 100644 --- a/app/api/external/image/generate/route.ts +++ b/app/api/external/image/generate/route.ts @@ -122,8 +122,19 @@ const requestSchema = z.object({ imageCount: z.coerce.number().int().min(1).max(8).optional(), aspectRatio: z.string().trim().max(32).optional(), imageSize: z.string().trim().max(32).optional(), + model: z.string().trim().optional(), + modelId: z.string().trim().optional(), + modelRef: z.string().trim().optional(), metadata: z.record(z.string(), z.any()).optional(), referenceImages: z.array(z.string().trim().min(1)).max(14).optional(), +}).superRefine((payload, ctx) => { + if (payload.model || payload.modelId || payload.modelRef) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["model"], + message: "第三方生图 API 不允许传入模型参数,系统会按后台配置自动选模", + }); + } }); function normalizeString(value: FormDataEntryValue | string | null | undefined) { @@ -151,6 +162,10 @@ function parseOptionalJsonRecord(value: FormDataEntryValue | null) { return undefined; } +function hasDisallowedModelOverride(formValue: string) { + return formValue.trim().length > 0; +} + function normalizeReferenceImageInputs(value: unknown): string[] { if (!value) return []; if (Array.isArray(value)) { @@ -171,6 +186,23 @@ async function parseRequestPayload(request: Request) { const contentType = request.headers.get("content-type") || ""; if (contentType.toLowerCase().includes("multipart/form-data")) { const formData = await request.formData(); + const forcedModel = normalizeString(formData.get("model")); + const forcedModelId = normalizeString(formData.get("modelId")); + const forcedModelRef = normalizeString(formData.get("modelRef")); + if ( + hasDisallowedModelOverride(forcedModel) || + hasDisallowedModelOverride(forcedModelId) || + hasDisallowedModelOverride(forcedModelRef) + ) { + throw new z.ZodError([ + { + code: "custom", + path: ["model"], + message: "第三方生图 API 不允许传入模型参数,系统会按后台配置自动选模", + }, + ]); + } + const fileEntries = [ ...formData.getAll("images"), ...formData.getAll("image"), @@ -203,6 +235,9 @@ async function parseRequestPayload(request: Request) { imageCount: parseOptionalInteger(formData.get("imageCount")), aspectRatio: normalizeString(formData.get("aspectRatio")) || undefined, imageSize: normalizeString(formData.get("imageSize")) || undefined, + model: forcedModel, + modelId: forcedModelId, + modelRef: forcedModelRef, metadata: parseOptionalJsonRecord(formData.get("metadata")), referenceImages: [...uploadedReferenceImages, ...hostedInlineImages], }); @@ -221,6 +256,9 @@ async function parseRequestPayload(request: Request) { const json = await request.json(); const parsed = requestSchema.parse({ ...json, + model: normalizeString((json as any)?.model), + modelId: normalizeString((json as any)?.modelId), + modelRef: normalizeString((json as any)?.modelRef), referenceImages: await ensureHostedReferenceImageUrls([ ...normalizeReferenceImageInputs((json as any)?.referenceImages), ...normalizeReferenceImageInputs((json as any)?.imageUrls), diff --git a/docs/第三方生图API调用说明.md b/docs/第三方生图API调用说明.md index 03eec85da..ccc62d2b7 100644 --- a/docs/第三方生图API调用说明.md +++ b/docs/第三方生图API调用说明.md @@ -14,6 +14,7 @@ 这套接口是 **图生图接口**。第三方调用方只需要上传参考图和提示词,**不需要也不允许指定内部模型名**。 我们服务端会按后台默认优先级自动选择模型,优先尝试 `GPT-image-2`,失败后自动故障迁移到其他可用图生图模型,尽量返回图片。 +如果调用方提交 `model / modelId / modelRef` 等模型字段,接口会直接返回 400。 生成结果图片会先上传到我们的储存桶,再写入我们的数据库与媒体资产表。 ## 2. 鉴权方式