fix(combo): avoid false ALL_ACCOUNTS_INACTIVE on quality failures (#1710)

Integrated into release/v3.7.3 — quality validation regression tests (production fix was already applied)
This commit is contained in:
Slavic Kozyuk
2026-04-28 06:30:49 -05:00
committed by GitHub
parent 42952ee42f
commit aa535748ef
2 changed files with 56 additions and 0 deletions

View File

@@ -1512,6 +1512,7 @@ export async function handleComboChat({
if (result.ok) {
const quality = await validateResponseQuality(result, clientRequestedStream, log);
if (!quality.valid) {
const qualityFailureReason = `Upstream response failed quality validation: ${quality.reason}`;
log.warn(
"COMBO",
`Model ${modelStr} returned 200 but failed quality check: ${quality.reason}`
@@ -1871,6 +1872,7 @@ async function handleRoundRobinCombo({
if (result.ok) {
const quality = await validateResponseQuality(result, clientRequestedStream, log);
if (!quality.valid) {
const qualityFailureReason = `Upstream response failed quality validation: ${quality.reason}`;
log.warn(
"COMBO-RR",
`${modelStr} returned 200 but failed quality check: ${quality.reason}`

View File

@@ -1839,6 +1839,60 @@ test("handleComboChat round-robin recovers from 400s when a later model succeeds
assert.deepEqual(calls, ["model-a", "model-b"]);
});
test("handleComboChat single-target quality failure returns explicit quality error instead of ALL_ACCOUNTS_INACTIVE", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "single-target-quality-failure",
strategy: "priority",
models: ["openai/model-a"],
config: { maxRetries: 0 },
},
handleSingleModel: async () =>
new Response('{"choices":[{"message":{"content":"unterminated"}}', {
status: 200,
headers: { "content-type": "application/json" },
}),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 502);
assert.match(payload.error.message, /quality validation/i);
assert.notEqual(payload.error.code, "ALL_ACCOUNTS_INACTIVE");
});
test("handleComboChat round-robin single-target quality failure returns explicit quality error instead of ALL_ACCOUNTS_INACTIVE", async () => {
const result = await handleComboChat({
body: {},
combo: {
name: "rr-single-target-quality-failure",
strategy: "round-robin",
models: ["openai/model-a"],
config: { maxRetries: 0, retryDelayMs: 1, concurrencyPerModel: 1, queueTimeoutMs: 5 },
},
handleSingleModel: async () =>
new Response('{"choices":[{"message":{"content":"unterminated"}}', {
status: 200,
headers: { "content-type": "application/json" },
}),
isModelAvailable: async () => true,
log: createLog(),
settings: null,
relayOptions: null as any,
allCombos: null,
});
const payload = (await result.json()) as any;
assert.equal(result.status, 502);
assert.match(payload.error.message, /quality validation/i);
assert.notEqual(payload.error.code, "ALL_ACCOUNTS_INACTIVE");
});
test("handleComboChat falls back to next model when first model returns all-accounts-rate-limited 503", async () => {
const calls: any[] = [];