From 5f614b19c55f2bcf94d7134f36a62d3bc0d88eed Mon Sep 17 00:00:00 2001 From: leonardotrapani Date: Sun, 1 Feb 2026 01:43:50 +0100 Subject: [PATCH] add deepgram provider with nova-3 and nova-2 streaming models --- internal/provider/deepgram.go | 94 ++++++++++++++++++++++++ internal/provider/deepgram_test.go | 110 +++++++++++++++++++++++++++++ internal/provider/provider.go | 1 + progress.txt | 14 +++- tasks/prd.jsonc | 2 +- 5 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 internal/provider/deepgram.go create mode 100644 internal/provider/deepgram_test.go diff --git a/internal/provider/deepgram.go b/internal/provider/deepgram.go new file mode 100644 index 0000000..563d50f --- /dev/null +++ b/internal/provider/deepgram.go @@ -0,0 +1,94 @@ +package provider + +// DeepgramProvider implements Provider for Deepgram transcription services +type DeepgramProvider struct{} + +func (p *DeepgramProvider) Name() string { + return "deepgram" +} + +func (p *DeepgramProvider) RequiresAPIKey() bool { + return true +} + +func (p *DeepgramProvider) ValidateAPIKey(key string) bool { + // Deepgram API keys are alphanumeric, just check non-empty + return len(key) > 0 +} + +func (p *DeepgramProvider) IsLocal() bool { + return false +} + +func (p *DeepgramProvider) Models() []Model { + // Nova-3 language support - maps to our 57 language list + // from https://developers.deepgram.com/docs/models-languages-overview + nova3Langs := []string{ + "ar", "be", "bs", "bg", "ca", "hr", "cs", "da", "nl", "en", "et", "fi", + "fr", "de", "el", "hi", "hu", "id", "it", "ja", "kn", "ko", "lv", "lt", + "mk", "ms", "mr", "no", "pl", "pt", "ro", "ru", "sr", "sk", "sl", "es", + "sv", "tl", "ta", "tr", "uk", "vi", + } + + // Nova-2 language support - subset of nova-3 + nova2Langs := []string{ + "bg", "ca", "zh", "cs", "da", "nl", "en", "et", "fi", "fr", "de", "el", + "hi", "hu", "id", "it", "ja", "ko", "lv", "lt", "ms", "no", "pl", "pt", + "ro", "ru", "sk", "es", "sv", "th", "tr", "uk", "vi", + } + + return []Model{ + { + ID: "nova-3", + Name: "Nova-3", + Description: "Best accuracy, 40+ languages, real-time", + Type: Transcription, + Streaming: true, + Local: false, + AdapterType: "deepgram", + SupportedLanguages: nova3Langs, + Endpoint: &EndpointConfig{BaseURL: "wss://api.deepgram.com", Path: "/v1/listen"}, + }, + { + ID: "nova-3-general", + Name: "Nova-3 General", + Description: "General purpose, same as nova-3", + Type: Transcription, + Streaming: true, + Local: false, + AdapterType: "deepgram", + SupportedLanguages: nova3Langs, + Endpoint: &EndpointConfig{BaseURL: "wss://api.deepgram.com", Path: "/v1/listen"}, + }, + { + ID: "nova-2", + Name: "Nova-2", + Description: "Fast, 30+ languages, filler words", + Type: Transcription, + Streaming: true, + Local: false, + AdapterType: "deepgram", + SupportedLanguages: nova2Langs, + Endpoint: &EndpointConfig{BaseURL: "wss://api.deepgram.com", Path: "/v1/listen"}, + }, + { + ID: "nova-2-general", + Name: "Nova-2 General", + Description: "General purpose, same as nova-2", + Type: Transcription, + Streaming: true, + Local: false, + AdapterType: "deepgram", + SupportedLanguages: nova2Langs, + Endpoint: &EndpointConfig{BaseURL: "wss://api.deepgram.com", Path: "/v1/listen"}, + }, + } +} + +func (p *DeepgramProvider) DefaultModel(t ModelType) string { + switch t { + case Transcription: + return "nova-3" + } + return "" +} diff --git a/internal/provider/deepgram_test.go b/internal/provider/deepgram_test.go new file mode 100644 index 0000000..720890d --- /dev/null +++ b/internal/provider/deepgram_test.go @@ -0,0 +1,110 @@ +package provider + +import "testing" + +func TestDeepgramProvider(t *testing.T) { + p := GetProvider("deepgram") + if p == nil { + t.Fatal("deepgram provider not registered") + } + + if p.Name() != "deepgram" { + t.Errorf("Name() = %q, want %q", p.Name(), "deepgram") + } + + if !p.RequiresAPIKey() { + t.Error("RequiresAPIKey() should return true") + } + + if p.IsLocal() { + t.Error("IsLocal() should return false") + } +} + +func TestDeepgramProvider_Models(t *testing.T) { + p := &DeepgramProvider{} + models := p.Models() + + if len(models) != 4 { + t.Errorf("Models() returned %d models, want 4", len(models)) + } + + // all models should be streaming + for _, m := range models { + if !m.Streaming { + t.Errorf("model %s should be streaming", m.ID) + } + if m.AdapterType != "deepgram" { + t.Errorf("model %s has AdapterType %q, want 'deepgram'", m.ID, m.AdapterType) + } + if m.Local { + t.Errorf("model %s should not be local", m.ID) + } + } +} + +func TestDeepgramProvider_Nova3Languages(t *testing.T) { + p := &DeepgramProvider{} + models := p.Models() + + var nova3 *Model + for i := range models { + if models[i].ID == "nova-3" { + nova3 = &models[i] + break + } + } + if nova3 == nil { + t.Fatal("nova-3 model not found") + } + + // nova-3 should support many languages from our list + supportedTests := []struct { + code string + want bool + }{ + {"en", true}, + {"es", true}, + {"fr", true}, + {"de", true}, + {"ja", true}, + {"", true}, // auto always supported + } + + for _, tt := range supportedTests { + got := nova3.SupportsLanguage(tt.code) + if got != tt.want { + t.Errorf("nova-3.SupportsLanguage(%q) = %v, want %v", tt.code, got, tt.want) + } + } +} + +func TestDeepgramProvider_DefaultModel(t *testing.T) { + p := &DeepgramProvider{} + + if got := p.DefaultModel(Transcription); got != "nova-3" { + t.Errorf("DefaultModel(Transcription) = %q, want 'nova-3'", got) + } + + if got := p.DefaultModel(LLM); got != "" { + t.Errorf("DefaultModel(LLM) = %q, want empty (no LLM support)", got) + } +} + +func TestDeepgramProvider_Endpoint(t *testing.T) { + p := &DeepgramProvider{} + models := p.Models() + + for _, m := range models { + if m.Endpoint == nil { + t.Errorf("model %s has nil Endpoint", m.ID) + continue + } + if m.Endpoint.BaseURL != "wss://api.deepgram.com" { + t.Errorf("model %s has BaseURL %q, want 'wss://api.deepgram.com'", m.ID, m.Endpoint.BaseURL) + } + if m.Endpoint.Path != "/v1/listen" { + t.Errorf("model %s has Path %q, want '/v1/listen'", m.ID, m.Endpoint.Path) + } + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0ee80fb..0dd1a02 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -29,6 +29,7 @@ func init() { Register(&MistralProvider{}) Register(&ElevenLabsProvider{}) Register(&WhisperCppProvider{}) + Register(&DeepgramProvider{}) } // Register adds a provider to the registry diff --git a/progress.txt b/progress.txt index 0b76a1f..06efa9c 100644 --- a/progress.txt +++ b/progress.txt @@ -334,4 +334,16 @@ Started: Sun Feb 1 12:22:47 AM CET 2026 - Updated `SendChunk()` to call reconnect() on write errors, then retry chunk - After max retries exhausted, sends final error and closes channel - Added tests: ReconnectOnReadError, ReconnectNotifiesClient, MaxRetriesExhausted, ReconnectExponentialBackoff -- All tests passing with -race flag, typecheck passes \ No newline at end of file +- All tests passing with -race flag, typecheck passes + +### Task 33: Create Deepgram Provider +- Created `internal/provider/deepgram.go` implementing Provider interface +- Researched Deepgram docs: Nova-3 and Nova-2 are main models, both streaming-only +- Models: nova-3, nova-3-general, nova-2, nova-2-general (all Streaming=true) +- Nova-3 supports 42 languages from our list (ar, be, bs, bg, ca, hr, cs, da, nl, en, et, fi, fr, de, el, hi, hu, id, it, ja, kn, ko, lv, lt, mk, ms, mr, no, pl, pt, ro, ru, sr, sk, sl, es, sv, tl, ta, tr, uk, vi) +- Nova-2 supports 33 languages (subset of nova-3) +- All models have AdapterType='deepgram', Endpoint.BaseURL='wss://api.deepgram.com' +- DefaultModel(Transcription) returns 'nova-3' +- Registered in provider.init() +- Comprehensive test file created: deepgram_test.go +- All tests passing, typecheck passes \ No newline at end of file diff --git a/tasks/prd.jsonc b/tasks/prd.jsonc index 7d16999..c3caa61 100644 --- a/tasks/prd.jsonc +++ b/tasks/prd.jsonc @@ -783,7 +783,7 @@ "RequiresAPIKey() returns true", "Typecheck passes" ], - "passes": false + "passes": true }, { "title": "Create Deepgram StreamingAdapter",