fix(ai): client-side retry on 429, server returns retryAfter, hardcoded llama-3.3 free model

This commit is contained in:
Michael committed 2026-02-21 22:04:08 +01:00
1 parent 41c3ab34b6
commit 3d26dc0b9b
2 files changed
+55 -37

No files matched your search

+29 -29
View File
@@ -62,39 +62,39 @@ Format attendu:
const userPrompt = `Analyse cette liste de produits :\n${JSON.stringify(products, null, 2)}`;
// Retry loop for 429 rate limiting — reads Retry-After header
let response: Response | null = null;
const MAX_RETRIES = 3;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
response_format: { type: "json_object" },
temperature: 0.1,
}),
});
// Always use the free Llama 3.3 model for batch — not the user's settings model
// (settings model may be a paid model not suitable here)
const BATCH_MODEL = "meta-llama/llama-3.3-70b-instruct:free";
if (response.status !== 429) break; // Success or non-retryable error
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: BATCH_MODEL,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
response_format: { type: "json_object" },
temperature: 0.1,
}),
});
const retryAfterHeader = response.headers.get("Retry-After") || response.headers.get("x-ratelimit-reset-requests");
const waitSeconds = retryAfterHeader ? Math.min(parseInt(retryAfterHeader, 10), 90) : 30 * attempt;
console.warn(`[batch-analyze] 429 on attempt ${attempt}/${MAX_RETRIES}. Waiting ${waitSeconds}s...`);
await new Promise(resolve => setTimeout(resolve, waitSeconds * 1000));
if (response.status === 429) {
// Read the retry-after header and pass it back to the client
const retryAfter = response.headers.get("Retry-After") || response.headers.get("x-ratelimit-reset-requests");
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
console.warn(`[batch-analyze] Rate limited. Retry after ${waitSeconds}s.`);
return NextResponse.json({ error: "rate_limited", retryAfter: waitSeconds }, { status: 429 });
}
if (!response || !response.ok) {
const err = response ? await response.text() : "No response";
console.error("OpenRouter API Error:", err);
return NextResponse.json({ error: "Erreur lors de l'appel à OpenRouter." }, { status: response?.status ?? 500 });
if (!response.ok) {
const err = await response.text();
console.error("OpenRouter API Error:", response.status, err);
return NextResponse.json({ error: "Erreur lors de l'appel à OpenRouter.", status: response.status }, { status: response.status });
}
const data = await response.json();
@@ -57,15 +57,33 @@ export function BulkAiAnalyzer() {
}));
try {
const res = await fetch("/api/ai/batch-analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rayon: chunk.rayon, products: payloadProducts })
});
let res: Response | null = null;
const MAX_CHUNK_RETRIES = 3;
if (!res.ok) {
const errText = await res.text();
console.error(`[BulkAiAnalyzer] Erreur HTTP ${res.status} sur le lot ${chunk.rayon}:`, errText);
for (let attempt = 1; attempt <= MAX_CHUNK_RETRIES; attempt++) {
res = await fetch("/api/ai/batch-analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rayon: chunk.rayon, products: payloadProducts })
});
if (res.status === 429) {
const errData = await res.json().catch(() => ({}));
const waitSecs = errData.retryAfter ?? (30 * attempt);
console.warn(`[BulkAiAnalyzer] Rate limited. Waiting ${waitSecs}s before retry ${attempt}/${MAX_CHUNK_RETRIES}`);
for (let t = waitSecs; t > 0; t--) {
setProgress(prev => ({ ...prev, message: `⏳ Lot ${completed + 1}/${chunks.length} — Rate limit, reprise dans ${t}s...` }));
await new Promise(r => setTimeout(r, 1000));
}
continue; // retry
}
break; // success or non-retryable error
}
if (!res || !res.ok) {
const errText = await res?.text().catch(() => "");
console.error(`[BulkAiAnalyzer] Erreur HTTP ${res?.status} sur le lot ${chunk.rayon}:`, errText);
setProgress(prev => ({ ...prev, errors: prev.errors + 1 }));
} else {
const data = await res.json();