feat: models test and fixes
This commit is contained in:
@@ -16,6 +16,10 @@ func (p *DeepgramProvider) ValidateAPIKey(key string) bool {
|
||||
return len(key) > 0
|
||||
}
|
||||
|
||||
func (p *DeepgramProvider) APIKeyURL() string {
|
||||
return "https://console.deepgram.com/project/keys"
|
||||
}
|
||||
|
||||
func (p *DeepgramProvider) IsLocal() bool {
|
||||
return false
|
||||
}
|
||||
@@ -23,7 +27,6 @@ func (p *DeepgramProvider) IsLocal() bool {
|
||||
func (p *DeepgramProvider) Models() []Model {
|
||||
// https://developers.deepgram.com/docs/models-languages-overview
|
||||
nova3Langs := deepgramNova3Languages
|
||||
// https://developers.deepgram.com/docs/models-languages-overview
|
||||
nova2Langs := deepgramNova2Languages
|
||||
|
||||
docsURL := "https://developers.deepgram.com/docs/language"
|
||||
@@ -32,7 +35,7 @@ func (p *DeepgramProvider) Models() []Model {
|
||||
{
|
||||
ID: "nova-3",
|
||||
Name: "Nova-3",
|
||||
Description: "Best accuracy, 40+ languages",
|
||||
Description: "Best accuracy; streaming available for faster response",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: true,
|
||||
@@ -46,7 +49,7 @@ func (p *DeepgramProvider) Models() []Model {
|
||||
{
|
||||
ID: "nova-2",
|
||||
Name: "Nova-2",
|
||||
Description: "Fast, 30+ languages, filler words",
|
||||
Description: "Cheaper legacy model; still solid accuracy",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: true,
|
||||
|
||||
@@ -29,11 +29,8 @@ func TestDeepgramProvider_Models(t *testing.T) {
|
||||
t.Errorf("Models() returned %d models, want 2", len(models))
|
||||
}
|
||||
|
||||
// all models should support both batch and streaming
|
||||
// all models should support both streaming and batch
|
||||
for _, m := range models {
|
||||
if !m.SupportsBatch {
|
||||
t.Errorf("model %s should support batch", m.ID)
|
||||
}
|
||||
if !m.SupportsStreaming {
|
||||
t.Errorf("model %s should support streaming", m.ID)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ func (p *ElevenLabsProvider) ValidateAPIKey(key string) bool {
|
||||
return len(key) > 0
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) APIKeyURL() string {
|
||||
return "https://elevenlabs.io/app/settings/api-keys"
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) IsLocal() bool {
|
||||
return false
|
||||
}
|
||||
@@ -29,40 +33,46 @@ func (p *ElevenLabsProvider) Models() []Model {
|
||||
{
|
||||
ID: "scribe_v1",
|
||||
Name: "Scribe v1",
|
||||
Description: "90+ languages, best accuracy",
|
||||
Description: "Most accurate; best for precision-critical work",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
Local: false,
|
||||
AdapterType: AdapterElevenLabs,
|
||||
StreamingAdapter: AdapterElevenLabsStream,
|
||||
SupportedLanguages: allLangs,
|
||||
Endpoint: &EndpointConfig{BaseURL: "https://api.elevenlabs.io", Path: "/v1/speech-to-text"},
|
||||
StreamingEndpoint: &EndpointConfig{BaseURL: "wss://api.elevenlabs.io", Path: "/v1/speech-to-text/realtime"},
|
||||
DocsURL: docsURL,
|
||||
},
|
||||
{
|
||||
ID: "scribe_v2",
|
||||
Name: "Scribe v2",
|
||||
Description: "Lower latency batch transcription",
|
||||
Description: "Faster processing with good accuracy",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
Local: false,
|
||||
AdapterType: AdapterElevenLabs,
|
||||
StreamingAdapter: AdapterElevenLabsStream,
|
||||
SupportedLanguages: allLangs,
|
||||
Endpoint: &EndpointConfig{BaseURL: "https://api.elevenlabs.io", Path: "/v1/speech-to-text"},
|
||||
StreamingEndpoint: &EndpointConfig{BaseURL: "wss://api.elevenlabs.io", Path: "/v1/speech-to-text/realtime"},
|
||||
DocsURL: docsURL,
|
||||
},
|
||||
{
|
||||
ID: "scribe_v2_realtime",
|
||||
Name: "Scribe v2 Realtime",
|
||||
Description: "Real-time streaming, <150ms latency",
|
||||
Description: "Instant words as you speak; faster but costs more",
|
||||
Type: Transcription,
|
||||
SupportsBatch: false,
|
||||
SupportsStreaming: true,
|
||||
Local: false,
|
||||
AdapterType: AdapterElevenLabsStream,
|
||||
AdapterType: AdapterElevenLabs,
|
||||
StreamingAdapter: AdapterElevenLabsStream,
|
||||
SupportedLanguages: allLangs,
|
||||
Endpoint: &EndpointConfig{BaseURL: "wss://api.elevenlabs.io", Path: "/v1/speech-to-text/realtime"},
|
||||
Endpoint: &EndpointConfig{BaseURL: "https://api.elevenlabs.io", Path: "/v1/speech-to-text"},
|
||||
StreamingEndpoint: &EndpointConfig{BaseURL: "wss://api.elevenlabs.io", Path: "/v1/speech-to-text/realtime"},
|
||||
DocsURL: docsURL,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ func (p *GroqProvider) ValidateAPIKey(key string) bool {
|
||||
return strings.HasPrefix(key, "gsk_")
|
||||
}
|
||||
|
||||
func (p *GroqProvider) APIKeyURL() string {
|
||||
return "https://console.groq.com/keys"
|
||||
}
|
||||
|
||||
func (p *GroqProvider) IsLocal() bool {
|
||||
return false
|
||||
}
|
||||
@@ -31,7 +35,7 @@ func (p *GroqProvider) Models() []Model {
|
||||
{
|
||||
ID: "whisper-large-v3",
|
||||
Name: "Whisper Large v3",
|
||||
Description: "Full Whisper v3 model, best accuracy",
|
||||
Description: "Best accuracy; generous free tier makes this great default",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
@@ -44,7 +48,7 @@ func (p *GroqProvider) Models() []Model {
|
||||
{
|
||||
ID: "whisper-large-v3-turbo",
|
||||
Name: "Whisper Large v3 Turbo",
|
||||
Description: "Faster Whisper v3 with slightly lower accuracy",
|
||||
Description: "Faster with slight accuracy tradeoff; still very good",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
@@ -58,7 +62,7 @@ func (p *GroqProvider) Models() []Model {
|
||||
{
|
||||
ID: "llama-3.3-70b-versatile",
|
||||
Name: "Llama 3.3 70B Versatile",
|
||||
Description: "Most capable Llama model",
|
||||
Description: "Best quality cleanup; smart rewrites, free tier available",
|
||||
Type: LLM,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
@@ -69,18 +73,7 @@ func (p *GroqProvider) Models() []Model {
|
||||
{
|
||||
ID: "llama-3.1-8b-instant",
|
||||
Name: "Llama 3.1 8B Instant",
|
||||
Description: "Fast and efficient",
|
||||
Type: LLM,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
Local: false,
|
||||
AdapterType: AdapterOpenAI,
|
||||
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/chat/completions"},
|
||||
},
|
||||
{
|
||||
ID: "mixtral-8x7b-32768",
|
||||
Name: "Mixtral 8x7B",
|
||||
Description: "Mixture of experts model",
|
||||
Description: "Very fast; good for simple cleanup tasks",
|
||||
Type: LLM,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
|
||||
@@ -34,6 +34,8 @@ var deepgramNova2Languages = []string{
|
||||
"ru", "sk", "es", "es-419", "sv", "sv-SE", "th", "th-TH", "tr", "uk", "vi",
|
||||
}
|
||||
|
||||
var deepgramFluxLanguages = []string{"en"}
|
||||
|
||||
var elevenLabsTranscriptionLanguages = []string{
|
||||
"bel", "bos", "bul", "cat", "hrv", "ces", "dan", "nld", "eng", "est", "fin", "fra",
|
||||
"glg", "deu", "ell", "hun", "isl", "ind", "ita", "jpn", "kan", "lav", "mkd", "msa",
|
||||
|
||||
@@ -16,6 +16,10 @@ func (p *MistralProvider) ValidateAPIKey(key string) bool {
|
||||
return len(key) > 0
|
||||
}
|
||||
|
||||
func (p *MistralProvider) APIKeyURL() string {
|
||||
return "https://admin.mistral.ai/organization/api-keys"
|
||||
}
|
||||
|
||||
func (p *MistralProvider) IsLocal() bool {
|
||||
return false
|
||||
}
|
||||
@@ -29,27 +33,12 @@ func (p *MistralProvider) Models() []Model {
|
||||
{
|
||||
ID: "voxtral-mini-latest",
|
||||
Name: "Voxtral Mini Latest",
|
||||
Description: "Latest Voxtral model, best for most uses",
|
||||
Description: "EU-hosted; good for data residency or Mistral ecosystem",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: true,
|
||||
SupportsStreaming: false,
|
||||
Local: false,
|
||||
AdapterType: AdapterOpenAI,
|
||||
StreamingAdapter: "mistral-streaming", // not yet implemented
|
||||
SupportedLanguages: allLangs,
|
||||
Endpoint: &EndpointConfig{BaseURL: "https://api.mistral.ai", Path: "/v1/audio/transcriptions"},
|
||||
DocsURL: docsURL,
|
||||
},
|
||||
{
|
||||
ID: "voxtral-mini-2507",
|
||||
Name: "Voxtral Mini 2507",
|
||||
Description: "Stable Voxtral version from July 2025",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: true,
|
||||
Local: false,
|
||||
AdapterType: AdapterOpenAI,
|
||||
StreamingAdapter: "mistral-streaming", // not yet implemented
|
||||
SupportedLanguages: allLangs,
|
||||
Endpoint: &EndpointConfig{BaseURL: "https://api.mistral.ai", Path: "/v1/audio/transcriptions"},
|
||||
DocsURL: docsURL,
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestModel_IsStreaming(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "streaming-only model",
|
||||
model: Model{ID: "scribe_v2_realtime", SupportsBatch: false, SupportsStreaming: true},
|
||||
model: Model{ID: "flux-general-en", SupportsBatch: false, SupportsStreaming: true},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -89,7 +89,7 @@ func TestModel_SupportsBothModes(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "streaming-only model",
|
||||
model: Model{ID: "scribe_v2_realtime", SupportsBatch: false, SupportsStreaming: true},
|
||||
model: Model{ID: "flux-general-en", SupportsBatch: false, SupportsStreaming: true},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -325,7 +325,7 @@ func TestAllTranscriptionModels_HaveDocsURL(t *testing.T) {
|
||||
"mistral": "https://docs.mistral.ai/capabilities/audio/",
|
||||
"elevenlabs": "https://elevenlabs.io/speech-to-text",
|
||||
"deepgram": "https://developers.deepgram.com/docs/language",
|
||||
"whisper-cpp": "https://github.com/openai/whisper#available-models-and-languages",
|
||||
"whisper-cpp": "https://github.com/ggml-org/whisper.cpp#models",
|
||||
}
|
||||
|
||||
for _, pName := range providers {
|
||||
|
||||
@@ -17,6 +17,10 @@ func (p *OpenAIProvider) ValidateAPIKey(key string) bool {
|
||||
return strings.HasPrefix(key, "sk-")
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) APIKeyURL() string {
|
||||
return "https://platform.openai.com/api-keys"
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) IsLocal() bool {
|
||||
return false
|
||||
}
|
||||
@@ -32,7 +36,7 @@ func (p *OpenAIProvider) Models() []Model {
|
||||
{
|
||||
ID: "whisper-1",
|
||||
Name: "Whisper 1",
|
||||
Description: "OpenAI's production speech-to-text model",
|
||||
Description: "Reliable and cost-effective; good default for most use cases",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
@@ -45,7 +49,7 @@ func (p *OpenAIProvider) Models() []Model {
|
||||
{
|
||||
ID: "gpt-4o-transcribe",
|
||||
Name: "GPT-4o Transcribe",
|
||||
Description: "High quality transcription with GPT-4o",
|
||||
Description: "Top accuracy; slower and pricier but best quality",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
@@ -58,7 +62,7 @@ func (p *OpenAIProvider) Models() []Model {
|
||||
{
|
||||
ID: "gpt-4o-mini-transcribe",
|
||||
Name: "GPT-4o Mini Transcribe",
|
||||
Description: "Fast transcription with GPT-4o Mini",
|
||||
Description: "Good balance of speed, cost, and quality",
|
||||
Type: Transcription,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
@@ -71,7 +75,7 @@ func (p *OpenAIProvider) Models() []Model {
|
||||
{
|
||||
ID: "gpt-4o-realtime-preview",
|
||||
Name: "GPT-4o Realtime Preview",
|
||||
Description: "Real-time streaming transcription with GPT-4o",
|
||||
Description: "Instant words as you speak; fastest but most expensive",
|
||||
Type: Transcription,
|
||||
SupportsBatch: false,
|
||||
SupportsStreaming: true,
|
||||
@@ -85,7 +89,7 @@ func (p *OpenAIProvider) Models() []Model {
|
||||
{
|
||||
ID: "gpt-4o-mini",
|
||||
Name: "GPT-4o Mini",
|
||||
Description: "Fast and affordable GPT-4 variant",
|
||||
Description: "Fast and cheap; good default for text cleanup",
|
||||
Type: LLM,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
@@ -96,7 +100,7 @@ func (p *OpenAIProvider) Models() []Model {
|
||||
{
|
||||
ID: "gpt-4o",
|
||||
Name: "GPT-4o",
|
||||
Description: "Most capable GPT-4 model",
|
||||
Description: "Best quality cleanup; pricier but smarter rewrites",
|
||||
Type: LLM,
|
||||
SupportsBatch: true,
|
||||
SupportsStreaming: false,
|
||||
|
||||
@@ -11,6 +11,7 @@ type Provider interface {
|
||||
Name() string
|
||||
RequiresAPIKey() bool
|
||||
ValidateAPIKey(key string) bool
|
||||
APIKeyURL() string
|
||||
IsLocal() bool
|
||||
Models() []Model
|
||||
DefaultModel(t ModelType) string
|
||||
|
||||
@@ -279,7 +279,7 @@ func TestValidateModelLanguage_ErrorFormat(t *testing.T) {
|
||||
}
|
||||
|
||||
// should contain docs URL
|
||||
if !strings.Contains(errMsg, "https://github.com/openai/whisper") {
|
||||
if !strings.Contains(errMsg, "https://github.com/ggml-org/whisper.cpp") {
|
||||
t.Errorf("error should contain docs URL, got: %s", errMsg)
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ func TestElevenLabsProvider(t *testing.T) {
|
||||
t.Errorf("ElevenLabsProvider.Models() = %d models, want 3", len(models))
|
||||
}
|
||||
|
||||
// Check batch-only models
|
||||
// Check batch + streaming models
|
||||
scribeV1, err := GetModel("elevenlabs", "scribe_v1")
|
||||
if err != nil {
|
||||
t.Fatalf("GetModel('elevenlabs', 'scribe_v1') error: %v", err)
|
||||
@@ -357,6 +357,9 @@ func TestElevenLabsProvider(t *testing.T) {
|
||||
if scribeV1.AdapterType != "elevenlabs" {
|
||||
t.Errorf("scribe_v1 AdapterType=%q, want 'elevenlabs'", scribeV1.AdapterType)
|
||||
}
|
||||
if scribeV1.StreamingAdapter != "elevenlabs-streaming" {
|
||||
t.Errorf("scribe_v1 StreamingAdapter=%q, want 'elevenlabs-streaming'", scribeV1.StreamingAdapter)
|
||||
}
|
||||
|
||||
scribeV2, err := GetModel("elevenlabs", "scribe_v2")
|
||||
if err != nil {
|
||||
@@ -371,8 +374,10 @@ func TestElevenLabsProvider(t *testing.T) {
|
||||
if scribeV2.AdapterType != "elevenlabs" {
|
||||
t.Errorf("scribe_v2 AdapterType=%q, want 'elevenlabs'", scribeV2.AdapterType)
|
||||
}
|
||||
if scribeV2.StreamingAdapter != "elevenlabs-streaming" {
|
||||
t.Errorf("scribe_v2 StreamingAdapter=%q, want 'elevenlabs-streaming'", scribeV2.StreamingAdapter)
|
||||
}
|
||||
|
||||
// Check streaming-only model
|
||||
scribeV2Realtime, err := GetModel("elevenlabs", "scribe_v2_realtime")
|
||||
if err != nil {
|
||||
t.Fatalf("GetModel('elevenlabs', 'scribe_v2_realtime') error: %v", err)
|
||||
@@ -383,8 +388,11 @@ func TestElevenLabsProvider(t *testing.T) {
|
||||
if !scribeV2Realtime.SupportsStreaming {
|
||||
t.Error("scribe_v2_realtime should have SupportsStreaming=true")
|
||||
}
|
||||
if scribeV2Realtime.AdapterType != "elevenlabs-streaming" {
|
||||
t.Errorf("scribe_v2_realtime AdapterType=%q, want 'elevenlabs-streaming'", scribeV2Realtime.AdapterType)
|
||||
if scribeV2Realtime.AdapterType != "elevenlabs" {
|
||||
t.Errorf("scribe_v2_realtime AdapterType=%q, want 'elevenlabs'", scribeV2Realtime.AdapterType)
|
||||
}
|
||||
if scribeV2Realtime.StreamingAdapter != "elevenlabs-streaming" {
|
||||
t.Errorf("scribe_v2_realtime StreamingAdapter=%q, want 'elevenlabs-streaming'", scribeV2Realtime.StreamingAdapter)
|
||||
}
|
||||
|
||||
// All models should share the same supported language list
|
||||
|
||||
@@ -17,16 +17,20 @@ func (p *WhisperCppProvider) ValidateAPIKey(key string) bool {
|
||||
return true // no API key needed
|
||||
}
|
||||
|
||||
func (p *WhisperCppProvider) APIKeyURL() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *WhisperCppProvider) IsLocal() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *WhisperCppProvider) Models() []Model {
|
||||
// https://github.com/openai/whisper#available-models-and-languages
|
||||
// https://github.com/ggml-org/whisper.cpp#models
|
||||
allLangs := whisperTranscriptionLanguages
|
||||
// https://github.com/openai/whisper#available-models-and-languages
|
||||
// https://github.com/ggml-org/whisper.cpp#models
|
||||
englishOnly := whisperEnglishOnlyLanguages
|
||||
docsURL := "https://github.com/openai/whisper#available-models-and-languages"
|
||||
docsURL := "https://github.com/ggml-org/whisper.cpp#models"
|
||||
|
||||
whisperModels := whisper.ListModels()
|
||||
result := make([]Model, 0, len(whisperModels))
|
||||
@@ -63,10 +67,36 @@ func (p *WhisperCppProvider) Models() []Model {
|
||||
}
|
||||
|
||||
func modelDescription(m whisper.ModelInfo) string {
|
||||
if m.Multilingual {
|
||||
return "Multilingual local transcription"
|
||||
switch m.ID {
|
||||
case "tiny.en":
|
||||
return "Free/offline; fastest but low accuracy, good for weak hardware"
|
||||
case "base.en":
|
||||
return "Free/offline; balanced speed and accuracy, recommended start"
|
||||
case "small.en":
|
||||
return "Free/offline; better accuracy, needs decent CPU"
|
||||
case "medium.en":
|
||||
return "Free/offline; best .en accuracy, needs good CPU/RAM"
|
||||
case "tiny":
|
||||
return "Free/offline multilingual; fastest but low accuracy"
|
||||
case "base":
|
||||
return "Free/offline multilingual; balanced, recommended start"
|
||||
case "small":
|
||||
return "Free/offline multilingual; better accuracy, needs decent CPU"
|
||||
case "medium":
|
||||
return "Free/offline multilingual; great accuracy, needs good CPU/RAM"
|
||||
case "large-v1":
|
||||
return "Free/offline; high accuracy, needs strong CPU/GPU"
|
||||
case "large-v2":
|
||||
return "Free/offline; high accuracy, needs strong CPU/GPU"
|
||||
case "large-v3":
|
||||
return "Free/offline; best accuracy available, needs strong hardware"
|
||||
case "large-v3-turbo":
|
||||
return "Free/offline; near-best accuracy with better speed"
|
||||
}
|
||||
return "English-only local transcription (faster)"
|
||||
if m.Multilingual {
|
||||
return "Free/offline multilingual model"
|
||||
}
|
||||
return "Free/offline English model"
|
||||
}
|
||||
|
||||
func (p *WhisperCppProvider) DefaultModel(t ModelType) string {
|
||||
|
||||
@@ -16,9 +16,9 @@ func TestWhisperCppProvider_Models(t *testing.T) {
|
||||
p := &WhisperCppProvider{}
|
||||
models := p.Models()
|
||||
|
||||
// verify we have 9 models
|
||||
if len(models) != 9 {
|
||||
t.Errorf("expected 9 models, got %d", len(models))
|
||||
// verify we have 12 models
|
||||
if len(models) != 12 {
|
||||
t.Errorf("expected 12 models, got %d", len(models))
|
||||
}
|
||||
|
||||
// verify all models have required fields
|
||||
@@ -77,11 +77,14 @@ func TestWhisperCppProvider_MultilingualModels(t *testing.T) {
|
||||
models := p.Models()
|
||||
|
||||
multilingualIDs := map[string]bool{
|
||||
"tiny": true,
|
||||
"base": true,
|
||||
"small": true,
|
||||
"medium": true,
|
||||
"large-v3": true,
|
||||
"tiny": true,
|
||||
"base": true,
|
||||
"small": true,
|
||||
"medium": true,
|
||||
"large-v1": true,
|
||||
"large-v2": true,
|
||||
"large-v3": true,
|
||||
"large-v3-turbo": true,
|
||||
}
|
||||
|
||||
for _, m := range models {
|
||||
|
||||
Reference in New Issue
Block a user