feat(external): add Atlas Cloud image provider - #9476
binyangzhu000-sudo wants to merge 5 commits into
Conversation
Signed-off-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com>
lstein
left a comment
There was a problem hiding this comment.
Since Atlas Cloud supports many popular image generation models, why don't you add support for multiple models? It doesn't cost the user anything. Otherwise this PR is overkill for adding a single model.
…rnal-provider # Conflicts: # invokeai/frontend/web/src/services/api/schema.ts
Atlas Cloud fronts many popular image models behind one endpoint, so ship a set of them rather than a single model. - Register 11 more text-to-image starter models across 8 vendors (FLUX.1 Dev, FLUX.2 Pro, Qwen Image 3.0, Z-Image Turbo, MAI-Image-2.5, Ideogram V4 Turbo/Quality, Krea 2 Turbo, HiDream O1 1.5, Grok Imagine Image 2.0 and Nano Banana 2), each with the capabilities its upstream model reports. - Record per-model request schemas in the provider: these models spell output dimensions as "size", "image_size" or "aspect_ratio", and batch size as "num_images", "n" or not at all, so a single payload shape does not fit them all. Unlisted models keep using the explicit-size schema, so custom installs via `external://atlascloud/<model_id>` are unaffected. - Cover every request-schema variant with tests.
|
Thanks for the review — that's a fair point, a single model didn't justify the provider. I pushed follow-up commit
Validation:
Notes:
|
|
Thanks for the review — that's a fair point, so I pushed follow-up commit Now 12 Atlas Cloud starter models across 8 vendors: FLUX.1 Schnell/Dev, FLUX.2 Pro, Qwen Image 3.0, Z-Image Turbo, MAI-Image-2.5, Ideogram V4 Turbo/Quality, Krea 2 Turbo, HiDream O1 1.5, Grok Imagine Image 2.0 and Nano Banana 2. The models don't share one request shape, which is why this needed a bit more than extra table rows:
So the provider now records each model's request schema and builds the payload from it. Models not in that table fall back to the explicit-size schema, so custom This commit also merges current Validation
One deliberate omission: I left out the |
Both CI failures had the same cause: the two config fields this branch
adds were in the source but not in the generated files that CI diffs
against.
- openapi-checks regenerates invokeai/frontend/web/openapi.json and
compares it to the committed copy. The InvokeAIAppConfig schema was
missing the external_atlascloud_api_key and external_atlascloud_base_url
properties, and its description string was missing the matching two
Attributes lines.
- check-and-build runs check-docs-data, which regenerates
docs/src/generated/settings.json. The same two settings were absent.
Both are inserted after the alibabacloud pair, matching the field order in
config_default.py, so a regeneration reproduces this ordering.
schema.ts already carried these fields and typegen-checks was green, so it
is untouched; its titles ("External Atlascloud Api Key" / "Base Url") were
used to confirm the openapi.json entries match what the generator emits.
Running the generators locally needs the full torch install, so the
artifacts were edited to the exact shape CI reported as missing rather
than regenerated here; prettier 3 reports openapi.json unchanged, which is
the formatting step openapi-checks applies before diffing.
|
Both CI failures had the same root cause, and neither was a code problem: the two config fields this
Both are now inserted after the
One note on how this was verified, since it bounds the claim: running the generators locally needs |
lstein
left a comment
There was a problem hiding this comment.
Thanks for the thorough follow-up — this is a much better shape for the PR, and I want to start with what I verified rather than what I found.
The per-model schema table is right. I checked all 12 registered models against Atlas Cloud's own published model pages. Every entry matches: the size / image_size / aspect_ratio split, num_images vs n vs unsupported, per-model seed support, and resolution really is lowercase 1k/2k/4k. I was suspicious of the two different preset vocabularies — portrait_3_4/portrait_9_16 for Ideogram and Krea, portrait_4_3/portrait_16_9 for HiDream — and they check out exactly as you documented them. Your note about 21:9 reducing to 7:3 and failing the capability check is also correct. pytest tests/app/services/external_generation/ gives 78 passed here, and pinned ruff@0.11.2 check/format --check are clean on the three changed files.
Reviewed at 6efcefc902. Three blockers below, then some smaller items.
Blocker 1 — the provider is unreachable from the UI, for all 12 models
EXTERNAL_PROVIDER_NODE_TYPES in invokeai/frontend/web/src/features/nodes/util/graph/generation/buildExternalGraph.ts:25 still lists only alibabacloud, gemini, openai and seedream. This PR touches no frontend source — only the generated schema.ts.
The backend is fully wired, which is what makes this reachable: sync_configured_external_starter_models installs every Atlas Cloud starter model as soon as external_atlascloud_api_key is set (dependencies.py:263), and readiness.ts:391 exempts external models from sub-model validation, so the Invoke button is enabled. Selecting any Atlas Cloud model and hitting Invoke produces a "Failed to build graph" toast and queues nothing.
I confirmed this with a throwaway vitest against the unchanged buildExternalGraph.ts:
FAIL has a node type registered for atlascloud
Error: Wrong assertion encountered: "No invocation node registered for external provider 'atlascloud'"
❯ Module.buildExternalGraph src/features/nodes/util/graph/generation/buildExternalGraph.ts:49
This predates the follow-up commit, but it means none of the new capability work is exercisable from the app. Worth noting pnpm lint:tsc cannot catch it — the lookup goes through an as keyof typeof cast, so a missing key is just undefined. The existing 'uses provider-specific node types' test in buildExternalGraph.test.ts is the pattern that would have; please add the atlascloud case alongside the map entry.
Blocker 2 — Nano Banana 2 declares both aspect_ratio_sizes and resolution_presets
It is the only model in the repo that declares both, and every consumer treats them as mutually exclusive. Gemini 3 Pro and 3.1 Flash deliberately set resolution_presets without aspect_ratio_sizes.
UI: modelSelected.ts:860 is if (resolution_presets) … else if (aspect_ratio_sizes), but the two dropdown components each render whenever their own field is present — ExternalModelResolutionSelect returns null only on !aspectRatioSizes, ExternalModelImageSizeSelect only on !presets. Both live in the same FormControlGroup in ExternalSettingsAccordion and both use t('parameters.resolution'), so this model shows two selects with the identical label "Resolution".
Backend: _bucket_request looks up aspect_ratio_sizes[ratio] before it considers anything else and rewrites width/height to it. Since _atlascloud_aspect_ratio_sizes is built at the default base=1024, all 18 of the 2K and 4K presets get clobbered back to the 1K dimensions. Driving each of the 27 presets through ExternalGenerationService._bucket_request:
1:1 (1K) — 1024×1024 req=1024x1024 -> 1024x1024
...
1:1 (2K) — 2048×2048 req=2048x2048 -> 1024x1024 <-- CHANGED
2:3 (2K) — 1344×2016 req=1344x2016 -> 672x1008 <-- CHANGED
9:16 (2K) — 1152×2048 req=1152x2048 -> 576x1024 <-- CHANGED
1:1 (4K) — 4096×4096 req=4096x4096 -> 1024x1024 <-- CHANGED
4:5 (4K) — 3264×4080 req=3264x4080 -> 768x960 <-- CHANGED
...18 of 27 presets changed
The payload happens to survive today because resolution comes from request.image_size, not from the dimensions — but the two controls do get out of sync. Concrete sequence: select the model (→ 1:1/1K), pick "4:3 (2K)" in the lower select (→ imageSize="2K", 2048×1536), then pick "16:9" in the upper one. aspectRatioIdChanged sets 1024×576 and leaves imageSize at "2K", so the request goes out as aspect_ratio: "16:9", resolution: "2k" and the returned image is 2048×1152 while the bbox reads 1024×576.
Suggested fix: drop aspect_ratio_sizes from atlascloud_nano_banana_2 and keep allowed_aspect_ratios + resolution_presets, matching Gemini 3 Pro.
Related: the new tests can't see any of this because they call provider._build_payload(request) directly. The app's real path is ExternalGenerationService.generate(), which runs _refresh_model_capabilities → _bucket_request → _drop_unsupported_capabilities → _validate_request first. At least one test that goes through the service would be worth having.
Blocker 3 — MAI-Image-2.5's declared maximum is a size the model cannot accept
max_image_size=ExternalImageSize(width=1360, height=1360),Upstream caps width × height at 1,049,088. 1360×1360 is 1,849,600, so the largest size Invoke advertises for this model is guaranteed to 400. The same doc gives a minimum of 768 per side, which ExternalModelCapabilities cannot express at all — I confirmed that 512×512 and even 100×100 pass validation and go out as size: "100*100".
max_image_size alone can't encode this constraint, but the machinery already in this PR can: give MAI allowed_aspect_ratios + aspect_ratio_sizes buckets that sit inside the legal envelope, e.g. 1:1 → 1024×1024 (1,048,576), 4:3 → 1152×864, 3:2 → 1248×832, 4:5 → 896×1120. Note that 16:9 and 9:16 are unreachable for this model — the short side must be ≥ 768 while the long side stays ≤ 1360, and 1365×768 breaks the 1360 cap. If you'd rather not add buckets, max_image_size should at least come down to 1024×1024.
Non-blocking
- Qwen Image 3.0 has no
max_image_sizeeven though upstream caps it at 2048, and its two siblings with identical limits (FLUX.2 Pro, Z-Image Turbo) both declare one. 4096×4096 currently passes validation and goes out assize: "4096*4096". - Qwen's seed range is
[0, 2147483647], but Invoke'sSEED_MAXis2**32-1. Not reachable from the linear UI, since no external starter model exposes the seed panel control, but roughly half of the seeds a user can enter in the workflow editor will 400. - Grok's
1:2and2:1aren't members ofzAspectRatioID(controlLayers/store/types.ts:727), so they render as<option>s that theisAspectRatioIDguard silently refuses to dispatch — dead entries in the dropdown. DALL-E 3's7:4/4:7already ships broken this way, so this matches existing precedent rather than introducing a new class of problem, but it's easy to avoid here. - Grok also accepts
resolution: "1k" | "2k", which the table leaves unwired (resolution_field=None), so it always generates at the 1k default no matter what the user picks. "square"in_STANDARD_SIZE_PRESETSis unreachable — it ties withsquare_hdat ratio 1.0 andmin()returns the first match.- Nothing pins
_MODEL_SCHEMASkeys against the starter-model sources. They agree today — I checked all 12 — but a typo on either side would silently fall back to the explicit-size schema and sendsizeto anaspect_ratio-only model with no test failing. A one-line test asserting the two key sets match would close that.
Sources for the schema verification: Nano Banana 2 · HiDream O1 1.5 · Krea 2 Turbo · Ideogram v4 Turbo · Ideogram v4 Quality · Grok Imagine Image 2.0 · Qwen Image 3.0 · MAI-Image-2.5 · FLUX.2 Pro · FLUX.1 Dev · Z-Image Turbo
Summary
Adds Atlas Cloud as a first-class external image generation provider.
Related Issues / Discussions
None.
QA Instructions
uv tool run ruff@0.11.2 format --check <changed Python files>uv tool run ruff@0.11.2 check <changed Python files>python -m compileall <changed Python files>pytest tests/app/services/external_generation/test_atlascloud_provider.py tests/app/services/external_generation/test_seedream_provider.py tests/app/services/external_generation/test_alibabacloud_provider.py tests/app/services/external_generation/test_external_provider_adapters.py -q(38 passed)python scripts/generate_openapi_schema.py | pnpm typegen(deterministic on a second run)pnpm lint:tscpnpm exec prettier --check src/services/api/schema.tspython -m pip wheel . --no-deps(wheel built successfully)Merge Plan
No special merge steps required.
Checklist
What's Newcopy (if doing a release after this PR)