config: add threads auto-detection and whisper-cpp validation
This commit is contained in:
@@ -3,6 +3,7 @@ package config
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -1770,3 +1771,164 @@ func TestConfig_LLMDefaultsPreserveExplicit(t *testing.T) {
|
|||||||
t.Error("AddPunctuation should remain false (explicit)")
|
t.Error("AddPunctuation should remain false (explicit)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfig_Validate_WhisperCpp(t *testing.T) {
|
||||||
|
baseConfig := func() *Config {
|
||||||
|
return &Config{
|
||||||
|
Recording: RecordingConfig{
|
||||||
|
SampleRate: 16000,
|
||||||
|
Channels: 1,
|
||||||
|
Format: "s16",
|
||||||
|
BufferSize: 8192,
|
||||||
|
ChannelBufferSize: 30,
|
||||||
|
Timeout: time.Minute,
|
||||||
|
},
|
||||||
|
Injection: InjectionConfig{
|
||||||
|
Backends: []string{"clipboard"},
|
||||||
|
YdotoolTimeout: 5 * time.Second,
|
||||||
|
WtypeTimeout: 5 * time.Second,
|
||||||
|
ClipboardTimeout: 3 * time.Second,
|
||||||
|
},
|
||||||
|
Notifications: NotificationsConfig{Type: "log"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("whisper-cpp valid without API key", func(t *testing.T) {
|
||||||
|
config := baseConfig()
|
||||||
|
config.Transcription = TranscriptionConfig{
|
||||||
|
Provider: "whisper-cpp",
|
||||||
|
Model: "base.en",
|
||||||
|
// No API key required
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.Validate()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Validate() should pass for whisper-cpp without API key: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("whisper-cpp valid multilingual model", func(t *testing.T) {
|
||||||
|
config := baseConfig()
|
||||||
|
config.Transcription = TranscriptionConfig{
|
||||||
|
Provider: "whisper-cpp",
|
||||||
|
Model: "large-v3",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.Validate()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Validate() should pass for whisper-cpp with large-v3: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("whisper-cpp invalid model", func(t *testing.T) {
|
||||||
|
config := baseConfig()
|
||||||
|
config.Transcription = TranscriptionConfig{
|
||||||
|
Provider: "whisper-cpp",
|
||||||
|
Model: "invalid-model",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Validate() should fail for whisper-cpp with invalid model")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("whisper-cpp invalid language", func(t *testing.T) {
|
||||||
|
config := baseConfig()
|
||||||
|
config.Transcription = TranscriptionConfig{
|
||||||
|
Provider: "whisper-cpp",
|
||||||
|
Model: "base.en",
|
||||||
|
Language: "invalid-lang",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Validate() should fail for whisper-cpp with invalid language")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("whisper-cpp valid with language", func(t *testing.T) {
|
||||||
|
config := baseConfig()
|
||||||
|
config.Transcription = TranscriptionConfig{
|
||||||
|
Provider: "whisper-cpp",
|
||||||
|
Model: "base",
|
||||||
|
Language: "en",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.Validate()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Validate() should pass for whisper-cpp with valid language: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_ThreadsDefault(t *testing.T) {
|
||||||
|
t.Run("threads defaults to NumCPU-1", func(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
Transcription: TranscriptionConfig{
|
||||||
|
Provider: "whisper-cpp",
|
||||||
|
Model: "base.en",
|
||||||
|
Threads: 0, // Not set
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config.applyThreadsDefault()
|
||||||
|
|
||||||
|
expectedThreads := runtime.NumCPU() - 1
|
||||||
|
if expectedThreads < 1 {
|
||||||
|
expectedThreads = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Transcription.Threads != expectedThreads {
|
||||||
|
t.Errorf("Threads = %d, want %d", config.Transcription.Threads, expectedThreads)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("explicit threads preserved", func(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
Transcription: TranscriptionConfig{
|
||||||
|
Provider: "whisper-cpp",
|
||||||
|
Model: "base.en",
|
||||||
|
Threads: 2, // Explicitly set
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config.applyThreadsDefault()
|
||||||
|
|
||||||
|
if config.Transcription.Threads != 2 {
|
||||||
|
t.Errorf("Threads = %d, want 2", config.Transcription.Threads)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("threads minimum is 1", func(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
Transcription: TranscriptionConfig{
|
||||||
|
Provider: "whisper-cpp",
|
||||||
|
Model: "base.en",
|
||||||
|
Threads: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config.applyThreadsDefault()
|
||||||
|
|
||||||
|
if config.Transcription.Threads < 1 {
|
||||||
|
t.Errorf("Threads = %d, should be at least 1", config.Transcription.Threads)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_ToTranscriberConfig_Threads(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
Transcription: TranscriptionConfig{
|
||||||
|
Provider: "whisper-cpp",
|
||||||
|
Model: "base.en",
|
||||||
|
Threads: 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
transcriberConfig := config.ToTranscriberConfig()
|
||||||
|
|
||||||
|
if transcriberConfig.Threads != 4 {
|
||||||
|
t.Errorf("Threads = %d, want 4", transcriberConfig.Threads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
@@ -76,11 +77,23 @@ func Load() (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.applyLLMDefaults()
|
config.applyLLMDefaults()
|
||||||
|
config.applyThreadsDefault()
|
||||||
|
|
||||||
log.Printf("Config: configuration loaded successfully")
|
log.Printf("Config: configuration loaded successfully")
|
||||||
return &config, nil
|
return &config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyThreadsDefault sets default threads for local transcription if not explicitly set
|
||||||
|
func (c *Config) applyThreadsDefault() {
|
||||||
|
if c.Transcription.Threads == 0 {
|
||||||
|
threads := runtime.NumCPU() - 1
|
||||||
|
if threads < 1 {
|
||||||
|
threads = 1
|
||||||
|
}
|
||||||
|
c.Transcription.Threads = threads
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// migrateTranscriptionAPIKey migrates old transcription.api_key to providers map
|
// migrateTranscriptionAPIKey migrates old transcription.api_key to providers map
|
||||||
func (c *Config) migrateTranscriptionAPIKey(apiKey string) {
|
func (c *Config) migrateTranscriptionAPIKey(apiKey string) {
|
||||||
if c.Providers == nil {
|
if c.Providers == nil {
|
||||||
|
|||||||
@@ -93,8 +93,22 @@ func (c *Config) Validate() error {
|
|||||||
return fmt.Errorf("invalid model for elevenlabs: %s (must be scribe_v1 or scribe_v2)", c.Transcription.Model)
|
return fmt.Errorf("invalid model for elevenlabs: %s (must be scribe_v1 or scribe_v2)", c.Transcription.Model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "whisper-cpp":
|
||||||
|
// whisper-cpp is local, no API key required
|
||||||
|
if c.Transcription.Language != "" && !isValidLanguageCode(c.Transcription.Language) {
|
||||||
|
return fmt.Errorf("invalid transcription.language: %s (use empty string for auto-detect or ISO-639-1 codes like 'en', 'es', 'fr')", c.Transcription.Language)
|
||||||
|
}
|
||||||
|
|
||||||
|
validWhisperModels := map[string]bool{
|
||||||
|
"tiny.en": true, "base.en": true, "small.en": true, "medium.en": true,
|
||||||
|
"tiny": true, "base": true, "small": true, "medium": true, "large-v3": true,
|
||||||
|
}
|
||||||
|
if c.Transcription.Model != "" && !validWhisperModels[c.Transcription.Model] {
|
||||||
|
return fmt.Errorf("invalid model for whisper-cpp: %s (must be tiny.en, base.en, small.en, medium.en, tiny, base, small, medium, or large-v3)", c.Transcription.Model)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported transcription.provider: %s (must be openai, groq-transcription, groq-translation, mistral-transcription, or elevenlabs)", c.Transcription.Provider)
|
return fmt.Errorf("unsupported transcription.provider: %s (must be openai, groq-transcription, groq-translation, mistral-transcription, elevenlabs, or whisper-cpp)", c.Transcription.Provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Transcription.Model == "" {
|
if c.Transcription.Model == "" {
|
||||||
|
|||||||
@@ -226,3 +226,12 @@ Started: Sun Feb 1 12:22:47 AM CET 2026
|
|||||||
- Returns error if whisper model ID is unknown
|
- Returns error if whisper model ID is unknown
|
||||||
- Added tests for whisper-cpp factory cases: valid config, no API key required, unknown model error
|
- Added tests for whisper-cpp factory cases: valid config, no API key required, unknown model error
|
||||||
- All tests passing, typecheck passes
|
- All tests passing, typecheck passes
|
||||||
|
|
||||||
|
### Task 23: Update config for local transcription
|
||||||
|
- Added `applyThreadsDefault()` to config.Load() - sets Threads to max(1, NumCPU-1) when 0
|
||||||
|
- Added whisper-cpp case to config validation (no API key required)
|
||||||
|
- Validates whisper model names: tiny.en, base.en, small.en, medium.en, tiny, base, small, medium, large-v3
|
||||||
|
- Validates language codes for whisper-cpp same as other providers
|
||||||
|
- Note: Threads field, ToTranscriberConfig, and template were already done in Task 16
|
||||||
|
- Added comprehensive tests for whisper-cpp validation and threads auto-detection
|
||||||
|
- All tests passing, typecheck passes
|
||||||
+1
-1
@@ -550,7 +550,7 @@
|
|||||||
"Config round-trips correctly with threads field",
|
"Config round-trips correctly with threads field",
|
||||||
"Typecheck passes"
|
"Typecheck passes"
|
||||||
],
|
],
|
||||||
"passes": false
|
"passes": true
|
||||||
},
|
},
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// PHASE 5: MODEL CLI COMMANDS
|
// PHASE 5: MODEL CLI COMMANDS
|
||||||
|
|||||||
Reference in New Issue
Block a user