mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-08 00:02:20 +03:00
Treat context metadata as a routing hint (#8944)
Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip)
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
* getComboModelsFromData, validateComboDAG, resolveNestedComboModels,
|
||||
* filterTargetsByRequestCompatibility) are re-exported from combo.ts for the
|
||||
* ~20 external consumers (chatCore.ts, the /api/combos routes, embeddings, etc.).
|
||||
* Context-window metadata is advisory: known-fitting targets are ordered first,
|
||||
* while catalog-too-small targets remain available for runtime fallback.
|
||||
* No barrel import — depends only on sibling leaves.
|
||||
*/
|
||||
|
||||
@@ -531,9 +533,7 @@ function hasKnownCompatibleContextLimit(
|
||||
return evaluateContextLimit(capabilities, requirements, target.modelStr) === true;
|
||||
}
|
||||
|
||||
function hasOnlyContextWindowFailures(reasons: string[]): boolean {
|
||||
return reasons.length > 0 && reasons.every((reason) => reason === "context_window");
|
||||
}
|
||||
const HARD_COMPAT_REASONS = new Set(["tools", "vision", "structured_output", "output_tokens"]);
|
||||
|
||||
/**
|
||||
* #8332: vision is a hard requirement, not a soft preference — a target whose vision
|
||||
@@ -694,67 +694,34 @@ export function filterTargetsByRequestCompatibility(
|
||||
if (!needsFiltering) return targets;
|
||||
|
||||
const rejected: Array<{ target: ResolvedComboTarget; reasons: string[] }> = [];
|
||||
const compatible = targets.filter((target) => {
|
||||
const targetReasons = new Map<ResolvedComboTarget, string[]>();
|
||||
for (const target of targets) {
|
||||
const reasons = getTargetCompatibilityFailures(target, requirements);
|
||||
if (reasons.length === 0) return true;
|
||||
rejected.push({ target, reasons });
|
||||
return false;
|
||||
});
|
||||
targetReasons.set(target, reasons);
|
||||
if (reasons.length > 0) rejected.push({ target, reasons });
|
||||
}
|
||||
|
||||
// Unknown context limits are safe only as a fallback. If this request already
|
||||
// filtered at least one known-too-small target and known-good targets remain,
|
||||
// prefer the known-good set over unknown metadata gaps. If no known-good
|
||||
// context target remains, fall back to the strategy order for context-only
|
||||
// candidates instead of letting unknown metadata be the only survivors.
|
||||
const rejectedForContextWindow = rejected.some((entry) =>
|
||||
// Context metadata is advisory. Keep every target that has no hard capability
|
||||
// mismatch, but prefer targets whose known limit fits. A stale catalog entry must
|
||||
// never remove the only target that could accept the request at runtime.
|
||||
const compatible = targets.filter((target) => {
|
||||
const reasons = targetReasons.get(target) || [];
|
||||
return !reasons.some((reason) => HARD_COMPAT_REASONS.has(reason));
|
||||
});
|
||||
const hadKnownTooSmallContextTarget = rejected.some((entry) =>
|
||||
entry.reasons.includes("context_window")
|
||||
);
|
||||
if (requirements.requiredContextTokens > 0 && rejectedForContextWindow) {
|
||||
if (
|
||||
requirements.requiredContextTokens > 0 &&
|
||||
hadKnownTooSmallContextTarget &&
|
||||
compatible.length > 1
|
||||
) {
|
||||
const knownContextCompatible = compatible.filter((target) =>
|
||||
hasKnownCompatibleContextLimit(target, requirements)
|
||||
);
|
||||
|
||||
if (knownContextCompatible.length > 0 && knownContextCompatible.length < compatible.length) {
|
||||
const knownContextCompatibleTargets = new Set(knownContextCompatible);
|
||||
for (const target of compatible) {
|
||||
if (!knownContextCompatibleTargets.has(target)) {
|
||||
rejected.push({ target, reasons: ["context_window_unknown"] });
|
||||
}
|
||||
}
|
||||
|
||||
log.info(
|
||||
"COMBO",
|
||||
`${label}: kept ${knownContextCompatible.length}/${targets.length} targets for request requirements`
|
||||
);
|
||||
log.debug?.(
|
||||
"COMBO",
|
||||
`${label}: rejected targets ${rejected
|
||||
.map((entry) => `${entry.target.modelStr}(${entry.reasons.join("+")})`)
|
||||
.join(", ")}`
|
||||
);
|
||||
return knownContextCompatible;
|
||||
}
|
||||
|
||||
if (knownContextCompatible.length === 0 && compatible.length > 0) {
|
||||
const rejectedByTarget = new Map(rejected.map((entry) => [entry.target, entry.reasons]));
|
||||
const contextOnlyFallback = targets.filter((target) => {
|
||||
const reasons = rejectedByTarget.get(target);
|
||||
return !reasons || hasOnlyContextWindowFailures(reasons);
|
||||
});
|
||||
|
||||
if (contextOnlyFallback.length > compatible.length) {
|
||||
log.warn(
|
||||
"COMBO",
|
||||
`${label}: no known-compatible context target remains; preserving strategy order for context-only candidates`
|
||||
);
|
||||
log.debug?.(
|
||||
"COMBO",
|
||||
`${label}: rejected targets ${rejected
|
||||
.map((entry) => `${entry.target.modelStr}(${entry.reasons.join("+")})`)
|
||||
.join(", ")}`
|
||||
);
|
||||
return contextOnlyFallback;
|
||||
}
|
||||
const knownSet = new Set(knownContextCompatible);
|
||||
return [...knownContextCompatible, ...compatible.filter((target) => !knownSet.has(target))];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
// Regression tests for the context-aware combo compatibility filter.
|
||||
// Unknown context metadata is only safe as a fallback. Once the context filter
|
||||
// has rejected known-too-small targets and a known-capacity target remains,
|
||||
// unknown-context targets must not survive over it.
|
||||
// Context metadata is advisory: known-fitting targets are preferred, while
|
||||
// unknown and catalog-too-small targets remain available for runtime fallback.
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-context-filter-"));
|
||||
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
|
||||
@@ -97,7 +96,7 @@ function bigContextBody(tokens: number) {
|
||||
|
||||
const noopLog = { info() {}, warn() {}, error() {}, debug() {} };
|
||||
|
||||
test("known compatible context target wins over unknown-context targets", () => {
|
||||
test("known compatible context target is preferred while unknown targets remain fallback", () => {
|
||||
saveModelsDevCapabilities({
|
||||
"unit-known-context": {
|
||||
tiny: capabilityEntry(8_000),
|
||||
@@ -118,7 +117,12 @@ test("known compatible context target wins over unknown-context targets", () =>
|
||||
|
||||
assert.deepEqual(
|
||||
out.map((entry) => entry.modelStr),
|
||||
["unit-known-context/million"]
|
||||
[
|
||||
"unit-known-context/million",
|
||||
"unit-unknown-context/mystery-a",
|
||||
"unit-known-context/tiny",
|
||||
"unit-unknown-context/mystery-b",
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user