Skip to content

feat(external): add Atlas Cloud image provider - #9476

Open
binyangzhu000-sudo wants to merge 5 commits into
invoke-ai:mainfrom
binyangzhu000-sudo:codex/atlascloud-external-provider
Open

binyangzhu000-sudo wants to merge 5 commits into
invoke-ai:mainfrom
binyangzhu000-sudo:codex/atlascloud-external-provider

Conversation

@binyangzhu000-sudo

Copy link
Copy Markdown

Summary

Adds Atlas Cloud as a first-class external image generation provider.

  • Implements the Atlas Cloud asynchronous submit, poll, and image download flow with rate-limit and response validation.
  • Adds API key/base URL configuration, dependency registration, and provider availability reporting.
  • Adds an Atlas Cloud image generation invocation and a FLUX.1 Schnell starter model.
  • Regenerates the frontend API schema and adds focused provider contract tests.

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:tsc
  • pnpm exec prettier --check src/services/api/schema.ts
  • python -m pip wheel . --no-deps (wheel built successfully)

Merge Plan

No special merge steps required.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Tests added / updated (if applicable)
  • ❗Changes to a redux slice have a corresponding migration
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

Signed-off-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com>
@github-actions github-actions Bot added api python PRs that change python files invocations PRs that change invocations backend PRs that change backend files services PRs that change app services frontend PRs that change frontend files python-tests PRs that change python tests labels Aug 7, 2026
@lstein lstein self-assigned this Aug 8, 2026
@lstein lstein added the 7.0.0 label Aug 8, 2026
@lstein lstein moved this to 7.0 Theme: Tabbed Layout UI in Invoke - Community Roadmap Aug 8, 2026

@lstein lstein left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.
@binyangzhu000-sudo

Copy link
Copy Markdown
Author

Thanks for the review — that's a fair point, a single model didn't justify the provider. I pushed follow-up commit 81d2ffd which addresses it:

  • Registered 11 more text-to-image models across 8 vendors, so the provider now ships 12: 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. Each entry declares the capabilities its upstream model actually reports (max batch size, seed support, size limits, allowed aspect ratios).
  • Recorded per-model request schemas in the provider. This turned out to be necessary rather than cosmetic: these models don't share one request shape. Output dimensions arrive as an explicit size string, a named image_size preset, or an aspect_ratio, and batch size is spelled num_images, n, or is unsupported. _build_payload now sends only the fields the target model accepts, so adding a model is a table entry. Models not in the table keep using the explicit-size schema, so custom installs via external://atlascloud/<model_id> are unaffected.
  • Merged main to clear the schema.ts conflict; the PR is mergeable again.

Validation:

  • pytest tests/app/services/external_generation/ — 78 passed (20 new tests, one per request-schema variant, plus the size-preset and aspect-ratio mapping cases).
  • ruff check and ruff format --check clean on the three changed files.
  • Cross-checked each registered model's generated payload against that model's published request schema (12 models × 7 different width/height inputs): no unknown fields, no missing required fields, and every enum-valued field within its allowed set. No paid generation calls were made — this compares against the published schemas, not live output.

Notes:

  • Aspect-ratio entries are limited to ratios already in lowest terms, because the capability check reduces the requested dimensions before comparing. That's why 21:9 isn't offered for Nano Banana 2 even though the upstream model accepts it — 21:9 reduces to 7:3 and would fail the check for the 2K/4K presets.
  • The img2img/inpaint modes remain unadvertised; every model here is registered as txt2img only, matching what I verified.

@binyangzhu000-sudo

Copy link
Copy Markdown
Author

Thanks for the review — that's a fair point, so I pushed follow-up commit 81d2ffd to ship a set of models instead of a single one.

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:

  • Output dimensions arrive as an explicit size string, a named image_size preset, or an aspect_ratio.
  • Batch size is spelled num_images, n, or isn't supported.
  • Seed support varies per model.

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 external://atlascloud/<model_id> installs behave exactly as before. Each starter model's declared capabilities (seed support, max images, size limits, allowed aspect ratios) mirror what its upstream model actually reports.

This commit also merges current main, so the branch is no longer conflicting.

Validation

  • pytest tests/app/services/external_generation/ — 78 passed, including 20 new tests covering every request-schema variant.
  • ruff check and ruff format --check — clean.
  • Cross-checked each registered model's generated payload against Atlas Cloud's published per-model OpenAPI schemas over 7 different width/height inputs: no unknown fields, no missing required fields, no out-of-enum values.

One deliberate omission: I left out the 21:9 aspect ratio for Nano Banana 2. Invoke's capability check reduces the requested dimensions to lowest terms before comparing, which turns 21:9 into 7:3 and would reject the ratio; every listed ratio is already in lowest terms so the 1K/2K/4K presets all validate.

lstein and others added 2 commits September 15, 2026 08:28
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.
@github-actions github-actions Bot added the docs PRs that change docs label Sep 16, 2026
@binyangzhu000-sudo

Copy link
Copy Markdown
Author

Both CI failures had the same root cause, and neither was a code problem: the two config fields this
PR adds were present in the source but missing from the generated files 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 there.

Both are now inserted after the alibabacloud pair, matching the field order in
config_default.py, so a regeneration reproduces this ordering rather than reshuffling it.

schema.ts already carried these fields and typegen-checks was green, so it is untouched — I used
its generated titles (External Atlascloud Api Key / External Atlascloud Base Url) to confirm the
openapi.json entries match exactly what the generator emits.

One note on how this was verified, since it bounds the claim: running the generators locally needs
the full torch install, so rather than regenerating I edited the two artifacts to the exact shape CI
reported as missing, then confirmed prettier 3 reports openapi.json unchanged — that being
the formatting step openapi-checks applies before diffing. I also re-read the failing run's log to
confirm settings.json was the only file in the git diff --exit-code output, so there is no third
artifact still out of date.

@lstein lstein left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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_size even 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 as size: "4096*4096".
  • Qwen's seed range is [0, 2147483647], but Invoke's SEED_MAX is 2**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:2 and 2:1 aren't members of zAspectRatioID (controlLayers/store/types.ts:727), so they render as <option>s that the isAspectRatioID guard silently refuses to dispatch — dead entries in the dropdown. DALL-E 3's 7:4/4:7 already 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_PRESETS is unreachable — it ties with square_hd at ratio 1.0 and min() returns the first match.
  • Nothing pins _MODEL_SCHEMAS keys 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 send size to an aspect_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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

7.0.0 api backend PRs that change backend files docs PRs that change docs frontend PRs that change frontend files invocations PRs that change invocations python PRs that change python files python-tests PRs that change python tests services PRs that change app services

Projects

Status: 7.0 Theme: Tabbed Layout UI

Development

Successfully merging this pull request may close these issues.

2 participants