fix(ruxa): resolve runtime model ids from config for image flows

This commit is contained in:
Your Name 2026-06-08 20:13:12 +08:00
parent 5a04304ea9
commit 6a9beb418a
6 changed files with 88 additions and 7 deletions

View File

@ -7,6 +7,7 @@ import { createHuankeGemini } from "@/lib/ai/providers/huanke-gemini";
import { createAlibabaImage } from "@/lib/ai/providers/alibaba-image";
import { createBflImage } from "@/lib/ai/providers/bfl-image";
import { createRuxaImage } from "@/lib/ai/providers/ruxa-image";
import { resolveModelRuntimeId } from "@/lib/ai/model-runtime";
import { createFalImage } from "@/lib/ai/providers/fal-image";
import { createOpenAIImage } from "@/lib/ai/providers/openai-image";
import { createStepfunImage } from "@/lib/ai/providers/stepfun-image";
@ -426,7 +427,10 @@ async function generateTextEditImageWithFailover(params: {
});
const provider = createTextEditProvider(selected.provider);
const result = await provider.chat(selected.model.modelId).doGenerate({
const runtimeModelId = resolveModelRuntimeId(selected.model, {
providerType: provider.type,
});
const result = await provider.chat(runtimeModelId).doGenerate({
messages: [
{
role: "user",

View File

@ -33,6 +33,7 @@ import {
} from "@/lib/scene-fusion/output-capabilities";
import { acquireCanvasImageModelSlot } from "@/lib/canvas/image-concurrency";
import { createLogger } from "@/lib/observability/logger";
import { resolveModelRuntimeId } from "@/lib/ai/model-runtime";
// 请求体 Schema
const sceneFusionRequestSchema = z.object({
@ -81,6 +82,7 @@ interface SceneFusionModelCandidate {
supportsVision: boolean;
imageGenConfig?: SceneFusionImageGenConfig | null;
excludeFromFailover?: boolean;
defaultParams?: Record<string, any> | null;
};
provider: {
id?: string;
@ -901,6 +903,7 @@ function buildSceneFusionCandidatePool(
supportsVision: Boolean(entry.model.supportsVision),
imageGenConfig: (entry.model.imageGenConfig || null) as SceneFusionImageGenConfig | null,
excludeFromFailover: Boolean(entry.model.excludeFromFailover),
defaultParams: (entry.model.defaultParams as Record<string, any> | null) || null,
},
provider: {
id: String(entry.provider.id || ""),
@ -1475,6 +1478,7 @@ async function processSceneWithProvider(
sceneMode: SceneFusionMode,
provider: { type: string; baseUrl: string; apiKey: string },
modelId: string,
runtimeModelId: string,
aspectRatio?: string,
imageSize?: string
): Promise<{ thumbnail: string; original: string; prompt: string }> {
@ -1501,7 +1505,15 @@ async function processSceneWithProvider(
return processWithAlibabaImage(productImageUrl, sceneImageUrl, prompt, provider, modelId, aspectRatio, imageSize);
case "ruxa-image":
return processWithRuxaImage(productImageUrl, sceneImageUrl, prompt, provider, modelId, aspectRatio, imageSize);
return processWithRuxaImage(
productImageUrl,
sceneImageUrl,
prompt,
provider,
runtimeModelId || modelId,
aspectRatio,
imageSize,
);
case "fal-image":
return processWithFalImage(productImageUrl, sceneImageUrl, prompt, provider, modelId, aspectRatio, imageSize);
@ -1957,6 +1969,9 @@ async function processSceneWithDiversity(
});
let result: Awaited<ReturnType<typeof processSceneWithProvider>>;
try {
const runtimeModelId = resolveModelRuntimeId(candidate.model, {
providerType: candidate.provider.type,
});
await params.onProcessingStart?.();
result = await processSceneWithProvider(
params.productImageUrl,
@ -1974,6 +1989,7 @@ async function processSceneWithDiversity(
apiKey: candidate.provider.apiKey,
},
candidate.model.modelId,
runtimeModelId,
normalizedOutputParams.aspectRatio,
normalizedOutputParams.imageSize
);

View File

@ -37,6 +37,7 @@ import {
isMoonshotOfficialBaseUrl,
} from "./detail-agent-kimi-profile";
import { isBltcyVideoBaseUrl } from "./bltcy-video-models";
import { resolveModelRuntimeId } from "./model-runtime";
// 缓存已创建的 provider 实例
const providerCache = new Map<string, any>();
@ -387,7 +388,10 @@ export async function getDynamicModel(
return null;
}
const resolvedModelId = String(overrideModelId || "").trim() || model.modelId;
const resolvedModelId = resolveModelRuntimeId(model, {
overrideModelId,
providerType: provider.type,
});
// 创建基础模型
logger.debug("model creation started", {

54
lib/ai/model-runtime.ts Normal file
View File

@ -0,0 +1,54 @@
interface ModelRuntimeLike {
modelId?: string | null;
defaultParams?: unknown;
}
interface ResolveModelRuntimeIdOptions {
overrideModelId?: string | null;
providerType?: string | null;
}
function fallbackRuxaRuntimeId(modelId: string) {
if (!modelId.startsWith("google/")) {
return modelId;
}
const candidate = modelId.slice("google/".length).trim();
return candidate || modelId;
}
function toRecord(value: unknown): Record<string, unknown> | null {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return null;
}
return value as Record<string, unknown>;
}
function normalizeRuntimeModelId(value: unknown): string | null {
if (typeof value !== "string") return null;
const normalized = value.trim();
return normalized || null;
}
export function resolveModelRuntimeId(
model: ModelRuntimeLike | null | undefined,
options?: ResolveModelRuntimeIdOptions,
): string {
const overrideModelId = normalizeRuntimeModelId(options?.overrideModelId);
if (overrideModelId) {
return overrideModelId;
}
const defaultParams = toRecord(model?.defaultParams);
const endpointModelId = normalizeRuntimeModelId(defaultParams?.endpoint);
if (endpointModelId) {
return endpointModelId;
}
const resolvedModelId = normalizeRuntimeModelId(model?.modelId) || "";
if (options?.providerType === "ruxa-image") {
return fallbackRuxaRuntimeId(resolvedModelId);
}
return resolvedModelId;
}

View File

@ -38,7 +38,7 @@ export const RUXA_IMAGE_MODEL_PRESETS: RuxaImageModelPreset[] = [
supportsImageGen: true,
defaultParams: {
provider: "ruxa",
endpoint: RUXA_NANO_BANANA_2_MODEL_ID,
endpoint: "nano-banana-2",
imageSize: "2K",
aspectRatio: "1:1",
},
@ -55,7 +55,7 @@ export const RUXA_IMAGE_MODEL_PRESETS: RuxaImageModelPreset[] = [
supportsImageGen: true,
defaultParams: {
provider: "ruxa",
endpoint: RUXA_NANO_BANANA_PRO_MODEL_ID,
endpoint: "nano-banana-pro",
imageSize: "2K",
aspectRatio: "1:1",
},
@ -71,4 +71,3 @@ export function getRuxaImageModelPresetById(modelId: string) {
(preset) => preset.modelId === String(modelId || "").trim()
);
}

View File

@ -14,6 +14,7 @@ import { createMidjourney } from "@/lib/ai/providers/midjourney";
import { createRunway } from "@/lib/ai/providers/runway";
import { createKrea } from "@/lib/ai/providers/krea";
import { createVolcanoVision } from "@/lib/ai/providers/volcano-vision";
import { resolveModelRuntimeId } from "@/lib/ai/model-runtime";
import { filterReachableRemoteUrls } from "@/lib/net/url-access";
import {
acquireCanvasImageModelSlot,
@ -579,7 +580,10 @@ export async function generateCanvasImage(
apiKey: provider.apiKey,
baseURL: provider.baseUrl,
});
const modelInstance = ruxaProvider.chat(selectedModel.model.modelId);
const runtimeModelId = resolveModelRuntimeId(selectedModel.model, {
providerType: provider.type,
});
const modelInstance = ruxaProvider.chat(runtimeModelId);
executeGenerateBatch = async (batchImageCount) => {
const result = await modelInstance.doGenerate({
messages,