STT provider seam with native Deepgram support (LC-593) #577
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/lc593-stt-providers"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes LC-593, the "provider flexibility" child of LC-589, on top of the LC-591
SttResultfoundation you had me land first.The problem
ReqwestSttClientwas hardcoded to the OpenAI/v1/audio/transcriptionsmultipart wire shape and{text}response. A Deepgram/AssemblyAI-style API (different endpoint, auth header, JSON request/response) needed an external OpenAI-compatible shim in front.The seam
SttProvider(openaidefault,deepgram) fromLETS_CHAT_STT_PROVIDER; an unrecognized value falls back toopenairather than erroring.transcribedispatches:verbose_jsonpath, moved verbatim intotranscribe_openai. Default and unchanged.transcribe_deepgramposts the raw audio as the request body (not multipart), withAuthorization: Token <key>(Deepgram's scheme, not Bearer) andmodel/language/punctuate/smart_formatas query params. It parsesresults.channels[0].alternatives[0]- transcript for text, word timings aggregated into one clip-level segment so the LC-591 duration/VTT path consumes it with no downstream changes.Both stay on
outbound_trusted_post(the trusted, un-SSRF-filtered path). Deepgram has nopromptequivalent, so the operator glossary is documented as OpenAI-only.Testability
The pieces that would otherwise need a live endpoint are split into pure functions -
deepgram_query,parse_deepgram_result, plus the existingparse_openai_result- so the request shape and the response parse are unit-tested directly. That's the "request shape and response parse via a mock" the ticket asks for.Tests
deepgramselected, unknown value falls back toopenai, case/whitespace toleratedmodel/punctuate/smart_formatalways;languageonly when hinted (trimmed)The OpenAI path is unchanged (moved verbatim) and stays covered by its LC-591 unit tests and the transcripts integration suite. Full suite green (184 test binaries),
just checkclean.Docs
LETS_CHAT_STT_PROVIDERin.env.standalone,.env.saas, andREADME.md, with the Deepgram URL/model/Token-auth specifics and the prompt-is-OpenAI-only caveat.This is the last of the transcription-hardening children I have in flight; the remaining LC-589 siblings (LC-590 reliability, LC-592 cost/load) are separate and untouched here.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FhDVMViNHqUbwmfe2aXTv5
Closes the transcription "provider flexibility" child of LC-589. `ReqwestSttClient` was hardcoded to the OpenAI `/v1/audio/transcriptions` multipart shape, so Deepgram/AssemblyAI-style APIs (different endpoint, auth, and JSON) needed an external OpenAI-compatible shim. This lands a provider seam and Deepgram end to end, both normalizing onto the LC-591 `SttResult { text, segments }`. `SttProvider` (`openai` default, `deepgram`) is read from `LETS_CHAT_STT_PROVIDER`; an unrecognized value falls back to `openai` rather than erroring. `transcribe` dispatches on it: - OpenAI: the existing multipart + verbose_json path, moved verbatim into `transcribe_openai`. Default and unchanged. - Deepgram: `transcribe_deepgram` posts the raw audio as the body (not multipart) with `Authorization: Token <key>` (Deepgram's scheme, not Bearer) and model/language/punctuate/smart_format as query params, then parses `results.channels[0].alternatives[0]` - transcript for text, word timings aggregated into one clip-level segment so the LC-591 duration/VTT path consumes it unchanged. Deepgram has no `prompt` equivalent, so the operator glossary is documented as OpenAI-only. Both providers stay on `outbound_trusted_post` (the un-SSRF-filtered trusted path). The request builders and response parsers are split into pure functions (`deepgram_query`, `parse_deepgram_result`, the existing `parse_openai_result`) so the wire shape and parsing are unit-testable without a live endpoint. Tests: provider selection from env (including the unknown-value fallback and the trim), the Deepgram query shape (model/punctuate/smart_format always, language only when hinted), Deepgram response parse with word timings -> real duration, no-words -> empty segments (synthetic fallback), and malformed-body -> empty not error. The OpenAI path is unchanged and stays covered by its LC-591 unit tests plus the transcripts integration suite. Full suite green (184 test binaries), `just check` clean. Docs: `LETS_CHAT_STT_PROVIDER` in `.env.standalone`, `.env.saas`, and `README.md`, with the Deepgram URL/model/auth specifics and the prompt caveat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhDVMViNHqUbwmfe2aXTv5