fix: fail over on upstream image provider errors
This commit is contained in:
parent
3096eb6752
commit
62c686a26b
@ -716,12 +716,11 @@ async function sendGrsaiGptImageRequest(params: {
|
||||
response.status,
|
||||
responseText.substring(0, 1000)
|
||||
);
|
||||
const providerError = getGrsaiErrorMessage(data);
|
||||
const errorMessage =
|
||||
data?.error?.message ||
|
||||
data?.message ||
|
||||
data?.error ||
|
||||
responseText ||
|
||||
`${response.status} ${response.statusText}`;
|
||||
providerError !== "未知错误"
|
||||
? providerError
|
||||
: responseText || `${response.status} ${response.statusText}`;
|
||||
throw new Error(`图片生成失败: ${errorMessage}`);
|
||||
}
|
||||
|
||||
@ -754,6 +753,13 @@ async function sendGrsaiGptImageRequest(params: {
|
||||
timeoutLabel: "Grsai GPT Image",
|
||||
});
|
||||
}
|
||||
|
||||
const providerError = getGrsaiErrorMessage(data);
|
||||
throw new Error(
|
||||
`图片生成失败: 未返回任务 ID 或可用图片${
|
||||
providerError !== "未知错误" ? `: ${providerError}` : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -857,18 +863,46 @@ function getGrsaiProgress(payload: any): number | null {
|
||||
|
||||
function getGrsaiErrorMessage(payload: any): string {
|
||||
const normalized = normalizeGrsaiPayload(payload);
|
||||
const normalizePart = (part: unknown) => {
|
||||
if (typeof part === "string" || typeof part === "number") {
|
||||
return String(part).trim();
|
||||
}
|
||||
if (!isRecord(part)) return "";
|
||||
return String(
|
||||
part.message ||
|
||||
part.msg ||
|
||||
part.detail ||
|
||||
part.reason ||
|
||||
part.code ||
|
||||
part.type ||
|
||||
""
|
||||
).trim();
|
||||
};
|
||||
const messages = [
|
||||
normalized?.failure_reason,
|
||||
normalized?.error?.message,
|
||||
normalized?.error?.msg,
|
||||
normalized?.error?.code,
|
||||
normalized?.error,
|
||||
normalized?.message,
|
||||
normalized?.msg,
|
||||
normalized?.detail,
|
||||
normalized?.reason,
|
||||
normalized?.code,
|
||||
payload?.error?.message,
|
||||
payload?.error?.msg,
|
||||
payload?.error?.code,
|
||||
payload?.msg,
|
||||
payload?.message,
|
||||
payload?.error,
|
||||
payload?.detail,
|
||||
payload?.reason,
|
||||
payload?.code,
|
||||
]
|
||||
.map((part) => String(part || "").trim())
|
||||
.map(normalizePart)
|
||||
.filter(Boolean);
|
||||
|
||||
return messages.join(" / ") || "未知错误";
|
||||
return Array.from(new Set(messages)).join(" / ") || "未知错误";
|
||||
}
|
||||
|
||||
function hasGrsaiImages(payload: any): boolean {
|
||||
@ -899,18 +933,36 @@ async function fetchWithTimeout(
|
||||
|
||||
async function readJsonResponse(response: Response, label: string): Promise<any> {
|
||||
const text = await response.text();
|
||||
const payload = parseGrsaiJsonOrStreamResponse(text);
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error(`${label} failed:`, response.status, text.substring(0, 500));
|
||||
throw new Error(`${label} 接口错误:${response.status} ${response.statusText}`);
|
||||
const providerError = getGrsaiErrorMessage(payload);
|
||||
const detail =
|
||||
providerError !== "未知错误"
|
||||
? providerError
|
||||
: String(payload?.raw || text || "").trim().slice(0, 300);
|
||||
throw new Error(
|
||||
`${label} 接口错误:${response.status} ${response.statusText}${
|
||||
detail ? `: ${detail}` : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
if (isRecord(payload) && !("raw" in payload)) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
if (!isRecord(payload)) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
if ("raw" in payload) {
|
||||
logger.error(`${label} returned non-JSON response:`, text.substring(0, 500));
|
||||
throw new Error(`${label} 返回格式异常,未能解析任务结果`);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function pollGrsaiNanoBananaResult(params: {
|
||||
@ -1047,11 +1099,16 @@ async function submitAndPollGrsaiNanoBanana(params: {
|
||||
|
||||
const taskId = getGrsaiTaskId(submitPayload);
|
||||
if (!taskId) {
|
||||
const providerError = getGrsaiErrorMessage(submitPayload);
|
||||
logger.error(
|
||||
`${params.apiLogPrefix} Async submit response has no task id:`,
|
||||
JSON.stringify(submitPayload).substring(0, 500)
|
||||
);
|
||||
throw new Error("GRSAI Nano Banana 异步任务提交失败:未返回任务 ID");
|
||||
throw new Error(
|
||||
`GRSAI Nano Banana 异步任务提交失败:未返回任务 ID${
|
||||
providerError !== "未知错误" ? `: ${providerError}` : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug(`${params.apiLogPrefix} Async task submitted:`, { taskId });
|
||||
|
||||
@ -151,7 +151,10 @@ export function classifyAiModelHealthFailure(error: unknown): {
|
||||
|
||||
if (
|
||||
normalized.includes("insufficient balance") ||
|
||||
normalized.includes("insufficient credit") ||
|
||||
normalized.includes("credits insufficient") ||
|
||||
normalized.includes("credit insufficient") ||
|
||||
normalized.includes("not enough credit") ||
|
||||
normalized.includes("quota") ||
|
||||
normalized.includes("余额") ||
|
||||
normalized.includes("额度") ||
|
||||
@ -209,10 +212,38 @@ export function classifyAiModelHealthFailure(error: unknown): {
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.includes("未返回任务 id") ||
|
||||
normalized.includes("未返回任务id") ||
|
||||
normalized.includes("no task id") ||
|
||||
normalized.includes("missing task id") ||
|
||||
normalized.includes("provider error") ||
|
||||
normalized.includes("provider_error") ||
|
||||
normalized.includes("provider request failed") ||
|
||||
normalized.includes("provider task failed") ||
|
||||
normalized.includes("upstream error") ||
|
||||
normalized.includes("upstream_error") ||
|
||||
normalized.includes("upstream request failed") ||
|
||||
normalized.includes("上游服务错误") ||
|
||||
normalized.includes("上游接口错误")
|
||||
) {
|
||||
return {
|
||||
eventType: "unavailable",
|
||||
severity: "critical",
|
||||
counted: true,
|
||||
reason: "provider_execution_failed",
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.includes("download") ||
|
||||
normalized.includes("no image url") ||
|
||||
normalized.includes("no image") ||
|
||||
normalized.includes("empty result") ||
|
||||
normalized.includes("empty response") ||
|
||||
normalized.includes("空结果") ||
|
||||
normalized.includes("返回结果为空") ||
|
||||
normalized.includes("响应为空") ||
|
||||
normalized.includes("未返回可用图片") ||
|
||||
normalized.includes("未包含可用图片") ||
|
||||
normalized.includes("未解析到图片") ||
|
||||
@ -245,14 +276,21 @@ export function shouldFailoverAiModelError(error: unknown) {
|
||||
normalized.includes("snapgen 任务已完成但未返回图片地址") ||
|
||||
normalized.includes("snapgen 未返回可用图片");
|
||||
|
||||
if (imageProviderTaskFailed) {
|
||||
return true;
|
||||
}
|
||||
const providerExecutionFailed =
|
||||
classification.reason === "provider_execution_failed" ||
|
||||
normalized.includes("未返回任务 id") ||
|
||||
normalized.includes("未返回任务id") ||
|
||||
normalized.includes("no task id") ||
|
||||
normalized.includes("missing task id");
|
||||
|
||||
if (classification.reason === "user_cancelled" || classification.reason === "non_provider_failure") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (imageProviderTaskFailed || providerExecutionFailed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
classification.eventType === "timeout" ||
|
||||
classification.reason === "transport_failure_no_provider_response"
|
||||
@ -263,6 +301,11 @@ export function shouldFailoverAiModelError(error: unknown) {
|
||||
if (classification.eventType === "download_error") {
|
||||
return (
|
||||
normalized.includes("no image") ||
|
||||
normalized.includes("empty result") ||
|
||||
normalized.includes("empty response") ||
|
||||
normalized.includes("空结果") ||
|
||||
normalized.includes("返回结果为空") ||
|
||||
normalized.includes("响应为空") ||
|
||||
normalized.includes("未返回可用图片") ||
|
||||
normalized.includes("未包含可用图片") ||
|
||||
normalized.includes("未解析到图片") ||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user