Repository navigation
Foundation: image requests are not accounted for: repeated and instruction-embedded images multiply silently and the 32 MiB aggregate caps are never checked #313
Description
Problem
#297 deferred "request accounting for repeated calls". #298's AuditedBackend (src/jev_cookbook/accounting.py) now counts repeated decide() calls, image calls included, and #213 (PR K) owns the generic count, duplicate and README-budget work. The image-specific cost is unowned. Per https://docs.typesafe.ai/images:
- "Repeated references add copies … each place its reference appears adds another copy of the image".
- "An image in
instructionsis sent … once for every question that includes it". - Separate 32 MiB caps apply to all distinct images and to the expanded request.
- Image tokens appear only inside
usage.input_tokens, and there is no estimator.
Reference-image recipes (69–76) put several reference photos in criteria on every call, and frame recipes put many frames in state. Neither the audit, nor the live ledger, nor any preflight reports distinct versus expanded image copies or bytes. A request over an aggregate cap is discovered only as a paid 413 (docs/vision.md: "Local file validation does not certify the API's aggregate request limits").
Acceptance criteria
-
vision.image_request_profile(state, questions, assets)is pure: it never reads files or stores bytes. It returns:- distinct asset count and bytes;
- expanded copy count and bytes, following the upstream rules (every occurrence in state and criteria values; instruction occurrences × questions);
- per-question copies.
Tests use hand-computed cases, including a reference used twice in one string and an instruction image shared by three questions.
- A preflight, called by
LiveBackend.decidebefore spending, raises a clear error when distinct or expanded image bytes exceed 32 MiB. A test assertsrequests_made == 0after the refusal. -
AuditedBackendaudit entries carry the profile counts (no bytes, no content) under the schema extension point PR K defines.tests/request_audit.pyverifies them without trusting the payload. -
live.recordledger lines add image counts beside the existing token fields. -
docs/vision.mdanddocs/backends.mdexplain copies versus distinct images, recommend state-over-instructions for shared images (as upstream does), and say that image tokens are only observable in recordedusage.input_tokens. - Text-only replay keys and audits are byte-identical to before (regression test).
Files likely touched
src/jev_cookbook/vision.py, src/jev_cookbook/accounting.py, src/jev_cookbook/live.py, tests/request_audit.py, tests/test_accounting.py, tests/test_multimodal_backends.py, docs/vision.md, docs/backends.md
Interface every multimodal recipe copies?
Yes: the audit line format checked by the guard. Two independent reviews.
Dependencies
#213 / PR K first (audit schema and declared count). The pure helper and preflight may land earlier if PR K slips.
Catalog ranks
61–100. Most acute: 69–76 (reference images in criteria), 85–92, 94, 95, 97–100.
Split with #213
#213 keeps: actual-attempt counting, duplicate-key failure, single declared count, README/budget derivation, per-recipe audit table, and the pre-recording gate. This issue adds only per-call image quantities and the aggregate-cap preflight. It adds no new pass/fail condition on request counts.
Opened 2026-10-20 from the FINISH.md Phase 3 readiness audit (Opus 5.5, read-only, main 373ea86). Triage: before any multimodal recipe (61–100) is opened for building.
Activity