mirror of
https://github.com/teamchong/pxpipe.git
synced 2026-07-22 02:02:51 +02:00
fix(gate): discriminate raw opts to fix override-gate default-value collision
The override-gate previously used `o.charsPerToken !== CHARS_PER_TOKEN` to detect "host pinning". After CHARS_PER_TOKEN=4 was replaced with slab/history-specific 2.5 constants, a host that deliberately passes the literal `4` (the same as DEFAULTS) was silently swapped to 2.5 - the gate couldn't distinguish "host passed nothing" from "host passed 4". Fix: check the raw opts (\`opts.charsPerToken !== undefined\`) instead of comparing the merged value against a sentinel. Host-supplied `4` now flows through as `4`, even though it matches the default. Tests pin both directions: - slab path (render.test.ts): explicit cpt=4 keeps the 161k slab REJECTED at cpt=4 (would be ACCEPTED at the built-in 2.5) - history path (history.test.ts): explicit cpt=4 keeps a 14-turn closed conversation REJECTED at cpt=4 (would collapse at the built-in 2.5), with companion cpt=2.5 test proving the fixture actually straddles 275 tests pass, typecheck clean, build clean.
This commit is contained in:
@@ -1402,8 +1402,10 @@ export async function transformRequest(
|
||||
// English-prose default 4 baked into CHARS_PER_TOKEN. Use a slab-specific
|
||||
// upper-bound cpt at this gate so JSON-dense system + tool-doc content
|
||||
// gets a fair break-even check. Host can still override via
|
||||
// `o.charsPerToken` (e.g., to plug in a live empirical fit).
|
||||
const slabCpt = o.charsPerToken !== undefined && o.charsPerToken !== CHARS_PER_TOKEN
|
||||
// `opts.charsPerToken` (e.g., to plug in a live empirical fit).
|
||||
// Discriminate on the *raw* `opts` so a host that genuinely wants the
|
||||
// English-prose `4` can pin to it without colliding with the merged default.
|
||||
const slabCpt = opts.charsPerToken !== undefined
|
||||
? o.charsPerToken
|
||||
: SLAB_CHARS_PER_TOKEN;
|
||||
if (!isCompressionProfitable(combined, o.cols, undefined, numCols, slabCpt)) {
|
||||
@@ -1701,9 +1703,12 @@ export async function transformRequest(
|
||||
// count after wrapping.
|
||||
// History cpt is empirically ~1.09 (N=10 rejected-events sample) — JSON-
|
||||
// dense like the slab, so use the same conservative upper-bound cpt
|
||||
// baked into HISTORY_CHARS_PER_TOKEN=2.5. Host override (o.charsPerToken)
|
||||
// baked into HISTORY_CHARS_PER_TOKEN=2.5. Host override (opts.charsPerToken)
|
||||
// wins if the dashboard ever feeds back a live empirical fit.
|
||||
const historyCpt = o.charsPerToken !== undefined && o.charsPerToken !== CHARS_PER_TOKEN
|
||||
// Same discriminator as the slab path: check the *raw* `opts` so a host
|
||||
// that genuinely wants `4` can pin to it without colliding with the merged
|
||||
// default.
|
||||
const historyCpt = opts.charsPerToken !== undefined
|
||||
? o.charsPerToken
|
||||
: HISTORY_CHARS_PER_TOKEN;
|
||||
const historyProfitable = (text: string, cols: number): boolean =>
|
||||
|
||||
+39
-8
@@ -474,14 +474,13 @@ describe('transformRequest history compression (always-on)', () => {
|
||||
expect(info.historyReason).toBe('collapsed');
|
||||
expect(info.collapsedTurns).toBe(10);
|
||||
|
||||
// Counterfactual: a host override slightly above 4 (= "English-prose
|
||||
// worse than the static default") forces the gate back into rejection
|
||||
// territory. We use 4.5 rather than 4 because passing exactly the
|
||||
// DEFAULTS value (4) collides with the `!== CHARS_PER_TOKEN` branch
|
||||
// and gets re-rewritten to HISTORY_CHARS_PER_TOKEN — by design, so
|
||||
// unspecified-host requests still get the empirical cpt. 4.5 bypasses
|
||||
// that override gate and confirms the fix actually flows through the
|
||||
// cpt argument, not some other side-effect.
|
||||
// Counterfactual: a host override above 4 (= "English-prose territory,
|
||||
// but worse than the historical default") forces the gate back into
|
||||
// rejection — confirms the fix actually flows through the `cpt`
|
||||
// argument, not some other side-effect. After fragility #2 (override-
|
||||
// gate default-value collision), the override-gate uses `!== undefined`
|
||||
// so 4.5 is honored as an explicit override on its own merits, not
|
||||
// because it differs from any sentinel.
|
||||
const stale = await transformRequest(mkBody(msgs, bigPlain(80_000)), {
|
||||
charsPerToken: 4.5,
|
||||
});
|
||||
@@ -489,6 +488,38 @@ describe('transformRequest history compression (always-on)', () => {
|
||||
expect(stale.info.collapsedTurns).toBeUndefined();
|
||||
});
|
||||
|
||||
it('explicit charsPerToken=4 is honored end-to-end (no silent swap to constants)', async () => {
|
||||
// Regression for fragility #2: the override-gate previously used a
|
||||
// `!== CHARS_PER_TOKEN` check that silently swapped 4 → SLAB_CHARS_PER_TOKEN
|
||||
// (2.5) for unspecified hosts. That coupling broke the distinction
|
||||
// between "host didn't override" and "host deliberately wants 4". The
|
||||
// gate now uses `!== undefined` so a literal 4 stays a literal 4.
|
||||
//
|
||||
// Observable proof: a borderline-density fixture (1200-char bodies × 14
|
||||
// turns) that is rejected at cpt=4 but accepted at cpt=2.5. If the gate
|
||||
// silently swapped explicit 4 → 2.5, this fixture would collapse — but
|
||||
// with the fix it stays rejected, confirming the gate honored the literal
|
||||
// 4. The companion test below pins cpt=2.5 collapse on the same shape.
|
||||
const msgs: Message[] = [];
|
||||
for (let i = 0; i < 14; i++) {
|
||||
const body = `turn ${i}: ` + bigPlain(1200);
|
||||
msgs.push(i % 2 === 0 ? usr(body) : asst(body));
|
||||
}
|
||||
const explicit4 = await transformRequest(mkBody(msgs, bigPlain(80_000)), {
|
||||
charsPerToken: 4,
|
||||
});
|
||||
expect(explicit4.info.historyReason).toBe('not_profitable');
|
||||
expect(explicit4.info.collapsedTurns).toBeUndefined();
|
||||
|
||||
// Same shape at cpt=2.5 collapses — proves the fixture actually straddles
|
||||
// the threshold and isn't a tautology.
|
||||
const explicit25 = await transformRequest(mkBody(msgs, bigPlain(80_000)), {
|
||||
charsPerToken: 2.5,
|
||||
});
|
||||
expect(explicit25.info.historyReason).toBe('collapsed');
|
||||
expect(explicit25.info.collapsedTurns).toBe(10);
|
||||
});
|
||||
|
||||
it('history-image blocks carry NO cache_control (conservative first-cut)', async () => {
|
||||
const msgs: Message[] = [];
|
||||
for (let i = 0; i < 14; i++) {
|
||||
|
||||
@@ -1900,6 +1900,44 @@ describe('transform', () => {
|
||||
expect(isCompressionProfitable(slab, 100, undefined, 2, 2.5)).toBe(true);
|
||||
});
|
||||
|
||||
it('TransformOptions.charsPerToken: explicit 4 is honored (no silent swap to SLAB_CHARS_PER_TOKEN)', async () => {
|
||||
// Fragility #2 regression: the override-gate previously used a `!==
|
||||
// CHARS_PER_TOKEN` check that silently swapped 4 → 2.5 because the static
|
||||
// default *also* happens to be 4. After the fix it uses `!== undefined`,
|
||||
// so passing exactly 4 is honored as an explicit override.
|
||||
//
|
||||
// Observable proof: the 161k production-shape slab is REJECTED at cpt=4
|
||||
// (text=40,275 tok < image=44,000 tok) and ACCEPTED at the built-in
|
||||
// SLAB_CHARS_PER_TOKEN=2.5 (text=64,440 tok). If the collision bypass
|
||||
// breaks, the slab will compress under an explicit `4` — which would
|
||||
// mean the host can't ever pin to the conservative English-prose value.
|
||||
const parts: string[] = [];
|
||||
let acc = 0;
|
||||
const target = 161_101;
|
||||
while (acc < target) {
|
||||
const len = 60 + (acc % 40);
|
||||
parts.push('A'.repeat(len) + (acc % 200 === 0 ? ' ' : ''));
|
||||
acc += len + 1;
|
||||
}
|
||||
const slab = parts.join('\n').slice(0, target);
|
||||
const req = JSON.stringify({
|
||||
model: 'claude-3-5-sonnet',
|
||||
messages: [{ role: 'user', content: 'hi' }],
|
||||
system: slab,
|
||||
});
|
||||
const bytes = new TextEncoder().encode(req);
|
||||
|
||||
// Built-in cpt (no override): slab compresses via SLAB_CHARS_PER_TOKEN=2.5.
|
||||
const builtin = await transformRequest(bytes, { multiCol: 2 });
|
||||
expect(builtin.info.compressed).toBe(true);
|
||||
|
||||
// Explicit cpt=4: host pinned to the English-prose value. The slab gate
|
||||
// must honor it and reject the slab — not silently fall back to 2.5.
|
||||
const overridden = await transformRequest(bytes, { multiCol: 2, charsPerToken: 4 });
|
||||
expect(overridden.info.compressed).toBe(false);
|
||||
expect(overridden.info.reason).toMatch(/^not_profitable/);
|
||||
});
|
||||
|
||||
// --- Adaptive break-even: CHARS_PER_IMAGE derived from atlas cell, not hardcoded ---
|
||||
// Brief: when font-rater swaps to a smaller cell (e.g. Cozette 4×7), more chars
|
||||
// pack into one image, so the N-image break-even thresholds shift. Tests below
|
||||
|
||||
Reference in New Issue
Block a user