From e8a91173de6bcf7152d6a888c5dfa1164f5cb0b3 Mon Sep 17 00:00:00 2001 From: "Bob.Hou" Date: Mon, 7 Sep 2026 07:57:27 -0400 Subject: [PATCH] fix(combos): inherit model_context_overrides onto effort-suffixed targets (#12475) (#12926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes. A busca do sufixo mais longo primeiro (`-xhigh` antes de `-high`) é o detalhe que faz a herança funcionar em vez de quase-funcionar. Manter `getResolvedModelContextOverride` fora do escopo, com o teste existente registrando que aquele caminho continua sem herança, deixa a fronteira explícita. --- .../12926-effort-suffix-context-override.md | 1 + .../services/combo/contextOverrideGate.ts | 52 +++++- ...fort-suffix-context-override-12475.test.ts | 168 ++++++++++++++++++ 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/12926-effort-suffix-context-override.md create mode 100644 tests/unit/combo-effort-suffix-context-override-12475.test.ts diff --git a/changelog.d/fixes/12926-effort-suffix-context-override.md b/changelog.d/fixes/12926-effort-suffix-context-override.md new file mode 100644 index 0000000000..d81fb8ad7e --- /dev/null +++ b/changelog.d/fixes/12926-effort-suffix-context-override.md @@ -0,0 +1 @@ +- **fix(combos):** Effort-suffixed combo members inherit the base model's `model_context_overrides` row so priority order is not inverted on large requests ([#12926](https://github.com/diegosouzapw/OmniRoute/pull/12926)) — thanks @HouMinXi diff --git a/open-sse/services/combo/contextOverrideGate.ts b/open-sse/services/combo/contextOverrideGate.ts index 4f03978a45..478fbd99be 100644 --- a/open-sse/services/combo/contextOverrideGate.ts +++ b/open-sse/services/combo/contextOverrideGate.ts @@ -23,6 +23,56 @@ */ import { getResolvedModelContextOverride } from "../../../src/lib/modelCapabilities"; +import { parseModel } from "../model.ts"; + +/** + * Longest-first so `-xhigh` is not eaten by `-high`. Mirrors + * `stripKnownEffortSuffix` in modelCapabilities.ts, but that helper's array + * order still matches `-high` first (`"…-xhigh".endsWith("-high")`). + */ +const EFFORT_SUFFIXES_LONGEST_FIRST = [ + "minimal", + "medium", + "xhigh", + "none", + "high", + "max", + "low", +] as const; + +function stripTrailingEffortSuffix(modelId: string): string | null { + const normalized = String(modelId || "").trim(); + if (!normalized) return null; + const lowered = normalized.toLowerCase(); + for (const suffix of EFFORT_SUFFIXES_LONGEST_FIRST) { + const token = `-${suffix}`; + if (lowered.length > token.length && lowered.endsWith(token)) { + return normalized.slice(0, -token.length); + } + } + return null; +} + +/** + * Exact override first; if missing, inherit the base id after stripping a + * trailing effort tier (#12475). Combo members are stored as + * `provider/GLM-5.3-high` while `model_context_overrides` is keyed on + * `GLM-5.3`. Dispatcher already strips the suffix; the compat filter did not. + */ +function lookupOverrideWithEffortInheritance(modelStr: string): number | null { + const exact = getResolvedModelContextOverride(modelStr); + if (exact != null) return exact; + + const parsed = parseModel(modelStr); + const modelId = typeof parsed.model === "string" ? parsed.model.trim() : ""; + const base = stripTrailingEffortSuffix(modelId); + if (!base || base === modelId) return null; + + if (parsed.provider) { + return getResolvedModelContextOverride({ provider: parsed.provider, model: base }); + } + return getResolvedModelContextOverride(base); +} /** * Resolve the context-fit verdict from a persisted per-model override, if one @@ -35,7 +85,7 @@ function resolveContextOverrideVerdict( requiredContextTokens: number ): boolean | undefined { if (!modelStr) return undefined; - const override = getResolvedModelContextOverride(modelStr); + const override = lookupOverrideWithEffortInheritance(modelStr); if (override == null) return undefined; return override >= requiredContextTokens; } diff --git a/tests/unit/combo-effort-suffix-context-override-12475.test.ts b/tests/unit/combo-effort-suffix-context-override-12475.test.ts new file mode 100644 index 0000000000..5bb3309fbb --- /dev/null +++ b/tests/unit/combo-effort-suffix-context-override-12475.test.ts @@ -0,0 +1,168 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// #12475: effort-suffixed combo targets must inherit the base model's +// model_context_overrides row. Without that, the compat filter falls back to +// the static catalog and inverts priority order (Diego confirmed the +// chokepoint is resolveContextOverrideVerdict). + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-12475-effort-override-")); +const ORIGINAL_DATA_DIR = process.env.DATA_DIR; +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const { setModelContextOverride, removeModelContextOverride } = + await import("../../src/lib/db/modelContextOverrides.ts"); +const { evaluateContextLimit } = + await import("../../open-sse/services/combo/contextOverrideGate.ts"); +const { saveModelsDevCapabilities, clearModelsDevCapabilities } = + await import("../../src/lib/modelsDevSync.ts"); +const { filterTargetsByRequestCompatibility } = + await import("../../open-sse/services/combo.ts"); + +test.after(() => { + core.resetDbInstance(); + if (ORIGINAL_DATA_DIR === undefined) { + delete process.env.DATA_DIR; + } else { + process.env.DATA_DIR = ORIGINAL_DATA_DIR; + } + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +test.beforeEach(() => { + clearModelsDevCapabilities(); +}); + +const LARGE_REQ = { + estimatedInputTokens: 300_000, + requiredContextTokens: 348_000, +}; + +function capabilityEntry(limitContext: number) { + return { + tool_call: true, + reasoning: false, + attachment: false, + structured_output: true, + temperature: true, + modalities_input: JSON.stringify(["text"]), + modalities_output: JSON.stringify(["text"]), + knowledge_cutoff: null, + release_date: null, + last_updated: null, + status: null, + family: null, + open_weights: false, + limit_context: limitContext, + limit_input: limitContext, + limit_output: 4096, + interleaved_field: null, + }; +} + +function target(modelStr: string) { + return { + kind: "model" as const, + stepId: modelStr, + executionKey: modelStr, + modelStr, + provider: modelStr.includes("/") ? modelStr.split("/")[0] : modelStr, + providerId: null, + connectionId: null, + weight: 1, + label: null, + }; +} + +function bigContextBody(tokens: number) { + return { + messages: [{ role: "user", content: "x".repeat(tokens * 4) }], + }; +} + +const noopLog = { info() {}, warn() {}, error() {}, debug() {} }; + +test("#12475 GLM-5.3-high inherits the GLM-5.3 override (catalog 200k would reject)", () => { + setModelContextOverride("unit-12475", "GLM-5.3", 385_351); + try { + const verdict = evaluateContextLimit( + { maxInputTokens: 200_000, contextWindow: 200_000 }, + LARGE_REQ, + "unit-12475/GLM-5.3-high" + ); + assert.equal(verdict, true, "base override 385351 must fit 348k after stripping -high"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.3"); + } +}); + +test("#12475 GLM-5.2-high spec 1M must not shadow the GLM-5.2 override", () => { + setModelContextOverride("unit-12475", "GLM-5.2", 128_450); + try { + const verdict = evaluateContextLimit( + { maxInputTokens: 1_000_000, contextWindow: 1_000_000 }, + LARGE_REQ, + "unit-12475/GLM-5.2-high" + ); + assert.equal(verdict, false, "base override 128450 must reject 348k even when spec says 1M"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.2"); + } +}); + +test("#12475 exact variant override still wins over the base row", () => { + setModelContextOverride("unit-12475", "GLM-5.3", 385_351); + setModelContextOverride("unit-12475", "GLM-5.3-high", 10_000); + try { + const verdict = evaluateContextLimit( + { maxInputTokens: 200_000, contextWindow: 200_000 }, + LARGE_REQ, + "unit-12475/GLM-5.3-high" + ); + assert.equal(verdict, false, "an explicit GLM-5.3-high row must not inherit the larger base"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.3-high"); + removeModelContextOverride("unit-12475", "GLM-5.3"); + } +}); + +test("#12475 xhigh / mixed-case suffix also inherit", () => { + setModelContextOverride("unit-12475", "GLM-5.3-Flash", 899_153); + try { + const verdict = evaluateContextLimit( + { maxInputTokens: 200_000, contextWindow: 200_000 }, + LARGE_REQ, + "unit-12475/GLM-5.3-Flash-xhigh" + ); + assert.equal(verdict, true, "GLM-5.3-Flash-xhigh must inherit GLM-5.3-Flash"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.3-Flash"); + } +}); + +test("#12475 filter prefers the larger-window effort variant, not the spec-inflated one", () => { + saveModelsDevCapabilities({ + "unit-12475": { + "GLM-5.3-high": capabilityEntry(200_000), + "GLM-5.2-high": capabilityEntry(1_000_000), + }, + }); + setModelContextOverride("unit-12475", "GLM-5.3", 1_000_000); + setModelContextOverride("unit-12475", "GLM-5.2", 128_000); + try { + const out = filterTargetsByRequestCompatibility( + [target("unit-12475/GLM-5.2-high"), target("unit-12475/GLM-5.3-high")], + bigContextBody(300_000), + noopLog + ); + assert.equal(out[0]?.modelStr, "unit-12475/GLM-5.3-high"); + assert.equal(out[1]?.modelStr, "unit-12475/GLM-5.2-high"); + } finally { + removeModelContextOverride("unit-12475", "GLM-5.3"); + removeModelContextOverride("unit-12475", "GLM-5.2"); + } +});