Skip to content

Inline raw multimodal images in the v1 train client#2120

Draft
eligotts wants to merge 19 commits into
mainfrom
feat/v1-inline-raw-images
Draft

Inline raw multimodal images in the v1 train client#2120
eligotts wants to merge 19 commits into
mainfrom
feat/v1-inline-raw-images

Conversation

@eligotts

@eligotts eligotts commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Intermediate multimodal storage mode — the inline bundle. Keeps the v1 raw-descriptor training contract (sidecars carry renderer descriptors + hashes + placeholders; processed tensors are rejected) but base64 data images stay inline in message content end to end — no ingress offload, no shared image directory.

  • Removed verifiers/utils/multimodal.py (the ingress offload walker), the Client.prepare_request_body hook, its TrainClient override, and the interception server's request-preparation step: requests flow to the model boundary unmodified.
  • graph.py sidecar validation requires the inline raw_image_data field on raw descriptors; processed-tensor payloads (pixel_values etc.) are still rejected.
  • The v0 renderer_client passes messages through unmodified.
  • Rollout records and traces carry the inline base64 images (deliberate for this bundle — expect larger results.jsonl on multimodal runs).

Relationship to the offload bundle

Cut from the offload branch (#1746); same processor-ownership architecture (renderer geometry math, vLLM-front + trainer materialization in prime-rl), minus the storage layer. The offload bundle (renderers #89 / verifiers #1746 / prime-rl #2836) adds content-addressed file:// offload at ingress on top of this contract.

Companion inline PRs: renderers #110, prime-rl feat/v1-inline-mm.

Validation

  • Full suite with the inline renderers checkout (minus PRIME_API_KEY-gated e2e/envs): all passing.

Note

Inline raw multimodal images in the v1 train client bridging path

  • Removes the multimodal-content prohibition in TrainClient.generate so bridging is now allowed for prompts with images; multimodal renderers receive concatenated previous-turn multimodal sidecars via previous_multi_modal_data during bridge_to_next_turn.
  • Adds validation in graph.py that multimodal sidecars are raw image descriptor dicts (containing raw_image_data), rejecting processed payloads with pixel_values, image_embeds, or image_features at (de)serialization time.
  • Extends multimodal attribution in _attribute_mm to track per-modality placeholder ranges alongside items and hashes, and exposes these via PendingTurn.previous_multi_modal_data and Branch.multi_modal_data.
  • Propagates multi_modal_data through the legacy token conversion path in legacy.py and includes live trajectory in legacy rollout outputs.
  • Risk: serialization of any previously accepted processed multimodal payloads will now raise TypeError/ValueError.
📊 Macroscope summarized 6e09559. 6 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

eligotts and others added 19 commits June 25, 2026 06:39
Every image carries its ref, so no cache miss can occur. Removes _generate_with_image_ref_retry / _has_descriptor_only_images / _retryable_mm_error_type / _json_error_type / _RETRYABLE_MM_ERROR_TYPES; rollouts call generate() directly. Obsolete retry tests removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fload

# Conflicts:
#	verifiers/v1/clients/train.py
…fload

# Conflicts:
#	verifiers/v1/cli/dashboard/eval.py
- prepare_images_inplace handles the full renderer part treaty: nested
  image_url dicts, direct-string image_url, direct image strings, and
  typed pydantic parts; non-string sources raise with the shape named.
- Interception server labels prepare_messages failures as
  InterceptionError instead of misattributing them to the user simulator.
- Test covers all shapes plus http rejection (skips until the renderers
  pin ships mm_store).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fload

Reconciles this branch's raw-image offload work with main's independent
retry-coalescing and harness-segment-resume refactor of the interception
server, and its v1 trace/graph model evolution:

- interception/server.py: main's single-shot handle_request (graph-atomicity
  retry coalescing) and _stream (record_call/error tracking, tools threading)
  are authoritative. Dropped this branch's obsolete mid-request user-simulator
  injection (session.user/session.opening, prepare_messages call sites) —
  main's d21100b moved that to Harness.launch/resume, so injected messages
  now re-enter as normal request bodies already covered by
  prepare_request_body (kept, re-wired at the top of handle_request).
- graph.py: kept finish_reason/usage/multi_modal_data/previous_multi_modal_data
  (this branch) alongside main's SkipJsonSchema wrapping convention and
  commit()'s new (tools, -> assistant node id) signature. Caught and fixed an
  auto-merge dropping the FinishReason/Usage imports (caused a pydantic
  model-not-fully-defined failure at Trace construction).
- trace.py: kept this branch's more accurate multi_modal_data docstring;
  took main's tuple-based bridge-mutation assertion in the test suite.
- ARCHITECTURE.md: main deleted this file (moved to the shorter docs/v1/
  architecture.md, which doesn't cover this depth of internals) — accepted
  the deletion; folded the one load-bearing fact (why multi_modal_data is
  JSON-excluded) into _NODE_DUMP_EXCLUDE's docstring instead of resurrecting
  a dedicated architecture doc.

Full suite (with local renderers checkout, minus PRIME_API_KEY-gated e2e):
all passing.
Its only callers were the interception server's mid-request user-simulator
injection sites, which main's harness-segment-resume refactor removed —
resumed user turns now re-enter as ordinary request bodies, already covered
by prepare_request_body at request ingress.
Intermediate storage mode: base64 data images stay inline in messages
through the interception server, trace graph, and rollout records; raw
descriptor sidecars carry the inline source (raw_image_data) instead of
file:// run-asset URIs.

- Removed verifiers/utils/multimodal.py (the ingress offload walker),
  the Client.prepare_request_body hook, its TrainClient override, and
  the interception server's request-preparation step.
- graph.py sidecar validation requires the inline raw_image_data field;
  processed-tensor payloads are still rejected.
- v0 renderer_client passes messages through unmodified.

Suite (with the inline renderers checkout, minus PRIME_API_KEY-gated
e2e/envs): all passing.
Comment thread verifiers/v1/graph.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium

MessageNode.model_construct(

The assistant node created in _commit_turn omits finish_reason and usage, so every committed response records finish_reason=None and usage=None even though Response carries those values. Truncation detection and provider usage/cost dashboards cannot work because the data is silently dropped. The MessageNode.model_construct(...) call for the assistant node needs to pass finish_reason=response.finish_reason and usage=response.usage.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/graph.py around line 633:

The assistant node created in `_commit_turn` omits `finish_reason` and `usage`, so every committed response records `finish_reason=None` and `usage=None` even though `Response` carries those values. Truncation detection and provider usage/cost dashboards cannot work because the data is silently dropped. The `MessageNode.model_construct(...)` call for the assistant node needs to pass `finish_reason=response.finish_reason` and `usage=response.usage`.

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.

2 participants