feat(ab-corpus): exponential backoff + bump retries=2 on ark/deepseek
ab-v3 left 36 ark empty + 15 timeout + 4 429 + 6 deepseek empty + 4 minimax timeout AFTER the existing retries=1 fired 88 times. Linear backoff 250ms*(attempt+1) was too tight when stepping up to retries=2 (500ms then 750ms isn't a typical Ark recovery window). Switches to exponential 250ms*4^attempt — 250 / 1000 / 4000ms spacing — and bumps ark + deepseek to retries=2. minimax opts in to retries=1 (its 4 errors were wall-clock timeouts, not the model's <think> truncation that retry can't fix anyway). 10 existing retry tests still pass; the retries=2 case now sleeps 1.25s instead of 0.75s, still well under vitest's default timeout.
This commit is contained in:
parent
c2252f2172
commit
7d419cffd1
|
|
@ -47,9 +47,11 @@ export async function callArk(args: CallArkArgs): Promise<ChatCallResult> {
|
|||
// failure was Ark returning empty `choices[0].message.content` or
|
||||
// exceeding the 120s wall clock, not a model-quality issue (the
|
||||
// model itself routed to the right element tool 80% of the time
|
||||
// when it did respond). One retry is the minimum that flips most
|
||||
// of those into successes; bumping higher would mostly waste
|
||||
// budget on the genuinely broken minority.
|
||||
retries: 1,
|
||||
// when it did respond). retries=1 left 36 empty + 15 timeout + 4
|
||||
// 429 still leaking through ab-v3 (520 calls); retries=2 with
|
||||
// exponential backoff gives the provider a 1000ms recovery window
|
||||
// before the third attempt, which on Ark's coding tier is usually
|
||||
// enough to clear the upstream brownout.
|
||||
retries: 2,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,9 +40,9 @@ export async function callDeepSeek(args: CallDeepSeekArgs): Promise<ChatCallResu
|
|||
maxTokens: args.maxTokens,
|
||||
label: 'deepseek',
|
||||
// ab-v2 garbage attribution: 6 of 40 deepseek-v4-pro runs returned
|
||||
// empty content (server-side flakiness, not model output). Same
|
||||
// rationale as ark.ts — one retry recovers the transient ones,
|
||||
// anything more would mostly burn budget on real failures.
|
||||
retries: 1,
|
||||
// empty content (server-side flakiness, not model output). retries=1
|
||||
// recovered most but ab-v3 still leaked 6 empty-content errors.
|
||||
// Bumped to 2 alongside ark to share the exponential backoff window.
|
||||
retries: 2,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,5 +35,10 @@ export async function callMinimax(args: CallMinimaxArgs): Promise<ChatCallResult
|
|||
temperature: args.temperature,
|
||||
maxTokens: args.maxTokens,
|
||||
label: 'minimax',
|
||||
// ab-v3 saw 4 minimax timeouts on 104 runs — all wall-clock aborts,
|
||||
// none were content-quality issues. One retry picks up the
|
||||
// recoverable ones without burning budget on the truncation /
|
||||
// <think> chatter that characterizes minimax model-side failures.
|
||||
retries: 1,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,16 +70,20 @@ export interface CallOpenAICompatArgs {
|
|||
*/
|
||||
timeoutMs?: number;
|
||||
/**
|
||||
* Retry attempts on transient errors. Default 0 (no retry). Set to 1
|
||||
* Retry attempts on transient errors. Default 0 (no retry). Set to 2
|
||||
* for providers known to flake on empty content / timeout (Ark hosting
|
||||
* GLM-5.1+Kimi-K2.6, DeepSeek api.deepseek.com). MiniMax / Bailian /
|
||||
* Codex don't enable this — their ab-v2 failures were model-quality
|
||||
* (truncation, malformed DSL), where retry burns budget for nothing.
|
||||
* GLM-5.1+Kimi-K2.6, DeepSeek api.deepseek.com). 1 is enough for
|
||||
* MiniMax (saw 4 timeouts in 104 ab-v3 runs); Codex / Bailian don't
|
||||
* enable this — their ab-v2 failures were model-quality (truncation,
|
||||
* malformed DSL), where retry burns budget for nothing.
|
||||
*
|
||||
* Retried: empty `choices[0].message.content`, abort/timeout, HTTP 5xx,
|
||||
* HTTP 429. NOT retried: HTTP 4xx other than 429 (auth / bad request).
|
||||
*
|
||||
* Backoff: linear, 250ms × (attempt+1).
|
||||
* Backoff: exponential, 250ms × 4^attempt — 250ms before retry 1,
|
||||
* 1000ms before retry 2, 4000ms before retry 3. Linear backoff was
|
||||
* too tight when retries=2 (500ms then 750ms is < a typical Ark
|
||||
* recovery window); exponential gives the provider room to settle.
|
||||
*/
|
||||
retries?: number;
|
||||
}
|
||||
|
|
@ -105,13 +109,14 @@ export async function callOpenAICompat(args: CallOpenAICompatArgs): Promise<Chat
|
|||
if (attempt === retries || !isTransientError(lastErr)) {
|
||||
throw lastErr;
|
||||
}
|
||||
const delayMs = 250 * 4 ** attempt;
|
||||
console.error(
|
||||
`[${label}] transient error on attempt ${attempt + 1}/${retries + 1}, retrying: ${truncate(
|
||||
`[${label}] transient error on attempt ${attempt + 1}/${retries + 1}, retrying in ${delayMs}ms: ${truncate(
|
||||
lastErr.message,
|
||||
200,
|
||||
)}`,
|
||||
);
|
||||
await sleep(250 * (attempt + 1));
|
||||
await sleep(delayMs);
|
||||
}
|
||||
}
|
||||
throw lastErr ?? new Error(`${label}: callOpenAICompat exited loop without result`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue