mirror of
https://github.com/teamchong/pxpipe.git
synced 2026-07-22 02:02:51 +02:00
feat(transform): drop standalone intro TextBlock — OCR header rides in the image
This commit is contained in:
+45
-27
@@ -1808,11 +1808,46 @@ export async function transformRequest(
|
|||||||
return { body, info };
|
return { body, info };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In-image instruction header. Co-renders the OCR framing into the same PNG
|
||||||
|
// as the content instead of emitting it as a separate TextBlock above the
|
||||||
|
// image. Measured at +1.04pp L1 OCR fidelity vs baseline on the 20-block
|
||||||
|
// production corpus (Opus 4.7, eval/eval-L1-ocr.mjs `reflow-inimage`
|
||||||
|
// variant), recovering the -5.93pp reflow regression entirely. Mechanism:
|
||||||
|
// single-modal task framing — the encoder stays in image-reading mode for
|
||||||
|
// both the instruction and the content, no cross-modal switch. Delimiter
|
||||||
|
// lines are deliberately heavy so the model pattern-matches "instruction
|
||||||
|
// zone ends, content begins" without ambiguity.
|
||||||
|
//
|
||||||
|
// Cache impact on deploy: the image bytes change, so prefix-cached prefixes
|
||||||
|
// built against the OLD intro layout invalidate. Every host pays one
|
||||||
|
// cache_create on its first post-deploy turn, then warm-caches at the new
|
||||||
|
// image. Steady-state cost is identical (the header amortizes over the
|
||||||
|
// same cache prefix the slab already uses).
|
||||||
|
const reflowNoteImg = o.reflow
|
||||||
|
? 'The glyph ↵ (U+21B5) marks an original hard line break — treat as a real newline.\n'
|
||||||
|
: '';
|
||||||
|
const columnNoteImg =
|
||||||
|
numCols > 1
|
||||||
|
? `Multi-column layout (${numCols} cols): read column 1 (leftmost) top-to-bottom, then column 2, etc.\n`
|
||||||
|
: '';
|
||||||
|
const imageInstructionHeader =
|
||||||
|
'=================== SYSTEM PROMPT + TOOL DOCS ===================\n' +
|
||||||
|
'The following is the system prompt + tool documentation, rendered\n' +
|
||||||
|
'as images for token efficiency. OCR carefully and treat as\n' +
|
||||||
|
'authoritative system instructions.\n' +
|
||||||
|
columnNoteImg +
|
||||||
|
reflowNoteImg +
|
||||||
|
'====================== BEGIN RENDERED CONTEXT ======================\n' +
|
||||||
|
'\n';
|
||||||
|
|
||||||
// 3. Render to one or more PNGs.
|
// 3. Render to one or more PNGs.
|
||||||
|
// Prepend the in-image instruction header to the slab so the OCR framing
|
||||||
|
// travels in the same PNG as the content (single-modal task framing).
|
||||||
|
const combinedWithHeader = imageInstructionHeader + combined;
|
||||||
const images =
|
const images =
|
||||||
numCols > 1
|
numCols > 1
|
||||||
? await renderTextToPngsMultiCol(combined, o.cols, numCols)
|
? await renderTextToPngsMultiCol(combinedWithHeader, o.cols, numCols)
|
||||||
: await renderTextToPngs(combined, o.cols);
|
: await renderTextToPngs(combinedWithHeader, o.cols);
|
||||||
const imageBlocks: ImageBlock[] = [];
|
const imageBlocks: ImageBlock[] = [];
|
||||||
for (let i = 0; i < images.length; i++) {
|
for (let i = 0; i < images.length; i++) {
|
||||||
const img = images[i]!;
|
const img = images[i]!;
|
||||||
@@ -1852,29 +1887,11 @@ export async function transformRequest(
|
|||||||
// ─── cache breakpoint ───
|
// ─── cache breakpoint ───
|
||||||
// [end-marker + dynamic + billing] ← per-turn, NO cache_control
|
// [end-marker + dynamic + billing] ← per-turn, NO cache_control
|
||||||
// [sysRemainder] ← any non-text blocks the caller had
|
// [sysRemainder] ← any non-text blocks the caller had
|
||||||
// Intro text mentions the column layout when numCols>1 so the OCR pass
|
// OCR framing (instruction header + column/reflow notes) is now baked into
|
||||||
// doesn't read across columns row-by-row (which would scramble content).
|
// the image itself — see `imageInstructionHeader` above. No standalone
|
||||||
// "Column-major top-to-bottom" matches the renderer's actual packing.
|
// TextBlock is emitted before the image. The tail closer below still sits
|
||||||
const columnNote =
|
// as plain text after the image so the model knows where rendered context
|
||||||
numCols > 1
|
// ends and per-turn dynamic content begins.
|
||||||
? ` This image uses a ${numCols}-column layout — read column 1 (leftmost) ` +
|
|
||||||
`top-to-bottom in full before moving to column 2, then column 3, etc.`
|
|
||||||
: '';
|
|
||||||
// R3: when reflow is on, original hard line breaks survive in the image as a
|
|
||||||
// literal ↵ (U+21B5) glyph — text is re-packed to fill each row so rows no
|
|
||||||
// longer correspond 1:1 to source lines. Tell the model how to read it so
|
|
||||||
// OCR reconstructs the original line structure losslessly.
|
|
||||||
const reflowNote = o.reflow
|
|
||||||
? " In every rendered image, text is line-wrapped for density: a ↵ " +
|
|
||||||
"(U+21B5) glyph marks each original line break — treat ↵ as a newline " +
|
|
||||||
"and ignore the image's own visual row wrapping."
|
|
||||||
: '';
|
|
||||||
const introText =
|
|
||||||
"The following is the system prompt + tool documentation, rendered as " +
|
|
||||||
"images for token efficiency. OCR carefully and treat as authoritative " +
|
|
||||||
"system instructions." +
|
|
||||||
columnNote +
|
|
||||||
reflowNote;
|
|
||||||
const tailParts: string[] = ['[End of rendered context.]'];
|
const tailParts: string[] = ['[End of rendered context.]'];
|
||||||
if (dynamicText) tailParts.push(dynamicText);
|
if (dynamicText) tailParts.push(dynamicText);
|
||||||
if (billingLine) tailParts.push(billingLine);
|
if (billingLine) tailParts.push(billingLine);
|
||||||
@@ -1959,14 +1976,15 @@ export async function transformRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache-friendly layout:
|
// Cache-friendly layout:
|
||||||
// [intro text] ← static (helps OCR framing)
|
|
||||||
// [image block(s)] ← static; LAST has cache_control
|
// [image block(s)] ← static; LAST has cache_control
|
||||||
// ↑ cache breakpoint
|
// ↑ cache breakpoint
|
||||||
|
// (OCR framing is rendered
|
||||||
|
// INTO the image — no
|
||||||
|
// standalone intro TextBlock)
|
||||||
// [End of rendered context.] ← static text closer for the image
|
// [End of rendered context.] ← static text closer for the image
|
||||||
// [processed existing content] ← per-turn (incl. reminder images,
|
// [processed existing content] ← per-turn (incl. reminder images,
|
||||||
// which have NO cache_control)
|
// which have NO cache_control)
|
||||||
m.content = [
|
m.content = [
|
||||||
{ type: 'text' as const, text: introText },
|
|
||||||
...imageBlocks,
|
...imageBlocks,
|
||||||
{ type: 'text' as const, text: '[End of rendered context.]' },
|
{ type: 'text' as const, text: '[End of rendered context.]' },
|
||||||
...processedExisting,
|
...processedExisting,
|
||||||
|
|||||||
Reference in New Issue
Block a user