From b5e5838a59e73b3c55efc460fe61899567268445 Mon Sep 17 00:00:00 2001 From: Xiangzhe Date: Tue, 18 Aug 2026 00:13:09 -0300 Subject: [PATCH] =?UTF-8?q?feat(compression):=20exp=C3=B5e=20os=20perfis?= =?UTF-8?q?=20sem=C3=A2nticos=20do=20omniglyph=20nos=20tr=C3=AAs=20wires?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O 1.4.0 trouxe perfis nomeados (coding-safe, balanced, aggressive, passthrough), mas só transformAnthropicMessages() os resolve sozinho: os transformadores OpenAI recebem TransformOptions cru e ignorariam o campo. Um perfil escolhido pelo operador valeria no wire Claude e sumiria no OpenAI. O adapter passa a mesclar o perfil com mergeCompressionProfileOptions() antes de chamar Chat Completions e Responses. O default segue aggressive — a política que os recibos publicados mediram. Medido nesta base: com coding-safe/balanced, uma sessão sem histórico acumulado para em below_min_chars e a engine não faz nada, porque os dois fixam minCompressChars no máximo e desligam system/tools/tool-results. Como a engine é opt-in, um default assim entregaria "ligado, 0% de ganho". O perfil é TETO, não piso: mergeCompressionProfileOptions não deixa um override do chamador reabrir uma lane lossy que o perfil fechou. Coberto por teste, por ser contra-intuitivo. Também fecha um caminho em que o OmniRoute violaria a própria política: o wire OpenAI do pacote não tem compressSystem — honra apenas compressTools, gptHistory, minCompressChars e reflow, e sempre troca a instrução por um ponteiro para a imagem. Com preserveSystemPrompt ligado, imagear assim queimaria o prefixo quente que a decisão cache-aware está protegendo, sem nada no corpo devolvido denunciando. A engine agora pula com skip:system_preservation_unsupported_on_wire. --- .../compression/engines/omniglyphAdapter.ts | 74 ++++++++++++--- .../compression/omniglyph-adapter.test.ts | 94 +++++++++++++++++++ 2 files changed, 153 insertions(+), 15 deletions(-) diff --git a/open-sse/services/compression/engines/omniglyphAdapter.ts b/open-sse/services/compression/engines/omniglyphAdapter.ts index 4e18fa7dbe..93882b862e 100644 --- a/open-sse/services/compression/engines/omniglyphAdapter.ts +++ b/open-sse/services/compression/engines/omniglyphAdapter.ts @@ -22,10 +22,12 @@ import type { CompressionResult } from "../types.ts"; import { createCompressionStats } from "../stats.ts"; import { isOmniGlyphSupportedModelForScope, + mergeCompressionProfileOptions, + resolveCompressionProfile, transformAnthropicMessages, transformOpenAIChatCompletions, transformOpenAIResponses, - transformRequest, + type CompressionProfile, type OmniGlyphSafetyScope, } from "omniglyph"; import { isModelImageable } from "omniglyph/applicability"; @@ -43,8 +45,26 @@ import { isModelImageable } from "omniglyph/applicability"; */ const MEASURED_MODEL_SCOPE: OmniGlyphSafetyScope = "coding-safe"; -/** Escopo semântico do transform. `aggressive` mantém a política medida atual. */ -const DEFAULT_TRANSFORM_SCOPE: OmniGlyphSafetyScope = "aggressive"; +/** + * Perfil padrão do OmniRoute. + * + * `aggressive` é a política que os recibos publicados mediram. `coding-safe` e + * `balanced` fixam `minCompressChars` no máximo e só colapsam histórico antigo: + * medido nesta base, uma sessão sem histórico acumulado fica em + * `below_min_chars` e a engine não faz nada — o operador veria "ligado, 0% de + * ganho". Ficam disponíveis como escolha explícita, não como default. + */ +const DEFAULT_PROFILE: OmniGlyphSafetyScope = "aggressive"; + +/** Perfil do passo (mais específico) > perfil global > default do OmniRoute. */ +function resolveProfileName(options?: CompressionEngineApplyOptions): string { + const step = options?.stepConfig?.profile; + if (typeof step === "string" && step.trim()) return step; + const global = (options?.config as { omniglyph?: { profile?: unknown } } | undefined)?.omniglyph + ?.profile; + if (typeof global === "string" && global.trim()) return global; + return DEFAULT_PROFILE; +} /** * O modelo precisa passar no teto medido E no escopo em vigor. Os dois wires @@ -159,9 +179,31 @@ async function applyOmniglyph( ) { return skip(body, "source_format_not_openai_responses"); } - if (!isModelWithinScope(model, DEFAULT_TRANSFORM_SCOPE)) { + let profile: CompressionProfile; + try { + profile = resolveCompressionProfile(resolveProfileName(options)); + } catch { + // `resolveCompressionProfile` lança em nome desconhecido. Um perfil que o + // pacote não entende não pode virar "roda com a política padrão". + return skip(body, "invalid_profile"); + } + if (profile.name === "passthrough") return skip(body, "profile_passthrough"); + if (!isModelWithinScope(model, profile.name)) { return skip(body, "model_not_approved"); } + const preserveSystemPrompt = + (typeof options?.stepConfig?.preserveSystemPrompt === "boolean" + ? options.stepConfig.preserveSystemPrompt + : options?.config?.preserveSystemPrompt) === true; + // `compressSystem` só existe no transform Anthropic. Os wires OpenAI honram + // apenas compressTools/gptHistory/minCompressChars/reflow e sempre trocam a + // instrução por um ponteiro para a imagem. Imagear o system quando o OmniRoute + // decidiu preservá-lo queimaria o prefixo quente que a política cache-aware + // está protegendo — e nada no corpo devolvido denunciaria isso. Sem como + // honrar a política nesse wire, a engine pula. + if (preserveSystemPrompt && wireFormat !== "claude") { + return skip(body, "system_preservation_unsupported_on_wire"); + } // OmniGlyph 1.3.x deliberately keeps unverified families (currently Grok) // text-only until the operator acknowledges them via its own env gate. if (!isModelImageable(model)) return skip(body, "model_not_imageable"); @@ -175,18 +217,20 @@ async function applyOmniglyph( const transformBody = wireFormat !== "claude" && model && body.model !== model ? { ...body, model } : body; const encoded = new TextEncoder().encode(JSON.stringify(transformBody)); - const preserveSystemPrompt = - (typeof options?.stepConfig?.preserveSystemPrompt === "boolean" - ? options.stepConfig.preserveSystemPrompt - : options?.config?.preserveSystemPrompt) === true; + const overrides = preserveSystemPrompt ? { compressSystem: false } : {}; + // Só `transformAnthropicMessages` resolve o perfil por conta própria; os + // transformadores OpenAI recebem TransformOptions cru e ignorariam o campo. + const openAIOptions = mergeCompressionProfileOptions(profile, overrides); const result = - wireFormat === "claude" && preserveSystemPrompt - ? await transformRequest(encoded, { compressSystem: false }) - : wireFormat === "claude" - ? await transformAnthropicMessages({ body: encoded, model }) - : wireFormat === "openai" - ? await transformOpenAIChatCompletions(encoded) - : await transformOpenAIResponses(encoded); + wireFormat === "claude" + ? await transformAnthropicMessages({ + body: encoded, + model, + options: { ...overrides, profile: profile.name }, + }) + : wireFormat === "openai" + ? await transformOpenAIChatCompletions(encoded, openAIOptions) + : await transformOpenAIResponses(encoded, openAIOptions); const applied = "applied" in result ? result.applied : result.info.compressed; if (!applied) return skip(body, result.info?.reason ?? "not_profitable"); outBody = JSON.parse(new TextDecoder().decode(result.body)) as Record; diff --git a/tests/unit/compression/omniglyph-adapter.test.ts b/tests/unit/compression/omniglyph-adapter.test.ts index 5e76777445..0961a090d1 100644 --- a/tests/unit/compression/omniglyph-adapter.test.ts +++ b/tests/unit/compression/omniglyph-adapter.test.ts @@ -229,3 +229,97 @@ test("OMNIGLYPH_PROFILE do host não amplia a allowlist de modelos do OmniRoute" assert.equal(r.compressed, false, "modelo sem recibo medido não pode entrar via env do host"); assert.ok(r.stats?.techniquesUsed.includes("skip:model_not_approved")); }); + +// --------------------------------------------------------------------------- +// Perfis semânticos (OmniGlyph 1.4.0) +// +// `transformAnthropicMessages()` resolve e mescla o perfil sozinho, mas os +// transformadores OpenAI recebem `TransformOptions` cru e NÃO conhecem +// `profile`. Sem o merge explícito do host, um perfil escolhido pelo operador +// valeria no wire Claude e seria silenciosamente ignorado no wire OpenAI. +// --------------------------------------------------------------------------- + +/** Histórico longo o bastante para o colapso valer mesmo com o system nativo. */ +function claudeBodyWithHistory(): Record { + const messages: Array> = []; + for (let i = 0; i < 40; i++) { + messages.push({ role: "user", content: [{ type: "text", text: `pergunta ${i}: ${DENSE}` }] }); + messages.push({ + role: "assistant", + content: [{ type: "text", text: `resposta ${i}: ${DENSE}` }], + }); + } + messages.push({ role: "user", content: [{ type: "text", text: "oi" }] }); + return { model: "claude-fable-5", max_tokens: 128, system: DENSE, messages }; +} + +test("perfil coding-safe chega ao wire OpenAI (não só ao wrapper Anthropic)", async () => { + const aggressive = await omniglyphEngine.applyAsync!(openaiChatBody(), { + ...GPT_OK, + stepConfig: { profile: "aggressive" } as never, + }); + assert.equal(aggressive.compressed, true, "aggressive é a política medida atual"); + + const codingSafe = await omniglyphEngine.applyAsync!(openaiChatBody(), { + ...GPT_OK, + stepConfig: { profile: "coding-safe" } as never, + }); + assert.equal( + codingSafe.compressed, + false, + "coding-safe mantém system e schemas de tools nativos no wire OpenAI" + ); + assert.ok( + codingSafe.stats?.techniquesUsed.some((t) => t.startsWith("skip:below_min_chars")), + `esperado skip por minCompressChars, veio ${JSON.stringify(codingSafe.stats?.techniquesUsed)}` + ); +}); + +test("perfil é TETO, não piso: coding-safe mantém o system nativo mesmo sem preserveSystemPrompt", async () => { + const body = claudeBodyWithHistory(); + const r = await omniglyphEngine.applyAsync!(body, { + ...OK, + stepConfig: { profile: "coding-safe", preserveSystemPrompt: false } as never, + }); + assert.equal(r.compressed, true, "o histórico antigo ainda colapsa"); + assert.equal( + (r.body as { system?: unknown }).system, + DENSE, + "coding-safe não deixa um override do chamador reabrir a compressão do system" + ); +}); + +test("perfil passthrough desliga a engine sem tocar no corpo", async () => { + const body = claudeBodyWithHistory(); + const r = await omniglyphEngine.applyAsync!(body, { + ...OK, + stepConfig: { profile: "passthrough" } as never, + }); + assert.equal(r.compressed, false); + assert.ok(r.stats?.techniquesUsed.includes("skip:profile_passthrough")); + assert.deepEqual(r.body, body); +}); + +test("perfil inválido falha fechado, não propaga exceção", async () => { + const r = await omniglyphEngine.applyAsync!(claudeBody(), { + ...OK, + stepConfig: { profile: "turbo-max" } as never, + }); + assert.equal(r.compressed, false); + assert.ok(r.stats?.techniquesUsed.includes("skip:invalid_profile")); +}); + +test("preserveSystemPrompt: wire OpenAI pula, porque o pacote não sabe preservar system lá", async () => { + // O transform OpenAI do OmniGlyph 1.4.0 honra apenas compressTools, gptHistory, + // minCompressChars e reflow — não existe compressSystem nesse wire, e a + // instrução vira sempre um ponteiro para a imagem. Imagear assim queimaria o + // prefixo quente que a decisão cache-aware do OmniRoute mandou preservar, e o + // OmniRoute não teria como saber. Sem opção de honrar a política, pula. + const r = await omniglyphEngine.applyAsync!(openaiChatBody(), { + ...GPT_OK, + config: { preserveSystemPrompt: true } as never, + }); + assert.equal(r.compressed, false); + assert.ok(r.stats?.techniquesUsed.includes("skip:system_preservation_unsupported_on_wire")); + assert.deepEqual(r.body, openaiChatBody()); +});