fix(combo): rotate unweighted quota-share targets instead of pinning the first

The combo resolver turns an unset step weight into 0 (comboStructure.ts), and
#10881 made normalizeWeight treat 0 as disabled plus return definition order when
the total weight is 0. A quota-share combo without explicit weights therefore had
no DRR quanta and dispatched every request to its first target — the
combo-matrix/quota-share integration suite saw openai six times out of six.

An all-zero set is now an unweighted combo and shares evenly; an explicit 0 still
disables a target when its siblings are weighted.

Refs #12732
This commit is contained in:
diegosouzapw
2026-09-14 19:04:40 -03:00
parent 895dda383e
commit 66229feaaa
3 changed files with 52 additions and 4 deletions

View File

@@ -0,0 +1 @@
- **fix(combo):** a `quota-share` combo whose steps carry no weight now rotates across its targets again instead of sending every request to the first one — the resolver turns an unset weight into 0 and #10881 made 0 mean "disabled", so an all-unweighted combo had no quanta and fell back to definition order; an explicit 0 still disables a target next to weighted siblings ([#12732](https://github.com/diegosouzapw/OmniRoute/issues/12732))

View File

@@ -180,12 +180,17 @@ function applyDrr(targets: ResolvedComboTarget[], comboName: string): ResolvedCo
if (targets.length <= 1) return targets.slice();
const deficits = getDrrDeficits(comboName);
const totalWeight = targets.reduce((sum, t) => sum + normalizeWeight(t.weight), 0);
if (totalWeight <= 0) return targets.slice();
const weights = targets.map((t) => normalizeWeight(t.weight));
const weightedTotal = weights.reduce((sum, w) => sum + w, 0);
// A 0 disables a target only relative to weighted siblings. The combo resolver turns an
// unset step weight into 0, so an all-zero set is an unweighted combo, not an all-disabled
// one: share evenly instead of returning definition order, which pinned the first target.
const unweighted = weightedTotal <= 0;
const totalWeight = unweighted ? targets.length : weightedTotal;
// Add each target's quantum (weight share) to its deficit.
for (const target of targets) {
const quantum = normalizeWeight(target.weight) / totalWeight;
for (const [index, target] of targets.entries()) {
const quantum = (unweighted ? 1 : weights[index]) / totalWeight;
deficits.set(target.executionKey, (deficits.get(target.executionKey) ?? 0) + quantum);
}

View File

@@ -282,6 +282,48 @@ describe("DRR: deficit round robin", () => {
);
});
test("unweighted steps (weight 0 from the resolver) still alternate instead of pinning the first", () => {
// comboStructure resolves a step with no weight to 0, and #10881 made 0 mean "disabled".
// With every target at 0 the total weight is 0 and DRR returned definition order, so a
// quota-share combo without explicit weights sent every request to its first target.
const t1 = makeTarget("ek-unweighted-1", "conn-unweighted-1", 0);
const t2 = makeTarget("ek-unweighted-2", "conn-unweighted-2", 0);
const selected: Array<string | undefined> = [];
for (let i = 0; i < 4; i++) {
const r = selectQuotaShareTarget(
[t1, t2],
"combo-unweighted",
"anthropic/claude-sonnet-4-5",
NOW
);
selected.push(r.target?.executionKey);
r.decrementInflight();
}
assert.deepEqual(selected, [
"ek-unweighted-1",
"ek-unweighted-2",
"ek-unweighted-1",
"ek-unweighted-2",
]);
});
test("an explicit weight 0 still disables that target while another target is weighted", () => {
const weighted = makeTarget("ek-on", "conn-on", 100);
const disabled = makeTarget("ek-off", "conn-off", 0);
for (let i = 0; i < 4; i++) {
const r = selectQuotaShareTarget(
[disabled, weighted],
"combo-disabled",
"anthropic/claude-sonnet-4-5",
NOW
);
assert.equal(r.target?.executionKey, "ek-on");
r.decrementInflight();
}
});
test("DRR state is isolated per comboName", () => {
const t = makeTarget("ek-shared", "conn-shared", 100);
selectQuotaShareTarget([t], "combo-A", "anthropic/claude-sonnet-4-5", NOW);