Skip to content

feat: forward optional language form field to faster-whisper - #19

Open
h3x89 wants to merge 1 commit into
didmar:mainfrom
h3x89:fix/forward-language-to-faster-whisper
Open

feat: forward optional language form field to faster-whisper#19
h3x89 wants to merge 1 commit into
didmar:mainfrom
h3x89:fix/forward-language-to-faster-whisper

Conversation

@h3x89

@h3x89 h3x89 commented Aug 22, 2026

Copy link
Copy Markdown

Problem

The OpenAI-compatible transcription endpoint currently silently ignores the multipart language field: it is not declared by the FastAPI route, so FastAPI drops it before any application code runs. Callers that send language=pl get automatic language detection and have no way to know their parameter had no effect.

Fix

  • add an optional language form parameter to POST /v1/audio/transcriptions;
  • validate it against faster-whisper's own language codes;
  • forward it into the existing WhisperModel.transcribe() call;
  • preserve auto-detection when the field is omitted.
whisper_args = {"whisper_model": MODEL_SIZE}
if settings_override is not None:
    whisper_args.update(settings_override)
if language is not None:
    whisper_args["language"] = language   # dedicated field wins

Contract

language omitted -> existing automatic detection (unchanged)
language=pl      -> faster-whisper receives language="pl"
invalid language -> deterministic HTTP 400 "Bad Request, bad language"

Validation uses faster_whisper.tokenizer._LANGUAGE_CODES — a private faster-whisper symbol, chosen deliberately so server-side validation is identical to the library's own case-sensitive Tokenizer check (the same tuple; anything we accept cannot fail downstream, and uppercase codes get a clean 400 instead of a 500 ValueError). The project pins its runtime via committed uv.lock + uv sync --frozen, so symbol availability is deterministic; happy to switch to a public API if one exists.

400 (rather than 422) matches this file's existing convention: bad file / bad response_format / bad temperature are all explicit HTTPException(400, "Bad Request, …").

Tests

First test suite in the repo (tests/test_language_forwarding.py, 11 tests, pytest + httpx added as dev dependencies):

$ uv run pytest tests/ -v
11 passed in 1.21s
$ uv run ruff check .
All checks passed!

Coverage proves: omitted → no language kwarg reaches transcribe(); language=pl → forwarded as "pl"; invalid → 400 with inference never invoked; empty string behaves as omitted (multipart stack delivers it as None); uppercase rejected like the Tokenizer would; the dedicated language field takes precedence over a language key inside settings_override; OpenAPI schema exposes optional language; existing json/text/srt/verbose_json shapes and response_format/temperature validation unchanged.

Docker/runtime proof (built from this exact commit):

$ docker build -t whisper-api-server:1427-dcf480a .   # OK
GET  /openapi.json        -> language: {anyOf: [string, null]}, title "Language"
POST without language     -> 200, Polish speech transcribed via auto-detect
POST language=pl          -> 200
POST language=xx-invalid  -> 400 {"detail": "Bad Request, bad language"}

A/B proof

Instrumented WhisperModel.transcribe kwargs for identical requests (MODEL_SIZE=base, real Polish audio):

BEFORE (current image):
  POST language=pl  -> transcribe kwargs {}                      # silently dropped

AFTER (this patch):
  POST language=pl  -> transcribe kwargs {"language": "pl"}      # forced Polish effective
  POST w/o language -> transcribe kwargs {}                       # auto-detect preserved

Compatibility

Requests without language are byte-for-byte unchanged (no new kwarg on the transcribe path). Clients already sending lowercase ISO-639-1 codes previously had them silently discarded; after this patch those requests actually take effect — there is no client for which behavior can regress.

Notes

  • No changes to model selection, detection policy, settings_override semantics (other than precedence of the dedicated field), or any existing endpoint behavior.
  • Out-of-scope pre-existing quirks observed while testing (untouched here): response_format=vtt raises 500 (seg["start"] dict access on NamedTuple), text format returns the JSON dict shape, wrong model value fails via assert.

Add an optional 'language' multipart form field to
POST /v1/audio/transcriptions and pass it into the existing
WhisperModel.transcribe() call as its language argument.

- omitted language keeps automatic language detection unchanged
- valid codes are forwarded as-is and take precedence over any
  'language' key inside settings_override
- codes are validated against faster_whisper.tokenizer._LANGUAGE_CODES
  (the same canonical, case-sensitive set the library's Tokenizer
  enforces); invalid codes are rejected with 400 Bad Request before
  any inference runs, instead of a silent ignore or a 500 ValueError

Also add a pytest regression suite (first tests in the repo) with
pytest/httpx declared as dev dependencies, refresh uv.lock, configure
pythonpath for 'uv run pytest', and document the field plus the
uv-based test workflow in the README.
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.

1 participant