mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
fix(combo): fall back to full pool when collapsed sole survivor is context-too-small (#12278)
Validado em lote sobre o tip de release/v3.8.51: boardou sem conflito, typecheck:core limpo e 17/17 em tests/unit/combo-context-window-filter.test.ts. Obrigado, @opensource-elearning.
This commit is contained in:
committed by
GitHub
parent
cb38bfa6ee
commit
3740839e2a
@@ -807,16 +807,48 @@ export function filterTargetsByRequestCompatibility(
|
||||
return [];
|
||||
}
|
||||
|
||||
// #12273: a sole survivor whose catalog window is known-too-small is a
|
||||
// guaranteed context_length_exceeded. Restore the remaining pool so combo.ts
|
||||
// can still try larger-context targets. Unknown context (`null`) is advisory
|
||||
// and must not resurrect hard-rejected targets (vision / output / tools).
|
||||
if (
|
||||
compatible.length === 1 &&
|
||||
(targetReasons.get(compatible[0]) || []).includes("context_window")
|
||||
) {
|
||||
// #8332: never restore a confirmed-non-vision target onto an image request.
|
||||
const restored = requirements.requiresVision
|
||||
? targets.filter((target) => !isVisionIncompatibleTarget(target, requirements))
|
||||
: targets;
|
||||
if (restored.length > compatible.length) {
|
||||
log.warn(
|
||||
"COMBO",
|
||||
`${label}: single compatible target ${compatible[0].modelStr} has known context too small for ${requirements.requiredContextTokens} token request; falling back to full pool (#12273)`
|
||||
);
|
||||
return restored;
|
||||
}
|
||||
}
|
||||
|
||||
log.info(
|
||||
"COMBO",
|
||||
`${label}: kept ${compatible.length}/${targets.length} targets for request requirements`
|
||||
);
|
||||
log.debug?.(
|
||||
"COMBO",
|
||||
`${label}: rejected targets ${rejected
|
||||
.map((entry) => `${entry.target.modelStr}(${entry.reasons.join("+")})`)
|
||||
.join(", ")}`
|
||||
);
|
||||
// #12273: When pool collapses significantly, log rejection reasons at info
|
||||
// level so the cause is diagnosable without enabling debug logging.
|
||||
if (compatible.length <= 2 && targets.length > 4) {
|
||||
log.info(
|
||||
"COMBO",
|
||||
`${label}: rejected targets ${rejected
|
||||
.map((entry) => `${entry.target.modelStr}(${entry.reasons.join("+")})`)
|
||||
.join(", ")}`
|
||||
);
|
||||
} else {
|
||||
log.debug?.(
|
||||
"COMBO",
|
||||
`${label}: rejected targets ${rejected
|
||||
.map((entry) => `${entry.target.modelStr}(${entry.reasons.join("+")})`)
|
||||
.join(", ")}`
|
||||
);
|
||||
}
|
||||
return compatible;
|
||||
}
|
||||
|
||||
|
||||
@@ -450,3 +450,95 @@ test("without an override the small-catalog target is ordered last for the large
|
||||
["unit-override/big", "unit-override/capped"]
|
||||
);
|
||||
});
|
||||
|
||||
// #12273: real Claude Code requests always carry `tools`, and the auto/coding
|
||||
// pool mixes coding-capable providers with providers whose catalog marks
|
||||
// toolCalling=false. Those non-coding targets are HARD-rejected (tools), so the
|
||||
// compat filter can collapse the whole pool to a single too-small-context
|
||||
// coding model (e.g. mimo-v2.5-free at 200k) for a much larger request — the
|
||||
// larger-context model was never assembled into the candidate pool. Routing to
|
||||
// that sole survivor is a guaranteed context_length_exceeded, so the filter
|
||||
// must fall back to the full pool instead of silently pinning the request.
|
||||
test("#12273 single known-too-small survivor falls back to the full pool", () => {
|
||||
saveModelsDevCapabilities({
|
||||
"unit-collapse": {
|
||||
small: capabilityEntry(200_000),
|
||||
},
|
||||
"unit-noncoding": {
|
||||
nocoder: { ...capabilityEntry(1_000_000), tool_call: false },
|
||||
},
|
||||
});
|
||||
const body = {
|
||||
...bigContextBody(300_000),
|
||||
tools: [{ type: "function" }], // Claude Code always sends tools
|
||||
};
|
||||
|
||||
const out = filterTargetsByRequestCompatibility(
|
||||
[target("unit-collapse/small"), target("unit-noncoding/nocoder")],
|
||||
body,
|
||||
noopLog
|
||||
);
|
||||
|
||||
// nocoder is hard-rejected (toolCalling=false); small (200k) is the only
|
||||
// compatible survivor but is known to be too small for a 300k request, so the
|
||||
// filter returns the full pool rather than dispatch to a guaranteed failure.
|
||||
assert.deepEqual(
|
||||
out.map((entry) => entry.modelStr),
|
||||
["unit-collapse/small", "unit-noncoding/nocoder"]
|
||||
);
|
||||
});
|
||||
|
||||
// Guard against regression: when the single survivor's window DOES fit the
|
||||
// request, the filter still collapses (existing behavior preserved).
|
||||
test("#12273 single compatible target that fits is still collapsed", () => {
|
||||
saveModelsDevCapabilities({
|
||||
"unit-collapse": {
|
||||
big: capabilityEntry(1_000_000),
|
||||
},
|
||||
"unit-noncoding": {
|
||||
nocoder: { ...capabilityEntry(1_000_000), tool_call: false },
|
||||
},
|
||||
});
|
||||
const body = {
|
||||
...bigContextBody(300_000),
|
||||
tools: [{ type: "function" }],
|
||||
};
|
||||
|
||||
const out = filterTargetsByRequestCompatibility(
|
||||
[target("unit-collapse/big"), target("unit-noncoding/nocoder")],
|
||||
body,
|
||||
noopLog
|
||||
);
|
||||
|
||||
// big (1M) fits the 300k request, so the collapse is legitimate.
|
||||
assert.deepEqual(
|
||||
out.map((entry) => entry.modelStr),
|
||||
["unit-collapse/big"]
|
||||
);
|
||||
});
|
||||
|
||||
// #12278: unknown context is advisory, not "known too small". Collapsing to a
|
||||
// single survivor whose context limit is unknown must NOT restore hard-rejected
|
||||
// targets (output_tokens here; vision is covered by combo-vision-aware-routing).
|
||||
test("#12273 unknown-context sole survivor does not restore hard-rejected targets", () => {
|
||||
saveModelsDevCapabilities({
|
||||
"unit-output": {
|
||||
tiny: capabilityEntryWithLimits(128_000, 128_000, 4096),
|
||||
},
|
||||
});
|
||||
const body = {
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
max_tokens: 32_000,
|
||||
};
|
||||
|
||||
const out = filterTargetsByRequestCompatibility(
|
||||
[target("unit-unknown/mystery"), target("unit-output/tiny")],
|
||||
body,
|
||||
noopLog
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
out.map((entry) => entry.modelStr),
|
||||
["unit-unknown/mystery"]
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user