Files
viettranx 8c8849365f feat(audio): Manager STT dispatch with channel override
Add Transcribe() method to Manager with STT provider dispatch and channel-specific overrides. Implements channelSTTOverrides map, WithChannel context propagation, RegisterChannelSTT registration, and resolveSTTChain fallback logic. Includes manager-level unit tests.
2026-04-15 11:24:57 +07:00

89 lines
2.9 KiB
Go

package audio
import (
"context"
"errors"
"fmt"
"log/slog"
)
// ErrAllSTTProvidersFailed is returned when every provider in the chain fails.
var ErrAllSTTProvidersFailed = errors.New("all STT providers failed")
// defaultSTTChain is the built-in fallback order when no explicit chain is set.
var defaultSTTChain = []string{"elevenlabs", "proxy"}
// Transcribe tries providers in chain order. Returns first success.
// Wraps last error with ErrAllSTTProvidersFailed on total failure.
func (m *Manager) Transcribe(ctx context.Context, in STTInput, opts STTOptions) (*TranscriptResult, error) {
chain := m.resolveSTTChain(ctx)
if len(chain) == 0 {
return nil, fmt.Errorf("%w: chain is empty", ErrAllSTTProvidersFailed)
}
var lastErr error
for _, name := range chain {
p, ok := m.sttProviders[name]
if !ok {
slog.Warn("audio.stt provider not registered, skipping", "provider", name)
continue
}
res, err := p.Transcribe(ctx, in, opts)
if err == nil {
return res, nil
}
slog.Warn("audio.stt provider failed", "provider", name, "error", err)
lastErr = err
}
if lastErr != nil {
return nil, fmt.Errorf("%w: %w", ErrAllSTTProvidersFailed, lastErr)
}
return nil, fmt.Errorf("%w: no providers matched in chain %v", ErrAllSTTProvidersFailed, chain)
}
// SetSTTChain sets an explicit provider order for STT dispatch.
// Call before first use; not thread-safe after concurrent Transcribe calls.
func (m *Manager) SetSTTChain(chain []string) {
m.sttChain = chain
}
// RegisterChannelSTT registers STT providers scoped to a specific channel name
// (e.g. "telegram"). Channel-scoped providers take precedence over the manager
// default chain when resolveSTTChain detects a matching channel in ctx.
func (m *Manager) RegisterChannelSTT(channelName string, providers ...STTProvider) {
if m.channelSTTOverrides == nil {
m.channelSTTOverrides = make(map[string][]string)
}
names := make([]string, 0, len(providers))
for _, p := range providers {
m.sttProviders[p.Name()+":"+channelName] = p
names = append(names, p.Name()+":"+channelName)
}
m.channelSTTOverrides[channelName] = names
}
// resolveSTTChain returns the ordered provider names for the current call.
// Precedence: (1) channel override from ctx, (2) explicit m.sttChain,
// (3) defaultSTTChain filtered to registered providers.
func (m *Manager) resolveSTTChain(ctx context.Context) []string {
// (1) Channel override: check if ctx carries a channel name.
if ch := channelFromCtx(ctx); ch != "" {
if overrides, ok := m.channelSTTOverrides[ch]; ok && len(overrides) > 0 {
return overrides
}
}
// (2) Explicit chain set via SetSTTChain.
if len(m.sttChain) > 0 {
return m.sttChain
}
// (3) Default: elevenlabs → proxy, filtered to what's registered.
var out []string
for _, name := range defaultSTTChain {
if _, ok := m.sttProviders[name]; ok {
out = append(out, name)
}
}
return out
}