Spaces:
Sleeping
Sleeping
Heaven K commited on
Commit ·
fd7e620
1
Parent(s): f67c466
fix: sequential AI parsing to prevent free-tier rate limit exhaustion
Browse files- CONCURRENT_PARSE: 25 -> 1 (sequential, one email at a time)
- CONCURRENT_FETCH: 20 -> 5 (gentler on Gmail API)
- Added 200ms delay between AI parse calls
- Firing 25 simultaneous Groq/Mistral calls instantly hits RPM limits
and disables all provider slots, causing Max retries exceeded on all emails
packages/server/src/services/scanService.ts
CHANGED
|
@@ -16,8 +16,9 @@ const DISABLE_SCREENSHOTS = process.env.DISABLE_SCREENSHOTS === 'true';
|
|
| 16 |
const logger = pino({ level: 'info', transport: { target: 'pino-pretty', options: { colorize: true } } });
|
| 17 |
|
| 18 |
const BATCH_SIZE = 100;
|
| 19 |
-
const CONCURRENT_FETCH =
|
| 20 |
-
const CONCURRENT_PARSE =
|
|
|
|
| 21 |
|
| 22 |
/** Extract a bare email address from a header like "Name <email@domain>" or "email@domain" */
|
| 23 |
function extractEmailAddress(header: string): string {
|
|
@@ -264,7 +265,7 @@ async function runScan(jobId: string, scanRequest: ScanRequest, userId: string,
|
|
| 264 |
}
|
| 265 |
}
|
| 266 |
|
| 267 |
-
// Phase 3: AI parsing with
|
| 268 |
for (let j = 0; j < emails.length; j += CONCURRENT_PARSE) {
|
| 269 |
const parseChunk = emails.slice(j, j + CONCURRENT_PARSE);
|
| 270 |
const parseResults = await Promise.allSettled(
|
|
@@ -273,6 +274,10 @@ async function runScan(jobId: string, scanRequest: ScanRequest, userId: string,
|
|
| 273 |
return { email, parsed };
|
| 274 |
})
|
| 275 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
for (const result of parseResults) {
|
| 278 |
if (result.status === 'fulfilled') {
|
|
|
|
| 16 |
const logger = pino({ level: 'info', transport: { target: 'pino-pretty', options: { colorize: true } } });
|
| 17 |
|
| 18 |
const BATCH_SIZE = 100;
|
| 19 |
+
const CONCURRENT_FETCH = 5;
|
| 20 |
+
const CONCURRENT_PARSE = 1; // Sequential: free-tier APIs (Groq/Mistral) have strict RPM limits
|
| 21 |
+
const PARSE_DELAY_MS = 200; // ms between AI calls to stay under rate limits
|
| 22 |
|
| 23 |
/** Extract a bare email address from a header like "Name <email@domain>" or "email@domain" */
|
| 24 |
function extractEmailAddress(header: string): string {
|
|
|
|
| 265 |
}
|
| 266 |
}
|
| 267 |
|
| 268 |
+
// Phase 3: AI parsing — sequential with delay to respect free-tier RPM limits
|
| 269 |
for (let j = 0; j < emails.length; j += CONCURRENT_PARSE) {
|
| 270 |
const parseChunk = emails.slice(j, j + CONCURRENT_PARSE);
|
| 271 |
const parseResults = await Promise.allSettled(
|
|
|
|
| 274 |
return { email, parsed };
|
| 275 |
})
|
| 276 |
);
|
| 277 |
+
// Small delay between batches to avoid rate limiting
|
| 278 |
+
if (j + CONCURRENT_PARSE < emails.length) {
|
| 279 |
+
await new Promise(resolve => setTimeout(resolve, PARSE_DELAY_MS));
|
| 280 |
+
}
|
| 281 |
|
| 282 |
for (const result of parseResults) {
|
| 283 |
if (result.status === 'fulfilled') {
|