File size: 4,857 Bytes
ef46e44 ff49634 ef46e44 ff49634 2b62b0c ff49634 df94b74 ff49634 ef46e44 ff49634 ef46e44 ff49634 ef46e44 ff49634 ef46e44 ff49634 ef46e44 ff49634 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | export function isNonRetryableProviderError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
const lower = message.toLowerCase();
if (/openai-compatible api error 5\d\d/i.test(message)) return true;
if (/api error 5\d\d/i.test(message)) return true;
if (lower.includes("midstreamfallbackerror")) return true;
if (lower.includes("apiconnectionerror")) return true;
if (lower.includes("upstream returned an html error page")) return true;
if (lower.includes("provider returned html")) return true;
if (/\berror 502\b/.test(lower) || /\berror 503\b/.test(lower) || /\berror 504\b/.test(lower)) {
return true;
}
return false;
}
function truncate(text: string, maxLen: number): string {
return text.length <= maxLen ? text : `${text.slice(0, maxLen)}…`;
}
function sectionHint(failedSections: string[]): string {
return failedSections.length > 0 ? ` Section gagal: ${failedSections.join(", ")}.` : "";
}
/** Turn raw pipeline/client errors into user-facing messages that name the real cause. */
export function formatGenerationErrorMessage(input: {
cause?: string;
failedSections?: string[];
}): string {
const failedSections = [...new Set(input.failedSections ?? [])];
const hint = sectionHint(failedSections);
const cause = input.cause?.trim();
if (!cause) {
return `Tidak ada soal yang berhasil dibuat.${hint}`;
}
const lower = cause.toLowerCase();
if (
lower.includes("redacted_thinking") ||
lower.includes("<thinking>") ||
/<\/?think>/.test(lower)
) {
return `Model reasoning tidak kompatibel dengan parser Pram.${hint} Coba non-reasoning model, atau update server ke versi terbaru.`;
}
if (
lower.includes("json response was truncated") ||
lower.includes("unterminated string")
) {
return `Respons model terpotong sebelum JSON selesai (biasanya max tokens terlalu kecil).${hint} Naikkan Max Tokens di Settings atau kurangi jumlah soal/format.`;
}
if (lower.includes("failed to parse ai response") || lower.includes("invalid json response")) {
return `Model merespons, tapi output tidak valid JSON.${hint} ${truncate(cause, 280)}`;
}
if (lower.includes("empty response from ai") || lower.includes("empty response body")) {
return `Model tidak mengembalikan konten.${hint} Coba model lain atau kurangi jumlah soal.`;
}
if (lower.includes("no valid questions")) {
return `Model menghasilkan soal, tapi semuanya gagal validasi.${hint} ${truncate(cause, 280)}`;
}
if (
lower.includes("upstream returned an html error page") ||
lower.includes("provider returned html") ||
lower.includes("openai-compatible api error") ||
/api error 5\d\d/i.test(cause) ||
lower.includes("apiconnectionerror") ||
lower.includes("midstreamfallbackerror")
) {
return `Gagal menghubungi API provider.${hint} ${truncate(cause, 280)}`;
}
if (lower.includes("metadata/private network") || lower.includes("invalid base url")) {
return `Base URL provider tidak valid atau tidak dapat diakses dari server.${hint}`;
}
return `Generasi gagal.${hint} ${truncate(cause, 280)}`;
}
export function resolveGenerationJobOutcome(input: {
requestedCount: number;
generatedCount: number;
failedSections?: string[];
cause?: string;
}): {
status: "completed" | "completed_partial" | "failed";
progressMessage: string;
errorMessage: string | null;
} {
const failedSections = [...new Set(input.failedSections ?? [])];
if (input.generatedCount <= 0) {
return {
status: "failed",
progressMessage: "Generation failed",
errorMessage: formatGenerationErrorMessage({
cause: input.cause,
failedSections,
}),
};
}
if (input.generatedCount < input.requestedCount) {
const sectionHintText =
failedSections.length > 0 ? ` Section gagal: ${failedSections.join(", ")}.` : "";
return {
status: "completed_partial",
progressMessage: `Selesai sebagian: ${input.generatedCount}/${input.requestedCount} soal`,
errorMessage: `Hanya ${input.generatedCount} dari ${input.requestedCount} soal berhasil dibuat.${sectionHintText}`,
};
}
return {
status: "completed",
progressMessage: "Completed",
errorMessage: null,
};
}
export function sectionsWithNoQuestions(
sectionSplits: Array<{ section: string; count: number }>,
questions: Array<{ section: string }>,
): string[] {
return sectionSplits
.filter((split) => !questions.some((q) => q.section === split.section))
.map((split) => split.section);
}
export function collectShardFailureCause(
failures: Iterable<{ error: string } | undefined>,
): string | undefined {
for (const failure of failures) {
const message = failure?.error?.trim();
if (message) return message;
}
return undefined;
}
|