Skip to content

feat(images): opt-in VLM transcription of image-dominant pages#94

Merged
dongwhee merged 2 commits into
developfrom
feat/image-transcription
Jul 7, 2026
Merged

feat(images): opt-in VLM transcription of image-dominant pages#94
dongwhee merged 2 commits into
developfrom
feat/image-transcription

Conversation

@dongwhee

@dongwhee dongwhee commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Stage 2 of image-dominant page support (follows #93). When a page is image-dominant and transcription is enabled, content images are downloaded (streamed, 4 MB/image cap), tall marketing images selected (height >= 800 px, aspect >= 1.5, max 5/fetch), transcribed via the vision LLM, and spliced into the markdown as ## Image text (...) sections before chunking — so transcripts embed, retrieve, rerank, and pass the injection scan like normal page text.

  • New src/trawl/image_transcribe.py: dimension probe (PIL if present, else fitz.Pixmap — pymupdf is already a hard dep), transcript cache (image sha256 + model + prompt version, 7-day TTL), Gemma enable_thinking:false + reasoning_content fallback.
  • Pipeline: image scan moved from post-rerank into a pre-chunking helper shared by both paths (also de-duplicates the scan+warn block from feat(images): detect image-dominant pages and surface content_images #93). Fetch-cache read is bypassed when transcription is requested (cache stores no raw HTML; write unchanged). Profile-path chunk_ms excludes transcription time.
  • MCP fetch_page gains transcribe_images (JSON null defers to env). PipelineResult.images_transcribed reports the count.
  • Default OFF: TRAWL_IMAGE_TRANSCRIBE=1 / fetch_relevant(transcribe_images=True) / MCP param.

Pre-registered gates (all PASS, whalesleep WS-1 reference page)

  • G1: rank-1 chunk is the density transcript for "메모리폼 밀도가 왜 중요한가"; top-6 contains 4 transcript chunks (incl. spec dimensions)
  • G2: parity 15/15 with flag OFF and ON
  • G3: OFF path +0 ms; ON cold 47 s (5 transcriptions at ~9 s each)
  • G4: warm transcript cache 6.5 s (~OFF + 1 s)
  • MCP smoke test PASS; pytest 48 offline tests pass

Known limits / follow-ups

  • Transcript chunks inherit the page's extractor/source_selector (no provenance marker yet)
  • Leading gallery images are fully downloaded before the tall filter rejects them
  • No failed-host cooldown on the VLM (acceptable while per-call opt-in)
  • Telemetry does not record images_transcribed

lyla added 2 commits July 7, 2026 16:33
Stage 2 of image-dominant page support. When a page is image-dominant
and transcription is enabled, content images are downloaded (streamed,
per-image byte cap), tall marketing images are selected
(MIN_IMAGE_HEIGHT_PX=800, MIN_ASPECT_RATIO=1.5 -- tall banners carry
text, square gallery photos do not; max 5 per fetch), transcribed via
the vision LLM at TRAWL_VLM_URL, and spliced into the markdown as
'## Image text (...)' sections BEFORE chunking, so transcripts embed,
retrieve, rerank, and pass the injection scan like normal page text.

New module src/trawl/image_transcribe.py: dimension probe (PIL if
present, else fitz.Pixmap -- pymupdf is already a hard dep; Pillow is
not installed in the trawl env), transcript cache keyed on image
sha256 + model + prompt version (TTL 7 days), enable_thinking:False +
reasoning_content fallback per the Gemma quirk.

Pipeline: the image scan moved from post-rerank to a pre-chunking
helper (_scan_and_transcribe_images) shared by both paths, which also
resolves the duplicated scan+warn block from PR #93. When transcription
is requested the C8 fetch-cache read is bypassed (cache stores no raw
HTML; write still happens), so transcribe_images=True works
deterministically on recently-fetched URLs. Profile path chunk_ms
excludes transcription time. MCP fetch_page gains a transcribe_images
param (JSON null defers to env).

Default OFF: TRAWL_IMAGE_TRANSCRIBE=1, fetch_relevant(transcribe_images=True),
or the MCP param. PipelineResult.images_transcribed reports the count.

Pre-registered gates, all PASS on whalesleep WS-1 (reference page):
G1 rank-1 chunk is the density transcript for query '메모리폼 밀도가
왜 중요한가' (top-6 has 4 transcript chunks incl. spec dimensions);
G2 parity 15/15 with flag OFF and ON; G3 OFF path +0 ms, ON cold 47 s
(5 transcriptions); G4 warm transcript cache 6.5 s (~OFF + 1 s).
MCP smoke test PASS, pytest 48 pass offline.

Known limits (follow-ups, not in this change): transcript chunks
inherit the page's extractor/source_selector (no provenance marker);
leading gallery images are fully downloaded before the tall filter
rejects them; no failed-host cooldown on the VLM (feature is per-call
opt-in); telemetry does not record images_transcribed.
Follow-up to d228f1b -- this test file was part of the review-fix batch
but missed the commit.
@dongwhee dongwhee merged commit 8ff34aa into develop Jul 7, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant