feat(multimodal): 视觉分层路由(多模态检测→主/视觉/OCR 工具)+ 语音 STT→TTS 降级链(异步)
This commit is contained in:
1 parent
02aae2bbb4
commit
0a936a3202
19 files changed
+2121
No files matched your search
@@ -0,0 +1,112 @@
|
||||
package voice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
|
||||
)
|
||||
|
||||
// Chain is a degrading provider list. STT and TTS walk the steps in order,
|
||||
// returning the first successful result and moving to the next step when a
|
||||
// provider is unavailable (nil direction) or fails. It implements STT and TTS so
|
||||
// it can be used anywhere a single provider is expected.
|
||||
type Chain struct {
|
||||
steps []Provider
|
||||
}
|
||||
|
||||
// NewChain builds a degrading chain from the given providers in the order they
|
||||
// should be tried. Passing a SilentProvider last guarantees the chain always
|
||||
// succeeds.
|
||||
func NewChain(steps ...Provider) *Chain {
|
||||
return &Chain{steps: steps}
|
||||
}
|
||||
|
||||
// STT walks the chain for a transcription.
|
||||
func (c *Chain) STT(ctx context.Context, audio Audio) (string, error) {
|
||||
if len(c.steps) == 0 {
|
||||
return "", ErrProviderFailed
|
||||
}
|
||||
var lastErr error
|
||||
for _, p := range c.steps {
|
||||
stt := p.STT()
|
||||
if stt == nil {
|
||||
continue
|
||||
}
|
||||
text, err := stt.STT(ctx, audio)
|
||||
if err == nil {
|
||||
return text, nil
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
return "", fmt.Errorf("voice: %w: %v", ErrProviderFailed, lastErr)
|
||||
}
|
||||
|
||||
// TTS walks the chain for synthesized audio.
|
||||
func (c *Chain) TTS(ctx context.Context, text string) (Audio, error) {
|
||||
if len(c.steps) == 0 {
|
||||
return Audio{}, ErrProviderFailed
|
||||
}
|
||||
var lastErr error
|
||||
for _, p := range c.steps {
|
||||
tts := p.TTS()
|
||||
if tts == nil {
|
||||
continue
|
||||
}
|
||||
audio, err := tts.TTS(ctx, text)
|
||||
if err == nil {
|
||||
return audio, nil
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
return Audio{}, fmt.Errorf("voice: %w: %v", ErrProviderFailed, lastErr)
|
||||
}
|
||||
|
||||
// Option customizes a chain built by New.
|
||||
type Option func(*chainConfig)
|
||||
|
||||
// chainConfig carries the assembled steps for New.
|
||||
type chainConfig struct {
|
||||
steps []Provider
|
||||
}
|
||||
|
||||
// WithProviders overrides the provider steps assembled from config. Tests use it
|
||||
// to inject deterministic mock providers instead of the real adaptation-point
|
||||
// rungs.
|
||||
func WithProviders(steps ...Provider) Option {
|
||||
return func(c *chainConfig) { c.steps = steps }
|
||||
}
|
||||
|
||||
// New assembles a chain from cfg using the fixed degradation order
|
||||
// cloud → local → edge-tts/espeak → silent.
|
||||
//
|
||||
// - cloud is included only when a cloud voice setting is present (today an
|
||||
// API key) so a mis-configured build degrades quietly.
|
||||
// - local is a pure adaptation point and is only included when supplied via
|
||||
// WithProviders; by default it is skipped.
|
||||
// - the edge-tts or espeak rung is included according to cfg.TTSProvider.
|
||||
// - silent is always appended last so the chain never fails outright.
|
||||
//
|
||||
// opts may supply a fully assembled step list to override this mapping.
|
||||
func New(cfg config.VoiceConfig, opts ...Option) *Chain {
|
||||
conf := &chainConfig{}
|
||||
for _, o := range opts {
|
||||
o(conf)
|
||||
}
|
||||
if len(conf.steps) > 0 {
|
||||
return NewChain(conf.steps...)
|
||||
}
|
||||
|
||||
steps := make([]Provider, 0, 5)
|
||||
if cfg.DashscopeAPIKey != "" {
|
||||
steps = append(steps, CloudProvider(newDashboardSTT(cfg), newDashboardTTS(cfg)))
|
||||
}
|
||||
switch cfg.TTSProvider {
|
||||
case "edge-tts", "edge":
|
||||
steps = append(steps, EdgeTTSProvider(newEdgeTTS()))
|
||||
case "espeak":
|
||||
steps = append(steps, EspeakProvider(newEspeak()))
|
||||
}
|
||||
steps = append(steps, SilentProvider())
|
||||
return NewChain(steps...)
|
||||
}
|
||||
Reference in new issue
Block a user