Last updated 2026-05-17
Deepgram Provider
The deepgram provider implements
Transcriber
(speech-to-text). It does not implement Speaker,
Provider, or Embedder — Deepgram is an
STT-only vendor in llmrouter's scope.
Import path
import ( "github.com/elloloop/llmrouter" "github.com/elloloop/llmrouter/providers/deepgram")Default endpoint
With no WithBaseURL override, the provider targets
https://api.deepgram.com/v1. Pre-recorded transcription
requests go to {BaseURL}/listen.
WebSocket streaming
(wss://api.deepgram.com/v1/listen) is on the
v0.4 roadmap;
v0.3 supports pre-recorded transcription only. Setting
TranscribeRequest.Stream = true currently falls back
to a single final segment.
Construction
p, err := deepgram.New( llmrouter.WithAPIKey(os.Getenv("DEEPGRAM_API_KEY")),)if err != nil { log.Fatal(err)}
Authentication is via the Authorization: Token <key>
header — note the Token scheme, not Bearer.
The provider sets this automatically.
Models
-
nova-3— latest production model (May 2025). Highest accuracy, lowest latency. Recommended default. 36 languages. -
nova-2— previous generation. Wider language coverage in some edge cases. -
nova-2-meeting,nova-2-phonecall,nova-2-finance,nova-2-medical— domain-tuned variants ofnova-2. -
enhanced— older non-Nova model. Still supported. -
base— legacy model. Lowest cost, lowest accuracy.
Language codes
Deepgram uses ISO-639-1 codes plus optional regional suffixes
(en-US, en-GB, es-419,
pt-BR, etc.). The library passes
TranscribeRequest.Language through unchanged — set
either the bare ISO code or the regional variant. Empty means
"auto-detect."
Response format
Deepgram returns JSON with channels, alternatives, and per-word
timing. The library always emits
TranscriptSegments.
With ResponseFormat="" or "json", you get
one Final=true segment with the concatenated transcript
and Words populated. With
ResponseFormat="verbose_json", the library splits per
Deepgram utterance into multiple segments.
Full STT example
package main
import ( "context" "fmt" "log" "os"
"github.com/elloloop/llmrouter" "github.com/elloloop/llmrouter/providers/deepgram")
func main() { p, err := deepgram.New(llmrouter.WithAPIKey(os.Getenv("DEEPGRAM_API_KEY"))) if err != nil { log.Fatal(err) }
f, err := os.Open("call.wav") if err != nil { log.Fatal(err) } defer f.Close()
stream, err := p.Transcribe(context.Background(), llmrouter.TranscribeRequest{ Model: "nova-3", Audio: f, AudioFormat: "audio/wav", Language: "en-US", ResponseFormat: "verbose_json", }) if err != nil { log.Fatal(err) }
for seg := range stream.Segments() { fmt.Printf("[%s] (%.2f) %s\n", seg.Start, seg.Confidence, seg.Text) for _, w := range seg.Words { fmt.Printf(" %-20s @ %s (%.2f)\n", w.Word, w.Start, w.Confidence) } } if err := stream.Err(); err != nil { log.Fatal(err) }}Speaker diarization
Deepgram's diarize parameter is not on the typed
surface. Pass it via Raw:
raw := []byte(`{ "diarize": true, "smart_format": true, "punctuate": true, "utterances": true}`)
stream, err := p.Transcribe(ctx, llmrouter.TranscribeRequest{ Model: "nova-3", Audio: f, AudioFormat: "audio/wav", Language: "en", Raw: raw,})
// Per-word speaker ids land on segment.Raw.for seg := range stream.Segments() { fmt.Println(string(seg.Raw))}
Diarized output puts speaker on each word in the wire
JSON. Read it off TranscriptSegment.Raw until the
typed surface grows a SpeakerID field (tracked in the
v0.4 work).
Confidence scores
Deepgram returns per-word confidence and an aggregate per-segment confidence. Both are populated:
TranscriptSegment.Confidence— aggregate over the segment, in [0, 1].TranscriptWord.Confidence— per-word, in [0, 1].
Other features via Raw
Deepgram has a large feature surface; the typed
TranscribeRequest models the common ones, and the rest
travel through Raw:
smart_format— automatic punctuation and number formatting.punctuate— add punctuation without smart formatting.profanity_filter— replace profanity with asterisks.redact— redact PII (pci,numbers,ssn).summarize— append a summary segment.detect_topics— surface topic labels.keywords— boost specific terms.search— return matches for query phrases.
All of these survive byte-passthrough and surface on
segment.Raw.
Error handling
Non-2xx responses surface as
*llmrouter.ErrUpstream
with Provider == "deepgram". Common cases:
401— bad or missing API key.402— insufficient credit.400— unsupported model + language combination, or invalidAudioFormat.429— concurrent-request limit hit.
Caveats
- v0.3: pre-recorded only. WebSocket streaming
lands in v0.4. The
Streamflag is accepted but the provider currently treats it as a no-op on pre-recorded audio. - No TTS. Deepgram offers an Aura TTS endpoint; in
v0.3,
llmrouteronly wraps the STT side. Consider Cartesia or ElevenLabs for TTS.
See also
- Audio concept — Transcriber semantics.
- ElevenLabs Scribe — alternative STT with diarization.
- OpenAI Whisper — alternative STT.