Skip to content

Let each image model carry its own request parameters - #198

Merged
davior merged 2 commits into
mainfrom
claude/zealous-sagan-fp0k24
Oct 1, 2026
Merged

davior merged 2 commits into
mainfrom
claude/zealous-sagan-fp0k24

Conversation

@davior

@davior davior commented Oct 1, 2026

Copy link
Copy Markdown
Owner

What

Image generation could only send the six named size presets, translated per model by a hard-coded list of size dialects. A model that wants something else (exact pixels such as {"width": 1600, "height": 900}, a resolution tier, step counts) couldn't be used that way without a code change.

Settings → AI Services → Images now has a Model parameters (JSON) editor per model. The JSON is merged into the fal request for that model, on every generation path (the editor's Generate image dialog and the assistant's generate_image action).

How it works

  • Stored per user, keyed by model id, inside the existing image_gen_config setting. Curated and custom models both work. No migration: old blobs just lack the key.
  • Merge rules (build_fal_image_body in routers/settings.py):
    • If the JSON sets image_size or aspect_ratio, it replaces the size the dropdown would send, so the two can't conflict. Other keys (resolution, seed, output_format, ...) go out alongside the dropdown size.
    • prompt, num_images and sync_mode are managed by the app. They're refused on save with a clear message, and stripped again at send time. Cost accounting and the download path assume exactly one fetchable image.
  • PUT /settings/images takes model_params as a per-model patch: an object sets that model's params, null/{} clears them, and models not mentioned are left alone.
  • The guide: example chips (exact pixels, aspect ratio, resolution tier, quality, PNG), a link to the model's schema on fal.ai, and a short "What can I put here?" note. Chips stack into the draft.
  • The Size dropdown in the Generate image dialog is disabled ("From parameters") with a link to Settings when a model's parameters set the size. The preset-based cost estimate is hidden in that case, since it scales by preset megapixels.

I did not change the preset list or the existing dialect mapping. The code comment says FLUX.1/.2 accept presets verbatim, so I can't tell from the code which models were misbehaving. The override works whatever fal says a model accepts.

Second commit: unrelated flaky test

test_a_beating_job_survives_the_sweeper slept a fixed 100 ms and then asserted. In this checkout it failed on every full run once the new tests were added (and passed again with either new wiring test deselected, or with a diagnostic plugin loaded), with no change to the code it tests. It now polls up to 5 s for the first beat. A heartbeat that never beats still fails it; I checked that by breaking runner.heartbeat temporarily. It's a separate commit (Wait for the first heartbeat...) so it's easy to drop if you'd rather handle it separately.

Verification

  • Backend: pytest -q → 1116 passed, 5 consecutive full runs (30 new tests in tests/test_image_params.py). Mutation check: dropping model_params from the persisted blob makes 6 of them fail.
  • Frontend: npm test → 190 passed (new utils/imageParams.test.ts), npm run build (tsc + vite) clean.
  • Browser pass with Playwright against a local backend/Vite on a throwaway DB: chips, stacking, invalid/array/reserved-key errors, save, persistence across reload, per-model drafts, Revert, clear, the modal's disabled Size dropdown, no failing requests. Light and dark screenshots checked.
  • Not verified: an actual generation against fal.ai (no key in the sandbox). The tests assert the exact request body sent for the model, but whether fal accepts a given key for a given model is down to that model's schema. fal's error message is passed through to the dialog if it rejects one.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MDpGog3zexuU9KNPpP3jEM


Generated by Claude Code

claude added 2 commits October 1, 2026 11:03
Image generation could only send the six named size presets, translated
per model by a hard-coded list of size dialects. A model that wants
something else (exact pixels such as {"width": 1600, "height": 900}, a
resolution tier, step counts) couldn't be used that way without a code
change.

Settings -> AI Services -> Images now has a "Model parameters (JSON)"
editor per model. The JSON is merged into the fal request for that model,
for every generation path (editor dialog and the assistant's
generate_image action). Stored per user alongside the other image
settings, so curated and custom models both work and no migration is
needed.

Merge rules (build_fal_image_body):
- If the JSON sets image_size or aspect_ratio it replaces the size the
  dropdown would send, so the two can never conflict. Other keys such as
  resolution or seed go out alongside the dropdown size.
- prompt, num_images and sync_mode are managed by the app and refused on
  save (and stripped again at send time): cost accounting and the
  download path assume exactly one fetchable image.

The editor explains itself: example chips (exact pixels, aspect ratio,
resolution, quality, PNG), a link to the model's schema on fal.ai, and a
short "What can I put here?" note. The Size dropdown in the Generate
image dialog is disabled with an explanation when a model's parameters
set the size.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDpGog3zexuU9KNPpP3jEM
test_a_beating_job_survives_the_sweeper started a heartbeat thread, slept
100 ms and asserted the sweeper left the job alone. That only holds if
the thread completes a beat inside that window; under load it may not,
and then the sweeper is judging a job that simply hasn't beaten yet.

The result moved with whatever else was in the suite: in this checkout it
failed on every full run once two new tests were added elsewhere, and
passed again with either one deselected, with no change to the code under
test. Poll for the first beat (up to 5 s) before asserting. A heartbeat
that never beats still fails the test, after the wait, with the job still
stale.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDpGog3zexuU9KNPpP3jEM
@davior
davior marked this pull request as ready for review October 1, 2026 11:17
@davior
davior merged commit 87609e8 into main Oct 1, 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