mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 05:32:19 +03:00
feat(compression): expõe os perfis semânticos do omniglyph nos três wires
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.
This commit is contained in:
@@ -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<string, unknown>;
|
||||
|
||||
@@ -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<string, unknown> {
|
||||
const messages: Array<Record<string, unknown>> = [];
|
||||
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());
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user