Skip to content

Add Workers AI model catalogue and multi-shape inference adapter - #8

Merged
guillaumelauzier merged 1 commit into
mainfrom
claude/generatedart-backend-fixes-dmhq3v
Sep 5, 2026
Merged

Add Workers AI model catalogue and multi-shape inference adapter#8
guillaumelauzier merged 1 commit into
mainfrom
claude/generatedart-backend-fixes-dmhq3v

Conversation

@guillaumelauzier

Copy link
Copy Markdown
Contributor

What

runWorkersAi assumed a single contract — {prompt, seed} in, binary image bytes out — and threw unexpected Workers AI response shape on anything else. Workers AI models don't share that contract, and the differences don't follow vendor or task:

Model Input Output
flux-1-schnell steps JSON {image: <base64>}
Stable Diffusion family num_steps ReadableStream
flux-2-dev / klein-4b / klein-9b multipart form-data JSON base64
leonardo/phoenix-1.0 JSON ReadableStream
leonardo/lucid-origin JSON JSON base64
vision / instruct image+prompt / messages JSON text

Two models from the same vendor (Leonardo) disagree with each other, so shape can't be inferred — it has to be recorded per model.

The live consequence: flux-1-schnell, the fastest and cheapest model in the catalogue, failed at render time — after the renderer had been debited. img2img, inpainting, vision and instruct models couldn't work at all, since their inputs (image, mask, messages) were never sent and their text outputs had no output kind.

Changes

src/ai/workersAiCatalogue.ts (new) — one row per allowlisted model recording request shape, response shape, content type, defaults and parameter allowlist. Every row was checked against that model's own docs page rather than copied from a neighbour.

src/ai/inference.tsrunWorkersAi dispatches on the catalogue row instead of guessing. img2img / inpaint / vision gain the binary inputs they always needed (image as a uint8 array, mask for inpainting). InferenceOutput gains a text kind for the companion lane.

ModelKind is deliberately unchanged — companion models are an internal assist for prompt expansion and reverse-prompting, not something a creator publishes. Widening the published-model schema touches registration validation and job persistence, so it belongs in its own change.

src/render/handlers.ts — closes a separate hole the same table fixes: provider_model_id was any free-form string up to 200 chars forwarded straight to env.AI.run(), so a typo or an unpriced model published fine and only failed once a user had been charged. Registration now rejects ids outside the catalogue and names what is allowed. Also fixes stored render objects always being named .png regardless of what the model emitted.

Note on cost

costUnits is a relative weight (cheapest = 1), clearly marked as a placeholder. Real per-model pricing is published per model page and is variously per-step, per-512px-tile, or both — this is an ordering for a future billing layer to reason about, not a dollar amount. Substitute real figures before charging against them.

Testing

  • 15 new unit tests over the pure helpers — all four response shapes (including the json_base64_image case that was the bug), per-family request building, the steps vs num_steps divergence, param allowlisting, and catalogue membership.
  • 48/48 unit tests. output_hash behaviour for existing binary models is unchanged, so reproducibility of already-recorded jobs is preserved.
  • 146/146 smoke checks against a live wrangler dev, including 3 new end-to-end checks that registration rejects an unlisted id and accepts a catalogued one.
  • npm run typecheck clean; wrangler deploy --dry-run clean on both environments.

One caveat worth knowing: running the smoke suite twice back-to-back trips the SIWE rate limiter and fails four auth-abuse checks. That's pre-existing test-environment behaviour, not a regression — a single run against fresh state is 146/146.


🤖 Generated with Claude Code

https://claude.ai/code/session_01QuSa99HABSrWRarT9BQ5Tz


Generated by Claude Code

runWorkersAi assumed one contract — {prompt, seed} in, binary image
bytes out — and threw `unexpected Workers AI response shape` on
anything else. Workers AI models do not share that contract, and the
differences don't follow vendor or task:

  - flux-1-schnell takes `steps` and returns JSON {image: <base64>},
    so the fastest model in the catalogue failed at render time, after
    the renderer had already been debited.
  - The Stable Diffusion family takes `num_steps` and returns a
    ReadableStream.
  - flux-2-* take multipart form-data, even for a prompt-only call.
  - Leonardo's two models disagree with each other: phoenix-1.0
    streams bytes, lucid-origin returns base64 JSON.
  - Vision and instruct models return text, which had no output kind.

Adds src/ai/workersAiCatalogue.ts: one row per allowlisted model
recording its request shape, response shape, content type, defaults and
parameter allowlist, each checked against that model's own docs page.
runWorkersAi now dispatches on the row rather than guessing, and
img2img/inpaint/vision gain the binary inputs they always needed
(image as a uint8 array, mask for inpainting).

The same table closes a separate hole: provider_model_id was any
free-form string forwarded straight to env.AI.run(), so a typo or an
unpriced model published successfully and only failed once a user had
been charged. Registration now rejects ids outside the catalogue and
names what is allowed.

InferenceOutput gains a `text` kind for the companion lane. ModelKind is
deliberately unchanged — companion models are an internal assist for
prompt expansion and reverse-prompting, not something a creator
publishes; widening the published-model schema is a separate change.

Stored render objects were also always named .png regardless of what the
model emitted; the extension now follows the content type.

Verification: 15 new unit tests over the pure helpers (all four response
shapes, per-family request building, param allowlisting, catalogue
membership), 48/48 unit tests, 146/146 smoke checks including three new
end-to-end registration checks, typecheck clean, dry-run deploy clean on
both environments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuSa99HABSrWRarT9BQ5Tz
@cloudflare-workers-and-pages

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
generatedart 1ba5757 Commit Preview URL

Branch Preview URL
Sep 05 2026, 02:53 PM

@guillaumelauzier
guillaumelauzier marked this pull request as ready for review September 5, 2026 15:02
@guillaumelauzier
guillaumelauzier merged commit 6cc627a into main Sep 5, 2026
2 checks passed
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