Skip to content

[API server][Bug fix] Keep /generate streaming when the form omits streaming on FastAPI 0.147+ - #714

Merged
merceod merged 2 commits into
mstar-project:mainfrom
arun-elr:fix/form-bool-default
Oct 20, 2026
Merged

merceod merged 2 commits into
mstar-project:mainfrom
arun-elr:fix/form-bool-default

Conversation

@arun-elr

Copy link
Copy Markdown
Contributor

Fixes #713.

What does this PR do?

FastAPI 0.147.0 reads a bool form field that the request leaves out as False (an unchecked HTML checkbox) instead of its declared default. mstar does not pin fastapi and CI now installs 0.150.0, so on the Python frontend a /generate request without streaming quietly gets one aggregated JSON body instead of the NDJSON stream. That hits the documented curl ... /generate -F 'text=...' commands (quickstart, examples/curl.sh, the windowed curl -sN video example), and output_modalities=video_frame without the field fails with 400. The Rust frontend still streams by default.

generate() now uses True when the parsed form (already cached on the request) has no streaming key; an explicit value is read as before. streaming is the only bool form field in the package (details below).

How was it tested?

5 new cases in test/modular/test_generate_route.py post to the real /generate route with a stand-in server: no streaming field (urlencoded and multipart) must stream NDJSON, streaming=false (both) must return JSON, and video_frame output without the field must be accepted. With FastAPI 0.147.0 and 0.150.0 (Starlette 1.9.0, as CI resolves), 3 of the 5 fail on main and all pass on the head; with 0.143.3 and 0.146.0 all 5 pass on both. The two test_video_frame_protocol.py cases that call generate() directly now pass a request stub with a form().

ruff check . passes. The CPU Core selection from ci.yml (same paths, -n 4 --dist worksteal) on the head: 4655 passed, 866 skipped with FastAPI 0.150.0, and the same with 0.143.3; the TP lockstep shard passed 7 of 7 on both.

Not tested

No served-model run: the change is in request parsing before submission, and the route tests drive the real FastAPI app. The Rust frontend is untouched; it already defaults streaming to true.

Form fields audited

generate() is the only route declaring Form(...) parameters. streaming: bool = Form(True) is the only bool one, so the only one the FastAPI change affects; text, input_modalities, model_kwargs and request_id are Optional[str] = Form(None) and output_modalities is str = Form("text"), and those keep their defaults. /v1/audio/transcriptions and /v1/images/edits read request.form() themselves, so FastAPI's parameter parsing does not touch them. There is no bool form field defaulting to False in the package.

Design notes
  • Declaring the field as Optional[bool] = Form(None) and mapping None to the default does not work: FastAPI 0.147+ applies the same rule to bool | None (_extract_form_body in fastapi/dependencies/utils.py checks annotation in (bool, bool | None)), so the omitted field still arrives as False. Measured on 0.147.0 and 0.150.0.
  • Typing the field as str and parsing it by hand would also work, but changes the OpenAPI schema from boolean to string and duplicates pydantic's bool parsing. The presence check keeps the schema and validation as they are.
  • Pinning fastapi<0.147 would also restore the default, but holds every install back on FastAPI for one field, and the bug comes back once the pin is lifted. The presence check works on both sides of 0.147, so no pin is added.

Checklist

  • ruff check . passes
  • Added or updated tests / docs where relevant

AI assistance: written with AI coding agent

… field

FastAPI 0.147 reads an omitted bool form field as False instead of its
declared default, so a /generate request without streaming got the
aggregated answer. Check the parsed form for the field and fall back to
True when it is absent, matching the Rust frontend.

@merceod merceod 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.

Reproduced the version dependence on CPU with the real route. With FastAPI 0.150.0 and Starlette 1.9.0 (unpacked next to the venv), main fails the three omitted-field cases (urlencoded and multipart /generate without streaming get the aggregated JSON body, and video_frame output without the field is refused) and this head passes all 39 tests in the two files; with FastAPI 0.141.1 all 39 pass on both arms, as the description says. The fix is the smallest one that works: the parsed form is already cached on the request, so the extra await request.form() costs nothing, streaming is the only bool form field in the package, and an explicit value still wins. ruff clean. Nothing to fix.

@merceod
merceod merged commit 25d5317 into mstar-project:main Oct 20, 2026
6 checks passed
@mstar-project mstar-project deleted a comment from github-actions Bot Oct 20, 2026
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.

API server: with FastAPI 0.147+, a /generate request without streaming no longer streams

2 participants