refactor transcriber factory to use model metadata

- factory now looks up Model via provider.GetModel()
- switches on model.AdapterType instead of provider name
- uses model.Endpoint for adapter configuration
- streaming models return clear error (not yet implemented)
- empty model falls back to provider default
- added tests for streaming and unknown model errors
This commit is contained in:
leonardotrapani
2026-02-01 01:00:21 +01:00
parent edc7a5f26b
commit 3563def987
4 changed files with 111 additions and 46 deletions
+76 -43
View File
@@ -3,6 +3,7 @@ package transcriber
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/leonardotrapani/hyprvoice/internal/provider" "github.com/leonardotrapani/hyprvoice/internal/provider"
"github.com/leonardotrapani/hyprvoice/internal/recording" "github.com/leonardotrapani/hyprvoice/internal/recording"
@@ -29,52 +30,84 @@ type Config struct {
Keywords []string Keywords []string
} }
// NewTranscriber creates a new simple transcriber // mapConfigProviderToRegistryName maps config provider names to provider registry names
func NewTranscriber(config Config) (Transcriber, error) { // Config uses names like "groq-transcription", "groq-translation", "mistral-transcription"
// Create the appropriate adapter // Registry uses base names like "groq", "mistral"
var adapter BatchAdapter func mapConfigProviderToRegistryName(configProvider string) string {
switch configProvider {
switch config.Provider { case "groq-transcription", "groq-translation":
case "openai": return "groq"
if config.APIKey == "" {
return nil, fmt.Errorf("OpenAI API key required")
}
adapter = NewOpenAIAdapterFromConfig(config)
case "groq-transcription":
if config.APIKey == "" {
return nil, fmt.Errorf("Groq API key required")
}
// use consolidated OpenAI adapter with Groq endpoint
endpoint := &provider.EndpointConfig{BaseURL: "https://api.groq.com/openai"}
adapter = NewOpenAIAdapter(endpoint, config.APIKey, config.Model, config.Language, config.Keywords, "groq")
case "groq-translation":
if config.APIKey == "" {
return nil, fmt.Errorf("Groq API key required")
}
adapter = NewGroqTranslationAdapter(config)
case "mistral-transcription": case "mistral-transcription":
if config.APIKey == "" { return "mistral"
return nil, fmt.Errorf("Mistral API key required")
}
// use consolidated OpenAI adapter with Mistral endpoint
endpoint := &provider.EndpointConfig{BaseURL: "https://api.mistral.ai"}
adapter = NewOpenAIAdapter(endpoint, config.APIKey, config.Model, config.Language, config.Keywords, "mistral")
case "elevenlabs":
if config.APIKey == "" {
return nil, fmt.Errorf("ElevenLabs API key required")
}
adapter = NewElevenLabsAdapterFromConfig(config)
default: default:
return nil, fmt.Errorf("unsupported provider: %s", config.Provider) return configProvider
}
}
// NewTranscriber creates a new transcriber based on model metadata
func NewTranscriber(config Config) (Transcriber, error) {
if config.Provider == "" {
return nil, fmt.Errorf("provider is required")
} }
// Create simple transcriber that collects all audio // special case: groq-translation uses CreateTranslation API (different from transcription)
transcriber := NewSimpleTranscriber(config, adapter) if config.Provider == "groq-translation" {
if config.APIKey == "" {
return nil, fmt.Errorf("Groq API key required")
}
adapter := NewGroqTranslationAdapter(config)
return NewSimpleTranscriber(config, adapter), nil
}
return transcriber, nil // map config provider name to registry provider name
registryProvider := mapConfigProviderToRegistryName(config.Provider)
// lookup provider
p := provider.GetProvider(registryProvider)
if p == nil {
return nil, fmt.Errorf("unknown provider: %s", config.Provider)
}
// check API key requirement
if p.RequiresAPIKey() && config.APIKey == "" {
return nil, fmt.Errorf("%s API key required", strings.Title(registryProvider))
}
// lookup model from provider
model, err := provider.GetModel(registryProvider, config.Model)
if err != nil {
// if model not found, try to use default model
if config.Model == "" {
defaultModel := p.DefaultModel(provider.Transcription)
if defaultModel != "" {
model, err = provider.GetModel(registryProvider, defaultModel)
}
}
if err != nil || model == nil {
return nil, fmt.Errorf("model not found: %s (provider: %s)", config.Model, config.Provider)
}
}
// check model type
if model.Type != provider.Transcription {
return nil, fmt.Errorf("model %s is not a transcription model", config.Model)
}
// streaming models not supported yet
if model.Streaming {
return nil, fmt.Errorf("streaming model %s not supported yet (coming soon)", config.Model)
}
// create adapter based on model.AdapterType
var adapter BatchAdapter
switch model.AdapterType {
case "openai":
adapter = NewOpenAIAdapter(model.Endpoint, config.APIKey, model.ID, config.Language, config.Keywords, registryProvider)
case "elevenlabs":
adapter = NewElevenLabsAdapter(model.Endpoint, config.APIKey, model.ID, config.Language)
default:
return nil, fmt.Errorf("unsupported adapter type: %s", model.AdapterType)
}
return NewSimpleTranscriber(config, adapter), nil
} }
+22 -2
View File
@@ -145,14 +145,34 @@ func TestNewTranscriber(t *testing.T) {
wantErr: true, wantErr: true,
}, },
{ {
name: "empty model", name: "empty model uses default",
config: Config{ config: Config{
Provider: "openai", Provider: "openai",
APIKey: "test-key", APIKey: "test-key",
Language: "en", Language: "en",
Model: "", Model: "",
}, },
wantErr: false, // Model validation is not implemented in NewTranscriber wantErr: false, // uses default model when empty
},
{
name: "streaming model returns error",
config: Config{
Provider: "elevenlabs",
APIKey: "test-key",
Language: "en",
Model: "scribe_v1-streaming",
},
wantErr: true, // streaming not yet supported
},
{
name: "unknown model returns error",
config: Config{
Provider: "openai",
APIKey: "test-key",
Language: "en",
Model: "nonexistent-model",
},
wantErr: true,
}, },
} }
+12
View File
@@ -134,3 +134,15 @@ Started: Sun Feb 1 12:22:47 AM CET 2026
- Updated factory to use `NewElevenLabsAdapterFromConfig` - Updated factory to use `NewElevenLabsAdapterFromConfig`
- Updated tests for new constructor signature - Updated tests for new constructor signature
- All tests passing, typecheck passes - All tests passing, typecheck passes
### Task 15: Update transcriber factory to use Model metadata
- Refactored `NewTranscriber()` to look up Model via `provider.GetModel()`
- Added `mapConfigProviderToRegistryName()` to map config provider names (e.g., "groq-transcription") to registry names (e.g., "groq")
- Factory now switches on `model.AdapterType` instead of provider name
- Special case: "groq-translation" still uses dedicated `GroqTranslationAdapter` (uses CreateTranslation API)
- For "openai" adapter type: creates `OpenAIAdapter` with model's endpoint config
- For "elevenlabs" adapter type: creates `ElevenLabsAdapter` with model's endpoint config
- Streaming models return clear error: "streaming model %s not supported yet (coming soon)"
- Empty model now uses provider's default transcription model
- Added tests for streaming model rejection and unknown model error
- All tests passing, typecheck passes
+1 -1
View File
@@ -373,7 +373,7 @@
"Streaming models return clear error until implemented", "Streaming models return clear error until implemented",
"Typecheck passes" "Typecheck passes"
], ],
"passes": false "passes": true
}, },
{ {
"title": "Update config.ToTranscriberConfig to work with new architecture", "title": "Update config.ToTranscriberConfig to work with new architecture",