export const CONTENT_MODERATION_ERROR_CODE = "sensitive_words_detected"; export const CONTENT_MODERATION_MESSAGE = "内容审核未通过,本次平台积分未扣除或已退还。请修改提示词后重新生成。"; const DEFAULT_GENERATION_ERROR_MESSAGE = "生成失败,请稍后重试。"; const NETWORK_ERROR_MESSAGE = "网络异常。"; export type GenerationFailureMetadata = { errorDetails: string; generationErrorCode?: string; failedPromptFingerprint?: string; }; export function generationFailureMetadata(error: unknown, prompt: string): GenerationFailureMetadata { const raw = rawGenerationError(error); if (!isContentModerationError(raw)) return { errorDetails: generationErrorMessage(error) }; return { errorDetails: generationErrorMessage(error), generationErrorCode: CONTENT_MODERATION_ERROR_CODE, failedPromptFingerprint: generationPromptFingerprint(prompt), }; } export function generationErrorMessage(error: unknown) { const raw = rawGenerationError(error); if (isContentModerationError(raw)) return contentModerationMessage(raw); const providerMessage = extractStructuredProviderMessage(raw) || extractWrappedProviderMessage(raw); const displayMessage = providerMessage || raw; if (isContentModerationError(displayMessage)) return contentModerationMessage(displayMessage); const resourceStorageMessage = resourceStorageFailureMessage(raw) || resourceStorageFailureMessage(displayMessage); if (resourceStorageMessage) return resourceStorageMessage; if (isNetworkFailure(displayMessage)) return NETWORK_ERROR_MESSAGE; if (!providerMessage) { if (!displayMessage.includes(";上游:")) { if (hasHttpStatus(raw, 429)) return "服务当前繁忙,请稍后重试。"; if (hasHttpStatus(raw, 401, 403)) return "生成服务鉴权失败,请检查渠道配置。"; if (hasHttpStatus(raw, 404)) return "生成服务地址不可用,请检查渠道配置。"; if (hasHttpStatus(raw, 500, 502, 503, 504)) return NETWORK_ERROR_MESSAGE; } if (containsInfrastructureDetails(raw)) return NETWORK_ERROR_MESSAGE; } return displayMessage || DEFAULT_GENERATION_ERROR_MESSAGE; } function contentModerationMessage(raw: string) { const detailIndex = raw.indexOf(";上游:"); // 保留服务端过滤后的拒绝原因,同时保持平台审核的积分提示及重试保护。 return detailIndex >= 0 && !containsInfrastructureDetails(raw) ? CONTENT_MODERATION_MESSAGE + raw.slice(detailIndex) : CONTENT_MODERATION_MESSAGE; } export function generationErrorCode(error: unknown) { if (error && typeof error === "object" && "code" in error && typeof (error as { code?: unknown }).code === "string") { const code = (error as { code: string }).code; if (/^(?:model|provider|origin)_[a-z0-9_]{2,80}$/.test(code)) return code; } return undefined; } export function isContentModerationError(value: unknown) { const text = value instanceof Error ? value.message : String(value || ""); return text.toLowerCase().includes(CONTENT_MODERATION_ERROR_CODE) || text.includes("内容审核未通过"); } export function unchangedModeratedPrompt(metadata: { errorDetails?: string; generationErrorCode?: string; failedPromptFingerprint?: string } | undefined, prompt: string) { const moderationFailure = metadata?.generationErrorCode === CONTENT_MODERATION_ERROR_CODE || isContentModerationError(metadata?.errorDetails); if (!moderationFailure) return false; if (!metadata?.failedPromptFingerprint) return true; return metadata.failedPromptFingerprint === generationPromptFingerprint(prompt); } // 指纹只用于识别“原样重试”,不是安全或鉴权用途。 export function generationPromptFingerprint(value: string) { const normalized = value.trim().replace(/\s+/g, " "); let hash = 2166136261; for (let index = 0; index < normalized.length; index += 1) { hash ^= normalized.charCodeAt(index); hash = Math.imul(hash, 16777619); } return `${normalized.length}:${(hash >>> 0).toString(36)}`; } function rawGenerationError(error: unknown) { if (error instanceof Error) return error.message.trim(); if (typeof error === "string") return error.trim(); return providerPayloadMessage(error); } function extractStructuredProviderMessage(raw: string) { for (let index = raw.indexOf("{"); index >= 0; index = raw.indexOf("{", index + 1)) { try { const message = providerPayloadMessage(JSON.parse(raw.slice(index).trim())); if (message) return message; } catch { // 上游常把 JSON 拼在 HTTP 状态后;不是完整 JSON 时继续尝试下一个对象起点。 } } return ""; } function extractWrappedProviderMessage(raw: string) { const interfaceFailure = raw.match(/^接口请求失败[::]\s*(.*)$/s); const requestFailure = raw.match(/^Request failed with status code \d{3}\s*[::-]?\s*(.+)$/is); const wrapped = interfaceFailure?.[1] ?? requestFailure?.[1]; if (!wrapped) return ""; const message = wrapped.replace(/^\d{3}(?:\s+(?:Bad Gateway|Service Unavailable|Gateway Timeout|Internal Server Error|Not Found|Unauthorized|Forbidden|Too Many Requests))?\s*[::-]?\s*/i, "").trim(); return message && !containsInfrastructureDetails(message) ? message : ""; } function providerPayloadMessage(payload: unknown): string { if (typeof payload === "string") return payload.trim(); if (!payload || typeof payload !== "object" || Array.isArray(payload)) return ""; const record = payload as Record; if (record.error && typeof record.error === "object") { const nested = providerPayloadMessage(record.error); if (nested) return nested; } for (const key of ["message", "msg", "detail"] as const) { const value = record[key]; if (typeof value === "string" && value.trim()) return value.trim(); } return typeof record.error === "string" ? record.error.trim() : ""; } function isNetworkFailure(value: string) { return /\b(?:dial tcp|connection refused|connection reset|no such host|i\/o timeout|context deadline exceeded|network error|failed to fetch|fetch failed|socket hang up|econnrefused|econnreset|etimedout)\b/i.test(value); } function hasHttpStatus(value: string, ...statuses: number[]) { return statuses.some((status) => new RegExp(`\\b${status}\\b`).test(value)); } function containsInfrastructureDetails(value: string) { return /(?:接口请求失败|Request failed with status code|https?:\/\/|\b(?:GET|POST|PUT|PATCH|DELETE)\s+["']?|Bad Gateway|Service Unavailable|Gateway Timeout|upstream_error)/i.test(value); } function resourceStorageFailureMessage(value: string) { if (!value) return ""; if (/\bUserDisable\b/i.test(value)) return "对象存储账号已停用,请检查或更换对象存储配置。"; if (/(?:参考(?:图片|媒体)上传失败|OSS 上传失败|对象存储|腾讯云 COS|七牛云)/i.test(value)) { return "参考素材上传到对象存储失败,请检查对象存储配置后重试。"; } return ""; }