feat(api): implement retry logic for Mistral API calls in receipt import and AI services

This commit is contained in:
Nils-Johan Gynther
2026-04-19 11:31:05 +02:00
parent 15c24df1a7
commit 045f160655
3 changed files with 107 additions and 74 deletions
+48 -30
View File
@@ -47,38 +47,56 @@ Regler:
const userPrompt = `Produkt: "${productName}"`;
let raw: string;
try {
const response = await fetch(MISTRAL_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: MODEL,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
max_tokens: 100,
temperature: 0.1,
response_format: { type: 'json_object' },
}),
});
let raw = '';
const MAX_RETRIES = 3;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const response = await fetch(MISTRAL_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: MODEL,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
max_tokens: 100,
temperature: 0.1,
response_format: { type: 'json_object' },
}),
});
if (!response.ok) {
const err = await response.text();
this.logger.error(`Mistral API-fel: ${response.status} ${err}`);
throw new ServiceUnavailableException('AI-tjänsten svarade inte korrekt');
if (response.status === 503 || response.status === 429) {
const err = await response.text();
this.logger.warn(`Mistral API ${response.status} (försök ${attempt}/${MAX_RETRIES}): ${err}`);
if (attempt < MAX_RETRIES) {
await new Promise((r) => setTimeout(r, attempt * 1500));
continue;
}
throw new ServiceUnavailableException('AI-tjänsten är tillfälligt otillgänglig, försök igen');
}
if (!response.ok) {
const err = await response.text();
this.logger.error(`Mistral API-fel: ${response.status} ${err}`);
throw new ServiceUnavailableException('AI-tjänsten svarade inte korrekt');
}
const data = await response.json() as { choices: { message: { content: string } }[] };
raw = data.choices?.[0]?.message?.content ?? '';
break;
} catch (err) {
if (err instanceof ServiceUnavailableException) throw err;
this.logger.error(`Mistral fetch-fel (försök ${attempt}/${MAX_RETRIES}): ${String(err)}`);
if (attempt < MAX_RETRIES) {
await new Promise((r) => setTimeout(r, attempt * 1500));
continue;
}
throw new ServiceUnavailableException('Kunde inte nå AI-tjänsten');
}
const data = await response.json() as { choices: { message: { content: string } }[] };
raw = data.choices?.[0]?.message?.content ?? '';
} catch (err) {
if (err instanceof ServiceUnavailableException) throw err;
this.logger.error(`Mistral fetch-fel: ${String(err)}`);
throw new ServiceUnavailableException('Kunde inte nå AI-tjänsten');
}
// Parsa och validera AI-svaret