为图片素材脚本补回同模型瞬时错误重试
- 两个脚本新增 fetchJsonWithRetry:对 408/429/5xx 按 1s/2s 退避重试同一 concrete model,最多 3 次,不引入跨模型回退 - requestImagePayload 改用带 requestId 的重试入口,单次 408/429/5xx 不再中断整批生成 - 删除只写不读的 providerBody / providerResponseParse 错误字段,解析失败与缺图改为把响应片段写进错误信息
This commit is contained in:
@@ -223,6 +223,15 @@ function inferExtensionFromBytes(bytes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Tiantoken 偶发 408/429/5xx 时重试同一个 concrete model;跨模型回退仍由
|
||||
// server-rs 负责,脚本不做任何模型替换。
|
||||
const transientRetryMaxAttempts = 3;
|
||||
const transientRetryBaseDelayMs = 1000;
|
||||
|
||||
function isTransientProviderStatus(status) {
|
||||
return status === 408 || status === 429 || status >= 500;
|
||||
}
|
||||
|
||||
async function fetchJson(url, options, timeoutMs) {
|
||||
const abortController = new AbortController();
|
||||
const timer = setTimeout(() => abortController.abort(), timeoutMs);
|
||||
@@ -237,15 +246,14 @@ async function fetchJson(url, options, timeoutMs) {
|
||||
`Tiantoken ${response.status}: ${text.slice(0, 600)}`,
|
||||
);
|
||||
error.providerStatus = response.status;
|
||||
error.providerBody = text;
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
error.providerResponseParse = true;
|
||||
error.providerBody = text;
|
||||
throw error;
|
||||
throw new Error(
|
||||
`Tiantoken 返回无法解析的响应体:${error.message};响应片段:${text.slice(0, 600)}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError') {
|
||||
@@ -257,6 +265,26 @@ async function fetchJson(url, options, timeoutMs) {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJsonWithRetry(url, options, timeoutMs, requestId) {
|
||||
for (let attempt = 1; ; attempt += 1) {
|
||||
try {
|
||||
return await fetchJson(url, options, timeoutMs);
|
||||
} catch (error) {
|
||||
if (
|
||||
attempt >= transientRetryMaxAttempts ||
|
||||
!isTransientProviderStatus(error?.providerStatus)
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
const delayMs = transientRetryBaseDelayMs * 2 ** (attempt - 1);
|
||||
console.warn(
|
||||
`Tiantoken ${error.providerStatus} on ${requestId}, retrying the same model in ${delayMs}ms (attempt ${attempt + 1}/${transientRetryMaxAttempts})`,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function requestImagePayload(env, entry) {
|
||||
const model = imageModel;
|
||||
const requestBody = {
|
||||
@@ -265,7 +293,7 @@ async function requestImagePayload(env, entry) {
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
};
|
||||
const payload = await fetchJson(
|
||||
const payload = await fetchJsonWithRetry(
|
||||
buildTiantokenImagesGenerationUrl(env.baseUrl),
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -277,15 +305,15 @@ async function requestImagePayload(env, entry) {
|
||||
body: JSON.stringify(requestBody),
|
||||
},
|
||||
env.timeoutMs,
|
||||
entry.id,
|
||||
);
|
||||
const base64Image = decodeStrictBase64Image(extractBase64Images(payload)[0]);
|
||||
if (extractImageUrls(payload)[0] || base64Image) {
|
||||
return payload;
|
||||
}
|
||||
const error = new Error(`Tiantoken returned no image for ${entry.id}`);
|
||||
error.providerResponseParse = true;
|
||||
error.providerBody = JSON.stringify(payload).slice(0, 600);
|
||||
throw error;
|
||||
throw new Error(
|
||||
`Tiantoken returned no image for ${entry.id}:${JSON.stringify(payload).slice(0, 600)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function downloadUrl(url, timeoutMs) {
|
||||
|
||||
@@ -193,6 +193,15 @@ function inferExtensionFromBytes(bytes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Tiantoken 偶发 408/429/5xx 时重试同一个 concrete model;跨模型回退仍由
|
||||
// server-rs 负责,脚本不做任何模型替换。
|
||||
const transientRetryMaxAttempts = 3;
|
||||
const transientRetryBaseDelayMs = 1000;
|
||||
|
||||
function isTransientProviderStatus(status) {
|
||||
return status === 408 || status === 429 || status >= 500;
|
||||
}
|
||||
|
||||
async function fetchJson(url, options, timeoutMs) {
|
||||
const abortController = new AbortController();
|
||||
const timer = setTimeout(() => abortController.abort(), timeoutMs);
|
||||
@@ -207,15 +216,14 @@ async function fetchJson(url, options, timeoutMs) {
|
||||
`Tiantoken ${response.status}: ${text.slice(0, 600)}`,
|
||||
);
|
||||
error.providerStatus = response.status;
|
||||
error.providerBody = text;
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
error.providerResponseParse = true;
|
||||
error.providerBody = text;
|
||||
throw error;
|
||||
throw new Error(
|
||||
`Tiantoken 返回无法解析的响应体:${error.message};响应片段:${text.slice(0, 600)}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError') {
|
||||
@@ -227,6 +235,26 @@ async function fetchJson(url, options, timeoutMs) {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJsonWithRetry(url, options, timeoutMs, requestId) {
|
||||
for (let attempt = 1; ; attempt += 1) {
|
||||
try {
|
||||
return await fetchJson(url, options, timeoutMs);
|
||||
} catch (error) {
|
||||
if (
|
||||
attempt >= transientRetryMaxAttempts ||
|
||||
!isTransientProviderStatus(error?.providerStatus)
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
const delayMs = transientRetryBaseDelayMs * 2 ** (attempt - 1);
|
||||
console.warn(
|
||||
`Tiantoken ${error.providerStatus} on ${requestId}, retrying the same model in ${delayMs}ms (attempt ${attempt + 1}/${transientRetryMaxAttempts})`,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function requestImagePayload(env, template) {
|
||||
const model = imageModel;
|
||||
const requestBody = {
|
||||
@@ -235,7 +263,7 @@ async function requestImagePayload(env, template) {
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
};
|
||||
const payload = await fetchJson(
|
||||
const payload = await fetchJsonWithRetry(
|
||||
buildTiantokenImagesGenerationUrl(env.baseUrl),
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -247,15 +275,15 @@ async function requestImagePayload(env, template) {
|
||||
body: JSON.stringify(requestBody),
|
||||
},
|
||||
env.timeoutMs,
|
||||
template.id,
|
||||
);
|
||||
const base64Image = decodeStrictBase64Image(extractBase64Images(payload)[0]);
|
||||
if (extractImageUrls(payload)[0] || base64Image) {
|
||||
return payload;
|
||||
}
|
||||
const error = new Error(`Tiantoken returned no image for ${template.id}`);
|
||||
error.providerResponseParse = true;
|
||||
error.providerBody = JSON.stringify(payload).slice(0, 600);
|
||||
throw error;
|
||||
throw new Error(
|
||||
`Tiantoken returned no image for ${template.id}:${JSON.stringify(payload).slice(0, 600)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function downloadUrl(url, timeoutMs) {
|
||||
|
||||
Reference in New Issue
Block a user