diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index d8a528425..fb70dc431 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -2296,6 +2296,7 @@ export async function POST(request: Request) { const tryGenerateImage = async (modelIdToUse: string, isRetry: boolean) => { const result = await (languageModel as any).doGenerate({ messages: modelMessages, + imageParams: effectiveImageParams || { aspectRatio: "auto", imageSize: "2K", imageCount: 1 }, overrideModelId: isRetry ? modelIdToUse : undefined, }); if (result?.finishReason === "error") { @@ -2333,6 +2334,84 @@ export async function POST(request: Request) { console.error(`[Chat API] ❌ Volcano fallback model ${fallbackModelId} failed:`, fallbackError); } } + if (!fallbackSuccess) { + const providerPriority: Record = { + gemini: 0, + "huanke-gemini": 1, + "alibaba-image": 2, + "ruxa-image": 3, + "volcano-vision": 4, + }; + const crossProviderCandidates = allModels + .filter((m) => { + if (!m.model.supportsImageGen || !m.model.isEnabled || m.model.isSubModel) { + return false; + } + if (m.model.id === finalModelId) return false; + if (m.provider.id === volcanoModelConfig?.providerId) return false; + return true; + }) + .sort((a, b) => { + const left = providerPriority[a.provider.type as string] ?? 99; + const right = providerPriority[b.provider.type as string] ?? 99; + if (left !== right) return left - right; + return (a.model.sortOrder ?? 0) - (b.model.sortOrder ?? 0); + }); + + for (const candidate of crossProviderCandidates) { + try { + console.log( + `[Chat API] 🔁 Volcano cross-provider failover attempt: ${candidate.model.displayName} (${candidate.provider.type})` + ); + + const candidateLanguageModel = await getLanguageModelById(candidate.model.id); + if (!candidateLanguageModel || typeof (candidateLanguageModel as any).doGenerate !== "function") { + console.log(`[Chat API] Skip candidate without doGenerate: ${candidate.model.id}`); + continue; + } + + const convertProvider = + candidate.provider.type === "ruxa-image" + ? "ruxa-image" + : candidate.provider.type === "gemini" + ? "gemini" + : candidate.provider.type === "huanke-gemini" + ? "huanke-gemini" + : candidate.provider.type === "volcano-vision" + ? "volcano" + : "alibaba-image"; + + const candidateMessages = await convertMessagesWithImages( + messagesForVolcano, + convertProvider + ); + + const candidateResult = await (candidateLanguageModel as any).doGenerate({ + messages: candidateMessages, + imageParams: effectiveImageParams || { aspectRatio: "auto", imageSize: "2K", imageCount: 1 }, + modelDisplayName: candidate.model.displayName, + overrideModelId: candidate.model.modelId, + }); + + if (candidateResult?.finishReason === "error") { + throw new Error(candidateResult.text || "图片生成失败"); + } + + imageMarkdown = candidateResult.text || "图片生成失败"; + usedModelId = candidate.model.modelId; + fallbackSuccess = true; + console.log( + `[Chat API] ✅ Volcano cross-provider failover succeeded: ${candidate.model.displayName} (${candidate.provider.type})` + ); + break; + } catch (crossError) { + console.error( + `[Chat API] ❌ Volcano cross-provider failover failed: ${candidate.model.displayName} (${candidate.provider.type})`, + crossError + ); + } + } + } if (!fallbackSuccess) { throw primaryError; }