feat: add provider interface and registry
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
package provider
|
||||
|
||||
// ElevenLabsProvider implements Provider for ElevenLabs services (transcription only)
|
||||
type ElevenLabsProvider struct{}
|
||||
|
||||
func (p *ElevenLabsProvider) Name() string {
|
||||
return "elevenlabs"
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) RequiresAPIKey() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) ValidateAPIKey(key string) bool {
|
||||
// ElevenLabs API keys don't have a consistent prefix, just check non-empty
|
||||
return len(key) > 0
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) SupportsTranscription() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) SupportsLLM() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) DefaultTranscriptionModel() string {
|
||||
return "scribe_v1"
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) DefaultLLMModel() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) TranscriptionModels() []string {
|
||||
return []string{"scribe_v1", "scribe_v2"}
|
||||
}
|
||||
|
||||
func (p *ElevenLabsProvider) LLMModels() []string {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package provider
|
||||
|
||||
import "strings"
|
||||
|
||||
// GroqProvider implements Provider for Groq services
|
||||
type GroqProvider struct{}
|
||||
|
||||
func (p *GroqProvider) Name() string {
|
||||
return "groq"
|
||||
}
|
||||
|
||||
func (p *GroqProvider) RequiresAPIKey() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *GroqProvider) ValidateAPIKey(key string) bool {
|
||||
return strings.HasPrefix(key, "gsk_")
|
||||
}
|
||||
|
||||
func (p *GroqProvider) SupportsTranscription() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *GroqProvider) SupportsLLM() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *GroqProvider) DefaultTranscriptionModel() string {
|
||||
return "whisper-large-v3-turbo"
|
||||
}
|
||||
|
||||
func (p *GroqProvider) DefaultLLMModel() string {
|
||||
return "llama-3.3-70b-versatile"
|
||||
}
|
||||
|
||||
func (p *GroqProvider) TranscriptionModels() []string {
|
||||
return []string{"whisper-large-v3", "whisper-large-v3-turbo"}
|
||||
}
|
||||
|
||||
func (p *GroqProvider) LLMModels() []string {
|
||||
return []string{"llama-3.3-70b-versatile", "llama-3.1-8b-instant", "mixtral-8x7b-32768"}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package provider
|
||||
|
||||
// MistralProvider implements Provider for Mistral services (transcription only)
|
||||
type MistralProvider struct{}
|
||||
|
||||
func (p *MistralProvider) Name() string {
|
||||
return "mistral"
|
||||
}
|
||||
|
||||
func (p *MistralProvider) RequiresAPIKey() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *MistralProvider) ValidateAPIKey(key string) bool {
|
||||
// Mistral API keys don't have a consistent prefix, just check non-empty
|
||||
return len(key) > 0
|
||||
}
|
||||
|
||||
func (p *MistralProvider) SupportsTranscription() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *MistralProvider) SupportsLLM() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *MistralProvider) DefaultTranscriptionModel() string {
|
||||
return "voxtral-mini-latest"
|
||||
}
|
||||
|
||||
func (p *MistralProvider) DefaultLLMModel() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *MistralProvider) TranscriptionModels() []string {
|
||||
return []string{"voxtral-mini-latest", "voxtral-mini-2507"}
|
||||
}
|
||||
|
||||
func (p *MistralProvider) LLMModels() []string {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package provider
|
||||
|
||||
import "strings"
|
||||
|
||||
// OpenAIProvider implements Provider for OpenAI services
|
||||
type OpenAIProvider struct{}
|
||||
|
||||
func (p *OpenAIProvider) Name() string {
|
||||
return "openai"
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) RequiresAPIKey() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) ValidateAPIKey(key string) bool {
|
||||
return strings.HasPrefix(key, "sk-")
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) SupportsTranscription() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) SupportsLLM() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) DefaultTranscriptionModel() string {
|
||||
return "whisper-1"
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) DefaultLLMModel() string {
|
||||
return "gpt-4o-mini"
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) TranscriptionModels() []string {
|
||||
return []string{"whisper-1"}
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) LLMModels() []string {
|
||||
return []string{"gpt-4o-mini", "gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo"}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package provider
|
||||
|
||||
// Provider defines the interface for a transcription/LLM service provider
|
||||
type Provider interface {
|
||||
Name() string
|
||||
RequiresAPIKey() bool
|
||||
ValidateAPIKey(key string) bool
|
||||
SupportsTranscription() bool
|
||||
SupportsLLM() bool
|
||||
DefaultTranscriptionModel() string
|
||||
DefaultLLMModel() string
|
||||
TranscriptionModels() []string
|
||||
LLMModels() []string
|
||||
}
|
||||
|
||||
// ProviderConfig holds configuration for a single provider
|
||||
type ProviderConfig struct {
|
||||
APIKey string `toml:"api_key"`
|
||||
}
|
||||
|
||||
var registry = make(map[string]Provider)
|
||||
|
||||
func init() {
|
||||
Register(&OpenAIProvider{})
|
||||
Register(&GroqProvider{})
|
||||
Register(&MistralProvider{})
|
||||
Register(&ElevenLabsProvider{})
|
||||
}
|
||||
|
||||
// Register adds a provider to the registry
|
||||
func Register(p Provider) {
|
||||
registry[p.Name()] = p
|
||||
}
|
||||
|
||||
// GetProvider returns a provider by name, or nil if not found
|
||||
func GetProvider(name string) Provider {
|
||||
return registry[name]
|
||||
}
|
||||
|
||||
// ListProviders returns all registered provider names
|
||||
func ListProviders() []string {
|
||||
names := make([]string, 0, len(registry))
|
||||
for name := range registry {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// ListProvidersWithTranscription returns providers that support transcription
|
||||
func ListProvidersWithTranscription() []string {
|
||||
var names []string
|
||||
for name, p := range registry {
|
||||
if p.SupportsTranscription() {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// ListProvidersWithLLM returns providers that support LLM
|
||||
func ListProvidersWithLLM() []string {
|
||||
var names []string
|
||||
for name, p := range registry {
|
||||
if p.SupportsLLM() {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProviderInterface(t *testing.T) {
|
||||
providers := []struct {
|
||||
name string
|
||||
hasTranscription bool
|
||||
hasLLM bool
|
||||
defaultTransModel string
|
||||
defaultLLMModel string
|
||||
}{
|
||||
{"openai", true, true, "whisper-1", "gpt-4o-mini"},
|
||||
{"groq", true, true, "whisper-large-v3-turbo", "llama-3.3-70b-versatile"},
|
||||
{"mistral", true, false, "voxtral-mini-latest", ""},
|
||||
{"elevenlabs", true, false, "scribe_v1", ""},
|
||||
}
|
||||
|
||||
for _, tc := range providers {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := GetProvider(tc.name)
|
||||
if p == nil {
|
||||
t.Fatalf("GetProvider(%q) returned nil", tc.name)
|
||||
}
|
||||
|
||||
if p.Name() != tc.name {
|
||||
t.Errorf("Name() = %q, want %q", p.Name(), tc.name)
|
||||
}
|
||||
|
||||
if p.SupportsTranscription() != tc.hasTranscription {
|
||||
t.Errorf("SupportsTranscription() = %v, want %v", p.SupportsTranscription(), tc.hasTranscription)
|
||||
}
|
||||
|
||||
if p.SupportsLLM() != tc.hasLLM {
|
||||
t.Errorf("SupportsLLM() = %v, want %v", p.SupportsLLM(), tc.hasLLM)
|
||||
}
|
||||
|
||||
if p.DefaultTranscriptionModel() != tc.defaultTransModel {
|
||||
t.Errorf("DefaultTranscriptionModel() = %q, want %q", p.DefaultTranscriptionModel(), tc.defaultTransModel)
|
||||
}
|
||||
|
||||
if p.DefaultLLMModel() != tc.defaultLLMModel {
|
||||
t.Errorf("DefaultLLMModel() = %q, want %q", p.DefaultLLMModel(), tc.defaultLLMModel)
|
||||
}
|
||||
|
||||
if !p.RequiresAPIKey() {
|
||||
t.Error("RequiresAPIKey() should be true for all providers")
|
||||
}
|
||||
|
||||
if tc.hasTranscription && len(p.TranscriptionModels()) == 0 {
|
||||
t.Error("TranscriptionModels() should not be empty for transcription provider")
|
||||
}
|
||||
|
||||
if tc.hasLLM && len(p.LLMModels()) == 0 {
|
||||
t.Error("LLMModels() should not be empty for LLM provider")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProviderNotFound(t *testing.T) {
|
||||
p := GetProvider("nonexistent")
|
||||
if p != nil {
|
||||
t.Errorf("GetProvider(nonexistent) should return nil, got %v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListProviders(t *testing.T) {
|
||||
providers := ListProviders()
|
||||
expected := []string{"openai", "groq", "mistral", "elevenlabs"}
|
||||
|
||||
for _, name := range expected {
|
||||
if !slices.Contains(providers, name) {
|
||||
t.Errorf("ListProviders() missing %q", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListProvidersWithTranscription(t *testing.T) {
|
||||
providers := ListProvidersWithTranscription()
|
||||
// All providers support transcription
|
||||
expected := []string{"openai", "groq", "mistral", "elevenlabs"}
|
||||
|
||||
for _, name := range expected {
|
||||
if !slices.Contains(providers, name) {
|
||||
t.Errorf("ListProvidersWithTranscription() missing %q", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListProvidersWithLLM(t *testing.T) {
|
||||
providers := ListProvidersWithLLM()
|
||||
expected := []string{"openai", "groq"}
|
||||
|
||||
for _, name := range expected {
|
||||
if !slices.Contains(providers, name) {
|
||||
t.Errorf("ListProvidersWithLLM() missing %q", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Mistral and ElevenLabs should NOT be in the list
|
||||
notExpected := []string{"mistral", "elevenlabs"}
|
||||
for _, name := range notExpected {
|
||||
if slices.Contains(providers, name) {
|
||||
t.Errorf("ListProvidersWithLLM() should not include %q", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAPIKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
provider string
|
||||
key string
|
||||
valid bool
|
||||
}{
|
||||
{"openai", "sk-abc123", true},
|
||||
{"openai", "invalid", false},
|
||||
{"openai", "", false},
|
||||
{"groq", "gsk_abc123", true},
|
||||
{"groq", "invalid", false},
|
||||
{"groq", "", false},
|
||||
{"mistral", "any-non-empty", true},
|
||||
{"mistral", "", false},
|
||||
{"elevenlabs", "any-non-empty", true},
|
||||
{"elevenlabs", "", false},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.provider+"_"+tc.key, func(t *testing.T) {
|
||||
p := GetProvider(tc.provider)
|
||||
if p.ValidateAPIKey(tc.key) != tc.valid {
|
||||
t.Errorf("ValidateAPIKey(%q) = %v, want %v", tc.key, !tc.valid, tc.valid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user