Fix RUXA nano-banana model input mode and runtime failover IDs
This commit is contained in:
parent
56e68ff5d0
commit
609700ab54
@ -25,6 +25,29 @@ export const IMAGE_FAILOVER_PROVIDER_ALLOWLIST = new Set([
|
||||
const IMAGE_FAILOVER_MODEL_COOLDOWN_MS = 30 * 60 * 1000;
|
||||
const imageFailoverModelCooldown = new Map<string, { until: number; reason: string }>();
|
||||
|
||||
export function resolveImageModelRuntimeId(
|
||||
model: { defaultParams?: unknown; modelId?: unknown; id?: unknown } | null | undefined,
|
||||
) {
|
||||
const defaultParams = model?.defaultParams;
|
||||
const defaultEndpoint =
|
||||
typeof defaultParams === "object" &&
|
||||
defaultParams !== null &&
|
||||
"endpoint" in defaultParams
|
||||
? String((defaultParams as Record<string, unknown>).endpoint || "").trim()
|
||||
: "";
|
||||
|
||||
if (defaultEndpoint) {
|
||||
return defaultEndpoint;
|
||||
}
|
||||
|
||||
const modelId = String(model?.modelId || "").trim();
|
||||
if (modelId) {
|
||||
return modelId;
|
||||
}
|
||||
|
||||
return String(model?.id || "").trim();
|
||||
}
|
||||
|
||||
export function normalizeFailoverErrorMessage(error: unknown): string {
|
||||
if (!error) return "";
|
||||
if (typeof error === "string") return error;
|
||||
@ -186,14 +209,17 @@ export async function generateImageWithFailoverCandidate(params: {
|
||||
}) {
|
||||
const { candidate, messages, imageParams, fallbackImageParams } = params;
|
||||
const providerType = String(candidate.provider?.type || "");
|
||||
const candidateRuntimeModelId = String(candidate.model?.modelId || candidate.model?.id || "");
|
||||
const candidateRuntimeModelId = resolveImageModelRuntimeId(candidate.model);
|
||||
if (
|
||||
providerType === "fal-image" &&
|
||||
isFalGptImage2EditModelId(candidateRuntimeModelId)
|
||||
) {
|
||||
throw new Error("FAL GPT Image 2 Edit 仅支持异步队列调用,已跳过同步故障迁移");
|
||||
}
|
||||
const candidateLanguageModel = await getLanguageModelById(candidate.model.id);
|
||||
const candidateLanguageModel = await getLanguageModelById(
|
||||
candidate.model.id,
|
||||
candidateRuntimeModelId
|
||||
);
|
||||
if (!candidateLanguageModel || typeof (candidateLanguageModel as any).doGenerate !== "function") {
|
||||
throw new Error(`备用模型不支持当前生图故障迁移调用:${candidate.model.displayName || candidate.model.id}`);
|
||||
}
|
||||
@ -207,7 +233,7 @@ export async function generateImageWithFailoverCandidate(params: {
|
||||
imageParams: imageParams || fallbackImageParams,
|
||||
imageGenConfig: candidate.model.imageGenConfig || null,
|
||||
modelDisplayName: candidate.model.displayName,
|
||||
overrideModelId: candidate.model.modelId,
|
||||
overrideModelId: candidateRuntimeModelId || candidate.model.modelId,
|
||||
});
|
||||
|
||||
if (candidateResult?.finishReason === "error") {
|
||||
|
||||
@ -261,6 +261,7 @@ import {
|
||||
generateImageWithFailoverCandidate,
|
||||
getImageCrossProviderCandidates,
|
||||
IMAGE_FAILOVER_PROVIDER_ALLOWLIST,
|
||||
resolveImageModelRuntimeId,
|
||||
normalizeFailoverErrorMessage,
|
||||
setImageFallbackModelCooldown,
|
||||
shouldCooldownImageFallbackModel,
|
||||
@ -7211,13 +7212,14 @@ export async function POST(request: Request) {
|
||||
messagesForAlibaba,
|
||||
convertProvider
|
||||
);
|
||||
const candidateRuntimeModelId = resolveImageModelRuntimeId(candidate.model);
|
||||
|
||||
const candidateResult = await (candidateLanguageModel as any).doGenerate({
|
||||
messages: candidateMessages,
|
||||
imageParams: effectiveImageGenerationParams,
|
||||
imageGenConfig: candidate.model.imageGenConfig || null,
|
||||
modelDisplayName: candidate.model.displayName,
|
||||
overrideModelId: candidate.model.modelId,
|
||||
overrideModelId: candidateRuntimeModelId || candidate.model.modelId,
|
||||
});
|
||||
|
||||
if (candidateResult?.finishReason === "error") {
|
||||
@ -7623,13 +7625,14 @@ export async function POST(request: Request) {
|
||||
messagesForVolcano,
|
||||
convertProvider
|
||||
);
|
||||
const candidateRuntimeModelId = resolveImageModelRuntimeId(candidate.model);
|
||||
|
||||
const candidateResult = await (candidateLanguageModel as any).doGenerate({
|
||||
messages: candidateMessages,
|
||||
imageParams: effectiveImageGenerationParams,
|
||||
imageGenConfig: candidate.model.imageGenConfig || null,
|
||||
modelDisplayName: candidate.model.displayName,
|
||||
overrideModelId: candidate.model.modelId,
|
||||
overrideModelId: candidateRuntimeModelId || candidate.model.modelId,
|
||||
});
|
||||
|
||||
if (candidateResult?.finishReason === "error") {
|
||||
@ -8478,13 +8481,14 @@ export async function POST(request: Request) {
|
||||
messagesForCrossProvider,
|
||||
convertProvider
|
||||
);
|
||||
const candidateRuntimeModelId = resolveImageModelRuntimeId(candidate.model);
|
||||
|
||||
const candidateResult = await (candidateLanguageModel as any).doGenerate({
|
||||
messages: candidateMessages,
|
||||
imageParams: effectiveImageGenerationParams,
|
||||
imageGenConfig: candidate.model.imageGenConfig || null,
|
||||
modelDisplayName: candidate.model.displayName,
|
||||
overrideModelId: candidate.model.modelId,
|
||||
overrideModelId: candidateRuntimeModelId || candidate.model.modelId,
|
||||
});
|
||||
|
||||
if (candidateResult?.finishReason === "error") {
|
||||
|
||||
@ -33,6 +33,9 @@ const imageGenConfigSchema = z.object({
|
||||
supportsNegativePrompt: z.boolean().optional(),
|
||||
supportsWatermark: z.boolean().optional(),
|
||||
supportsSeed: z.boolean().optional(),
|
||||
ruxaTaskInputMode: z
|
||||
.enum(["size-image", "resolution-based"])
|
||||
.optional(),
|
||||
creditCostPerImage: z.number().min(0).max(100000).optional(),
|
||||
creditCostMultiplierBySize: z.record(z.string(), z.number().min(0.01).max(100)).optional(),
|
||||
creditDiscountRate: z.number().min(0.1).max(10).optional(),
|
||||
|
||||
@ -20,6 +20,7 @@ interface RuxaImageModelConfig {
|
||||
supports1K?: boolean;
|
||||
supports2K?: boolean;
|
||||
supports4K?: boolean;
|
||||
ruxaTaskInputMode?: "size-image" | "resolution-based";
|
||||
}
|
||||
|
||||
interface UploadedImage {
|
||||
@ -102,6 +103,26 @@ function isResolutionSupported(resolution: string, config?: RuxaImageModelConfig
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveRuxaTaskInputMode({
|
||||
activeModelId,
|
||||
modelConfig,
|
||||
}: {
|
||||
activeModelId: string;
|
||||
modelConfig?: RuxaImageModelConfig | null;
|
||||
}) {
|
||||
if (modelConfig?.ruxaTaskInputMode === "size-image") {
|
||||
return "size-image";
|
||||
}
|
||||
|
||||
if (modelConfig?.ruxaTaskInputMode === "resolution-based") {
|
||||
return "resolution-based";
|
||||
}
|
||||
|
||||
return activeModelId === "gemini-3.1-flash-image-preview"
|
||||
? "size-image"
|
||||
: "resolution-based";
|
||||
}
|
||||
|
||||
function buildRuxaTaskInput({
|
||||
prompt,
|
||||
imageUrls,
|
||||
@ -109,6 +130,7 @@ function buildRuxaTaskInput({
|
||||
resolution,
|
||||
outputFormat,
|
||||
modelConfig,
|
||||
taskInputMode,
|
||||
}: {
|
||||
prompt: string;
|
||||
imageUrls: string[];
|
||||
@ -116,6 +138,7 @@ function buildRuxaTaskInput({
|
||||
resolution: string;
|
||||
outputFormat: string;
|
||||
modelConfig?: RuxaImageModelConfig | null;
|
||||
taskInputMode: "size-image" | "resolution-based";
|
||||
}) {
|
||||
const input: Record<string, unknown> = {
|
||||
prompt,
|
||||
@ -124,16 +147,18 @@ function buildRuxaTaskInput({
|
||||
const ratio = aspectRatio || "1:1";
|
||||
|
||||
input.size = ratio;
|
||||
input.aspect_ratio = ratio;
|
||||
if (isResolutionSupported(resolution, modelConfig)) {
|
||||
input.resolution = resolution;
|
||||
}
|
||||
|
||||
if (imageUrls[0]) {
|
||||
input.image = imageUrls[0];
|
||||
}
|
||||
if (imageUrls.length > 0) {
|
||||
input.image_input = imageUrls.slice(0, 8);
|
||||
if (taskInputMode === "size-image") {
|
||||
if (imageUrls[0]) {
|
||||
input.image = imageUrls[0];
|
||||
}
|
||||
} else {
|
||||
input.aspect_ratio = ratio;
|
||||
if (isResolutionSupported(resolution, modelConfig)) {
|
||||
input.resolution = resolution;
|
||||
}
|
||||
if (imageUrls.length > 0) {
|
||||
input.image_input = imageUrls.slice(0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
@ -445,6 +470,10 @@ export function createRuxaImage({ apiKey, baseURL }: RuxaImageConfig) {
|
||||
const resolution = normalizeResolution(options.imageParams?.imageSize);
|
||||
const requestedModelId = options.overrideModelId || modelId;
|
||||
const activeModelId = normalizeRuxaModelId(requestedModelId);
|
||||
const taskInputMode = resolveRuxaTaskInputMode({
|
||||
activeModelId,
|
||||
modelConfig: options.imageGenConfig,
|
||||
});
|
||||
const outputFormat = normalizeOutputFormat(options.imageParams?.outputFormat);
|
||||
|
||||
const createEndpoint = joinApiPath(baseURL, "/api/v1/tasks/create");
|
||||
@ -456,6 +485,7 @@ export function createRuxaImage({ apiKey, baseURL }: RuxaImageConfig) {
|
||||
resolution,
|
||||
outputFormat,
|
||||
modelConfig: options.imageGenConfig,
|
||||
taskInputMode,
|
||||
});
|
||||
const requestBody = {
|
||||
input,
|
||||
|
||||
@ -45,6 +45,7 @@ export const RUXA_IMAGE_MODEL_PRESETS: RuxaImageModelPreset[] = [
|
||||
},
|
||||
imageGenConfig: {
|
||||
...RUXA_IMAGE_CONFIG,
|
||||
ruxaTaskInputMode: "size-image",
|
||||
},
|
||||
sortOrder: 10,
|
||||
},
|
||||
@ -62,6 +63,7 @@ export const RUXA_IMAGE_MODEL_PRESETS: RuxaImageModelPreset[] = [
|
||||
},
|
||||
imageGenConfig: {
|
||||
...RUXA_IMAGE_CONFIG,
|
||||
ruxaTaskInputMode: "resolution-based",
|
||||
},
|
||||
sortOrder: 20,
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user