Add OpenAI-compatible POST /v1/audio/transcriptions route - #50
Open
sumgup0 wants to merge 2 commits into
Open
Conversation
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.
This adds an OpenAI-compatible transcription route on the existing local
service so any standard OpenAI client/frontend can use TransClip as its
transcription backend (
base_url=http://127.0.0.1:8765/v1; the SDK appends/audio/transcriptions).Contract (deliberate compat subset): multipart
file(formats = whatthe bundled soundfile/libsndfile decodes — WAV/FLAC/OGG/mp3 on libsndfile
1.2.2; m4a/webm unsupported, no ffmpeg);
model/language/prompt/temperatureaccepted and ignored (single configured backend serves, thelocal-OpenAI-server norm);
response_formatjson(default,{"text"})or
text(text/plain); anything else 400. OpenAI error JSON shape on thisroute only. 25 MB cap (
26_214_400, OpenAI's own limit) enforced againstContent-Length BEFORE the body is read — which also gives the server the
request-size bound it previously lacked on this path. Streaming,
verbose_json/srt/vtt, and /v1/audio/translations are explicitly out.
Dictation path untouched: the route is a self-contained branch in
do_POST(after_guard(), before the JSON dispatch and its exceptionwrapper) with its own writer and its own error handling — the internal
{"error": str, "debug_capture_dir": ...}shape is unreachable from it(pinned by test). The only shared-surface changes: a thin
InferenceEngine.transcribe_raw()(raw ASR — no cleanup/voice-mode/history/debug-capture: OpenAI semantics) and
authorizationadded to theCORS allow-headers (browser OpenAI clients preflight it; grants nothing —
the loopback-origin guard still gates everything).
Safety details: undecodable audio → 400 via soundfile pre-validation in
the route, with a static message (libsndfile's exception text embeds the
server temp path — never echoed; same discipline for the 500 path, both
pinned by tests). Uploaded-filename suffix sanitized before the tempfile
call. A module-level lock serializes THIS route's backend calls; note the
pre-existing service posture (no inference lock across
/transcribe/dictation) is unchanged — flagging rather than fixing in a thin PR.
The over-limit path drains the request body (bounded, in chunks) before
sending the 413, so a real oversize upload receives a clean status instead
of a connection reset that an SDK would retry.
Tests: 29 unit (multipart parser incl. UTF-8/RFC 2231 filenames, binary
Content-Transfer-Encodinground-trips, sanitizer,handler with fakes) + 10 socket round trips (json/text/400s, a real
oversize upload → clean 413, 411, 500-no-leak, 403, preflight) via the
existing
serve_test_engineharness + a new multipart helper. Full suite:490 OK (5 skipped) at this branch.
One pre-existing quirk noticed, not touched (exists at ba25cb0): the
ruff formatnon-compliance inserver.py's_guard/_corsblocks (and_worker_loop). Also, on Windows the pre-existing_guardearly-rejectpath (403 before the body is read) can occasionally reset the connection
before a client with a body in flight reads the response — the new route's
own reject paths drain first to avoid this, but I left the shared
_guarduntouched in a thin PR; happy to apply the same drain there if you'd like.