Skip to content

feat: MLX provider family — mlx_image + mlx_video + mlx_caption (shared env)#311

Open
ziyu4huang wants to merge 4 commits into
calesthio:mainfrom
ziyu4huang:feat/mlx-image-provider
Open

feat: MLX provider family — mlx_image + mlx_video + mlx_caption (shared env)#311
ziyu4huang wants to merge 4 commits into
calesthio:mainfrom
ziyu4huang:feat/mlx-image-provider

Conversation

@ziyu4huang

@ziyu4huang ziyu4huang commented Jul 6, 2026

Copy link
Copy Markdown

Bridges the sibling mlx-movie-director Apple-Silicon runtime into OpenMontage as the $0, offline, MPS-native option where existing local tools need CUDA. Three providers, one shared env module.

The trio

Provider Capability Bridges Fills
mlx_image (graphics) image_generation run.py image t2i/i2i/controlnet/faceswap/LoRA the ControlNet/i2i/LoRA/faceswap surface no cloud provider fully covers
mlx_video (video) video_generation run.py video generate/t2i2v (LTX-2.3) the Apple-Silicon MPS-native i2v/t2v path (wan/hunyuan/ltx/cogvideo need CUDA)
mlx_caption (analysis) analysis run.py caption (Qwen3-VL via LM Studio localhost:1234) the local LM-Studio VLM path (video_understand is a direct-transformers VLM that downloads+loads a model each run)

Shared env module (tools/_mlx/env.py)

