From 3804ffb6ff7b29a6af09feb232f7a54826dc1aba Mon Sep 17 00:00:00 2001 From: Paco Cartones <253313177+pacocartones@users.noreply.github.com> Date: Tue, 25 Aug 2026 18:12:15 +0200 Subject: [PATCH] fix(router): preserve selected connection identity (#11530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated in a combined 10-PR batch worktree off release/v3.8.51 tip. - Focused test: tests/unit/router-strategies.test.ts — 22/22 pass - typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK - Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff ⚠️ base-red inherited: #11449 Thanks for carrying the winning connectionId through cost/latency/SLA/LKGP so duplicate accounts dispatch through the connection actually ranked. --- .../fixes/oss026-auto-router-connection-id.md | 1 + open-sse/services/autoCombo/routerStrategy.ts | 8 ++- open-sse/services/autoCombo/speedRanking.ts | 2 + tests/unit/router-strategies.test.ts | 68 +++++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/oss026-auto-router-connection-id.md diff --git a/changelog.d/fixes/oss026-auto-router-connection-id.md b/changelog.d/fixes/oss026-auto-router-connection-id.md new file mode 100644 index 0000000000..41820ef077 --- /dev/null +++ b/changelog.d/fixes/oss026-auto-router-connection-id.md @@ -0,0 +1 @@ +- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model. diff --git a/open-sse/services/autoCombo/routerStrategy.ts b/open-sse/services/autoCombo/routerStrategy.ts index 9bf207a28a..2bc0c59edb 100644 --- a/open-sse/services/autoCombo/routerStrategy.ts +++ b/open-sse/services/autoCombo/routerStrategy.ts @@ -115,7 +115,7 @@ class CostStrategyImpl implements RouterStrategy { readonly name = "cost"; readonly description = "Always selects cheapest available provider (by costPer1MTokens)"; - select(pool: ProviderCandidate[], context: RoutingContext): RoutingDecision { + select(pool: ProviderCandidate[], _context: RoutingContext): RoutingDecision { const healthy = pool.filter((c) => c.circuitBreakerState !== "OPEN"); const candidates = healthy.length > 0 ? healthy : pool; const sorted = [...candidates].sort((a, b) => a.costPer1MTokens - b.costPer1MTokens); @@ -128,6 +128,7 @@ class CostStrategyImpl implements RouterStrategy { reason: `CostStrategy: cheapest at $${best.costPer1MTokens.toFixed(3)}/1M tokens`, candidatesConsidered: candidates.length, finalScore: best.costPer1MTokens === 0 ? 1.0 : 1 / best.costPer1MTokens, + connectionId: best.connectionId, }; } } @@ -139,7 +140,7 @@ class LatencyStrategyImpl implements RouterStrategy { readonly description = "Prioritizes the fastest reliable provider-model pair using TTFT, TPS, E2E latency, health, fail rate, and stability"; - select(pool: ProviderCandidate[], context: RoutingContext): RoutingDecision { + select(pool: ProviderCandidate[], _context: RoutingContext): RoutingDecision { const ranked = rankBySpeed(pool.map(toSpeedCandidate)); const winner = ranked[0]; if (!winner) { @@ -153,6 +154,7 @@ class LatencyStrategyImpl implements RouterStrategy { reason: latencyDecisionReason(winner), candidatesConsidered: ranked.length, finalScore: winner.score, + connectionId: winner.connectionId, }; } } @@ -292,6 +294,7 @@ class SLAStrategyImpl implements RouterStrategy { reason: `SLAStrategy: p95=${best.candidate.p95LatencyMs}ms/${policy.targetP95Ms}ms, errorRate=${(best.candidate.errorRate * 100).toFixed(2)}%/${(policy.maxErrorRate * 100).toFixed(2)}%, cost=$${best.candidate.costPer1MTokens.toFixed(3)}/1M${fallbackNote}`, candidatesConsidered: candidates.length, finalScore: best.score, + connectionId: best.candidate.connectionId, }; } } @@ -320,6 +323,7 @@ class LKGPStrategyImpl implements RouterStrategy { reason: `LKGP: using last known good provider ${best.provider}`, candidatesConsidered: 1, finalScore: 1.0, + connectionId: best.connectionId, }; } } diff --git a/open-sse/services/autoCombo/speedRanking.ts b/open-sse/services/autoCombo/speedRanking.ts index a3d7d81027..6b3ae58f1f 100644 --- a/open-sse/services/autoCombo/speedRanking.ts +++ b/open-sse/services/autoCombo/speedRanking.ts @@ -60,6 +60,7 @@ export interface SpeedFactors { export interface SpeedRankedCandidate { provider: string; model: string; + connectionId?: string; /** Final composite score in [0..1]; higher is faster+more-reliable. */ score: number; factors: SpeedFactors; @@ -309,6 +310,7 @@ export function rankBySpeed( return { provider: candidate.provider, model: candidate.model, + connectionId: candidate.connectionId, score, factors, metrics, diff --git a/tests/unit/router-strategies.test.ts b/tests/unit/router-strategies.test.ts index 36ec2439b8..2a162c35ed 100644 --- a/tests/unit/router-strategies.test.ts +++ b/tests/unit/router-strategies.test.ts @@ -45,6 +45,17 @@ test("cost — selects the cheapest healthy candidate", () => { assert.equal(d.strategy, "cost"); }); +test("cost — preserves the selected connection when provider and model are shared", () => { + const pool = [ + cand({ provider: "shared", model: "shared/m", connectionId: "conn-a", costPer1MTokens: 5 }), + cand({ provider: "shared", model: "shared/m", connectionId: "conn-b", costPer1MTokens: 1 }), + ]; + + const decision = getStrategy("cost").select(pool, ctx); + + assert.equal(decision.connectionId, "conn-b"); +}); + test("cost — excludes OPEN-breaker candidates even if cheaper", () => { const pool = [ cand({ provider: "cheap-open", costPer1MTokens: 0.1, circuitBreakerState: "OPEN" }), @@ -107,6 +118,31 @@ test("latency — uses TTFT, TPS, and E2E metrics to pick the fastest provider-m assert.match(decision.reason, /tps=120/); }); +test("latency — preserves the selected connection through speed ranking", () => { + const pool = [ + cand({ + provider: "shared", + model: "shared/m", + connectionId: "conn-a", + p95LatencyMs: 900, + avgTtftMs: 400, + avgE2ELatencyMs: 1_200, + }), + cand({ + provider: "shared", + model: "shared/m", + connectionId: "conn-b", + p95LatencyMs: 100, + avgTtftMs: 30, + avgE2ELatencyMs: 300, + }), + ]; + + const decision = getStrategy("latency").select(pool, ctx); + + assert.equal(decision.connectionId, "conn-b"); +}); + test("latency — failure rate can outweigh excellent raw speed", () => { const pool = [ cand({ @@ -148,6 +184,25 @@ test("sla-aware — prefers a candidate meeting the p95/error SLOs", () => { assert.equal(d.strategy, "sla-aware"); }); +test("sla-aware — preserves the selected connection for dispatch", () => { + const pool = [ + cand({ + provider: "shared", + model: "shared/m", + connectionId: "conn-a", + p95LatencyMs: 5_000, + }), + cand({ provider: "shared", model: "shared/m", connectionId: "conn-b", p95LatencyMs: 300 }), + ]; + + const decision = getStrategy("sla-aware").select(pool, { + taskType: "default", + sla: { targetP95Ms: 2_000 }, + }); + + assert.equal(decision.connectionId, "conn-b"); +}); + test("sla-aware — 'sla' alias resolves to sla-aware", () => { assert.equal(getStrategy("sla").name, "sla-aware"); }); @@ -175,6 +230,19 @@ test("lkgp — returns the last known good provider when healthy", () => { assert.equal(d.strategy, "lkgp"); }); +test("lkgp — preserves the connection of the selected last-known-good candidate", () => { + const pool = [ + cand({ provider: "shared", connectionId: "conn-b" }), + cand({ provider: "other", connectionId: "conn-a" }), + ]; + const decision = getStrategy("lkgp").select(pool, { + taskType: "default", + lastKnownGoodProvider: "shared", + }); + + assert.equal(decision.connectionId, "conn-b"); +}); + test("lkgp — falls back to rules when the LKGP is OPEN", () => { const pool = [cand({ provider: "x" }), cand({ provider: "y", circuitBreakerState: "OPEN" })]; const d = getStrategy("lkgp").select(pool, {