mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Merge pull request #1758 from oyi77/feat/compression-phase6
feat(mcp): Phase 6 MCP Compression Tools + Provider-Aware Caching
This commit is contained in:
@@ -1613,9 +1613,19 @@ export async function handleChatCore({
|
||||
);
|
||||
}
|
||||
}
|
||||
const mode = selectCompressionStrategy(config, compressionComboKey, estimatedTokens);
|
||||
const compressionInputBody = body as Record<string, unknown>;
|
||||
const mode = selectCompressionStrategy(
|
||||
config,
|
||||
compressionComboKey,
|
||||
estimatedTokens,
|
||||
compressionInputBody,
|
||||
{ provider, targetFormat, model: effectiveModel }
|
||||
);
|
||||
if (mode !== "off") {
|
||||
const result = applyCompression(body, mode, { model: effectiveModel, config });
|
||||
const result = applyCompression(compressionInputBody, mode, {
|
||||
model: effectiveModel,
|
||||
config,
|
||||
});
|
||||
if (result.compressed && result.stats) {
|
||||
body = result.body as typeof body;
|
||||
estimatedTokens = result.stats.compressedTokens;
|
||||
@@ -1646,6 +1656,39 @@ export async function handleChatCore({
|
||||
);
|
||||
}
|
||||
})();
|
||||
void (async () => {
|
||||
try {
|
||||
const { detectCachingContext } =
|
||||
await import("../services/compression/cachingAware.ts");
|
||||
const { recordCacheStats } =
|
||||
await import("../../src/lib/db/compressionCacheStats.ts");
|
||||
const cacheContext = detectCachingContext(compressionInputBody, {
|
||||
provider,
|
||||
targetFormat,
|
||||
model: effectiveModel,
|
||||
});
|
||||
const tokensSavedCompression = Math.max(
|
||||
0,
|
||||
result.stats.originalTokens - result.stats.compressedTokens
|
||||
);
|
||||
recordCacheStats({
|
||||
provider: cacheContext.provider ?? provider ?? "unknown",
|
||||
model: effectiveModel ?? "",
|
||||
compressionMode: mode,
|
||||
cacheControlPresent: cacheContext.hasCacheControl,
|
||||
estimatedCacheHit: cacheContext.hasCacheControl && cacheContext.isCachingProvider,
|
||||
tokensSavedCompression,
|
||||
tokensSavedCaching: 0,
|
||||
netSavings: tokensSavedCompression,
|
||||
});
|
||||
} catch (err) {
|
||||
log?.debug?.(
|
||||
"COMPRESSION",
|
||||
"Compression cache stats write skipped: " +
|
||||
(err instanceof Error ? err.message : String(err))
|
||||
);
|
||||
}
|
||||
})();
|
||||
log?.info?.(
|
||||
"COMPRESSION",
|
||||
`Prompt compressed (${mode}): ${result.stats.originalTokens} -> ${result.stats.compressedTokens} tokens (${result.stats.savingsPercent}% saved, techniques: ${result.stats.techniquesUsed.join(",")})`
|
||||
|
||||
@@ -45,7 +45,7 @@ export OMNIROUTE_API_KEY="your-api-key"
|
||||
|
||||
# Optional: Scope enforcement (default: disabled)
|
||||
export OMNIROUTE_MCP_ENFORCE_SCOPES="true"
|
||||
export OMNIROUTE_MCP_SCOPES="read:health,read:combos,read:quota,read:usage,read:models,execute:completions,write:combos,write:budget,write:resilience"
|
||||
export OMNIROUTE_MCP_SCOPES="read:health,read:combos,read:quota,read:usage,read:models,read:cache,read:compression,execute:completions,write:combos,write:budget,write:resilience,write:cache,write:compression"
|
||||
```
|
||||
|
||||
### 2. stdio Transport (IDE Integration)
|
||||
@@ -143,6 +143,15 @@ omniroute --mcp
|
||||
| 15 | `omniroute_explain_route` | `read:health`, `read:usage` | Explain why a request was routed to a provider (scoring factors, fallbacks) |
|
||||
| 16 | `omniroute_get_session_snapshot` | `read:usage` | Full session snapshot: cost, tokens, top models, errors, budget status |
|
||||
|
||||
### Cache and Compression Tools
|
||||
|
||||
| # | Tool | Scopes | Description |
|
||||
| --- | --------------------------------- | ------------------- | ---------------------------------------------------------------------------- |
|
||||
| 21 | `omniroute_cache_stats` | `read:cache` | Semantic cache, prompt-cache, and idempotency statistics |
|
||||
| 22 | `omniroute_cache_flush` | `write:cache` | Flush cache entries globally or by signature/model |
|
||||
| 23 | `omniroute_compression_status` | `read:compression` | Compression settings, analytics summary, and provider-aware cache statistics |
|
||||
| 24 | `omniroute_compression_configure` | `write:compression` | Configure compression mode and trigger thresholds at runtime |
|
||||
|
||||
---
|
||||
|
||||
## Client Examples
|
||||
@@ -530,9 +539,13 @@ The MCP server supports **fine-grained scope enforcement** for multi-tenant envi
|
||||
| `read:quota` | `check_quota` |
|
||||
| `read:usage` | `cost_report`, `explain_route`, `get_session_snapshot` |
|
||||
| `read:models` | `list_models_catalog` |
|
||||
| `read:cache` | `cache_stats` |
|
||||
| `read:compression` | `compression_status` |
|
||||
| `write:combos` | `switch_combo` |
|
||||
| `write:budget` | `set_budget_guard` |
|
||||
| `write:resilience` | `set_resilience_profile` |
|
||||
| `write:cache` | `cache_flush` |
|
||||
| `write:compression` | `compression_configure` |
|
||||
| `execute:completions` | `route_request`, `test_combo` |
|
||||
|
||||
**Wildcard scopes:** Use `read:*` to grant all read scopes, or `*` for full access.
|
||||
|
||||
@@ -991,6 +991,87 @@ export const cacheFlushTool: McpToolDefinition<typeof cacheFlushInput, typeof ca
|
||||
sourceEndpoints: ["/api/cache"],
|
||||
};
|
||||
|
||||
// ============ Compression Tools ============
|
||||
|
||||
export const compressionStatusInput = z.object({}).describe("No parameters required");
|
||||
|
||||
export const compressionStatusOutput = z.object({
|
||||
enabled: z.boolean(),
|
||||
strategy: z.string(),
|
||||
settings: z.object({
|
||||
maxTokens: z.number(),
|
||||
targetRatio: z.number(),
|
||||
aggressiveness: z.string(),
|
||||
}),
|
||||
analytics: z.object({
|
||||
totalRequests: z.number(),
|
||||
compressedRequests: z.number(),
|
||||
tokensSaved: z.number(),
|
||||
avgCompressionRatio: z.number(),
|
||||
}),
|
||||
cacheStats: z
|
||||
.object({
|
||||
hits: z.number(),
|
||||
misses: z.number(),
|
||||
hitRate: z.string(),
|
||||
tokensSaved: z.number(),
|
||||
})
|
||||
.nullable(),
|
||||
});
|
||||
|
||||
export const compressionStatusTool: McpToolDefinition<
|
||||
typeof compressionStatusInput,
|
||||
typeof compressionStatusOutput
|
||||
> = {
|
||||
name: "omniroute_compression_status",
|
||||
description:
|
||||
"Returns current compression configuration, strategy, analytics summary (requests compressed, tokens saved, avg ratio), and provider-aware cache statistics.",
|
||||
inputSchema: compressionStatusInput,
|
||||
outputSchema: compressionStatusOutput,
|
||||
scopes: ["read:compression"],
|
||||
auditLevel: "basic",
|
||||
phase: 2,
|
||||
sourceEndpoints: ["/api/compression/status"],
|
||||
};
|
||||
|
||||
export const compressionConfigureInput = z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
strategy: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Compression strategy: 'none' | 'standard' | 'aggressive' | 'ultra'"),
|
||||
maxTokens: z.number().optional().describe("Maximum tokens before compression triggers"),
|
||||
targetRatio: z.number().optional().describe("Target compression ratio (0.0–1.0)"),
|
||||
aggressiveness: z.string().optional().describe("Aggressiveness level: 'low' | 'medium' | 'high'"),
|
||||
});
|
||||
|
||||
export const compressionConfigureOutput = z.object({
|
||||
success: z.boolean(),
|
||||
updated: z.record(z.string(), z.unknown()),
|
||||
settings: z.object({
|
||||
enabled: z.boolean(),
|
||||
strategy: z.string(),
|
||||
maxTokens: z.number(),
|
||||
targetRatio: z.number(),
|
||||
aggressiveness: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const compressionConfigureTool: McpToolDefinition<
|
||||
typeof compressionConfigureInput,
|
||||
typeof compressionConfigureOutput
|
||||
> = {
|
||||
name: "omniroute_compression_configure",
|
||||
description:
|
||||
"Configure compression settings at runtime. Supports enabling/disabling compression, changing strategy (none/standard/aggressive/ultra), adjusting maxTokens threshold, targetRatio, and aggressiveness level.",
|
||||
inputSchema: compressionConfigureInput,
|
||||
outputSchema: compressionConfigureOutput,
|
||||
scopes: ["write:compression"],
|
||||
auditLevel: "full",
|
||||
phase: 2,
|
||||
sourceEndpoints: ["/api/compression/configure"],
|
||||
};
|
||||
|
||||
// ============ Tool Registry ============
|
||||
|
||||
/** All MCP tool definitions, ordered by phase then name */
|
||||
@@ -1017,6 +1098,8 @@ export const MCP_TOOLS = [
|
||||
syncPricingTool,
|
||||
cacheStatsTool,
|
||||
cacheFlushTool,
|
||||
compressionStatusTool,
|
||||
compressionConfigureTool,
|
||||
] as const;
|
||||
|
||||
/** Essential tools only (Phase 1) */
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
} from "./tools/advancedTools.ts";
|
||||
import { memoryTools } from "./tools/memoryTools.ts";
|
||||
import { skillTools } from "./tools/skillTools.ts";
|
||||
import { compressionTools } from "./tools/compressionTools.ts";
|
||||
import { normalizeQuotaResponse } from "../../src/shared/contracts/quota.ts";
|
||||
import { resolveOmniRouteBaseUrl } from "../../src/shared/utils/resolveOmniRouteBaseUrl.ts";
|
||||
|
||||
@@ -877,6 +878,29 @@ export function createMcpServer(): McpServer {
|
||||
);
|
||||
});
|
||||
|
||||
// ── Compression Tools ─────────────────────────
|
||||
Object.values(compressionTools).forEach((toolDef) => {
|
||||
server.registerTool(
|
||||
toolDef.name,
|
||||
{
|
||||
description: toolDef.description,
|
||||
// @ts-ignore: dynamic zod access
|
||||
inputSchema: toolDef.inputSchema,
|
||||
},
|
||||
withScopeEnforcement(toolDef.name, async (args) => {
|
||||
try {
|
||||
const parsedArgs = toolDef.inputSchema.parse(args ?? {});
|
||||
// @ts-ignore: handler expected specific object
|
||||
const result = await toolDef.handler(parsedArgs);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
return { content: [{ type: "text" as const, text: `Error: ${msg}` }], isError: true };
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
185
open-sse/mcp-server/tools/compressionTools.ts
Normal file
185
open-sse/mcp-server/tools/compressionTools.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* OmniRoute MCP Compression Tools — Manage and monitor prompt compression.
|
||||
*
|
||||
* Tools:
|
||||
* 1. omniroute_compression_status — Get compression config, analytics, and cache stats
|
||||
* 2. omniroute_compression_configure — Update compression settings
|
||||
*/
|
||||
|
||||
import { logToolCall } from "../audit.ts";
|
||||
import {
|
||||
getCompressionSettings,
|
||||
updateCompressionSettings,
|
||||
} from "../../../src/lib/db/compression.ts";
|
||||
import { getCompressionAnalyticsSummary } from "../../../src/lib/db/compressionAnalytics.ts";
|
||||
import { getCacheStatsSummary } from "../../../src/lib/db/compressionCacheStats.ts";
|
||||
import type { McpToolExtraLike } from "../scopeEnforcement.ts";
|
||||
|
||||
/**
|
||||
* Handle compression_status tool: return current compression config, analytics, and cache stats
|
||||
*/
|
||||
export async function handleCompressionStatus(
|
||||
args: Record<string, never>,
|
||||
extra?: McpToolExtraLike
|
||||
): Promise<{
|
||||
enabled: boolean;
|
||||
strategy: string;
|
||||
settings: {
|
||||
maxTokens: number;
|
||||
targetRatio: number;
|
||||
aggressiveness: string;
|
||||
};
|
||||
analytics: {
|
||||
totalRequests: number;
|
||||
compressedRequests: number;
|
||||
tokensSaved: number;
|
||||
avgCompressionRatio: number;
|
||||
};
|
||||
cacheStats: {
|
||||
hits: number;
|
||||
misses: number;
|
||||
hitRate: string;
|
||||
tokensSaved: number;
|
||||
} | null;
|
||||
}> {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const settings = await getCompressionSettings();
|
||||
const analyticsSummary = getCompressionAnalyticsSummary();
|
||||
const cacheStats = getCacheStatsSummary();
|
||||
|
||||
const result = {
|
||||
enabled: settings.enabled,
|
||||
strategy: settings.defaultMode || "standard",
|
||||
settings: {
|
||||
maxTokens: settings.autoTriggerTokens,
|
||||
targetRatio: 0.7, // Default target ratio
|
||||
aggressiveness: settings.defaultMode || "standard",
|
||||
},
|
||||
analytics: {
|
||||
totalRequests: analyticsSummary.totalRequests,
|
||||
compressedRequests: analyticsSummary.byMode?.standard?.count || 0,
|
||||
tokensSaved: analyticsSummary.totalTokensSaved,
|
||||
avgCompressionRatio: analyticsSummary.byMode?.standard?.avgSavingsPct || 0,
|
||||
},
|
||||
cacheStats: cacheStats
|
||||
? {
|
||||
hits: Math.round(cacheStats.cacheHitRate * (cacheStats.totalRequests || 1)),
|
||||
misses: Math.round((1 - cacheStats.cacheHitRate) * (cacheStats.totalRequests || 1)),
|
||||
hitRate: `${(cacheStats.cacheHitRate * 100).toFixed(2)}%`,
|
||||
tokensSaved: Math.round(cacheStats.avgNetSavings),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
const duration = Date.now() - start;
|
||||
await logToolCall("omniroute_compression_status", args, result, duration, true);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - start;
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
await logToolCall(
|
||||
"omniroute_compression_status",
|
||||
args,
|
||||
{ error: errorMessage },
|
||||
duration,
|
||||
false,
|
||||
"ERROR"
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle compression_configure tool: update compression settings
|
||||
*/
|
||||
export async function handleCompressionConfigure(
|
||||
args: {
|
||||
enabled?: boolean;
|
||||
strategy?: string;
|
||||
maxTokens?: number;
|
||||
targetRatio?: number;
|
||||
aggressiveness?: string;
|
||||
},
|
||||
extra?: McpToolExtraLike
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
updated: Record<string, unknown>;
|
||||
settings: {
|
||||
enabled: boolean;
|
||||
strategy: string;
|
||||
maxTokens: number;
|
||||
targetRatio: number;
|
||||
aggressiveness: string;
|
||||
};
|
||||
}> {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const updates: Record<string, unknown> = {};
|
||||
|
||||
if (args.enabled !== undefined) {
|
||||
updates.enabled = args.enabled;
|
||||
}
|
||||
if (args.strategy !== undefined) {
|
||||
updates.defaultMode = args.strategy;
|
||||
}
|
||||
if (args.maxTokens !== undefined) {
|
||||
updates.autoTriggerTokens = args.maxTokens;
|
||||
}
|
||||
if (args.aggressiveness !== undefined) {
|
||||
updates.defaultMode = args.aggressiveness;
|
||||
}
|
||||
|
||||
const settings = await updateCompressionSettings(updates);
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
updated: updates,
|
||||
settings: {
|
||||
enabled: settings.enabled,
|
||||
strategy: settings.defaultMode || "standard",
|
||||
maxTokens: settings.autoTriggerTokens,
|
||||
targetRatio: 0.7, // Default target ratio
|
||||
aggressiveness: settings.defaultMode || "standard",
|
||||
},
|
||||
};
|
||||
|
||||
const duration = Date.now() - start;
|
||||
await logToolCall("omniroute_compression_configure", args, result, duration, true);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - start;
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
await logToolCall(
|
||||
"omniroute_compression_configure",
|
||||
args,
|
||||
{ error: errorMessage },
|
||||
duration,
|
||||
false,
|
||||
"ERROR"
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
import { z } from "zod";
|
||||
import { compressionStatusInput, compressionConfigureInput } from "../schemas/tools.ts";
|
||||
|
||||
export const compressionTools = {
|
||||
omniroute_compression_status: {
|
||||
name: "omniroute_compression_status",
|
||||
description:
|
||||
"Returns current compression configuration, strategy, analytics summary (requests compressed, tokens saved, avg ratio), and provider-aware cache statistics.",
|
||||
inputSchema: compressionStatusInput,
|
||||
handler: (args: z.infer<typeof compressionStatusInput>) => handleCompressionStatus(args),
|
||||
},
|
||||
omniroute_compression_configure: {
|
||||
name: "omniroute_compression_configure",
|
||||
description:
|
||||
"Configure compression settings at runtime. Supports enabling/disabling compression, changing strategy (none/standard/aggressive/ultra), adjusting maxTokens threshold, targetRatio, and aggressiveness level.",
|
||||
inputSchema: compressionConfigureInput,
|
||||
handler: (args: z.infer<typeof compressionConfigureInput>) => handleCompressionConfigure(args),
|
||||
},
|
||||
};
|
||||
124
open-sse/services/compression/cachingAware.ts
Normal file
124
open-sse/services/compression/cachingAware.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Cache-aware strategy selection for AI model compression.
|
||||
* Implements logic to detect caching context from request body and adjust strategies accordingly.
|
||||
*
|
||||
* @file cachingAware.ts
|
||||
* @exports CachingContext, CacheAwareStrategy, detectCachingContext, getCacheAwareStrategy
|
||||
*/
|
||||
|
||||
import { providerSupportsCaching } from "../../utils/cacheControlPolicy.ts";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
export interface CachingDetectionContext {
|
||||
provider?: string | null;
|
||||
targetFormat?: string | null;
|
||||
model?: string | null;
|
||||
}
|
||||
|
||||
export interface CachingContext {
|
||||
hasCacheControl: boolean;
|
||||
provider: string | null;
|
||||
targetFormat: string | null;
|
||||
isCachingProvider: boolean;
|
||||
}
|
||||
|
||||
export interface CacheAwareStrategy {
|
||||
strategy: string;
|
||||
skipSystemPrompt: boolean;
|
||||
deterministicOnly: boolean;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is JsonRecord {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizeString(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||
}
|
||||
|
||||
function inferProviderFromModel(model: unknown): string | null {
|
||||
const normalized = normalizeString(model);
|
||||
if (!normalized) return null;
|
||||
const slashIndex = normalized.indexOf("/");
|
||||
if (slashIndex <= 0) return null;
|
||||
return normalized.slice(0, slashIndex).toLowerCase();
|
||||
}
|
||||
|
||||
function hasOwnCacheControl(value: unknown): boolean {
|
||||
return isRecord(value) && value.cache_control !== undefined && value.cache_control !== null;
|
||||
}
|
||||
|
||||
function arrayHasCacheControl(values: unknown): boolean {
|
||||
return Array.isArray(values) && values.some((value) => hasCacheControl(value));
|
||||
}
|
||||
|
||||
function hasCacheControl(value: unknown): boolean {
|
||||
if (hasOwnCacheControl(value)) return true;
|
||||
if (Array.isArray(value)) return value.some((item) => hasCacheControl(item));
|
||||
if (!isRecord(value)) return false;
|
||||
|
||||
if (arrayHasCacheControl(value.system)) return true;
|
||||
if (arrayHasCacheControl(value.tools)) return true;
|
||||
if (arrayHasCacheControl(value.messages)) return true;
|
||||
if (arrayHasCacheControl(value.input)) return true;
|
||||
if (arrayHasCacheControl(value.contents)) return true;
|
||||
|
||||
if (isRecord(value.request) && arrayHasCacheControl(value.request.contents)) return true;
|
||||
if (isRecord(value.content) || Array.isArray(value.content)) {
|
||||
return hasCacheControl(value.content);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the caching context from the request body.
|
||||
*
|
||||
* @param body - The request body to analyze
|
||||
* @param context - Explicit provider/format/model metadata from the routing pipeline
|
||||
* @returns A CachingContext object
|
||||
*/
|
||||
export function detectCachingContext(
|
||||
body: unknown,
|
||||
context: CachingDetectionContext = {}
|
||||
): CachingContext {
|
||||
const bodyRecord = isRecord(body) ? body : {};
|
||||
const provider =
|
||||
normalizeString(context.provider)?.toLowerCase() ??
|
||||
inferProviderFromModel(context.model) ??
|
||||
inferProviderFromModel(bodyRecord.model);
|
||||
const targetFormat = normalizeString(context.targetFormat)?.toLowerCase() ?? null;
|
||||
|
||||
return {
|
||||
hasCacheControl: hasCacheControl(body),
|
||||
provider,
|
||||
targetFormat,
|
||||
isCachingProvider: providerSupportsCaching(provider, targetFormat),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cache-aware strategy based on the given strategy and caching context.
|
||||
*
|
||||
* @param strategy - The initial strategy (e.g., "aggressive", "ultra", etc.)
|
||||
* @param ctx - The caching context
|
||||
* @returns A CacheAwareStrategy object
|
||||
*/
|
||||
export function getCacheAwareStrategy(strategy: string, ctx: CachingContext): CacheAwareStrategy {
|
||||
if (ctx.isCachingProvider && ctx.hasCacheControl) {
|
||||
// Adjust strategy for caching providers with cache control
|
||||
return {
|
||||
strategy: ["aggressive", "ultra"].includes(strategy) ? "standard" : strategy,
|
||||
skipSystemPrompt: true,
|
||||
deterministicOnly: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Return the original strategy with no modifications
|
||||
return {
|
||||
strategy,
|
||||
skipSystemPrompt: false,
|
||||
deterministicOnly: false,
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,11 @@ import { cavemanCompress } from "./caveman.ts";
|
||||
import { compressAggressive } from "./aggressive.ts";
|
||||
import { ultraCompress } from "./ultra.ts";
|
||||
import { createCompressionStats } from "./stats.ts";
|
||||
import {
|
||||
detectCachingContext,
|
||||
getCacheAwareStrategy,
|
||||
type CachingDetectionContext,
|
||||
} from "./cachingAware.ts";
|
||||
|
||||
export function checkComboOverride(
|
||||
config: CompressionConfig,
|
||||
@@ -35,9 +40,20 @@ export function getEffectiveMode(
|
||||
export function selectCompressionStrategy(
|
||||
config: CompressionConfig,
|
||||
comboId: string | null,
|
||||
estimatedTokens: number
|
||||
estimatedTokens: number,
|
||||
body?: Record<string, unknown>,
|
||||
context?: CachingDetectionContext
|
||||
): CompressionMode {
|
||||
return getEffectiveMode(config, comboId, estimatedTokens);
|
||||
const selectedMode = getEffectiveMode(config, comboId, estimatedTokens);
|
||||
|
||||
// Apply caching-aware adjustments if body is provided
|
||||
if (body) {
|
||||
const ctx = detectCachingContext(body, context);
|
||||
const cacheAware = getCacheAwareStrategy(selectedMode, ctx);
|
||||
return cacheAware.strategy as CompressionMode;
|
||||
}
|
||||
|
||||
return selectedMode;
|
||||
}
|
||||
|
||||
export function applyCompression(
|
||||
|
||||
112
src/lib/db/compressionCacheStats.ts
Normal file
112
src/lib/db/compressionCacheStats.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { getDbInstance } from "./core";
|
||||
|
||||
export interface CacheStatsEntry {
|
||||
provider: string;
|
||||
model?: string;
|
||||
compressionMode: string;
|
||||
cacheControlPresent: boolean;
|
||||
estimatedCacheHit: boolean;
|
||||
tokensSavedCompression: number;
|
||||
tokensSavedCaching: number;
|
||||
netSavings: number;
|
||||
}
|
||||
|
||||
export interface CacheStatsSummary {
|
||||
totalRequests: number;
|
||||
avgNetSavings: number;
|
||||
cacheHitRate: number;
|
||||
byProvider: Record<string, { count: number; avgNetSavings: number; cacheHitRate: number }>;
|
||||
}
|
||||
|
||||
export function recordCacheStats(entry: CacheStatsEntry): void {
|
||||
const db = getDbInstance();
|
||||
|
||||
const sql = `INSERT INTO compression_cache_stats (
|
||||
provider,
|
||||
model,
|
||||
compression_mode,
|
||||
cache_control_present,
|
||||
estimated_cache_hit,
|
||||
tokens_saved_compression,
|
||||
tokens_saved_caching,
|
||||
net_savings
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||
|
||||
db.prepare(sql).run(
|
||||
entry.provider,
|
||||
entry.model ?? "",
|
||||
entry.compressionMode,
|
||||
entry.cacheControlPresent ? 1 : 0,
|
||||
entry.estimatedCacheHit ? 1 : 0,
|
||||
entry.tokensSavedCompression,
|
||||
entry.tokensSavedCaching,
|
||||
entry.netSavings
|
||||
);
|
||||
}
|
||||
|
||||
export function getCacheStatsSummary(since?: Date): CacheStatsSummary {
|
||||
const db = getDbInstance();
|
||||
const whereClause = since ? "WHERE created_at >= ?" : "";
|
||||
const params = since ? [since.toISOString()] : [];
|
||||
|
||||
// Global aggregates
|
||||
const globalRow = since
|
||||
? (db
|
||||
.prepare(
|
||||
`SELECT COUNT(*) as totalRequests, AVG(net_savings) as avgNetSavings, SUM(estimated_cache_hit) * 1.0 / COUNT(*) as cacheHitRate FROM compression_cache_stats WHERE created_at >= ?`
|
||||
)
|
||||
.get(since.toISOString()) as
|
||||
| { totalRequests: number; avgNetSavings: number; cacheHitRate: number }
|
||||
| undefined)
|
||||
: (db
|
||||
.prepare(
|
||||
`SELECT COUNT(*) as totalRequests, AVG(net_savings) as avgNetSavings, SUM(estimated_cache_hit) * 1.0 / COUNT(*) as cacheHitRate FROM compression_cache_stats`
|
||||
)
|
||||
.get() as
|
||||
| { totalRequests: number; avgNetSavings: number; cacheHitRate: number }
|
||||
| undefined);
|
||||
|
||||
if (!globalRow || globalRow.totalRequests === 0) {
|
||||
return { totalRequests: 0, avgNetSavings: 0, cacheHitRate: 0, byProvider: {} };
|
||||
}
|
||||
|
||||
// Per-provider aggregates
|
||||
const providerRows = since
|
||||
? (db
|
||||
.prepare(
|
||||
`SELECT provider, COUNT(*) as count, AVG(net_savings) as avgNetSavings, SUM(estimated_cache_hit) * 1.0 / COUNT(*) as cacheHitRate FROM compression_cache_stats WHERE created_at >= ? GROUP BY provider`
|
||||
)
|
||||
.all(since.toISOString()) as Array<{
|
||||
provider: string;
|
||||
count: number;
|
||||
avgNetSavings: number;
|
||||
cacheHitRate: number;
|
||||
}>)
|
||||
: (db
|
||||
.prepare(
|
||||
`SELECT provider, COUNT(*) as count, AVG(net_savings) as avgNetSavings, SUM(estimated_cache_hit) * 1.0 / COUNT(*) as cacheHitRate FROM compression_cache_stats GROUP BY provider`
|
||||
)
|
||||
.all() as Array<{
|
||||
provider: string;
|
||||
count: number;
|
||||
avgNetSavings: number;
|
||||
cacheHitRate: number;
|
||||
}>);
|
||||
|
||||
const byProvider: Record<string, { count: number; avgNetSavings: number; cacheHitRate: number }> =
|
||||
{};
|
||||
for (const row of providerRows) {
|
||||
byProvider[row.provider] = {
|
||||
count: row.count,
|
||||
avgNetSavings: row.avgNetSavings,
|
||||
cacheHitRate: row.cacheHitRate,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
totalRequests: globalRow.totalRequests,
|
||||
avgNetSavings: globalRow.avgNetSavings ?? 0,
|
||||
cacheHitRate: globalRow.cacheHitRate ?? 0,
|
||||
byProvider,
|
||||
};
|
||||
}
|
||||
15
src/lib/db/migrations/039_compression_cache_stats.sql
Normal file
15
src/lib/db/migrations/039_compression_cache_stats.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- Phase 6 compression cache statistics.
|
||||
CREATE TABLE IF NOT EXISTS compression_cache_stats (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
provider TEXT NOT NULL,
|
||||
model TEXT NOT NULL DEFAULT '',
|
||||
compression_mode TEXT NOT NULL,
|
||||
cache_control_present INTEGER NOT NULL DEFAULT 0,
|
||||
estimated_cache_hit INTEGER NOT NULL DEFAULT 0,
|
||||
tokens_saved_compression INTEGER NOT NULL DEFAULT 0,
|
||||
tokens_saved_caching INTEGER NOT NULL DEFAULT 0,
|
||||
net_savings INTEGER NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_compression_cache_stats_provider ON compression_cache_stats(provider);
|
||||
CREATE INDEX IF NOT EXISTS idx_compression_cache_stats_created ON compression_cache_stats(created_at);
|
||||
@@ -79,6 +79,8 @@ export {
|
||||
deleteCombo,
|
||||
} from "./db/combos";
|
||||
|
||||
export * from "./db/compressionCacheStats";
|
||||
|
||||
export {
|
||||
// API Keys
|
||||
getApiKeys,
|
||||
|
||||
@@ -19,6 +19,11 @@ export const MCP_SCOPE_LIST = [
|
||||
"execute:search",
|
||||
"write:budget",
|
||||
"write:resilience",
|
||||
"pricing:write",
|
||||
"read:cache",
|
||||
"write:cache",
|
||||
"read:compression",
|
||||
"write:compression",
|
||||
] as const;
|
||||
|
||||
export type McpScope = (typeof MCP_SCOPE_LIST)[number];
|
||||
@@ -48,6 +53,11 @@ export const MCP_TOOL_SCOPES: Record<string, readonly McpScope[]> = {
|
||||
omniroute_explain_route: ["read:health", "read:usage"],
|
||||
omniroute_get_session_snapshot: ["read:usage"],
|
||||
omniroute_db_health_check: ["read:health", "write:resilience"],
|
||||
omniroute_sync_pricing: ["pricing:write"],
|
||||
omniroute_cache_stats: ["read:cache"],
|
||||
omniroute_cache_flush: ["write:cache"],
|
||||
omniroute_compression_status: ["read:compression"],
|
||||
omniroute_compression_configure: ["write:compression"],
|
||||
} as const;
|
||||
|
||||
// ============ Scope Groups ============
|
||||
@@ -61,13 +71,21 @@ export const MCP_SCOPE_PRESETS = {
|
||||
"read:quota",
|
||||
"read:usage",
|
||||
"read:models",
|
||||
"read:cache",
|
||||
"read:compression",
|
||||
] as const satisfies readonly McpScope[],
|
||||
|
||||
/** Full access including writes and execution */
|
||||
full: [...MCP_SCOPE_LIST] as McpScope[],
|
||||
|
||||
/** Monitoring only — health and metrics */
|
||||
monitor: ["read:health", "read:quota", "read:usage"] as const satisfies readonly McpScope[],
|
||||
monitor: [
|
||||
"read:health",
|
||||
"read:quota",
|
||||
"read:usage",
|
||||
"read:cache",
|
||||
"read:compression",
|
||||
] as const satisfies readonly McpScope[],
|
||||
|
||||
/** Agent — can execute completions and read state */
|
||||
agent: [
|
||||
@@ -76,6 +94,8 @@ export const MCP_SCOPE_PRESETS = {
|
||||
"read:quota",
|
||||
"read:usage",
|
||||
"read:models",
|
||||
"read:cache",
|
||||
"read:compression",
|
||||
"execute:completions",
|
||||
"execute:search",
|
||||
] as const satisfies readonly McpScope[],
|
||||
|
||||
151
tests/unit/compression/cachingAware.test.ts
Normal file
151
tests/unit/compression/cachingAware.test.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Unit tests for open-sse/services/compression/cachingAware.ts
|
||||
*/
|
||||
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
detectCachingContext,
|
||||
getCacheAwareStrategy,
|
||||
} from "../../../open-sse/services/compression/cachingAware.ts";
|
||||
|
||||
describe("detectCachingContext", () => {
|
||||
it("returns hasCacheControl=true when body has cache_control", () => {
|
||||
const ctx = detectCachingContext({ cache_control: { type: "ephemeral" } });
|
||||
assert.equal(ctx.hasCacheControl, true);
|
||||
});
|
||||
|
||||
it("returns hasCacheControl=false when body has no cache_control", () => {
|
||||
const ctx = detectCachingContext({ model: "anthropic/claude-3" });
|
||||
assert.equal(ctx.hasCacheControl, false);
|
||||
});
|
||||
|
||||
it("extracts anthropic provider from model string", () => {
|
||||
const ctx = detectCachingContext({ model: "anthropic/claude-3-sonnet" });
|
||||
assert.equal(ctx.provider, "anthropic");
|
||||
assert.equal(ctx.isCachingProvider, true);
|
||||
});
|
||||
|
||||
it("extracts openai provider from model string", () => {
|
||||
const ctx = detectCachingContext({ model: "openai/gpt-4o" });
|
||||
assert.equal(ctx.provider, "openai");
|
||||
assert.equal(ctx.isCachingProvider, false);
|
||||
});
|
||||
|
||||
it("extracts google provider from model string", () => {
|
||||
const ctx = detectCachingContext({ model: "google/gemini-pro" });
|
||||
assert.equal(ctx.provider, "google");
|
||||
assert.equal(ctx.isCachingProvider, false);
|
||||
});
|
||||
|
||||
it("keeps provider prefix and applies the shared caching policy", () => {
|
||||
const ctx = detectCachingContext({ model: "deepseek/deepseek-chat" });
|
||||
assert.equal(ctx.provider, "deepseek");
|
||||
assert.equal(ctx.isCachingProvider, true);
|
||||
});
|
||||
|
||||
it("handles null/undefined body gracefully", () => {
|
||||
const ctx = detectCachingContext(null);
|
||||
assert.equal(ctx.hasCacheControl, false);
|
||||
assert.equal(ctx.provider, null);
|
||||
assert.equal(ctx.isCachingProvider, false);
|
||||
});
|
||||
|
||||
it("handles empty object body", () => {
|
||||
const ctx = detectCachingContext({});
|
||||
assert.equal(ctx.hasCacheControl, false);
|
||||
assert.equal(ctx.provider, null);
|
||||
assert.equal(ctx.isCachingProvider, false);
|
||||
});
|
||||
|
||||
it("detects cache_control in Claude message content blocks", () => {
|
||||
const ctx = detectCachingContext(
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "cached", cache_control: { type: "ephemeral" } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ provider: "anthropic", targetFormat: "claude" }
|
||||
);
|
||||
|
||||
assert.equal(ctx.hasCacheControl, true);
|
||||
assert.equal(ctx.provider, "anthropic");
|
||||
assert.equal(ctx.targetFormat, "claude");
|
||||
assert.equal(ctx.isCachingProvider, true);
|
||||
});
|
||||
|
||||
it("detects cache_control in Claude tools", () => {
|
||||
const ctx = detectCachingContext(
|
||||
{
|
||||
tools: [{ name: "lookup", cache_control: { type: "ephemeral" } }],
|
||||
},
|
||||
{ provider: "qwen", targetFormat: "claude" }
|
||||
);
|
||||
|
||||
assert.equal(ctx.hasCacheControl, true);
|
||||
assert.equal(ctx.isCachingProvider, true);
|
||||
});
|
||||
|
||||
it("prefers explicit provider context over the body model prefix", () => {
|
||||
const ctx = detectCachingContext(
|
||||
{ model: "openai/gpt-4o", cache_control: { type: "ephemeral" } },
|
||||
{ provider: "anthropic", targetFormat: "claude", model: "claude-3-5-sonnet" }
|
||||
);
|
||||
|
||||
assert.equal(ctx.provider, "anthropic");
|
||||
assert.equal(ctx.isCachingProvider, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCacheAwareStrategy", () => {
|
||||
it("downgrades aggressive to standard for caching provider with cache_control", () => {
|
||||
const ctx = { hasCacheControl: true, provider: "anthropic", isCachingProvider: true };
|
||||
const result = getCacheAwareStrategy("aggressive", ctx);
|
||||
assert.equal(result.strategy, "standard");
|
||||
assert.equal(result.skipSystemPrompt, true);
|
||||
assert.equal(result.deterministicOnly, true);
|
||||
});
|
||||
|
||||
it("downgrades ultra to standard for caching provider with cache_control", () => {
|
||||
const ctx = { hasCacheControl: true, provider: "openai", isCachingProvider: true };
|
||||
const result = getCacheAwareStrategy("ultra", ctx);
|
||||
assert.equal(result.strategy, "standard");
|
||||
assert.equal(result.skipSystemPrompt, true);
|
||||
assert.equal(result.deterministicOnly, true);
|
||||
});
|
||||
|
||||
it("keeps standard strategy unchanged for caching provider with cache_control", () => {
|
||||
const ctx = { hasCacheControl: true, provider: "anthropic", isCachingProvider: true };
|
||||
const result = getCacheAwareStrategy("standard", ctx);
|
||||
assert.equal(result.strategy, "standard");
|
||||
assert.equal(result.skipSystemPrompt, true);
|
||||
assert.equal(result.deterministicOnly, true);
|
||||
});
|
||||
|
||||
it("keeps strategy unchanged for non-caching provider", () => {
|
||||
const ctx = { hasCacheControl: true, provider: "deepseek", isCachingProvider: false };
|
||||
const result = getCacheAwareStrategy("aggressive", ctx);
|
||||
assert.equal(result.strategy, "aggressive");
|
||||
assert.equal(result.skipSystemPrompt, false);
|
||||
assert.equal(result.deterministicOnly, false);
|
||||
});
|
||||
|
||||
it("keeps strategy unchanged when no cache_control even for caching provider", () => {
|
||||
const ctx = { hasCacheControl: false, provider: "anthropic", isCachingProvider: true };
|
||||
const result = getCacheAwareStrategy("aggressive", ctx);
|
||||
assert.equal(result.strategy, "aggressive");
|
||||
assert.equal(result.skipSystemPrompt, false);
|
||||
assert.equal(result.deterministicOnly, false);
|
||||
});
|
||||
|
||||
it("returns none strategy unchanged", () => {
|
||||
const ctx = { hasCacheControl: false, provider: null, isCachingProvider: false };
|
||||
const result = getCacheAwareStrategy("none", ctx);
|
||||
assert.equal(result.strategy, "none");
|
||||
assert.equal(result.skipSystemPrompt, false);
|
||||
assert.equal(result.deterministicOnly, false);
|
||||
});
|
||||
});
|
||||
109
tests/unit/compression/compressionMcpTools.test.ts
Normal file
109
tests/unit/compression/compressionMcpTools.test.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { describe, it, before } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const tmpDir = mkdtempSync(join(tmpdir(), "omniroute-mcp-test-"));
|
||||
process.env.DATA_DIR = tmpDir;
|
||||
|
||||
const { handleCompressionStatus, handleCompressionConfigure } =
|
||||
await import("../../../open-sse/mcp-server/tools/compressionTools.ts");
|
||||
const { compressionConfigureTool, compressionStatusTool } =
|
||||
await import("../../../open-sse/mcp-server/schemas/tools.ts");
|
||||
|
||||
describe("compression MCP tool schemas", () => {
|
||||
it("uses canonical read/write compression scopes", () => {
|
||||
assert.deepEqual(compressionStatusTool.scopes, ["read:compression"]);
|
||||
assert.deepEqual(compressionConfigureTool.scopes, ["write:compression"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleCompressionStatus", () => {
|
||||
it("returns an object with enabled field", async () => {
|
||||
const result = await handleCompressionStatus({});
|
||||
assert.ok("enabled" in result);
|
||||
assert.equal(typeof result.enabled, "boolean");
|
||||
});
|
||||
|
||||
it("returns strategy string", async () => {
|
||||
const result = await handleCompressionStatus({});
|
||||
assert.equal(typeof result.strategy, "string");
|
||||
});
|
||||
|
||||
it("returns settings with maxTokens number", async () => {
|
||||
const result = await handleCompressionStatus({});
|
||||
assert.ok("settings" in result);
|
||||
assert.equal(typeof result.settings.maxTokens, "number");
|
||||
});
|
||||
|
||||
it("returns settings with targetRatio 0.7", async () => {
|
||||
const result = await handleCompressionStatus({});
|
||||
assert.equal(result.settings.targetRatio, 0.7);
|
||||
});
|
||||
|
||||
it("returns analytics with totalRequests number", async () => {
|
||||
const result = await handleCompressionStatus({});
|
||||
assert.ok("analytics" in result);
|
||||
assert.equal(typeof result.analytics.totalRequests, "number");
|
||||
});
|
||||
|
||||
it("returns analytics with tokensSaved number", async () => {
|
||||
const result = await handleCompressionStatus({});
|
||||
assert.equal(typeof result.analytics.tokensSaved, "number");
|
||||
});
|
||||
|
||||
it("returns cacheStats as null or object", async () => {
|
||||
const result = await handleCompressionStatus({});
|
||||
assert.ok(result.cacheStats === null || typeof result.cacheStats === "object");
|
||||
});
|
||||
|
||||
it("returns analytics compressedRequests as number", async () => {
|
||||
const result = await handleCompressionStatus({});
|
||||
assert.equal(typeof result.analytics.compressedRequests, "number");
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleCompressionConfigure", () => {
|
||||
it("returns success=true when called with empty args", async () => {
|
||||
const result = await handleCompressionConfigure({});
|
||||
assert.equal(result.success, true);
|
||||
});
|
||||
|
||||
it("returns settings object after configure", async () => {
|
||||
const result = await handleCompressionConfigure({});
|
||||
assert.ok("settings" in result);
|
||||
assert.equal(typeof result.settings.enabled, "boolean");
|
||||
});
|
||||
|
||||
it("returns updated object", async () => {
|
||||
const result = await handleCompressionConfigure({ enabled: true });
|
||||
assert.ok("updated" in result);
|
||||
});
|
||||
|
||||
it("sets enabled=false and returns success", async () => {
|
||||
const result = await handleCompressionConfigure({ enabled: false });
|
||||
assert.equal(result.success, true);
|
||||
});
|
||||
|
||||
it("sets strategy and returns success", async () => {
|
||||
const result = await handleCompressionConfigure({ strategy: "aggressive" });
|
||||
assert.equal(result.success, true);
|
||||
});
|
||||
|
||||
it("sets maxTokens and returns success", async () => {
|
||||
const result = await handleCompressionConfigure({ maxTokens: 8000 });
|
||||
assert.equal(result.success, true);
|
||||
assert.ok("updated" in result);
|
||||
});
|
||||
|
||||
it("returns settings.maxTokens as number", async () => {
|
||||
const result = await handleCompressionConfigure({ maxTokens: 2000 });
|
||||
assert.equal(typeof result.settings.maxTokens, "number");
|
||||
});
|
||||
|
||||
it("returns settings.targetRatio as 0.7", async () => {
|
||||
const result = await handleCompressionConfigure({});
|
||||
assert.equal(result.settings.targetRatio, 0.7);
|
||||
});
|
||||
});
|
||||
@@ -98,6 +98,27 @@ describe("selectCompressionStrategy", () => {
|
||||
it("returns effective mode", () => {
|
||||
assert.equal(selectCompressionStrategy(baseConfig, null, 100), "lite");
|
||||
});
|
||||
|
||||
it("downgrades aggressive cache-control requests for caching-aware providers", () => {
|
||||
const config = { ...baseConfig, defaultMode: "aggressive" as const };
|
||||
const body = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "cached", cache_control: { type: "ephemeral" } }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
selectCompressionStrategy(config, null, 100, body, {
|
||||
provider: "anthropic",
|
||||
targetFormat: "claude",
|
||||
model: "claude-3-5-sonnet",
|
||||
}),
|
||||
"standard"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyCompression", () => {
|
||||
|
||||
Reference in New Issue
Block a user