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;
20
- const CONCURRENT_PARSE = 25;
 
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 concurrency limit
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') {