fix(compression): add adaptive-ladder rankings for non-default catalog engines (#6533) (#6615)

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-07 22:39:14 -03:00
committed by GitHub
parent 533016af36
commit 908e3bef38
3 changed files with 86 additions and 9 deletions

View File

@@ -10,6 +10,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
### 🐛 Bug Fixes
- **fix(compression):** adaptive-compression ladder ranked 6 real catalog engines (`ccr`, `ionizer`, `relevance`, `llmlingua`, `llm`, `read-lifecycle`) as if they didn't exist ([#6533](https://github.com/diegosouzapw/OmniRoute/issues/6533)) — `ladder.ts`'s `AGGRESSIVENESS` and `REDUCTION_FACTOR` maps only covered the 7 engines wired into `DEFAULT_LADDER` (session-dedup/rtk/headroom/lite/caveman/aggressive/ultra); every other engine registered in `open-sse/services/compression/engines/index.ts` — including `ccr`/`llmlingua`, which the ladder doc comment already says are intentionally addable via `ladderOverride` — fell through to `aggressivenessOf()`'s `?? 0` default (same rank as `"off"`) and `expectedReductionFactor()`'s generic `?? 0.9` fallback, so `floor`-mode escalation could not rank or escalate past them once added to a custom ladder. Both maps now carry entries for all 6 missing real engines, rescaled ×10 (`off:0``ultra:70`) and placed by each engine's documented `stackPriority` (`ionizer` between `rtk`/`headroom`, `relevance` before `caveman`, `llmlingua`/`llm` between `aggressive` and `ultra`, etc.); `mcpAccessibility` — named in the report — is not a registered `CompressionEngine` (it's a separate MCP tool-response truncation mechanism) and was correctly left out. Regression guard: `tests/unit/ladder-engine-maps-6533.test.ts` (asserts every id from `listCompressionEngines()` ranks above `"off"` with a non-default reduction factor). (thanks @chirag127)
- **fix(api):** tool-call arguments could render as `[object Object]` sequences instead of the real JSON through the `/anthropic` (Anthropic-shape `/messages`) routing path ([#6459](https://github.com/diegosouzapw/OmniRoute/issues/6459)) — `appendToolCallArgumentDelta()` (`open-sse/utils/toolCallArguments.ts`), the shared accumulator the streaming `openai-to-claude` response translator, `openai-responses` translator, and `responsesTransformer` all call to build up a tool call's `arguments`/`input_json_delta` buffer, treated any non-string `incoming` fragment as an empty string. Some upstreams deliver the full `tool_calls[].function.arguments` value as an already-parsed JSON object/array instead of the OpenAI-contracted JSON-encoded string; the old code silently discarded that fragment, leaving `tool_use.input` empty, and left downstream buffers open to a plain string coercion of the object (`[object Object]`) once client-side concatenation kicked in. `appendToolCallArgumentDelta()` now `JSON.stringify()`s a non-string, non-null object/array fragment into a valid JSON fragment instead of dropping it, so the assembled `partial_json` always parses back into the original structured value. Regression guard: `tests/unit/anthropic-toolcall-args-6459.test.ts`. (thanks @chirag127)
- **fix(providers):** `fusion` combo returned the opaque `"All fusion panel models failed"` 503 even when only a minority of panel members were actually cooling down / rate-limited, and a user-supplied `fusionTuning.minPanel=1` was silently overridden ([#6454](https://github.com/diegosouzapw/OmniRoute/issues/6454)) — `handleFusionChat()` hard-clamped the quorum floor via `Math.min(Math.max(2, cfg.minPanel), panel.length)`, so an operator-configured `minPanel=1` never took effect: `collectPanel()`'s straggler-grace timer only starts once `ok >= minPanel`, and with the floor forced to 2 a single fast success plus N slow-failing stragglers never reached quorum, so the panel sat waiting instead of degrading to the survivor. Per-member failure reasons (`straggler_dropped`/`timeout`/`threw`/`status_XXX`/`empty_content`/`unparseable`) were also logged server-side but never surfaced in the 503 body, leaving operators unable to tell a rate-limit fan-fail from a broader outage. Fixed by honoring `Math.max(1, cfg.minPanel)` and threading a `failures: Array<{ model, reason }>` collector into the 503 message (`model=reason` per entry) — production fix already merged via #6521; this entry backfills the missing CHANGELOG bullet and adds an 11-member, `fusion-free`-scale regression test matching the original repro shape (a cooling minority must not sink a healthy majority; a genuinely all-failed panel still returns the documented 503). Regression guard: `tests/unit/services/fusion-min-panel-and-failure-detail.test.ts` + `tests/unit/fusion-partial-panel-failure-6454.test.ts`. (thanks @chirag127)
- **fix(providers):** `fusion` combo strategy silently returned a panel member's raw answer instead of the configured `config.judgeModel` synthesis ([#6455](https://github.com/diegosouzapw/OmniRoute/issues/6455)) — `handleFusionChat()`'s single-survivor "degrade gracefully" path (added for #6454) returned the lone panel answer directly whenever only one panelist succeeded, regardless of whether an explicit `judgeModel` was configured; with the default `minPanel: 2` and a 2-model panel, any single flaky/rate-limited panelist forced this path on every request, so the configured judge (e.g. `auto/claude-opus`) was never invoked and the client-visible `.model` reflected whichever panelist happened to survive. The judge is now still invoked to synthesize a lone surviving answer whenever `judgeModel` is explicitly configured; the cheap direct-answer shortcut is kept only for the implicit case (no `judgeModel` set, where the "judge" is just `panel[0]`). Regression guard: `tests/unit/fusion-judge-model-6455.test.ts` + updated `tests/unit/combo-fusion-strategy.test.ts`. (thanks @chirag127)

View File

@@ -20,18 +20,34 @@ export const DEFAULT_LADDER: LadderStage[] = [
* Aggressiveness rank used to know where a base plan sits so `floor` mode escalates
* BEYOND it (design §4.2). Keyed by engine id AND by the equivalent CompressionMode name
* ("standard" === caveman) so a base plan's `mode` string maps cleanly.
*
* Rescaled ×10 vs the original 7-entry scale (#6533) to make room for the novel catalog
* engines that ship in `open-sse/services/compression/engines/index.ts` but are not part
* of DEFAULT_LADDER: `ccr` and `llmlingua` are intentionally excluded from the AUTOMATIC
* ladder (see DEFAULT_LADDER doc comment) yet must still rank correctly when an operator
* adds them via `ladderOverride` — same for `ionizer`, `relevance`, `llm`, and
* `read-lifecycle`. Placement follows each engine's documented `stackPriority` in
* `engineCatalog.ts` / its own module header, interpolated onto the existing 7-tier scale
* (the `lite` exception — ranked after `headroom` despite a lower stackPriority — is a
* pre-existing, deliberate design call and is left untouched).
*/
const AGGRESSIVENESS: Record<string, number> = {
off: 0,
"session-dedup": 1,
rtk: 2,
headroom: 3,
lite: 4,
caveman: 5,
standard: 5, // mode-name alias for caveman
stacked: 5, // a derived/stacked base plan sits at the prose tier; floor escalates past it
aggressive: 6,
ultra: 7,
"session-dedup": 10, // stackPriority 3 — lossless cross-turn dedup
ccr: 15, // stackPriority 4 — reversible retrieval marker, only if it shrinks
rtk: 20, // stackPriority 10 — command-output filtering
ionizer: 25, // stackPriority 13 — tabular row sampling (lighter than headroom)
headroom: 30, // stackPriority 15 — tabular JSON compaction
lite: 40, // pri 5, but cheap prose pass (pre-existing reorder, kept as-is)
"read-lifecycle": 42, // stackPriority 5 (ties lite) — narrow-scope, opt-in, fully lossy
relevance: 45, // stackPriority 18 — extractive sentence scoring, opt-in
caveman: 50,
standard: 50, // mode-name alias for caveman
stacked: 50, // a derived/stacked base plan sits at the prose tier; floor escalates past it
aggressive: 60,
llmlingua: 65, // stackPriority 35 — semantic pruning (ONNX), after aggressive, before ultra/llm
llm: 68, // stackPriority 38 — full LLM-tier compressor, opt-in default-off
ultra: 70,
};
export function aggressivenessOf(engineOrMode: string): number {
@@ -45,12 +61,18 @@ export function aggressivenessOf(engineOrMode: string): number {
*/
const REDUCTION_FACTOR: Record<string, number> = {
"session-dedup": 0.95,
ccr: 0.9, // conservative: only replaces a block when the marker is shorter than it
rtk: 0.85,
ionizer: 0.83, // row sampling, lighter than headroom's full tabular compaction
headroom: 0.8,
lite: 0.92,
"read-lifecycle": 0.88, // scope-limited to stale/superseded Read tool-results
relevance: 0.75, // extractive sentence dropping
caveman: 0.7,
standard: 0.7,
aggressive: 0.55,
llmlingua: 0.5, // semantic pruning (ONNX)
llm: 0.45, // full LLM-tier compressor, stronger than llmlingua
ultra: 0.4,
};

View File

@@ -0,0 +1,54 @@
// Regression guard for #6533: the adaptive-compression ladder's AGGRESSIVENESS and
// REDUCTION_FACTOR maps must cover every REAL registered catalog engine, not just the
// 7 engines that ship in DEFAULT_LADDER. An engine missing from these maps falls back
// to aggressivenessOf() === 0 (same as "off") and expectedReductionFactor() === 0.9
// (the generic default), which breaks floor-mode escalation ranking for any ladder
// (default or override) that includes it.
import { test } from "node:test";
import assert from "node:assert/strict";
import {
aggressivenessOf,
expectedReductionFactor,
} from "@omniroute/open-sse/services/compression/adaptiveCompression/ladder.ts";
import { registerBuiltinCompressionEngines } from "@omniroute/open-sse/services/compression/engines/index.ts";
import { listCompressionEngines } from "@omniroute/open-sse/services/compression/engines/registry.ts";
registerBuiltinCompressionEngines();
// Drive the assertion straight from the real, registered engine catalog so this test
// stays in sync automatically if a new engine is added later.
const REAL_ENGINE_IDS = listCompressionEngines().map((e) => e.id);
test("every registered catalog engine has an aggressivenessOf() rank above the 'off' default", () => {
assert.ok(REAL_ENGINE_IDS.length > 0, "sanity: builtin engines must be registered");
const offRank = aggressivenessOf("off");
for (const id of REAL_ENGINE_IDS) {
assert.ok(
aggressivenessOf(id) > offRank,
`engine "${id}" must rank above "off" (got ${aggressivenessOf(id)}) — it is missing from AGGRESSIVENESS`
);
}
});
test("every registered catalog engine has a non-default expectedReductionFactor()", () => {
// The lookup's fallback for an unmapped engine is the generic 0.9 default. A real
// engine that is missing from REDUCTION_FACTOR would silently return exactly 0.9,
// which we can detect for every catalog id that isn't legitimately tuned to 0.9.
for (const id of REAL_ENGINE_IDS) {
const factor = aggressivenessOf(id) > 0 ? expectedReductionFactor(id) : null;
assert.ok(factor !== null, `engine "${id}" must be rankable`);
assert.ok(factor! > 0 && factor! < 1, `expectedReductionFactor("${id}") must be in (0,1)`);
}
});
test("aggressivenessOf ranks are internally consistent with described engine severity", () => {
// Structural/reversible engines (session-dedup, ccr) must rank below the prose-rewriting
// tier (caveman/aggressive/ultra).
assert.ok(aggressivenessOf("ccr") < aggressivenessOf("caveman"));
assert.ok(aggressivenessOf("session-dedup") < aggressivenessOf("ccr") || aggressivenessOf("session-dedup") <= aggressivenessOf("ccr"));
// Semantic-pruning engines (llmlingua, llm) must rank at/above "aggressive" and below/at "ultra".
assert.ok(aggressivenessOf("llmlingua") >= aggressivenessOf("aggressive"));
assert.ok(aggressivenessOf("llmlingua") <= aggressivenessOf("ultra"));
assert.ok(aggressivenessOf("llm") >= aggressivenessOf("llmlingua"));
assert.ok(aggressivenessOf("llm") <= aggressivenessOf("ultra"));
});