From e17ea13745ba8757321c3ed16cf3e9b2c2ef5c6c Mon Sep 17 00:00:00 2001 From: Matt Gunnin Date: Thu, 14 May 2026 08:46:33 -0500 Subject: [PATCH] fix(ai/embed): recognize OpenAI 'maximum request size' error in isTokenLimitError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenAI's /v1/embeddings endpoint hard-caps a single request at 300k tokens total across all input items. When the cap is exceeded it returns: Invalid 'input': maximum request size is 300000 tokens per request. None of the three existing regexes in isTokenLimitError matched this phrasing, so the recursive-halving safety net in embedSubBatch never engaged for OpenAI. The same fat page (a token-dense markdown export, e.g. a Discord transcript) would re-fail every pass, blocking forward progress on the whole batch indefinitely. Locally reproduced on a 31,129-chunk Postgres brain: 2,125 chunks stuck at 'remaining' across 30+ embed --stale passes with retry loops + sleep delays. Adding the two new patterns lets halving fire; the same backlog cleared in one pass after the regex change (the companion max_batch_tokens recipe fix from PR #924 caps fresh batches, but existing oversize pages still need halving to recover). Adds: - /maximum request size.*tokens/i — OpenAI verbatim - /max.*tokens.*per.*request/i — defensive against minor rewording Tests: - Regression test for the exact OpenAI error string - Coverage for the generic 'max tokens per request' variant - All 25 tests in adaptive-embed-batch.test.ts pass No behavior change for providers whose errors already matched. (cherry picked from commit b834e84c56e939e687659f6228b4d60fc4c7dce3) --- src/core/ai/gateway.ts | 5 ++++- test/ai/adaptive-embed-batch.test.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/core/ai/gateway.ts b/src/core/ai/gateway.ts index 05ac7a7b3..a4da36d7f 100644 --- a/src/core/ai/gateway.ts +++ b/src/core/ai/gateway.ts @@ -1259,7 +1259,10 @@ export function isTokenLimitError(err: unknown): boolean { return ( /max.*allowed.*tokens.*batch/i.test(msg) || /batch.*too.*many.*tokens/i.test(msg) || - /token.*limit.*exceeded/i.test(msg) + /token.*limit.*exceeded/i.test(msg) || + // OpenAI embeddings: "Invalid 'input': maximum request size is 300000 tokens per request." + /maximum request size.*tokens/i.test(msg) || + /max.*tokens.*per.*request/i.test(msg) ); } diff --git a/test/ai/adaptive-embed-batch.test.ts b/test/ai/adaptive-embed-batch.test.ts index 8ad53df0d..33050d4d4 100644 --- a/test/ai/adaptive-embed-batch.test.ts +++ b/test/ai/adaptive-embed-batch.test.ts @@ -155,6 +155,21 @@ describe('isTokenLimitError (pure helper)', () => { expect(isTokenLimitError(new Error('Batch contains too many tokens'))).toBe(true); }); + test('matches OpenAI embeddings "maximum request size" error (regression: PR ###)', () => { + // Real error string returned by OpenAI's /v1/embeddings endpoint when the + // sum of all input items exceeds 300k tokens. Without this match, gbrain's + // recursive-halving safety net never engages on OpenAI and the queue stalls + // forever on token-dense pages. + const openaiErr = new Error( + "Invalid 'input': maximum request size is 300000 tokens per request.", + ); + expect(isTokenLimitError(openaiErr)).toBe(true); + }); + + test('matches generic "max tokens per request" phrasing', () => { + expect(isTokenLimitError(new Error('Exceeded 300000 max tokens per request'))).toBe(true); + }); + test('does not match unrelated errors', () => { expect(isTokenLimitError(new Error('Connection refused'))).toBe(false); expect(isTokenLimitError(new Error('Invalid API key'))).toBe(false);