mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 00:52:18 +03:00
fix(router): preserve selected connection identity (#11530)
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.
This commit is contained in:
1
changelog.d/fixes/oss026-auto-router-connection-id.md
Normal file
1
changelog.d/fixes/oss026-auto-router-connection-id.md
Normal file
@@ -0,0 +1 @@
|
||||
- Fixed auto-router strategies preserving the selected connection through ranking and dispatch when multiple connections share the same provider and model.
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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, {
|
||||
|
||||
Reference in New Issue
Block a user