feat(api): default /label and /predict to async polling, support ?wait=true for compat (#9)#16
Merged
Merged
Conversation
…t=true for compat (#9) Move the blocking `_dispatch_and_wait` Celery `.get(timeout=180)` out of `POST /api/v1/label/preview`. The endpoint now defaults to dispatching the SAM 3.1 playground task and returning `202 Accepted` with `{job_id, status, result_url}`; clients poll the new `GET /api/v1/job/{job_id}` until the task completes. `?wait=true` keeps the legacy synchronous shape for one release. The polling endpoint is generic: it maps Celery's PENDING / STARTED / SUCCESS / FAILURE / REVOKED states into a four-value `queued | running | completed | failed` enum and returns the task's return value in `result` on success. UI callers route through a new `pollJob<T>()` helper in `ui/src/api.ts`, so existing components (PlaygroundPage, LabelPage) still `await previewPrompts(...)` and get a `PreviewResponse` back without knowing about the polling. A concurrency regression test (`tests/test_concurrent_label.py`) fires 50 simultaneous `/label` calls against the ASGI app and asserts `/health` keeps responding under 200 ms during the storm. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #9.
Summary
Moves the blocking
Celery .get(timeout=180)call out of the request path onPOST /api/v1/label/preview— under load this was filling FastAPI's thread pool and stalling fast endpoints like/health. The endpoint now dispatches the SAM 3.1 playground task and returns202 Acceptedwith{job_id, status, result_url}immediately; clients poll the new genericGET /api/v1/job/{job_id}until completion.Migration guide
Existing API consumers that POSTed to
/label/previewand received a synchronousPreviewResponsebody should switch to the polling pattern: readjob_idfrom the 202 envelope, thenGET /api/v1/job/{job_id}untilstatus == "completed"and read the body fromresult. For one release we keep the legacy behavior available behind?wait=true(still bounded at the original 180 s timeout) so callers can migrate without breakage; the flag is documented as deprecated and will be removed in a future release. The UI helperpreviewPrompts()already polls under the hood, so React components thatawait previewPrompts(...)keep working unchanged.Endpoints changed
POST /api/v1/label/preview— defaults to async (202+{job_id, status, result_url}); legacy sync body available behind?wait=true(deprecated).GET /api/v1/job/{job_id}— new. Generic Celery task polling surface; mapsPENDING/RECEIVED/RETRY → queued,STARTED → running,SUCCESS → completed,FAILURE/REVOKED → failed, returns the task's return value inresulton success.The other endpoints listed in the issue's migration table either already returned
202 + job_id(e.g./label,/label/exemplar,/predict/videofor long videos,/comparisons/run) or run in-process withasyncio.to_threadrather than via Celery (/predict/image,/predict/sam,/predict/sam/video,/predict/videoshort-path) — converting those to a Celery polling pattern would be a much larger change beyond #9's stated scope. The/job/{job_id}endpoint is the building block for those future migrations.Test plan
uv run pytest -q— full suite. Same baseline asmain(14 failed / 7 skipped, all infra-dependent), +2 new passing tests.tests/test_concurrent_label.py::test_label_storm_does_not_starve_health— 50 simultaneous/labelPOSTs against the ASGI app; asserts/healthworst-case latency stays under 200 ms during the storm.tests/test_concurrent_label.py::test_job_endpoint_maps_celery_states— verifies the four-state status mapping forGET /api/v1/job/{job_id}.cd ui && npm run build— clean build, no TS or lint errors.npx tsc --noEmitandnpx eslint --max-warnings=0 src/api.ts— clean.?wait=trueflag, and the newGET /api/v1/job/{job_id}endpoint. README example pipeline uses the polling pattern.make build PROFILE=apple, bring up the stack, hit/api/v1/label/preview, confirm 202, poll, confirm result. (Skipped in this worktree — the Apple profile needs MLX on macOS hardware; deferred to local validation.)🤖 Generated with Claude Code