fix: add failover chain for openai-compatible image generation
This commit is contained in:
parent
d89e8590a9
commit
3e594d723f
@ -4219,6 +4219,39 @@ export async function POST(request: Request) {
|
||||
// 🆕 处理 OpenAI 兼容的生图模型输出(防止返回 data:image;base64 原文)
|
||||
if (isOpenAICompatImageModel) {
|
||||
console.log("[Chat API] Using generateText for OpenAI-compatible image model");
|
||||
const configuredFallbacks = [
|
||||
currentModelConfig?.fallbackModelId1,
|
||||
currentModelConfig?.fallbackModelId2,
|
||||
].filter(Boolean) as string[];
|
||||
const allModels = await getEnabledAiModelsWithProvider();
|
||||
const sameProviderFallbacks = currentModelConfig
|
||||
? allModels
|
||||
.filter(
|
||||
(m) =>
|
||||
m.provider.id === currentModelConfig.providerId &&
|
||||
m.model.supportsImageGen &&
|
||||
m.model.isEnabled &&
|
||||
!m.model.isSubModel &&
|
||||
m.model.modelId !== actualModelId
|
||||
)
|
||||
.map((m) => m.model.modelId)
|
||||
: [];
|
||||
const fallbackModelIds = Array.from(
|
||||
new Set([...configuredFallbacks, ...sameProviderFallbacks])
|
||||
).filter((modelId) => modelId !== actualModelId);
|
||||
console.log("[Chat API] OpenAI-compatible fallback model IDs:", fallbackModelIds);
|
||||
|
||||
const lastUserMessage = uiMessagesForModel.filter((m) => m.role === "user").pop();
|
||||
let messagesForCrossProvider = lastUserMessage ? [lastUserMessage] : uiMessagesForModel;
|
||||
messagesForCrossProvider = applyOptimizedPromptToLatestUserMessage(
|
||||
messagesForCrossProvider,
|
||||
smartAutoOptimizedPrompt,
|
||||
hiddenPromptPrefix,
|
||||
);
|
||||
messagesForCrossProvider = injectHistoryImagesToLastUserMessage(
|
||||
messagesForCrossProvider,
|
||||
smartAutoHistoryImages
|
||||
);
|
||||
|
||||
// 生成开始标记
|
||||
dataStream.write({
|
||||
@ -4231,22 +4264,159 @@ export async function POST(request: Request) {
|
||||
});
|
||||
|
||||
try {
|
||||
const imageRequestTimeoutMs = 180_000;
|
||||
const generated = await generateText({
|
||||
model: languageModel,
|
||||
system: systemPrompt({ selectedChatModel: finalModelId, requestHints }),
|
||||
messages: convertedMessages,
|
||||
abortSignal: AbortSignal.timeout(imageRequestTimeoutMs),
|
||||
});
|
||||
const tryGenerateWithOpenAICompatModel = async (modelToUse: any) => {
|
||||
const imageRequestTimeoutMs = 180_000;
|
||||
const generated = await generateText({
|
||||
model: modelToUse,
|
||||
system: systemPrompt({ selectedChatModel: finalModelId, requestHints }),
|
||||
messages: convertedMessages,
|
||||
abortSignal: AbortSignal.timeout(imageRequestTimeoutMs),
|
||||
});
|
||||
|
||||
const rawText = generated.text?.trim() || "图片生成失败,请稍后重试";
|
||||
const normalizedText = await normalizeAssistantImageMarkdown(
|
||||
rawText,
|
||||
effectiveImageParams?.imageSize || "2K"
|
||||
);
|
||||
const rawText = generated.text?.trim() || "图片生成失败,请稍后重试";
|
||||
return normalizeAssistantImageMarkdown(
|
||||
rawText,
|
||||
effectiveImageParams?.imageSize || "2K"
|
||||
);
|
||||
};
|
||||
|
||||
let normalizedText = "";
|
||||
let usedModelId = actualModelId;
|
||||
|
||||
try {
|
||||
normalizedText = await tryGenerateWithOpenAICompatModel(languageModel);
|
||||
} catch (primaryError) {
|
||||
console.error("[Chat API] OpenAI-compatible primary model failed:", primaryError);
|
||||
|
||||
let fallbackSuccess = false;
|
||||
for (const fallbackModelId of fallbackModelIds) {
|
||||
try {
|
||||
console.log(
|
||||
`[Chat API] 🔄 OpenAI-compatible fallback model: ${fallbackModelId}`
|
||||
);
|
||||
const fallbackLanguageModel = await getLanguageModelById(
|
||||
finalModelId,
|
||||
fallbackModelId
|
||||
);
|
||||
if (!fallbackLanguageModel) {
|
||||
console.log(
|
||||
`[Chat API] Skip unavailable OpenAI-compatible fallback model: ${fallbackModelId}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
normalizedText = await tryGenerateWithOpenAICompatModel(fallbackLanguageModel);
|
||||
usedModelId = fallbackModelId;
|
||||
fallbackSuccess = true;
|
||||
console.log(
|
||||
`[Chat API] ✅ OpenAI-compatible fallback model ${fallbackModelId} succeeded!`
|
||||
);
|
||||
break;
|
||||
} catch (fallbackError) {
|
||||
console.error(
|
||||
`[Chat API] ❌ OpenAI-compatible fallback model ${fallbackModelId} failed:`,
|
||||
fallbackError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fallbackSuccess) {
|
||||
const providerPriority: Record<string, number> = {
|
||||
"volcano-vision": 0,
|
||||
"alibaba-image": 1,
|
||||
"ruxa-image": 2,
|
||||
"fal-image": 3,
|
||||
gemini: 4,
|
||||
"huanke-gemini": 5,
|
||||
};
|
||||
const crossProviderCandidates = getImageCrossProviderCandidates(allModels, {
|
||||
currentModelId: finalModelId,
|
||||
currentProviderId: currentModelConfig?.providerId,
|
||||
providerPriority,
|
||||
});
|
||||
|
||||
for (const candidate of crossProviderCandidates) {
|
||||
try {
|
||||
console.log(
|
||||
`[Chat API] 🔁 OpenAI-compatible cross-provider failover: ${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 cross-provider candidate without doGenerate: ${candidate.model.id}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const convertProvider =
|
||||
candidate.provider.type === "ruxa-image"
|
||||
? "ruxa-image"
|
||||
: candidate.provider.type === "fal-image"
|
||||
? "fal-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(
|
||||
messagesForCrossProvider,
|
||||
convertProvider
|
||||
);
|
||||
|
||||
const candidateResult = await (candidateLanguageModel as any).doGenerate({
|
||||
messages: candidateMessages,
|
||||
imageParams:
|
||||
effectiveImageParams || {
|
||||
aspectRatio: defaultImageQualityParams.aspectRatio,
|
||||
imageSize: defaultImageQualityParams.imageSize,
|
||||
imageCount: defaultImageQualityParams.imageCount,
|
||||
},
|
||||
modelDisplayName: candidate.model.displayName,
|
||||
overrideModelId: candidate.model.modelId,
|
||||
});
|
||||
|
||||
if (candidateResult?.finishReason === "error") {
|
||||
throw new Error(candidateResult.text || "图片生成失败");
|
||||
}
|
||||
|
||||
normalizedText = candidateResult.text || "图片生成失败";
|
||||
usedModelId = candidate.model.modelId;
|
||||
fallbackSuccess = true;
|
||||
console.log(
|
||||
`[Chat API] ✅ OpenAI-compatible cross-provider failover succeeded: ${candidate.model.displayName} (${candidate.provider.type})`
|
||||
);
|
||||
break;
|
||||
} catch (crossError) {
|
||||
const crossErrorMessage = normalizeFailoverErrorMessage(crossError);
|
||||
if (shouldCooldownImageFallbackModel(crossErrorMessage)) {
|
||||
setImageFallbackModelCooldown(candidate.model.id, crossErrorMessage);
|
||||
}
|
||||
console.error(
|
||||
`[Chat API] ❌ OpenAI-compatible cross-provider failover failed: ${candidate.model.displayName} (${candidate.provider.type})`,
|
||||
crossError
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fallbackSuccess) {
|
||||
throw primaryError;
|
||||
}
|
||||
}
|
||||
|
||||
const usedModelDisplayName =
|
||||
resolveModelDisplayNameByModelId(allModels, usedModelId) ||
|
||||
currentModelConfig?.displayName ||
|
||||
actualModelId;
|
||||
const normalizedTextWithModel = prependImageModelBadge(
|
||||
normalizedText,
|
||||
currentModelConfig?.displayName || actualModelId,
|
||||
usedModelDisplayName,
|
||||
);
|
||||
|
||||
const messageId = generateUUID();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user