From 193e7a64174faf389b1c963c4ccbdab278f46463 Mon Sep 17 00:00:00 2001 From: Paijo <14921983+oyi77@users.noreply.github.com> Date: Mon, 18 May 2026 05:54:32 +0700 Subject: [PATCH] feat(routing): LGKP remembers last good account per provider (#2338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrated into release/v3.8.0 — added unit tests for connectionId-aware getLKGP/setLKGP --- open-sse/services/autoCombo/routerStrategy.ts | 5 +-- open-sse/services/combo.ts | 36 +++++++++++++------ src/lib/db/readCache.ts | 17 ++++++--- src/lib/db/settings.ts | 26 +++++++++++--- tests/unit/db-read-cache.test.ts | 6 ++-- tests/unit/db-settings-crud.test.ts | 31 ++++++++++++++-- 6 files changed, 93 insertions(+), 28 deletions(-) diff --git a/open-sse/services/autoCombo/routerStrategy.ts b/open-sse/services/autoCombo/routerStrategy.ts index 1580441ff7..0cdd932168 100644 --- a/open-sse/services/autoCombo/routerStrategy.ts +++ b/open-sse/services/autoCombo/routerStrategy.ts @@ -130,10 +130,11 @@ class LKGPStrategyImpl implements RouterStrategy { } if (context.lastKnownGoodProvider) { - const best = pool.find( + const candidates = pool.filter( (c) => c.provider === context.lastKnownGoodProvider && c.circuitBreakerState !== "OPEN" ); - if (best) { + if (candidates.length > 0) { + const best = candidates[0]; return { provider: best.provider, model: best.model, diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 2ee5a166c1..9f8a661c72 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -1742,7 +1742,7 @@ export async function handleComboChat({ try { const { getLKGP } = await import("../../src/lib/localDb"); const lkgp = await getLKGP(combo.name, combo.id || combo.name); - if (lkgp) lastKnownGoodProvider = lkgp; + if (lkgp) lastKnownGoodProvider = lkgp.provider; } catch (err) { log.warn("COMBO", "Failed to retrieve Last Known Good Provider. This is non-fatal.", { err }); } @@ -1819,22 +1819,34 @@ export async function handleComboChat({ const lkgpProvider = await getLKGP(combo.name, combo.id || combo.name); if (lkgpProvider) { - const lkgpIndex = orderedTargets.findIndex( - (target) => - target.provider === lkgpProvider || target.modelStr.startsWith(`${lkgpProvider}/`) - ); + const lkgpRecord = lkgpProvider; + const providerName = lkgpRecord.provider; + const connId = lkgpRecord.connectionId; + + let lkgpIndex = -1; + if (connId) { + lkgpIndex = orderedTargets.findIndex( + (target) => target.provider === providerName && target.connectionId === connId + ); + } + if (lkgpIndex < 0) { + lkgpIndex = orderedTargets.findIndex( + (target) => + target.provider === providerName || target.modelStr.startsWith(`${providerName}/`) + ); + } if (lkgpIndex > 0) { const [lkgpTarget] = orderedTargets.splice(lkgpIndex, 1); orderedTargets.unshift(lkgpTarget); log.info( "COMBO", - `[LKGP] Prioritizing last known good provider ${lkgpProvider} for combo "${combo.name}"` + `[LKGP] Prioritizing last known good provider ${providerName}${connId ? ` (account ${connId})` : ""} for combo "${combo.name}"` ); } else if (lkgpIndex === 0) { log.debug( "COMBO", - `[LKGP] Last known good provider ${lkgpProvider} already first for combo "${combo.name}"` + `[LKGP] Last known good provider ${providerName}${connId ? ` (account ${connId})` : ""} already first for combo "${combo.name}"` ); } } @@ -2070,12 +2082,13 @@ export async function handleComboChat({ // Record last known good provider (LKGP) for this combo/model (#919) if (provider) { + const connId = target.connectionId || undefined; void (async () => { try { const { setLKGP } = await import("../../src/lib/localDb"); await Promise.all([ - setLKGP(combo.name, target.executionKey, provider), - setLKGP(combo.name, combo.id || combo.name, provider), + setLKGP(combo.name, target.executionKey, provider, connId), + setLKGP(combo.name, combo.id || combo.name, provider, connId), ]); } catch (err) { log.warn("COMBO", "Failed to record Last Known Good Provider. This is non-fatal.", { @@ -2400,12 +2413,13 @@ async function handleRoundRobinCombo({ }); recordedAttempts++; if (provider) { + const connId = target.connectionId || undefined; void (async () => { try { const { setLKGP } = await import("../../src/lib/localDb"); await Promise.all([ - setLKGP(combo.name, target.executionKey, provider), - setLKGP(combo.name, combo.id || combo.name, provider), + setLKGP(combo.name, target.executionKey, provider, connId), + setLKGP(combo.name, combo.id || combo.name, provider, connId), ]); } catch (err) { log.warn( diff --git a/src/lib/db/readCache.ts b/src/lib/db/readCache.ts index 100ea83c41..fd5e7e6076 100644 --- a/src/lib/db/readCache.ts +++ b/src/lib/db/readCache.ts @@ -110,9 +110,17 @@ export async function getCachedProviderConnections( // ──────────────── LKGP Cache Wrappers ──────────────── -const lkgpCache = new TTLCache(SETTINGS_TTL_MS); +interface LKGPRecordCache { + provider: string; + connectionId?: string; +} -export async function getCachedLKGP(comboName: string, modelId: string): Promise { +const lkgpCache = new TTLCache(SETTINGS_TTL_MS); + +export async function getCachedLKGP( + comboName: string, + modelId: string +): Promise { const cacheKey = `lkgp:${comboName}:${modelId}`; const cached = lkgpCache.get(cacheKey); if (cached !== undefined) return cached; @@ -126,10 +134,11 @@ export async function getCachedLKGP(comboName: string, modelId: string): Promise export async function setCachedLKGP( comboName: string, modelId: string, - providerId: string + providerId: string, + connectionId?: string ): Promise { const { setLKGP } = await import("@/lib/db/settings"); - await setLKGP(comboName, modelId, providerId); + await setLKGP(comboName, modelId, providerId, connectionId); lkgpCache.invalidate(`lkgp:${comboName}:${modelId}`); } diff --git a/src/lib/db/settings.ts b/src/lib/db/settings.ts index ddd7475408..d61c840f9f 100644 --- a/src/lib/db/settings.ts +++ b/src/lib/db/settings.ts @@ -405,7 +405,12 @@ export async function resetAllPricing() { // ──────────────── LKGP (Last Known Good Provider) ──────────────── -export async function getLKGP(comboName: string, modelId: string): Promise { +export interface LKGPRecord { + provider: string; + connectionId?: string; +} + +export async function getLKGP(comboName: string, modelId: string): Promise { const db = getDbInstance(); const key = `${comboName}:${modelId}`; const row = db @@ -413,18 +418,29 @@ export async function getLKGP(comboName: string, modelId: string): Promise { await settingsDb.setLKGP("combo-a", "model-a", "openai"); await settingsDb.setLKGP("combo-a", "model-b", "anthropic"); - assert.equal(await settingsDb.getLKGP("combo-a", "model-a"), "openai"); - assert.equal(await settingsDb.getLKGP("combo-a", "model-b"), "anthropic"); + assert.deepEqual(await settingsDb.getLKGP("combo-a", "model-a"), { provider: "openai" }); + assert.deepEqual(await settingsDb.getLKGP("combo-a", "model-b"), { provider: "anthropic" }); settingsDb.clearAllLKGP(); assert.equal(await settingsDb.getLKGP("combo-a", "model-a"), null); }); +test("LKGP stores and retrieves connectionId", async () => { + await settingsDb.setLKGP("combo-c", "model-c", "openai", "conn-abc123"); + + const record = await settingsDb.getLKGP("combo-c", "model-c"); + assert.deepEqual(record, { provider: "openai", connectionId: "conn-abc123" }); +}); + +test("LKGP without connectionId omits the field", async () => { + await settingsDb.setLKGP("combo-d", "model-d", "anthropic"); + + const record = await settingsDb.getLKGP("combo-d", "model-d"); + assert.deepEqual(record, { provider: "anthropic" }); + assert.equal("connectionId" in (record as object), false); +}); + +test("LKGP overwrites connectionId when updated without one", async () => { + await settingsDb.setLKGP("combo-e", "model-e", "openai", "conn-old"); + await settingsDb.setLKGP("combo-e", "model-e", "openai"); + + const record = await settingsDb.getLKGP("combo-e", "model-e"); + assert.deepEqual(record, { provider: "openai" }); +}); + test("pricing helpers ignore malformed synced data and LKGP falls back to raw values", async () => { const db = core.getDbInstance(); @@ -234,7 +257,9 @@ test("pricing helpers ignore malformed synced data and LKGP falls back to raw va assert.equal(pricing["broken-provider"], undefined); assert.equal(await settingsDb.getPricingForModel("alias-provider", "missing-model"), null); - assert.equal(await settingsDb.getLKGP("combo-raw", "model-raw"), "raw-provider-id"); + assert.deepEqual(await settingsDb.getLKGP("combo-raw", "model-raw"), { + provider: "raw-provider-id", + }); }); test("pricing helpers resolve aliased providers and tolerate no-op resets", async () => {