Factors the duplicated _resolve_env() out of mlx_image + mlx_video (the TODO both files carried: “if a third MLX provider appears, factor into tools/_mlx/env.py”). resolve_mlx_env(need_models, need_lm_studio) serves all three; mlx_caption uses need_models=False, need_lm_studio=True (socket-probes localhost:1234 so the menu shows ANALYZE::mlx unavailable when LM Studio isn't running).

Env contract

  • MLX_MOVIE_DIRECTOR_DIR — repo root with python/mlx-movie-director/run.py
  • MLX_VENV_PYTHON — the MLX venv interpreter (per-machine, not auto-created)
  • LM Studio at http://localhost:1234/v1 with a vision model (caption only)

Tests

  • All spawn-free (no runtime/LM-Studio dependency in CI): 70 mlx-family tests + 228 broader tests/tools/ pass, 0 regressions.
  • mlx_caption: capability surface, env gates (incl. LM-Studio-down → UNAVAILABLE), arg-mapping for all --style/--lang/--model/--api-url combos, output parsing (styles-map + flat-legacy + dict-with-text).
  • Auto-discovered by the registry (mlx_caption is the 13th analysis tool; zero selector edits).

Known follow-ups (tracked in the repo's next-goal-md)

  • mlx_image doesn't yet expose restore/quality/expansion; mlx_video doesn't expose relay/segment.
  • An opt-in live integration smoke test (LM Studio + staged models) is deferred.
  • The install-instructions gap (opencv-python/mflux/ltx-2-mlx members not in the mlx repo's requirements.txt) is documented but not yet upstreamed.

ziyu4huang and others added 2 commits July 6, 2026 22:58
…ontage

Add the first Apple-Silicon-native image provider. `mlx_image` shells to the
sibling mlx-movie-director repo's `run.py image` (Z-Image / Flux2 Klein / Lens)
and is auto-discovered by `image_selector` with zero selector edits — the
selector-plus-provider seam from the story→image review.

Closes OpenMontage's biggest verified image gap: a local, $0 path that
advertises the full control surface (controlnet / img2img / reference_image /
faceswap / lora / multi_lora) that today only grok_image's edit mode partially
covers. The scorer rewards it accordingly — it ranks calesthio#1 by weighted score
(control=1.0, cost_efficiency=1.0) and survives the edit-mode candidate filter.

- tools/graphics/mlx_image.py: provider; action routing (t2i/i2i/controlnet/
  faceswap), CLI arg mapping, availability gate (arm64 + MLX_MOVIE_DIRECTOR_DIR
  + venv + models), JSON_SUMMARY output parsing with dir-scan fallback.
- .agents/skills/mlx-movie-director/SKILL.md: Layer-3 bridge (env contract,
  pipeline choice, controlnet/i2i/faceswap mapping, LoRA stacks).
- tests/tools/test_mlx_image.py: 24 tests — registration, capability surface,
  zero-edit discovery, edit-filter survival, gate (env/venv/full), action
  routing, arg building, mocked-subprocess execute success + error paths.
- docs/REVIEW-story-to-image.md, docs/REVIEW-image-to-video-voice.md: the
  grounded reviews that motivated this provider (gap analysis + MLX side-by-side).

Verified end-to-end: a real Z-Image generation (828 KB PNG, 17.6s, $0) flows
through image_selector -> mlx_image -> run.py and lands at output_path with
asset_manifest-ready provenance (source_tool=mlx_image, model=mlx-zimage, seed).

Two upstream MLX-repo deps (opencv-python, mflux) are missing from its
requirements.txt — noted in install_instructions + the skill (tracked upstream).

Co-Authored-By: Claude <noreply@anthropic.com>
The motion stitch of the OM↔MLX seam. `mlx_video` wraps LTX-2.3 22B
(`run.py video generate` / `video t2i2v`) and is auto-discovered by
`video_selector` with zero selector edits.

Refined value (REVIEW-image-to-video-voice §7): OM's four $0 local i2v
providers (wan/hunyuan/ltx/cogvideo) need CUDA diffusers and don't run on
Apple Silicon. MLX LTX-2.3 is the MPS-native i2v/t2v path — a free offline
local default, NOT a premium-cinema replacement (deliberately does NOT
advertise cinematic_quality/lip_sync/multi_shot/native_audio).

- tools/video/mlx_video.py: provider; action routing (prompt-only → t2i2v
  3-stage; image present → generate I2V; explicit action override),
  LTX-2.3 arg mapping (frames 8k+1, /64 dims), availability gate reusing
  the mlx_image pattern, dir-scan output fallback (video subparser doesn't
  register --json-summary — an upstream MLX-repo asymmetry, documented).
  fallback_tools excludes image_selector so a motion-required brief can
  never silently degrade to a still.
- .agents/skills/mlx-movie-director/SKILL.md: video section (action routing
  table, honest surface, motion-required governance note).
- tests/tools/test_mlx_video.py: 25 tests — registration, honest-surface
  assertion (premium flags MUST be absent), zero-edit discovery, i2v filter
  survival, gate (env/venv/full), action routing, arg building, output
  parsing, mocked-subprocess execute success + error paths.

End-to-end smoke status: DEFERRED to G1. The MLX video path needs a heavier
upstream dep stack than requirements.txt provides — cv2, mflux, and the three
ltx-2-mlx workspace members (ltx-core-mlx / ltx-pipelines-mlx / ltx-trainer,
installable per-member since the ltx-2-mlx root fails uv install under PEP
639), plus a transformers/mlx_lm version conflict at the NewlineTokenizer
register call. The provider logic is fully covered by mocked-subprocess
tests; the image analog (mlx_image) is smoked end-to-end. install_instructions
documents the full chain.

Co-Authored-By: Claude <noreply@anthropic.com>
@ziyu4huang ziyu4huang requested a review from calesthio as a code owner July 6, 2026 15:01
…LX env

The third leg of the MLX-runtime trio (image gen + video gen + VLM analysis),
all bridging the sibling mlx-movie-director run.py on Apple Silicon.

mlx_caption (tools/analysis/mlx_caption.py):
- Bridges run.py `caption` (Qwen3-VL via LM Studio localhost:1234/v1) into the
  `analysis` capability — the $0, offline, private image/video-understanding
  path. Fills a verified gap: no tool used local LM Studio before (video_understand
  is a direct-transformers VLM that downloads+loads a model each run; mlx_caption
  talks to an already-running server the box already serves mlx_image/video from).
- Auto-discovered by the registry (13th analysis tool, zero selector edits).
- Availability gate: MLX_MOVIE_DIRECTOR_DIR + venv + LM Studio socket-probe
  (need_models=False — caption talks to LM Studio, not the mlx-models stack).
- 21 spawn-free tests (capability surface, env gates incl. LM Studio down,
  arg-mapping for all --style/--lang/--model/--api-url combos, output parsing).

Shared env module (tools/_mlx/env.py):
- Factors the duplicated _resolve_env() out of mlx_image + mlx_video (the TODO
  both files carried: 'if a third MLX provider appears, factor into
  tools/_mlx/env.py'). resolve_mlx_env(need_models, need_lm_studio) serves all
  three. All 49 existing mlx_image/mlx_video tests still green.
@ziyu4huang ziyu4huang changed the title feat: mlx_image + mlx_video providers — bridge MLX native gen into OpenMontage feat: MLX provider family — mlx_image + mlx_video + mlx_caption (shared env) Jul 6, 2026

@calesthio calesthio left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you for this contribution. The MLX provider family is directionally valuable for OpenMontage, especially as a $0 Apple-Silicon-native path for image/video generation and local VLM analysis, and the provider metadata/skill linkage looks generally aligned with the registry model.

I need to request changes because the advertised spawn-free test set is not currently portable: on this Windows/AMD64 review machine, the focused MLX tests fail before merge.

Verification I ran:

python -m py_compile tools/_mlx/env.py tools/analysis/mlx_caption.py tools/graphics/mlx_image.py tools/video/mlx_video.py tests/tools/test_mlx_caption.py tests/tools/test_mlx_image.py tests/tools/test_mlx_video.py
# passed

python -m pytest tests/tools/test_mlx_caption.py tests/tools/test_mlx_image.py tests/tools/test_mlx_video.py -q
# 64 passed, 4 skipped, 2 failed

The failing tests are:

tests/tools/test_mlx_image.py::test_status_available_with_full_env
tests/tools/test_mlx_video.py::test_status_available_with_full_env

Both build a fake complete MLX env and then assert env["ok"] is True, but resolve_mlx_env() correctly rejects non-Apple-Silicon hosts with MLX runs on Apple Silicon only (got AMD64). Please make these tests explicitly mock the platform/architecture when asserting the arm64-available path, or otherwise skip/branch the ok=True assertion on non-arm64 CI while still testing the unavailable path.

I did confirm the registry sees mlx_image, mlx_video, and mlx_caption in their intended capability buckets. I did not find an actionable security issue in the reviewed diff, but the focused test suite needs to be green before merge.

The two test_status_available_with_full_env tests asserted env["ok"] is True
unconditionally, but resolve_mlx_env() correctly rejects non-Apple-Silicon
hosts (MLX runs on Apple Silicon only). So both tests failed on the Windows/
AMD64 review machine and on ubuntu x86_64 CI — the production code was right,
the tests were not portable.

Mock platform.machine() -> "arm64" inside these two tests so the full
available-path (including the arch guard) executes on ANY host, and drop the
now-stale `if env["arm64"]` skip-guard in favor of direct assertions. The
unavailable-path tests stay honest (they don't force arch).

Verified:
- real arm64:        70 passed (3 MLX suites)
- simulated AMD64:   66 passed, 4 skipped (unrelated caption/LM Studio), 0 failed
  — incl. both previously-failing tests green under the simulated host

Addresses calesthio review on calesthio#311 (CHANGES_REQUESTED).

Co-Authored-By: Claude <noreply@anthropic.com>
@ziyu4huang

Copy link
Copy Markdown
Author

Thanks for the careful review @calesthio — you're right, the production resolve_mlx_env() arch guard was correct and the two _with_full_env tests weren't portable. Fixed in 491efaf using your first suggested shape.

Change (test-only; tools/_mlx/env.py untouched). Both test_status_available_with_full_env tests now mock the platform to Apple Silicon:

# Mock Apple Silicon so the full available-path (incl. the arch guard in
# resolve_mlx_env) runs on ANY CI host, not just native arm64 reviewers.
monkeypatch.setattr("tools._mlx.env.platform.machine", lambda: "arm64")

…then assert env["ok"] is True, env["arm64"] is True, and get_status() == AVAILABLE directly. The stale if env["arm64"]: skip-guard is removed — with the mock, the available-path runs everywhere. The unavailable-path tests (..._when_env_unset, ..._when_venv_missing) stay honest: they don't force the arch, so they still exercise the ok=False branch on every host.

I went with mocking rather than skipUnless(platform.machine()=="arm64") so the available-path is actually exercised on x86_64 CI, not silently skipped.

Verification. This Mac is arm64, so I couldn't natively reproduce your AMD64 failure — I simulated the review host by rebindng platform.machine process-wide to "AMD64" and re-ran:

# real arm64 host
.venv/bin/python -m pytest tests/tools/test_mlx_image.py tests/tools/test_mlx_video.py tests/tools/test_mlx_caption.py -q
→ 70 passed

# simulated AMD64 review host (platform.machine forced to "AMD64" process-wide)
→ 66 passed, 4 skipped (unrelated caption/LM Studio), 0 failed
→ tests/tools/test_mlx_image.py::test_status_available_with_full_env  PASSED
→ tests/tools/test_mlx_video.py::test_status_available_with_full_env   PASSED

The previously-failing tests are green under the simulated non-arm64 host; the focused suite is clean. CI (ubuntu x86_64, platform.machine()=="x86_64") will hit the same mocked path. Re-requesting your review.

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