server: forward language only where the model's contract accepts it - #400
Merged
Merged
Conversation
A model whose spec does not declare `language` returned HTTP 500 for every
transcription the WebUI sent, because build_openai_transcription_request wrote
request.options["language"] whenever the body carried the key. Clients send
that field on every request, empty or not, and spec-backed models validate
their request options strictly, so the model rejected its own client:
POST /v1/audio/transcriptions
{"model":"parakeet-tdt","audio":"...wav","language":"","text":"","options":{}}
-> {"error":{"message":"unknown Parakeet TDT request option: language"}}
Parakeet-TDT could not transcribe at all from the WebUI. AudioSR, ControlFoley,
MiDashengLM-Gen, PersonaPlex and HeartMuLa fail the same way through
/v1/tasks/run, which folds a top-level `language` into the option map.
The option is now forwarded only when the caller actually chose a language and
the model's contract accepts it. Acceptance is resolved once at registration
next to the existing accepts_reference_text flag, and for the same reason:
resolving it per request re-reads the embedded spec on the request thread.
A `language` the caller placed inside `options` is left alone and still
produces the strict rejection, which is the correct answer to an explicit
choice. The language continues to reach every model through text_input, so
nothing loses the feature; qwen3_forced_aligner requires it from there, which
is why the fix belongs on the server rather than in the client's form.
Validation, on a server built from this branch with Parakeet-TDT loaded
(Metal, Apple M4 Max):
language:"" at the top level -> 200, transcript returned
language:"en" at the top level -> 200, transcript returned
options:{"language":"en"} -> 500, "unknown Parakeet TDT request
option: language" (unchanged, deliberate)
ctest: 40/40.
CryptVenture
marked this pull request as ready for review
September 3, 2026 02:12
This was referenced Sep 3, 2026
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.
Split out of #369 as requested: this PR is only the
languagecontract gate. The ASR detail fields, the backend startup probe and the unload locking follow as separate PRs.The problem
A model whose spec does not declare
languagereturned HTTP 500 for every transcription the WebUI sent.build_openai_transcription_requestwroterequest.options["language"]whenever the body carried the key, clients send that field on every request whether or not the user chose one, and spec-backed models validate their request options strictly — so the model rejected its own client:Parakeet-TDT could not transcribe at all from the WebUI. AudioSR, ControlFoley, MiDashengLM-Gen, PersonaPlex and HeartMuLa fail the same way through
/v1/tasks/run, which folds a top-levellanguageinto the option map.The change
The option is forwarded only when the caller actually chose a language and the model's contract accepts it. Acceptance is resolved once at registration, next to the existing
accepts_reference_textflag and for the same reason — resolving it per request re-reads the embedded spec on the request thread, which costs about 0.9 s for large GGUFs.A
languagethe caller placed insideoptionsis left alone and still produces the strict rejection: an explicit option is a deliberate choice, and the error is the right answer to it./v1/tasks/rungets the same treatment throughdrop_unsupported_language_option, which removes only the option that route synthesised from the top-level field.The language keeps reaching every model through
text_input, so no feature is lost.qwen3_forced_alignerreads it from there, which is why this belongs on the server rather than in the client's form — hiding the input would have broken forced alignment.Validation
Backend: Metal, Apple M4 Max, macOS 15. Server built from this branch, Parakeet-TDT (
parakeet-tdt-0.6b-v3-q8_0.gguf) loaded, real requests against/v1/audio/transcriptions:"language": ""at top level"language": "en"at top level"options": {"language": "en"}unknown Parakeet TDT request option: language— unchanged, deliberateBuild and tests:
Scope
Server only. No engine, model or output behaviour changes; affects
/v1/audio/transcriptionsand/v1/tasks/runrequest construction. Families checked against the contract: Parakeet TDT, AudioSR, ControlFoley, MiDashengLM-Gen, PersonaPlex, HeartMuLa, Qwen3 forced aligner.