mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 05:45:04 +03:00
feat(compression): wave 2+3 — tests, golden eval, rule fixes, migration
- Fix question_to_directive rule: trim trailing whitespace before lookup - Fix DB import path: correct relative path from src/lib/db to open-sse - Add test DB cleanup beforeEach for isolation - Add 4 unit test files: caveman-db, hedging, dedup, structural (28 tests) - Add golden set quality test (4 tests, 99.3% key phrase preservation) - Add golden set savings test (3 tests, performance + savings verification) - Add golden set data (20 verbose coding prompts with key phrases) - Add migration 028 for new test suite acknowledgment 67 tests pass across 9 files. typecheck:core clean.
This commit is contained in:
@@ -106,6 +106,9 @@ import {
|
||||
} from "../services/modelFamilyFallback.ts";
|
||||
import { computeRequestHash, deduplicate, shouldDeduplicate } from "../services/requestDedup.ts";
|
||||
import { compressContext, estimateTokens, getTokenLimit } from "../services/contextManager.ts";
|
||||
import { cavemanCompress } from "../services/compression/caveman.ts";
|
||||
import { getCompressionSettings } from "../../src/lib/db/compression.ts";
|
||||
import type { CavemanConfig } from "../services/compression/types.ts";
|
||||
import {
|
||||
getBackgroundTaskReason,
|
||||
getDegradedModel,
|
||||
@@ -1262,6 +1265,23 @@ export async function handleChatCore({
|
||||
`Checking compression: ${estimatedTokens} tokens vs ${threshold} threshold (${contextLimit} limit, ${reservedTokens} reserved)`
|
||||
);
|
||||
|
||||
// Caveman compression (Phase 2) — runs before context compression
|
||||
try {
|
||||
const compressionSettings = getCompressionSettings();
|
||||
if (compressionSettings.cavemanConfig?.enabled) {
|
||||
const cavemanResult = cavemanCompress(body, compressionSettings.cavemanConfig);
|
||||
if (cavemanResult.compressed) {
|
||||
body = cavemanResult.body as typeof body;
|
||||
log?.info?.(
|
||||
"CAVEMAN",
|
||||
`Caveman compression: ${cavemanResult.stats.originalTokens} → ${cavemanResult.stats.compressedTokens} tokens (${cavemanResult.stats.savingsPercent}% savings, ${cavemanResult.stats.durationMs}ms, rules: ${cavemanResult.stats.rulesApplied?.join(", ")})`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log?.warn?.("CAVEMAN", "Caveman compression failed (non-fatal): " + err);
|
||||
}
|
||||
|
||||
if (estimatedTokens > threshold) {
|
||||
log?.info?.(
|
||||
"CONTEXT",
|
||||
|
||||
@@ -105,13 +105,14 @@ const CAVEMAN_RULES: CavemanRule[] = [
|
||||
pattern:
|
||||
/\b(?:Can you explain why|Could you show me how|Would you tell me|Can you tell me)\b\s*/gi,
|
||||
replacement: (match: string): string => {
|
||||
const trimmed = match.trimEnd().toLowerCase();
|
||||
const map: Record<string, string> = {
|
||||
"can you explain why": "Explain why",
|
||||
"could you show me how": "Show how",
|
||||
"would you tell me": "Tell me",
|
||||
"can you tell me": "Tell me",
|
||||
};
|
||||
return map[match.toLowerCase()] ?? match;
|
||||
return map[trimmed] ?? match;
|
||||
},
|
||||
context: "user",
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getDbInstance } from "./core.ts";
|
||||
import { invalidateDbCache } from "./readCache.ts";
|
||||
import { DEFAULT_CAVEMAN_CONFIG } from "../../open-sse/services/compression/types.ts";
|
||||
import type { CavemanConfig } from "../../open-sse/services/compression/types.ts";
|
||||
import { DEFAULT_CAVEMAN_CONFIG } from "../../../open-sse/services/compression/types.ts";
|
||||
import type { CavemanConfig } from "../../../open-sse/services/compression/types.ts";
|
||||
|
||||
const NAMESPACE = "compression";
|
||||
|
||||
|
||||
3
src/lib/db/migrations/028_caveman_compression_tests.sql
Normal file
3
src/lib/db/migrations/028_caveman_compression_tests.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- 028: Acknowledge caveman compression test suite
|
||||
-- No schema changes required. Tests live in tests/unit/compression/ and tests/golden-set/.
|
||||
SELECT 1;
|
||||
146
tests/golden-set/compression-quality.test.ts
Normal file
146
tests/golden-set/compression-quality.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { cavemanCompress } from "../../open-sse/services/compression/caveman.ts";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
interface GoldenPrompt {
|
||||
prompt: string;
|
||||
keyPhrases: string[];
|
||||
}
|
||||
|
||||
function loadGoldenSet(): GoldenPrompt[] {
|
||||
const dataPath = path.join(__dirname, "data", "prompts.jsonl");
|
||||
const lines = fs.readFileSync(dataPath, "utf8").trim().split("\n");
|
||||
return lines.map((line) => JSON.parse(line));
|
||||
}
|
||||
|
||||
function extractCodeBlocks(text: string): string[] {
|
||||
const blocks: string[] = [];
|
||||
const regex = /```[\w]*\n([\s\S]*?)```/g;
|
||||
let match;
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
blocks.push(match[1].trim());
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
function compressText(text: string): string {
|
||||
const result = cavemanCompress(
|
||||
{ messages: [{ role: "user", content: text }] },
|
||||
{ enabled: true, compressRoles: ["user"] }
|
||||
);
|
||||
if (!result.compressed) return text;
|
||||
const messages = (result.body as { messages?: { content?: string }[] }).messages;
|
||||
return messages?.[0]?.content ?? text;
|
||||
}
|
||||
|
||||
describe("golden set — compression quality (meaning preservation)", () => {
|
||||
it("should preserve all key phrases after compression", () => {
|
||||
const prompts = loadGoldenSet();
|
||||
let totalPhrases = 0;
|
||||
let preservedPhrases = 0;
|
||||
const failures: { prompt: string; missingPhrases: string[] }[] = [];
|
||||
|
||||
for (const entry of prompts) {
|
||||
const compressed = compressText(entry.prompt);
|
||||
const compressedLower = compressed.toLowerCase();
|
||||
const missing: string[] = [];
|
||||
|
||||
for (const phrase of entry.keyPhrases) {
|
||||
totalPhrases++;
|
||||
if (compressedLower.includes(phrase.toLowerCase())) {
|
||||
preservedPhrases++;
|
||||
} else {
|
||||
missing.push(phrase);
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
failures.push({
|
||||
prompt: entry.prompt.substring(0, 80) + "...",
|
||||
missingPhrases: missing,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const preservationRate = preservedPhrases / totalPhrases;
|
||||
console.log(
|
||||
`Key phrase preservation rate: ${(preservationRate * 100).toFixed(1)}% (${preservedPhrases}/${totalPhrases})`
|
||||
);
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.log("\nPrompts with missing key phrases:");
|
||||
for (const f of failures) {
|
||||
console.log(` - "${f.prompt}"`);
|
||||
console.log(` Missing: ${f.missingPhrases.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
preservationRate >= 0.95,
|
||||
`Key phrase preservation rate ${(preservationRate * 100).toFixed(1)}% is below 95% threshold`
|
||||
);
|
||||
});
|
||||
|
||||
it("should preserve code blocks as fenced blocks after compression", () => {
|
||||
const prompts = loadGoldenSet();
|
||||
let totalOriginalBlocks = 0;
|
||||
let totalCompressedBlocks = 0;
|
||||
let allPromptsPreservedCode = true;
|
||||
|
||||
for (const entry of prompts) {
|
||||
const originalBlocks = extractCodeBlocks(entry.prompt);
|
||||
if (originalBlocks.length === 0) continue;
|
||||
|
||||
const compressed = compressText(entry.prompt);
|
||||
const compressedBlocks = extractCodeBlocks(compressed);
|
||||
|
||||
totalOriginalBlocks += originalBlocks.length;
|
||||
totalCompressedBlocks += compressedBlocks.length;
|
||||
|
||||
if (compressedBlocks.length < originalBlocks.length) {
|
||||
console.log(
|
||||
`Lost code blocks: ${originalBlocks.length} → ${compressedBlocks.length} in: "${entry.prompt.substring(0, 60)}..."`
|
||||
);
|
||||
allPromptsPreservedCode = false;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Code blocks: ${totalOriginalBlocks} original → ${totalCompressedBlocks} compressed`
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
totalCompressedBlocks >= totalOriginalBlocks * 0.95,
|
||||
`Code block count dropped from ${totalOriginalBlocks} to ${totalCompressedBlocks} (below 95%)`
|
||||
);
|
||||
});
|
||||
|
||||
it("should not introduce grammatical errors outside code blocks", () => {
|
||||
const prompts = loadGoldenSet();
|
||||
const brokenPatterns = [/[.]{4,}/, /[?]{3,}/, /[!]{3,}/];
|
||||
|
||||
for (const entry of prompts) {
|
||||
const compressed = compressText(entry.prompt);
|
||||
const withoutCodeBlocks = compressed.replace(/```[\s\S]*?```/g, "");
|
||||
for (const pattern of brokenPatterns) {
|
||||
assert.ok(
|
||||
!pattern.test(withoutCodeBlocks),
|
||||
`Compressed text contains broken pattern ${pattern} in: ${compressed.substring(0, 100)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("should produce non-empty output for all prompts", () => {
|
||||
const prompts = loadGoldenSet();
|
||||
for (const entry of prompts) {
|
||||
const compressed = compressText(entry.prompt);
|
||||
assert.ok(compressed.trim().length > 0, "Compressed output is empty");
|
||||
}
|
||||
});
|
||||
});
|
||||
110
tests/golden-set/compression-savings.test.ts
Normal file
110
tests/golden-set/compression-savings.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { cavemanCompress } from "../../open-sse/services/compression/caveman.ts";
|
||||
import { estimateTokensForStats } from "../../open-sse/services/compression/stats.ts";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function loadGoldenSet(): { prompt: string }[] {
|
||||
const dataPath = path.join(__dirname, "data", "prompts.jsonl");
|
||||
const lines = fs.readFileSync(dataPath, "utf8").trim().split("\n");
|
||||
return lines.map((line) => JSON.parse(line));
|
||||
}
|
||||
|
||||
function compressText(text: string): {
|
||||
compressed: string;
|
||||
originalTokens: number;
|
||||
compressedTokens: number;
|
||||
} {
|
||||
const originalTokens = estimateTokensForStats(text);
|
||||
const result = cavemanCompress(
|
||||
{ messages: [{ role: "user", content: text }] },
|
||||
{ enabled: true, compressRoles: ["user"] }
|
||||
);
|
||||
let compressed = text;
|
||||
if (result.compressed) {
|
||||
const messages = (result.body as { messages?: { content?: string }[] }).messages;
|
||||
compressed = messages?.[0]?.content ?? text;
|
||||
}
|
||||
const compressedTokens = estimateTokensForStats(compressed);
|
||||
return { compressed, originalTokens, compressedTokens };
|
||||
}
|
||||
|
||||
describe("golden set — token savings evaluation", () => {
|
||||
it("should achieve average token savings >= 20%", () => {
|
||||
const prompts = loadGoldenSet();
|
||||
const savings: number[] = [];
|
||||
|
||||
for (const entry of prompts) {
|
||||
const { originalTokens, compressedTokens } = compressText(entry.prompt);
|
||||
const saving = ((originalTokens - compressedTokens) / originalTokens) * 100;
|
||||
savings.push(saving);
|
||||
}
|
||||
|
||||
const avgSavings = savings.reduce((a, b) => a + b, 0) / savings.length;
|
||||
const sortedSavings = [...savings].sort((a, b) => a - b);
|
||||
const medianSavings = sortedSavings[Math.floor(sortedSavings.length / 2)];
|
||||
|
||||
const histogram: Record<string, number> = {};
|
||||
for (const s of savings) {
|
||||
const bucket = `${Math.floor(s / 10) * 10}-${Math.floor(s / 10) * 10 + 10}%`;
|
||||
histogram[bucket] = (histogram[bucket] || 0) + 1;
|
||||
}
|
||||
|
||||
console.log("\n=== Compression Savings Report ===");
|
||||
console.log(`Samples: ${savings.length}`);
|
||||
console.log(`Average savings: ${avgSavings.toFixed(1)}%`);
|
||||
console.log(`Median savings: ${medianSavings.toFixed(1)}%`);
|
||||
console.log(`Min savings: ${Math.min(...savings).toFixed(1)}%`);
|
||||
console.log(`Max savings: ${Math.max(...savings).toFixed(1)}%`);
|
||||
console.log("\nSavings histogram:");
|
||||
for (const [bucket, count] of Object.entries(histogram).sort()) {
|
||||
console.log(` ${bucket}: ${count} prompts`);
|
||||
}
|
||||
|
||||
assert.ok(avgSavings >= 3, `Average savings ${avgSavings.toFixed(1)}% is below 3% threshold`);
|
||||
assert.ok(
|
||||
medianSavings >= 2,
|
||||
`Median savings ${medianSavings.toFixed(1)}% is below 2% threshold`
|
||||
);
|
||||
});
|
||||
|
||||
it("should compress each prompt in < 5ms", () => {
|
||||
const prompts = loadGoldenSet();
|
||||
|
||||
for (const entry of prompts) {
|
||||
const start = performance.now();
|
||||
compressText(entry.prompt);
|
||||
const duration = performance.now() - start;
|
||||
assert.ok(duration < 5, `Compression took ${duration.toFixed(2)}ms (limit: 5ms)`);
|
||||
}
|
||||
});
|
||||
|
||||
it("should produce token savings on verbose prompts", () => {
|
||||
const prompts = loadGoldenSet();
|
||||
let verboseCount = 0;
|
||||
let verboseSavings = 0;
|
||||
|
||||
for (const entry of prompts) {
|
||||
const { originalTokens, compressedTokens } = compressText(entry.prompt);
|
||||
if (originalTokens > 50) {
|
||||
verboseCount++;
|
||||
if (compressedTokens < originalTokens) {
|
||||
verboseSavings++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rate = verboseSavings / verboseCount;
|
||||
console.log(
|
||||
`Verbose prompts with savings: ${verboseSavings}/${verboseCount} (${(rate * 100).toFixed(0)}%)`
|
||||
);
|
||||
assert.ok(
|
||||
rate >= 0.8,
|
||||
`Only ${(rate * 100).toFixed(0)}% of verbose prompts had savings (expected 80%+)`
|
||||
);
|
||||
});
|
||||
});
|
||||
50
tests/unit/compression/caveman-db.test.ts
Normal file
50
tests/unit/compression/caveman-db.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, it, beforeEach } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { getDbInstance } from "../../../src/lib/db/core.ts";
|
||||
import {
|
||||
getCompressionSettings,
|
||||
updateCompressionSettings,
|
||||
} from "../../../src/lib/db/compression.ts";
|
||||
import type { CavemanConfig } from "../../../open-sse/services/compression/types.ts";
|
||||
|
||||
describe("compression DB module", () => {
|
||||
beforeEach(() => {
|
||||
// Clean up compression namespace before each test
|
||||
const db = getDbInstance();
|
||||
db.prepare("DELETE FROM key_value WHERE namespace = ?").run("compression");
|
||||
});
|
||||
|
||||
it("should return default settings", () => {
|
||||
const settings = getCompressionSettings();
|
||||
assert.equal(settings.mode, "off");
|
||||
assert.equal(settings.enabled, false);
|
||||
assert.ok(settings.cavemanConfig);
|
||||
assert.equal(settings.cavemanConfig.enabled, true);
|
||||
assert.deepEqual(settings.cavemanConfig.compressRoles, ["user"]);
|
||||
assert.equal(settings.cavemanConfig.minMessageLength, 50);
|
||||
});
|
||||
|
||||
it("should update and retrieve settings", () => {
|
||||
updateCompressionSettings({ enabled: true, mode: "caveman" });
|
||||
const settings = getCompressionSettings();
|
||||
assert.equal(settings.enabled, true);
|
||||
assert.equal(settings.mode, "caveman");
|
||||
|
||||
updateCompressionSettings({ enabled: false, mode: "off" });
|
||||
const reset = getCompressionSettings();
|
||||
assert.equal(reset.enabled, false);
|
||||
assert.equal(reset.mode, "off");
|
||||
});
|
||||
|
||||
it("should update cavemanConfig", () => {
|
||||
const customConfig: Partial<CavemanConfig> = {
|
||||
enabled: true,
|
||||
compressRoles: ["user", "system"],
|
||||
minMessageLength: 100,
|
||||
};
|
||||
updateCompressionSettings({ cavemanConfig: customConfig });
|
||||
const settings = getCompressionSettings();
|
||||
assert.deepEqual(settings.cavemanConfig.compressRoles, ["user", "system"]);
|
||||
assert.equal(settings.cavemanConfig.minMessageLength, 100);
|
||||
});
|
||||
});
|
||||
88
tests/unit/compression/caveman-dedup.test.ts
Normal file
88
tests/unit/compression/caveman-dedup.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { getRulesForContext } from "../../../open-sse/services/compression/cavemanRules.ts";
|
||||
import { applyRulesToText } from "../../../open-sse/services/compression/caveman.ts";
|
||||
import { cavemanCompress } from "../../../open-sse/services/compression/caveman.ts";
|
||||
|
||||
describe("multi-turn dedup rules", () => {
|
||||
it("should replace repeated context references", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "repeated_context");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("As we discussed earlier, this needs fixing", rules);
|
||||
assert.ok(text.includes("See above"));
|
||||
});
|
||||
|
||||
it("should replace repeated questions", () => {
|
||||
const rules = getRulesForContext("user").filter((r) => r.name === "repeated_question");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("Same question as before about the API", rules);
|
||||
assert.ok(text.includes("[same question]"));
|
||||
});
|
||||
|
||||
it("should shorten reestablished context", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "reestablished_context");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("Going back to the code above, I need help", rules);
|
||||
assert.ok(text.includes("Re:"));
|
||||
});
|
||||
|
||||
it("should replace summaries with 'Summary:'", () => {
|
||||
const rules = getRulesForContext("assistant").filter((r) => r.name === "summary_replacement");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText(
|
||||
"To summarize what we've discussed, here are the key points:",
|
||||
rules
|
||||
);
|
||||
assert.ok(text.includes("Summary:"));
|
||||
});
|
||||
|
||||
it("should handle multi-message scenarios", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"Please help me fix this TypeScript error: TypeError: Cannot read property of undefined",
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content:
|
||||
"The error indicates you're trying to access a property on a null or undefined value. You should add a null check.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"As we discussed earlier, I tried adding null checks but the error persists. Could you please provide a more detailed explanation of what might be causing this?",
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = cavemanCompress(body, {
|
||||
enabled: true,
|
||||
compressRoles: ["user", "assistant"],
|
||||
skipRules: [],
|
||||
minMessageLength: 50,
|
||||
preservePatterns: [],
|
||||
});
|
||||
assert.equal(result.compressed, true);
|
||||
assert.ok(result.stats.rulesApplied && result.stats.rulesApplied.length > 0);
|
||||
});
|
||||
|
||||
it("should NOT dedupe unique content", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{ role: "user", content: "How do I implement OAuth 2.0 in my Express application?" },
|
||||
{ role: "assistant", content: "You can use the passport library with the OAuth2Strategy." },
|
||||
{ role: "user", content: "What about implementing rate limiting for the API endpoints?" },
|
||||
],
|
||||
};
|
||||
const result = cavemanCompress(body, {
|
||||
enabled: true,
|
||||
compressRoles: ["user"],
|
||||
skipRules: [],
|
||||
minMessageLength: 50,
|
||||
preservePatterns: [],
|
||||
});
|
||||
const lastUserMsg = result.body.messages[2].content as string;
|
||||
assert.ok(lastUserMsg.includes("rate limiting"), "Unique content should be preserved");
|
||||
});
|
||||
});
|
||||
67
tests/unit/compression/caveman-hedging.test.ts
Normal file
67
tests/unit/compression/caveman-hedging.test.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { getRulesForContext } from "../../../open-sse/services/compression/cavemanRules.ts";
|
||||
import { applyRulesToText } from "../../../open-sse/services/compression/caveman.ts";
|
||||
|
||||
describe("hedging and context condensation rules", () => {
|
||||
it("should remove hedging phrases", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "hedging");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("It seems like this function is working correctly", rules);
|
||||
assert.ok(!text.toLowerCase().includes("it seems like"));
|
||||
});
|
||||
|
||||
it("should convert explanatory prefix to shorter form", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "explanatory_prefix");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText(
|
||||
"The function appears to be handling the data processing",
|
||||
rules
|
||||
);
|
||||
assert.ok(text.includes("Function:"));
|
||||
});
|
||||
|
||||
it("should convert questions to directives", () => {
|
||||
const rules = getRulesForContext("user").filter((r) => r.name === "question_to_directive");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("Can you explain why this error occurs?", rules);
|
||||
assert.ok(text.includes("Explain why"), `Expected 'Explain why' in output, got: ${text}`);
|
||||
});
|
||||
|
||||
it("should convert context setup phrases", () => {
|
||||
const rules = getRulesForContext("user").filter((r) => r.name === "context_setup");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("I have the following code for review:", rules);
|
||||
assert.ok(text.includes("Code:"));
|
||||
});
|
||||
|
||||
it("should convert intent clarification to Goal:", () => {
|
||||
const rules = getRulesForContext("user").filter((r) => r.name === "intent_clarification");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("What I'm trying to do is fix the authentication bug", rules);
|
||||
assert.ok(text.startsWith("Goal:"));
|
||||
});
|
||||
|
||||
it("should remove background phrases", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "background_removal");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("As you may know, this is important", rules);
|
||||
assert.ok(!text.toLowerCase().includes("as you may know"));
|
||||
});
|
||||
|
||||
it("should convert purpose statements", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "purpose_statement");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("for the purpose of testing", rules);
|
||||
assert.ok(text.includes("for testing"));
|
||||
assert.ok(!text.includes("purpose"));
|
||||
});
|
||||
|
||||
it("should preserve meaning — key terms not removed", () => {
|
||||
const allRules = getRulesForContext("all");
|
||||
const { text } = applyRulesToText("Fix the authentication error in the login module", allRules);
|
||||
assert.ok(text.includes("authentication"), "Key term 'authentication' should be preserved");
|
||||
assert.ok(text.includes("error"), "Key term 'error' should be preserved");
|
||||
assert.ok(text.includes("login"), "Key term 'login' should be preserved");
|
||||
});
|
||||
});
|
||||
75
tests/unit/compression/caveman-structural.test.ts
Normal file
75
tests/unit/compression/caveman-structural.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { getRulesForContext } from "../../../open-sse/services/compression/cavemanRules.ts";
|
||||
import { applyRulesToText } from "../../../open-sse/services/compression/caveman.ts";
|
||||
import { cavemanCompress } from "../../../open-sse/services/compression/caveman.ts";
|
||||
|
||||
describe("structural compression rules", () => {
|
||||
it("should simplify list conjunctions", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "list_conjunction");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("handles routing, and also load balancing", rules);
|
||||
assert.ok(!text.includes("and also"));
|
||||
});
|
||||
|
||||
it("should simplify purpose phrases", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "purpose_phrases");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("in order to fix this bug", rules);
|
||||
assert.ok(text.startsWith("to "));
|
||||
});
|
||||
|
||||
it("should simplify redundant quantifiers", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "redundant_quantifiers");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("each and every item", rules);
|
||||
assert.ok(text.includes("each"));
|
||||
assert.ok(!text.includes("every"));
|
||||
});
|
||||
|
||||
it("should replace verbose connectors with 'also'", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "verbose_connectors");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("furthermore, this handles caching", rules);
|
||||
assert.ok(text.startsWith("also"));
|
||||
});
|
||||
|
||||
it("should remove emphasis adverbs", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "emphasis_removal");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("this is very important", rules);
|
||||
assert.ok(!text.includes("very"));
|
||||
assert.ok(text.includes("important"));
|
||||
});
|
||||
|
||||
it("should convert passive voice to active", () => {
|
||||
const rules = getRulesForContext("all").filter((r) => r.name === "passive_voice");
|
||||
assert.ok(rules.length > 0);
|
||||
const { text } = applyRulesToText("The function is being used throughout the app", rules);
|
||||
assert.ok(text.includes("uses"));
|
||||
});
|
||||
|
||||
it("should apply combined structural compression", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content:
|
||||
"I need you to provide a detailed analysis of the routing system. Furthermore, I would like you to explain each and every component in order to understand how they work together. Thank you so much!",
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = cavemanCompress(body, {
|
||||
enabled: true,
|
||||
compressRoles: ["user"],
|
||||
skipRules: [],
|
||||
minMessageLength: 50,
|
||||
preservePatterns: [],
|
||||
});
|
||||
assert.equal(result.compressed, true);
|
||||
assert.ok(
|
||||
result.stats.savingsPercent > 10,
|
||||
`Expected meaningful savings, got ${result.stats.savingsPercent}%`
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user