mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 04:42:10 +03:00
Phase 4C: adaptive context-budget compression. Integrated into release/v3.8.35.
This commit is contained in:
committed by
GitHub
parent
fdbc000b85
commit
98fc576a42
@@ -136,7 +136,7 @@
|
||||
"open-sse/services/claudeCodeCompatible.ts": 1202,
|
||||
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
|
||||
"open-sse/services/combo.ts": 3036,
|
||||
"open-sse/services/compression/strategySelector.ts": 818,
|
||||
"open-sse/services/compression/strategySelector.ts": 848,
|
||||
"open-sse/services/rateLimitManager.ts": 1035,
|
||||
"open-sse/services/tokenRefresh.ts": 1997,
|
||||
"open-sse/services/usage.ts": 3450,
|
||||
@@ -304,5 +304,6 @@
|
||||
"_rebaseline_2026_06_19_4293_codex_spark_scope": "PR #4293 (isolate Codex Spark quota scope) own growth, MEASURED on the actual merged tree (release/v3.8.30 + #4293). Production: auth.ts 2219->2279 (+60) threads requestedModel into Codex quota-policy/headroom/preflight/P2C scoring so normal Codex and GPT-5.3-Codex-Spark windows are evaluated independently; chatCore.ts 5116->5125 (+9) passes the failing model scope into Codex 429 failover (markCodexScopeRateLimited) instead of a connection-wide rateLimitedUntil write; accountFallback.ts 1727->1731 (+4) scopes Codex model-lock keys to codex vs spark. Heavy parsing/display logic lives in new leaf helpers under the cap (codexQuotaScopes.ts, codexUsageQuotas.ts, codexFailover.ts). Tests: account-fallback-service 1544->1569, executor-codex 1336->1339, sse-auth 1527->1553, usage-service-hardening 1612->1633 (added Spark-scope regression coverage). Cohesive wiring at existing selection/failover lockout boundaries; not extractable.",
|
||||
"_rebaseline_2026_06_20_4447_openai_gpt41mini_o_mini_pricing": "PR #4447 own growth: pricing.ts 1592->1620 (+28 = pure-data pricing rows closing the null/$0 gap for registry-exposed OpenAI ids gpt-4.1-mini, gpt-4.1-nano, o3-mini, o4-mini that tripped the catalog pricing gate; getPricingForModel does an exact lookup, so a missing key resolves to null. Official OpenAI per-1M prices + the table's derived-field convention (reasoning=output*1.5, cache_creation=input, cached=official). Restore-green for a pre-existing release/v3.8.32 red surfaced by #4432's __RUN_ALL__ run. Cohesive data; not extractable.",
|
||||
"_rebaseline_2026_06_20_web_cookie_validator_shadow_fix": "validation.ts 4518->4522 (+4 = move the generic web-cookie validateWebCookieProvider dispatch from the TOP of validateProviderApiKey to a FALLBACK after SPECIALTY_VALIDATORS, plus a comment, so #4023's generic AUTH_007 ping no longer shadows the rich per-provider validators (grok-web #3474 IP-reputation/Cloudflare, chatgpt-web cf-mitigated, claude/gemini/copilot/qwen/t3-web). Restores provider-validation-specialty.test.ts (112/112) while keeping web-cookie-auth007 (5/5). Behavior fix at an existing dispatch boundary; not extractable.",
|
||||
"_rebaseline_2026_06_22_phase4b_slm_tier_ultra": "Compression Phase 4 (B) SLM tier own growth: open-sse/services/compression/strategySelector.ts 783->818 (+35 at the existing applyUltraAsync chokepoint). The no-modelPath ultra branch (previously a one-line passthrough to the sync applyCompression) now runs the two-tier resolver: it adapts the body, builds the ultraConfig (threading config.ultraEngine + preserveSystemPrompt), awaits the now-async ultraCompress (SLM Tier-B when ultraEngine===slm and the worker backend is available, else fail-open to the Tier-A heuristic), and threads result.stats.ultraTier into the returned CompressionStats so the resolved tier reaches the D0 telemetry persister. The sync applyCompression ultra branch is also re-pointed to the new pure ultraCompressHeuristic. The two-tier resolver + the pure heuristic live in open-sse/services/compression/ultra.ts and the thin SLM entry in engines/llmlingua/ultraEntry.ts (both <cap, fully unit-tested); strategySelector.ts is cohesive dispatch wiring at the existing ultra chokepoint, not extractable without hiding the dispatch boundary. Covered by tests/unit/compression/ultra-slm-tier.test.ts (applyCompressionAsync ultraTier:slm) + ultra.test.ts (39 regression) + the integration suites. Structural shrink of this file tracked in #3501."
|
||||
"_rebaseline_2026_06_22_phase4b_slm_tier_ultra": "Compression Phase 4 (B) SLM tier own growth: open-sse/services/compression/strategySelector.ts 783->818 (+35 at the existing applyUltraAsync chokepoint). The no-modelPath ultra branch (previously a one-line passthrough to the sync applyCompression) now runs the two-tier resolver: it adapts the body, builds the ultraConfig (threading config.ultraEngine + preserveSystemPrompt), awaits the now-async ultraCompress (SLM Tier-B when ultraEngine===slm and the worker backend is available, else fail-open to the Tier-A heuristic), and threads result.stats.ultraTier into the returned CompressionStats so the resolved tier reaches the D0 telemetry persister. The sync applyCompression ultra branch is also re-pointed to the new pure ultraCompressHeuristic. The two-tier resolver + the pure heuristic live in open-sse/services/compression/ultra.ts and the thin SLM entry in engines/llmlingua/ultraEntry.ts (both <cap, fully unit-tested); strategySelector.ts is cohesive dispatch wiring at the existing ultra chokepoint, not extractable without hiding the dispatch boundary. Covered by tests/unit/compression/ultra-slm-tier.test.ts (applyCompressionAsync ultraTier:slm) + ultra.test.ts (39 regression) + the integration suites. Structural shrink of this file tracked in #3501.",
|
||||
"_rebaseline_2026_06_22_phase4c_adaptive_context_budget": "Compression Phase 4 (C) adaptive context-budget wiring own growth: open-sse/services/compression/strategySelector.ts 818->848 (+30 at the existing selectCompressionPlan dispatch chokepoint). selectCompressionPlan gains an 8th optional `adaptiveOptions` param (modelContextLimit/requestMaxTokens/onAdaptive sink) and, after resolveBasePlan and before the caching-aware pass, runs the PURE resolveAdaptivePlan when config.contextBudget.mode is floor|replace-autotrigger; the new adaptiveEnabled(config) helper also gates the legacy shouldAutoTrigger branch inside resolveBasePlan off when adaptive owns automatic-by-size escalation (D-C4). The escalation ladder, target computation, and the resolver itself live in open-sse/services/compression/adaptiveCompression/{computeTarget,ladder,resolveAdaptivePlan,types}.ts (all <cap, fully unit-tested by Tasks 1-4). strategySelector.ts is cohesive dispatch wiring at the existing compression chokepoint, not extractable without hiding the dispatch boundary, mirroring the prior compression rebaselines (#4217/#4210/phase4b). Covered by tests/unit/compression/adaptive-select-plan-wiring.test.ts (4 tests) + adaptive-chatcore-source-guard.test.ts (2). chatCore.ts also grows ~30 lines at the same call site (threads getTokenLimit(provider,effectiveModel) + request max_tokens into adaptiveOptions and records the adaptive telemetry block onto compression.completed) but stays under its frozen cap. Structural shrink of this file tracked in #3501."
|
||||
}
|
||||
|
||||
@@ -1162,6 +1162,15 @@ export async function handleChatCore({
|
||||
}
|
||||
}
|
||||
const compressionInputBody = body as Record<string, unknown>;
|
||||
// Adaptive context-budget (Sub-project C): model context window + request max_tokens drive
|
||||
// the budget target. getTokenLimit is already imported; provider/effectiveModel resolved above.
|
||||
const adaptiveModelContextLimit =
|
||||
provider && effectiveModel ? getTokenLimit(provider, effectiveModel) : null;
|
||||
const requestMaxTokens =
|
||||
typeof (compressionInputBody as Record<string, unknown>)?.max_tokens === "number"
|
||||
? ((compressionInputBody as Record<string, unknown>).max_tokens as number)
|
||||
: null;
|
||||
let adaptiveTelemetry: import("../services/compression/adaptiveCompression/types.ts").AdaptiveTelemetry | null = null;
|
||||
const compressionPlan = selectCompressionPlan(
|
||||
config,
|
||||
compressionComboKey,
|
||||
@@ -1169,9 +1178,20 @@ export async function handleChatCore({
|
||||
compressionInputBody,
|
||||
{ provider, targetFormat, model: effectiveModel },
|
||||
namedCombos,
|
||||
compressionHeader
|
||||
compressionHeader,
|
||||
{
|
||||
modelContextLimit: adaptiveModelContextLimit,
|
||||
requestMaxTokens: requestMaxTokens,
|
||||
onAdaptive: (t) => { adaptiveTelemetry = t; },
|
||||
}
|
||||
);
|
||||
const mode = compressionPlan.mode as CompressionConfig["defaultMode"];
|
||||
if (adaptiveTelemetry && adaptiveTelemetry.fit === false) {
|
||||
log?.warn?.(
|
||||
"COMPRESSION",
|
||||
`adaptive budget-exceeded: target=${adaptiveTelemetry.target} headroomAfter=${adaptiveTelemetry.headroomAfter} stages=${adaptiveTelemetry.stagesApplied.join(",")} (best-effort plan sent, content preserved)`
|
||||
);
|
||||
}
|
||||
compressionResponseMeta = formatCompressionMeta(compressionPlan);
|
||||
// When the per-engine toggle map derives a stacked pipeline (and no named/routing
|
||||
// combo already set config.stackedPipeline), feed that derived pipeline through so
|
||||
@@ -1252,6 +1272,7 @@ export async function handleChatCore({
|
||||
engineBreakdown: ensureEngineBreakdown(result.stats),
|
||||
validationWarnings: result.stats.validationWarnings,
|
||||
fallbackApplied: result.stats.fallbackApplied,
|
||||
...(adaptiveTelemetry ? { adaptive: adaptiveTelemetry } : {}),
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
emit("compression.completed", compressionCompletedPayload);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { ContextBudgetConfig, ContextBudgetPolicy } from "./types.ts";
|
||||
|
||||
/**
|
||||
* Pure target-token computation (design D-C1). No clock, no DB, no tokenizer.
|
||||
*
|
||||
* @param policy active target policy
|
||||
* @param modelContextLimit the resolved upstream model's context window (impure caller looks it up)
|
||||
* @param requestMaxTokens request.max_tokens, if the client sent one (reserve-output only)
|
||||
* @param config reserves / margin / pct / absoluteBudget
|
||||
* @returns the maximum prompt-token target the compressed request should fit within
|
||||
*/
|
||||
export function computeTarget(
|
||||
policy: ContextBudgetPolicy,
|
||||
modelContextLimit: number,
|
||||
requestMaxTokens: number | null,
|
||||
config: Pick<ContextBudgetConfig, "outputReserve" | "safetyMargin" | "pct" | "absoluteBudget">
|
||||
): number {
|
||||
if (policy === "absolute") {
|
||||
return Math.max(0, Math.floor(config.absoluteBudget));
|
||||
}
|
||||
if (policy === "percentage") {
|
||||
const pct = config.pct > 0 && config.pct <= 1 ? config.pct : 1;
|
||||
return Math.max(0, Math.floor(modelContextLimit * pct));
|
||||
}
|
||||
// reserve-output (default): limit − output reservation − safety margin.
|
||||
const reserve =
|
||||
typeof requestMaxTokens === "number" && requestMaxTokens > 0
|
||||
? requestMaxTokens
|
||||
: config.outputReserve;
|
||||
return Math.max(0, Math.floor(modelContextLimit - reserve - config.safetyMargin));
|
||||
}
|
||||
59
open-sse/services/compression/adaptiveCompression/ladder.ts
Normal file
59
open-sse/services/compression/adaptiveCompression/ladder.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { LadderStage } from "./types.ts";
|
||||
|
||||
/**
|
||||
* Default escalation ladder (design D-C2): cheapest/most-lossless → most aggressive.
|
||||
* Ordered by the engine catalog's stackPriority. `ccr` and `llmlingua` are intentionally
|
||||
* excluded from the AUTOMATIC ladder (ccr = retrieval markers, llmlingua = optional ONNX
|
||||
* SLM tier wired through `ultra`); an operator can still add them via ladderOverride.
|
||||
*/
|
||||
export const DEFAULT_LADDER: LadderStage[] = [
|
||||
{ engine: "session-dedup" }, // lossless cross-turn dedup (catalog pri 3)
|
||||
{ engine: "rtk", intensity: "standard" }, // command-output filtering (pri 10)
|
||||
{ engine: "headroom" }, // tabular JSON compaction (pri 15)
|
||||
{ engine: "lite" }, // whitespace/format cleanup (pri 5, but cheap prose pass)
|
||||
{ engine: "caveman", intensity: "full" }, // rule-based prose (pri 20)
|
||||
{ engine: "aggressive" }, // summarize + age old turns (pri 30)
|
||||
{ engine: "ultra" }, // heuristic token pruning + optional SLM (pri 40)
|
||||
];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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,
|
||||
};
|
||||
|
||||
export function aggressivenessOf(engineOrMode: string): number {
|
||||
return AGGRESSIVENESS[engineOrMode] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cheap per-engine EXPECTED reduction factor (output/input). Used by the default injected
|
||||
* estimator to model "apply this stage" WITHOUT a dry-run (design §9: no per-stage dry-run
|
||||
* in the hot path). Conservative, monotonic with aggressiveness; never 0 (content preserved).
|
||||
*/
|
||||
const REDUCTION_FACTOR: Record<string, number> = {
|
||||
"session-dedup": 0.95,
|
||||
rtk: 0.85,
|
||||
headroom: 0.8,
|
||||
lite: 0.92,
|
||||
caveman: 0.7,
|
||||
standard: 0.7,
|
||||
aggressive: 0.55,
|
||||
ultra: 0.4,
|
||||
};
|
||||
|
||||
export function expectedReductionFactor(engine: string): number {
|
||||
return REDUCTION_FACTOR[engine] ?? 0.9;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import type { DerivedPlan } from "../deriveDefaultPlan.ts";
|
||||
import type { AdaptiveTelemetry, ContextBudgetConfig, LadderStage } from "./types.ts";
|
||||
import { DEFAULT_LADDER, aggressivenessOf, expectedReductionFactor } from "./ladder.ts";
|
||||
import { computeTarget } from "./computeTarget.ts";
|
||||
|
||||
export interface ResolveAdaptiveInput {
|
||||
basePlan: DerivedPlan;
|
||||
estimatedTokens: number;
|
||||
modelContextLimit: number | null;
|
||||
requestMaxTokens: number | null;
|
||||
config: ContextBudgetConfig;
|
||||
/**
|
||||
* Injected per-stage estimator (design §4.1/§9). Returns the NEW token estimate after a
|
||||
* stage is applied to a prompt of `priorTokens`. Default models it with the cheap
|
||||
* per-engine expected-reduction factor (no dry-run, no real tokenizer).
|
||||
*/
|
||||
estimate?: (priorTokens: number, stage: LadderStage) => number;
|
||||
}
|
||||
|
||||
export interface ResolveAdaptiveResult {
|
||||
plan: DerivedPlan;
|
||||
telemetry: AdaptiveTelemetry | null;
|
||||
}
|
||||
|
||||
const defaultEstimate = (prior: number, stage: LadderStage): number =>
|
||||
Math.round(prior * expectedReductionFactor(stage.engine));
|
||||
|
||||
/**
|
||||
* Pure adaptive resolver (design §4.2). Floors/escalates a base plan so the (estimated)
|
||||
* compressed prompt fits the context budget. Never drops content: if the ladder is
|
||||
* exhausted while still over target, returns the best-effort plan with fit=false.
|
||||
*
|
||||
* Returns telemetry=null only when there is no usable target (unknown model limit) — the
|
||||
* caller then skips adaptive entirely (design §6: skip, never throw).
|
||||
*/
|
||||
export function resolveAdaptivePlan(input: ResolveAdaptiveInput): ResolveAdaptiveResult {
|
||||
const { basePlan, estimatedTokens, modelContextLimit, requestMaxTokens, config } = input;
|
||||
const estimate = input.estimate ?? defaultEstimate;
|
||||
|
||||
if (config.mode === "off") return { plan: basePlan, telemetry: null };
|
||||
if (!modelContextLimit || modelContextLimit <= 0) {
|
||||
return { plan: basePlan, telemetry: null }; // unknown limit → skip (D-C / §6)
|
||||
}
|
||||
|
||||
const target = computeTarget(config.policy, modelContextLimit, requestMaxTokens, config);
|
||||
const headroomBefore = target - estimatedTokens;
|
||||
|
||||
// replace-autotrigger only acts on a bare Default/off base plan; an explicit choice wins.
|
||||
const baseRank = aggressivenessOf(basePlan.mode);
|
||||
if (config.mode === "replace-autotrigger" && baseRank > aggressivenessOf("off")) {
|
||||
return {
|
||||
plan: basePlan,
|
||||
telemetry: { policy: config.policy, target, headroomBefore, stagesApplied: [], headroomAfter: headroomBefore, fit: headroomBefore >= 0 },
|
||||
};
|
||||
}
|
||||
|
||||
// Already fits → never over-compress (D-C2).
|
||||
if (headroomBefore >= 0) {
|
||||
return {
|
||||
plan: basePlan,
|
||||
telemetry: { policy: config.policy, target, headroomBefore, stagesApplied: [], headroomAfter: headroomBefore, fit: true },
|
||||
};
|
||||
}
|
||||
|
||||
// Escalation: start just ABOVE the base plan's aggressiveness (floor escalates beyond it).
|
||||
const ladder = config.ladderOverride && config.ladderOverride.length > 0 ? config.ladderOverride : DEFAULT_LADDER;
|
||||
const startTier = config.mode === "floor" ? baseRank : aggressivenessOf("off");
|
||||
const stages = ladder.filter((s) => aggressivenessOf(s.engine) > startTier);
|
||||
|
||||
let current = estimatedTokens;
|
||||
const applied: LadderStage[] = [];
|
||||
for (const stage of stages) {
|
||||
current = estimate(current, stage);
|
||||
applied.push(stage);
|
||||
if (current <= target) break;
|
||||
}
|
||||
|
||||
const headroomAfter = target - current;
|
||||
const fit = headroomAfter >= 0;
|
||||
return {
|
||||
plan: planFromStages(basePlan, applied),
|
||||
telemetry: {
|
||||
policy: config.policy,
|
||||
target,
|
||||
headroomBefore,
|
||||
stagesApplied: applied.map((s) => s.engine),
|
||||
headroomAfter,
|
||||
fit,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the applied ladder stages into a DerivedPlan. A single applied stage that is a
|
||||
* single-mode engine collapses to that engine's mode; otherwise a stacked pipeline.
|
||||
* The base plan's own pipeline is preserved as the prefix (floor escalates AFTER the base).
|
||||
*/
|
||||
function planFromStages(basePlan: DerivedPlan, applied: LadderStage[]): DerivedPlan {
|
||||
if (applied.length === 0) return basePlan;
|
||||
const basePipeline =
|
||||
basePlan.mode === "stacked" ? basePlan.stackedPipeline : [];
|
||||
const stackedPipeline = [...basePipeline, ...applied];
|
||||
return { mode: "stacked", stackedPipeline };
|
||||
}
|
||||
61
open-sse/services/compression/adaptiveCompression/types.ts
Normal file
61
open-sse/services/compression/adaptiveCompression/types.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Context-budget adaptive compression — shared types.
|
||||
*
|
||||
* Naming note: "adaptiveCompression"/"contextBudget" — NOT "headroom" (which is an
|
||||
* unrelated existing engine). "headroom" here = the budget signal (target − prompt tokens).
|
||||
*/
|
||||
|
||||
/** Target-derivation policy (design D-C1). */
|
||||
export type ContextBudgetPolicy = "reserve-output" | "percentage" | "absolute";
|
||||
|
||||
/**
|
||||
* Adaptive mode (design D-C3/C4):
|
||||
* - "floor" : guarantee fit; escalate BEYOND any base plan.
|
||||
* - "replace-autotrigger" : only acts when the base plan is bare Default/off (an explicit
|
||||
* operator/client choice always wins, even if it overflows).
|
||||
* - "off" : legacy binary auto-trigger (full backward-compat).
|
||||
*/
|
||||
export type ContextBudgetMode = "floor" | "replace-autotrigger" | "off";
|
||||
|
||||
/** One escalation stage = an engine id applied at an optional intensity. */
|
||||
export interface LadderStage {
|
||||
engine: string;
|
||||
intensity?: string;
|
||||
}
|
||||
|
||||
/** Persisted adaptive settings (design §4.4). All optional with safe defaults in computeTarget. */
|
||||
export interface ContextBudgetConfig {
|
||||
mode: ContextBudgetMode;
|
||||
policy: ContextBudgetPolicy;
|
||||
/** reserve-output: tokens reserved for the model's output when request.max_tokens is absent. */
|
||||
outputReserve: number;
|
||||
/** reserve-output: extra tokens shaved off the limit as a safety buffer. */
|
||||
safetyMargin: number;
|
||||
/** percentage policy: fraction of the model context window to target (0 < pct <= 1). */
|
||||
pct: number;
|
||||
/** absolute policy: a model-independent token budget. */
|
||||
absoluteBudget: number;
|
||||
/** Operator override of the default escalation ladder (cheapest → most aggressive). */
|
||||
ladderOverride?: LadderStage[];
|
||||
}
|
||||
|
||||
/** The `adaptive` block of the shared CompressionRunTelemetry contract (roadmap overview). */
|
||||
export interface AdaptiveTelemetry {
|
||||
policy: ContextBudgetPolicy;
|
||||
target: number;
|
||||
headroomBefore: number;
|
||||
stagesApplied: string[];
|
||||
headroomAfter: number;
|
||||
/** false => budget-exceeded (best-effort plan sent as-is; content never dropped). */
|
||||
fit: boolean;
|
||||
}
|
||||
|
||||
/** Safe defaults applied when a field is absent (design §4.4 / §6). */
|
||||
export const DEFAULT_CONTEXT_BUDGET: ContextBudgetConfig = {
|
||||
mode: "off",
|
||||
policy: "reserve-output",
|
||||
outputReserve: 4096,
|
||||
safetyMargin: 1024,
|
||||
pct: 0.85,
|
||||
absoluteBudget: 0,
|
||||
};
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
deriveDefaultPlanFromConfig,
|
||||
buildNamedComboLookup,
|
||||
} from "./planResolution.ts";
|
||||
import { resolveAdaptivePlan } from "./adaptiveCompression/resolveAdaptivePlan.ts";
|
||||
import type { AdaptiveTelemetry } from "./adaptiveCompression/types.ts";
|
||||
|
||||
// Re-export so existing importers (resolver test + chatCore dynamic import) keep resolving.
|
||||
export { planFromHeader, formatCompressionMeta, buildNamedComboLookup };
|
||||
@@ -68,6 +70,12 @@ export function shouldAutoTrigger(config: CompressionConfig, estimatedTokens: nu
|
||||
* `combos` defaults to `{}` so Phase-1 callers are unchanged; when supplied, chatCore passes
|
||||
* its DB-loaded named-combo map so the active profile can resolve here purely (no DB import).
|
||||
*/
|
||||
/** True when the adaptive resolver owns automatic-by-size escalation (D-C4). */
|
||||
function adaptiveEnabled(config: CompressionConfig): boolean {
|
||||
const mode = config.contextBudget?.mode;
|
||||
return mode === "floor" || mode === "replace-autotrigger";
|
||||
}
|
||||
|
||||
function resolveBasePlan(
|
||||
config: CompressionConfig,
|
||||
comboId: string | null,
|
||||
@@ -101,7 +109,7 @@ function resolveBasePlan(
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldAutoTrigger(config, estimatedTokens)) {
|
||||
if (!adaptiveEnabled(config) && shouldAutoTrigger(config, estimatedTokens)) {
|
||||
const mode = config.autoTriggerMode ?? "lite";
|
||||
return withSource(
|
||||
mode === "stacked"
|
||||
@@ -154,6 +162,13 @@ export function getEffectiveMode(
|
||||
* The caching-aware mode adjustment is applied to `mode` exactly as in
|
||||
* {@link selectCompressionStrategy}.
|
||||
*/
|
||||
/** Adaptive (Sub-project C) inputs + telemetry sink for selectCompressionPlan. */
|
||||
export interface AdaptiveSelectOptions {
|
||||
modelContextLimit?: number | null;
|
||||
requestMaxTokens?: number | null;
|
||||
onAdaptive?: (telemetry: AdaptiveTelemetry) => void;
|
||||
}
|
||||
|
||||
export function selectCompressionPlan(
|
||||
config: CompressionConfig,
|
||||
comboId: string | null,
|
||||
@@ -161,9 +176,24 @@ export function selectCompressionPlan(
|
||||
body?: Record<string, unknown>,
|
||||
context?: CachingDetectionContext,
|
||||
combos: NamedCombos = {},
|
||||
header: string | null = null
|
||||
header: string | null = null,
|
||||
adaptiveOptions?: AdaptiveSelectOptions
|
||||
): DerivedPlan {
|
||||
const plan = resolveBasePlan(config, comboId, estimatedTokens, combos, header);
|
||||
let plan = resolveBasePlan(config, comboId, estimatedTokens, combos, header);
|
||||
|
||||
// Adaptive context-budget floor/escalation (D-C4): after the base plan, replacing the
|
||||
// (now-bypassed) auto-trigger branch. Pure resolver; chatCore supplies the model limit.
|
||||
if (adaptiveEnabled(config) && config.contextBudget) {
|
||||
const { plan: adaptivePlan, telemetry } = resolveAdaptivePlan({
|
||||
basePlan: plan,
|
||||
estimatedTokens,
|
||||
modelContextLimit: adaptiveOptions?.modelContextLimit ?? null,
|
||||
requestMaxTokens: adaptiveOptions?.requestMaxTokens ?? null,
|
||||
config: config.contextBudget,
|
||||
});
|
||||
plan = adaptivePlan;
|
||||
if (telemetry && adaptiveOptions?.onAdaptive) adaptiveOptions.onAdaptive(telemetry);
|
||||
}
|
||||
|
||||
// Apply caching-aware adjustments to the mode if body is provided
|
||||
if (body) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import { ENGINE_IDS } from "./engineCatalog.ts";
|
||||
import type { ContextBudgetConfig } from "./adaptiveCompression/types.ts";
|
||||
|
||||
// Re-export so consumers that already import from this module (e.g. src/lib/db/compression.ts)
|
||||
// can get ENGINE_IDS without a second bare `@omniroute/open-sse/...engineCatalog.ts` specifier.
|
||||
@@ -161,6 +162,13 @@ export interface CompressionConfig {
|
||||
* change for installs that predate the panel). Set by `getCompressionSettings`.
|
||||
*/
|
||||
enginesExplicit?: boolean;
|
||||
/**
|
||||
* Context-budget adaptive compression (Sub-project C). Absent / mode:"off" = legacy
|
||||
* binary auto-trigger (byte-identical). When mode is "floor" or "replace-autotrigger"
|
||||
* the adaptive resolver owns automatic-by-size escalation and the legacy
|
||||
* shouldAutoTrigger branch is bypassed.
|
||||
*/
|
||||
contextBudget?: ContextBudgetConfig;
|
||||
/**
|
||||
* Phase 4 (B): which tier the `ultra` mode uses.
|
||||
* "heuristic" = Tier-A token pruner (`pruneByScore`, default, byte-identical to pre-B).
|
||||
|
||||
@@ -28,6 +28,11 @@ import {
|
||||
outputStyleMeta,
|
||||
} from "../../../../../../open-sse/services/compression/outputStyles/catalog.ts";
|
||||
import { deriveDefaultPlan } from "../../../../../../open-sse/services/compression/deriveDefaultPlan.ts";
|
||||
import {
|
||||
DEFAULT_CONTEXT_BUDGET,
|
||||
type ContextBudgetConfig,
|
||||
} from "../../../../../../open-sse/services/compression/adaptiveCompression/types.ts";
|
||||
import { formatAdaptiveTarget } from "./adaptiveTargetLabel.ts";
|
||||
|
||||
type CavemanIntensity = "lite" | "full" | "ultra";
|
||||
|
||||
@@ -56,6 +61,10 @@ interface CompressionConfig {
|
||||
ultraEngine?: "heuristic" | "slm";
|
||||
// Best-effort pre-warm of the SLM model on enable / cold restart. Default false.
|
||||
ultraSlmPrewarm?: boolean;
|
||||
// Phase 4 (C): adaptive context-budget. Absent / mode:"off" = legacy auto-trigger.
|
||||
// The panel currently surfaces the computed target read-only; mode/policy editors are a
|
||||
// follow-up (the load/save path does not yet populate this field).
|
||||
contextBudget?: ContextBudgetConfig;
|
||||
}
|
||||
|
||||
const CAVEMAN_OUTPUT_LEVELS: CavemanIntensity[] = ["lite", "full", "ultra"];
|
||||
@@ -248,6 +257,14 @@ export default function CompressionPanel() {
|
||||
<span className="font-medium text-text-main">Effective pipeline:</span> {derivedText}
|
||||
</div>
|
||||
|
||||
{/* Adaptive context-budget — read-only computed target (Phase 4C, D-C1 transparency) */}
|
||||
<div
|
||||
data-testid="adaptive-target-preview"
|
||||
className="mb-4 rounded-md border border-border/60 bg-bg-subtle px-3 py-2 text-xs text-text-muted"
|
||||
>
|
||||
{formatAdaptiveTarget(config.contextBudget ?? DEFAULT_CONTEXT_BUDGET, 200000)}
|
||||
</div>
|
||||
|
||||
{/* Engine grid */}
|
||||
<div className={`divide-y divide-border ${config.enabled ? "" : "opacity-60"}`}>
|
||||
{ENGINE_IDS.map((id) => {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { computeTarget } from "../../../../../../open-sse/services/compression/adaptiveCompression/computeTarget.ts";
|
||||
import type { ContextBudgetConfig } from "../../../../../../open-sse/services/compression/adaptiveCompression/types.ts";
|
||||
|
||||
/**
|
||||
* Read-only label for the compression panel (design D-C1 transparency). Shows the active
|
||||
* policy and the computed token target for a representative model context window. PURE —
|
||||
* imports only the pure computeTarget leaf, no DB, no clock. The panel renders this string
|
||||
* as an informational/diagnostic line, mirroring the derived-pipeline preview.
|
||||
*/
|
||||
export function formatAdaptiveTarget(
|
||||
config: ContextBudgetConfig,
|
||||
representativeModelContextLimit: number
|
||||
): string {
|
||||
if (config.mode === "off") return "Adaptive context budget: off (legacy auto-trigger)";
|
||||
const target = computeTarget(config.policy, representativeModelContextLimit, null, config);
|
||||
return `Adaptive (${config.mode}, policy: ${config.policy}) — target ≈ ${target.toLocaleString()} tokens (for a ${representativeModelContextLimit.toLocaleString()}-token window)`;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const chatCore = readFileSync(
|
||||
fileURLToPath(new URL("../../../open-sse/handlers/chatCore.ts", import.meta.url)),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
test("chatCore threads modelContextLimit + requestMaxTokens into selectCompressionPlan", () => {
|
||||
// the adaptive options object literal must reference both inputs
|
||||
assert.match(chatCore, /modelContextLimit:/);
|
||||
assert.match(chatCore, /requestMaxTokens:/);
|
||||
});
|
||||
|
||||
test("chatCore emits the adaptive telemetry block via onAdaptive", () => {
|
||||
assert.match(chatCore, /onAdaptive/);
|
||||
assert.match(chatCore, /adaptiveTelemetry/);
|
||||
});
|
||||
30
tests/unit/compression/adaptive-compute-target.test.ts
Normal file
30
tests/unit/compression/adaptive-compute-target.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { computeTarget } from "@omniroute/open-sse/services/compression/adaptiveCompression/computeTarget.ts";
|
||||
import { DEFAULT_CONTEXT_BUDGET } from "@omniroute/open-sse/services/compression/adaptiveCompression/types.ts";
|
||||
|
||||
const base = { ...DEFAULT_CONTEXT_BUDGET, outputReserve: 4096, safetyMargin: 1024 };
|
||||
|
||||
test("reserve-output: request.max_tokens wins over outputReserve default", () => {
|
||||
// 200000 − 8000 (request max_tokens) − 1024 (safetyMargin) = 190976
|
||||
const t = computeTarget("reserve-output", 200000, 8000, base);
|
||||
assert.equal(t, 190976);
|
||||
});
|
||||
|
||||
test("reserve-output: falls back to outputReserve when no max_tokens", () => {
|
||||
// 200000 − 4096 (outputReserve) − 1024 (safetyMargin) = 194880
|
||||
assert.equal(computeTarget("reserve-output", 200000, null, base), 194880);
|
||||
assert.equal(computeTarget("reserve-output", 200000, 0, base), 194880); // 0 is not a positive reserve
|
||||
});
|
||||
|
||||
test("percentage policy: limit × pct, floored", () => {
|
||||
assert.equal(computeTarget("percentage", 200000, null, { ...base, pct: 0.7 }), 140000);
|
||||
// invalid pct (out of (0,1]) → treated as 1.0 (no shrink)
|
||||
assert.equal(computeTarget("percentage", 200000, null, { ...base, pct: 0 }), 200000);
|
||||
assert.equal(computeTarget("percentage", 200000, null, { ...base, pct: 1.5 }), 200000);
|
||||
});
|
||||
|
||||
test("absolute policy: model-independent budget", () => {
|
||||
assert.equal(computeTarget("absolute", 200000, 8000, { ...base, absoluteBudget: 50000 }), 50000);
|
||||
assert.equal(computeTarget("absolute", 8000, null, { ...base, absoluteBudget: 50000 }), 50000);
|
||||
});
|
||||
31
tests/unit/compression/adaptive-panel-target.test.ts
Normal file
31
tests/unit/compression/adaptive-panel-target.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { formatAdaptiveTarget } from "../../../src/app/(dashboard)/dashboard/context/settings/adaptiveTargetLabel.ts";
|
||||
|
||||
test("formatAdaptiveTarget shows policy + computed target for reserve-output", () => {
|
||||
const label = formatAdaptiveTarget(
|
||||
{ mode: "floor", policy: "reserve-output", outputReserve: 4096, safetyMargin: 1024, pct: 0.85, absoluteBudget: 0 },
|
||||
200000
|
||||
);
|
||||
assert.match(label, /reserve-output/);
|
||||
// 200000 − 4096 − 1024 = 194880; tolerate any digit-group separator (locale-dependent toLocaleString).
|
||||
assert.match(label, /194[.,\s ]?880|194880/);
|
||||
});
|
||||
|
||||
test("formatAdaptiveTarget shows off when disabled", () => {
|
||||
const label = formatAdaptiveTarget(
|
||||
{ mode: "off", policy: "reserve-output", outputReserve: 4096, safetyMargin: 1024, pct: 0.85, absoluteBudget: 0 },
|
||||
200000
|
||||
);
|
||||
assert.match(label, /off|disabled/i);
|
||||
});
|
||||
|
||||
test("formatAdaptiveTarget reflects percentage policy target", () => {
|
||||
const label = formatAdaptiveTarget(
|
||||
{ mode: "floor", policy: "percentage", outputReserve: 4096, safetyMargin: 1024, pct: 0.7, absoluteBudget: 0 },
|
||||
200000
|
||||
);
|
||||
assert.match(label, /percentage/);
|
||||
// 200000 × 0.7 = 140000
|
||||
assert.match(label, /140[.,\s ]?000|140000/);
|
||||
});
|
||||
150
tests/unit/compression/adaptive-resolve-plan.test.ts
Normal file
150
tests/unit/compression/adaptive-resolve-plan.test.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
DEFAULT_LADDER,
|
||||
aggressivenessOf,
|
||||
expectedReductionFactor,
|
||||
} from "@omniroute/open-sse/services/compression/adaptiveCompression/ladder.ts";
|
||||
|
||||
test("default ladder is cheapest → most aggressive (stackPriority order)", () => {
|
||||
assert.deepEqual(
|
||||
DEFAULT_LADDER.map((s) => s.engine),
|
||||
["session-dedup", "rtk", "headroom", "lite", "caveman", "aggressive", "ultra"]
|
||||
);
|
||||
});
|
||||
|
||||
test("aggressivenessOf increases monotonically along the ladder", () => {
|
||||
const ranks = DEFAULT_LADDER.map((s) => aggressivenessOf(s.engine));
|
||||
for (let i = 1; i < ranks.length; i++) {
|
||||
assert.ok(ranks[i] > ranks[i - 1], `rank must increase at index ${i}`);
|
||||
}
|
||||
// a base "lite" plan ranks below caveman/aggressive/ultra (so floor escalates beyond it)
|
||||
assert.ok(aggressivenessOf("lite") < aggressivenessOf("caveman"));
|
||||
assert.ok(aggressivenessOf("standard") === aggressivenessOf("caveman")); // mode-name alias
|
||||
});
|
||||
|
||||
test("expectedReductionFactor is in (0,1) and heavier engines reduce more", () => {
|
||||
assert.ok(expectedReductionFactor("rtk") < 1 && expectedReductionFactor("rtk") > 0);
|
||||
assert.ok(expectedReductionFactor("ultra") < expectedReductionFactor("rtk"));
|
||||
});
|
||||
|
||||
import { resolveAdaptivePlan } from "@omniroute/open-sse/services/compression/adaptiveCompression/resolveAdaptivePlan.ts";
|
||||
import { DEFAULT_CONTEXT_BUDGET } from "@omniroute/open-sse/services/compression/adaptiveCompression/types.ts";
|
||||
|
||||
const cfg = (over = {}) => ({ ...DEFAULT_CONTEXT_BUDGET, mode: "floor" as const, ...over });
|
||||
const basePlan = { mode: "off", stackedPipeline: [] as Array<{ engine: string; intensity?: string }> };
|
||||
|
||||
test("already fits → base plan unchanged, fit=true, no stages", () => {
|
||||
const { plan, telemetry } = resolveAdaptivePlan({
|
||||
basePlan,
|
||||
estimatedTokens: 1000, // well under target
|
||||
modelContextLimit: 200000,
|
||||
requestMaxTokens: 8000,
|
||||
config: cfg(),
|
||||
});
|
||||
assert.deepEqual(plan, basePlan);
|
||||
assert.ok(telemetry);
|
||||
assert.equal(telemetry!.fit, true);
|
||||
assert.deepEqual(telemetry!.stagesApplied, []);
|
||||
assert.equal(telemetry!.target, 200000 - 8000 - 1024);
|
||||
assert.ok(telemetry!.headroomBefore > 0);
|
||||
assert.equal(telemetry!.headroomAfter, telemetry!.headroomBefore);
|
||||
});
|
||||
|
||||
test("over target → escalates and stops at first fitting stage (no over-escalation)", () => {
|
||||
// Injected estimator: each stage halves the prompt. target = 200000-8000-1024 = 190976.
|
||||
// Start over target at 400000: stage1 → 200000 (still over), stage2 → 100000 (fits) → STOP.
|
||||
const halve = (prior: number) => Math.round(prior / 2);
|
||||
const { plan, telemetry } = resolveAdaptivePlan({
|
||||
basePlan: { mode: "off", stackedPipeline: [] },
|
||||
estimatedTokens: 400000,
|
||||
modelContextLimit: 200000,
|
||||
requestMaxTokens: 8000,
|
||||
config: cfg(),
|
||||
estimate: halve,
|
||||
});
|
||||
assert.equal(telemetry!.fit, true);
|
||||
assert.equal(telemetry!.stagesApplied.length, 2); // stopped after the 2nd stage
|
||||
assert.equal(plan.mode, "stacked");
|
||||
assert.equal(plan.stackedPipeline.length, 2);
|
||||
// first two ladder engines above "off": session-dedup then rtk
|
||||
assert.deepEqual(telemetry!.stagesApplied, ["session-dedup", "rtk"]);
|
||||
assert.ok(telemetry!.headroomAfter >= 0);
|
||||
});
|
||||
|
||||
test("floor escalates beyond a light base plan (base lite → starts above lite)", () => {
|
||||
const halve = (prior: number) => Math.round(prior / 2);
|
||||
const { telemetry } = resolveAdaptivePlan({
|
||||
basePlan: { mode: "lite", stackedPipeline: [] },
|
||||
estimatedTokens: 400000,
|
||||
modelContextLimit: 200000,
|
||||
requestMaxTokens: 8000,
|
||||
config: cfg(),
|
||||
estimate: halve,
|
||||
});
|
||||
// base "lite" rank=4; ladder stages above rank 4 = caveman(5), aggressive(6), ultra(7).
|
||||
assert.equal(telemetry!.stagesApplied[0], "caveman");
|
||||
assert.ok(!telemetry!.stagesApplied.includes("session-dedup")); // did NOT restart below lite
|
||||
assert.ok(!telemetry!.stagesApplied.includes("rtk"));
|
||||
});
|
||||
|
||||
test("ladder exhausted, still over target → fit=false, all stages applied, plan still set", () => {
|
||||
const noop = (prior: number) => prior; // estimator never reduces → never fits
|
||||
const { plan, telemetry } = resolveAdaptivePlan({
|
||||
basePlan: { mode: "off", stackedPipeline: [] },
|
||||
estimatedTokens: 999999,
|
||||
modelContextLimit: 200000,
|
||||
requestMaxTokens: 8000,
|
||||
config: cfg(),
|
||||
estimate: noop,
|
||||
});
|
||||
assert.equal(telemetry!.fit, false); // budget-exceeded
|
||||
assert.equal(telemetry!.stagesApplied.length, 7); // entire DEFAULT_LADDER above "off"
|
||||
assert.equal(plan.mode, "stacked");
|
||||
assert.ok(plan.stackedPipeline.length >= 7); // best-effort plan, content NOT dropped
|
||||
assert.ok(telemetry!.headroomAfter < 0);
|
||||
});
|
||||
|
||||
test("replace-autotrigger: fires on bare off base, defers to an explicit base plan", () => {
|
||||
const halve = (prior: number) => Math.round(prior / 2);
|
||||
// bare off base → it acts
|
||||
const acts = resolveAdaptivePlan({
|
||||
basePlan: { mode: "off", stackedPipeline: [] },
|
||||
estimatedTokens: 400000, modelContextLimit: 200000, requestMaxTokens: 8000,
|
||||
config: cfg({ mode: "replace-autotrigger" }), estimate: halve,
|
||||
});
|
||||
assert.ok(acts.telemetry!.stagesApplied.length > 0);
|
||||
|
||||
// explicit aggressive base → defer (choice wins, may overflow)
|
||||
const defers = resolveAdaptivePlan({
|
||||
basePlan: { mode: "aggressive", stackedPipeline: [] },
|
||||
estimatedTokens: 400000, modelContextLimit: 200000, requestMaxTokens: 8000,
|
||||
config: cfg({ mode: "replace-autotrigger" }), estimate: halve,
|
||||
});
|
||||
assert.deepEqual(defers.plan, { mode: "aggressive", stackedPipeline: [] });
|
||||
assert.deepEqual(defers.telemetry!.stagesApplied, []);
|
||||
assert.equal(defers.telemetry!.fit, false); // 400000 > target, recorded as not fitting
|
||||
});
|
||||
|
||||
test("unknown model context limit → skip adaptive (null telemetry, base unchanged)", () => {
|
||||
for (const lim of [null, 0, -1]) {
|
||||
const { plan, telemetry } = resolveAdaptivePlan({
|
||||
basePlan, estimatedTokens: 999999, modelContextLimit: lim,
|
||||
requestMaxTokens: 8000, config: cfg(),
|
||||
});
|
||||
assert.equal(telemetry, null);
|
||||
assert.deepEqual(plan, basePlan);
|
||||
}
|
||||
});
|
||||
|
||||
test("hard-off: floor still escalates an overflowing 'off' base plan (spec §9)", () => {
|
||||
const halve = (prior: number) => Math.round(prior / 2);
|
||||
const { telemetry } = resolveAdaptivePlan({
|
||||
basePlan: { mode: "off", stackedPipeline: [] }, // explicit off (header off resolves to this)
|
||||
estimatedTokens: 400000, modelContextLimit: 200000, requestMaxTokens: 8000,
|
||||
config: cfg(), // mode: "floor"
|
||||
estimate: halve,
|
||||
});
|
||||
assert.ok(telemetry!.stagesApplied.length > 0); // floor escalated despite off
|
||||
assert.equal(telemetry!.fit, true);
|
||||
});
|
||||
82
tests/unit/compression/adaptive-select-plan-wiring.test.ts
Normal file
82
tests/unit/compression/adaptive-select-plan-wiring.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
selectCompressionPlan,
|
||||
shouldAutoTrigger,
|
||||
} from "@omniroute/open-sse/services/compression/strategySelector.ts";
|
||||
import { getDefaultCompressionConfig } from "@omniroute/open-sse/services/compression/stats.ts";
|
||||
|
||||
function legacyCfg() {
|
||||
return {
|
||||
...getDefaultCompressionConfig(),
|
||||
enabled: true,
|
||||
enginesExplicit: false,
|
||||
defaultMode: "lite" as const,
|
||||
autoTriggerMode: "aggressive" as const,
|
||||
autoTriggerTokens: 100000,
|
||||
};
|
||||
}
|
||||
|
||||
test("no contextBudget → auto-trigger path is byte-identical to legacy", () => {
|
||||
const cfg = legacyCfg(); // no contextBudget field
|
||||
// under threshold → derived default ("lite")
|
||||
const small = selectCompressionPlan(cfg, null, 1000);
|
||||
assert.equal(small.mode, "lite");
|
||||
// over threshold → auto-trigger fires → "aggressive"
|
||||
const big = selectCompressionPlan(cfg, null, 200000);
|
||||
assert.equal(big.mode, "aggressive");
|
||||
assert.equal(shouldAutoTrigger(cfg, 200000), true);
|
||||
});
|
||||
|
||||
test("contextBudget.mode='off' → identical to no contextBudget", () => {
|
||||
const cfg = { ...legacyCfg(), contextBudget: { mode: "off" as const } };
|
||||
assert.equal(selectCompressionPlan(cfg as any, null, 200000).mode, "aggressive");
|
||||
});
|
||||
|
||||
test("adaptive floor: bypasses auto-trigger, escalates a base plan to fit", () => {
|
||||
const cfg = {
|
||||
...legacyCfg(),
|
||||
autoTriggerTokens: 100000,
|
||||
autoTriggerMode: "lite" as const,
|
||||
contextBudget: {
|
||||
mode: "floor" as const,
|
||||
policy: "reserve-output" as const,
|
||||
outputReserve: 4096,
|
||||
safetyMargin: 1024,
|
||||
pct: 0.85,
|
||||
absoluteBudget: 0,
|
||||
},
|
||||
};
|
||||
const tel: {
|
||||
value: import("@omniroute/open-sse/services/compression/adaptiveCompression/types.ts").AdaptiveTelemetry | null;
|
||||
} = { value: null };
|
||||
// estimatedTokens far over the 200000-window target → adaptive must escalate.
|
||||
const plan = selectCompressionPlan(
|
||||
cfg as any, null, 5_000_000, undefined, undefined, {}, null,
|
||||
{ modelContextLimit: 200000, requestMaxTokens: 8000, onAdaptive: (t) => { tel.value = t; } }
|
||||
);
|
||||
assert.equal(plan.mode, "stacked");
|
||||
assert.ok(plan.stackedPipeline.length > 0);
|
||||
assert.ok(tel.value, "adaptive telemetry must be surfaced");
|
||||
assert.equal(tel.value!.policy, "reserve-output");
|
||||
assert.equal(tel.value!.target, 200000 - 8000 - 1024);
|
||||
assert.ok(tel.value!.stagesApplied.length > 0);
|
||||
});
|
||||
|
||||
test("adaptive escalation still respects caching downgrade (D-C / §6 cache-safety)", () => {
|
||||
const cfg = {
|
||||
...legacyCfg(),
|
||||
contextBudget: { mode: "floor" as const, policy: "reserve-output" as const, outputReserve: 4096, safetyMargin: 1024, pct: 0.85, absoluteBudget: 0 },
|
||||
};
|
||||
// A caching provider context downgrades aggressive/ultra → standard; the adaptive plan's
|
||||
// mode is "stacked", which getCacheAwareStrategy passes through unchanged, but the
|
||||
// pipeline engines remain those that the existing apply path already cache-guards.
|
||||
const body = { model: "openai/gpt-5", messages: [{ role: "user", content: "x" }] };
|
||||
const plan = selectCompressionPlan(
|
||||
cfg as any, null, 5_000_000, body, { provider: "openai", model: "openai/gpt-5" }, {}, null,
|
||||
{ modelContextLimit: 200000, requestMaxTokens: 8000 }
|
||||
);
|
||||
// mode is still a valid CompressionMode string after the cache-aware pass
|
||||
assert.ok(typeof plan.mode === "string");
|
||||
assert.ok(plan.stackedPipeline.length > 0);
|
||||
});
|
||||
Reference in New Issue
Block a user