mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 10:52:15 +00:00
feat(speech): add auto-discovery and wire speech package init
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
16d793652b
commit
6dc4ad1dbb
@@ -1 +1,10 @@
|
||||
"""Speech subsystem — STT backends and transcription types."""
|
||||
"""Speech subsystem — speech-to-text backends."""
|
||||
|
||||
import importlib
|
||||
|
||||
# Optional backends — each registers itself via @SpeechRegistry.register()
|
||||
for _mod in ("faster_whisper", "openai_whisper", "deepgram"):
|
||||
try:
|
||||
importlib.import_module(f".{_mod}", __name__)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Auto-discover available speech-to-text backends."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openjarvis.core.config import JarvisConfig
|
||||
from openjarvis.speech._stubs import SpeechBackend
|
||||
|
||||
# Priority order: local first, then cloud
|
||||
DISCOVERY_ORDER = [
|
||||
"faster-whisper",
|
||||
"openai",
|
||||
"deepgram",
|
||||
]
|
||||
|
||||
|
||||
def _create_backend(
|
||||
key: str,
|
||||
config: "JarvisConfig",
|
||||
) -> Optional["SpeechBackend"]:
|
||||
"""Try to instantiate a speech backend by registry key."""
|
||||
from openjarvis.core.registry import SpeechRegistry
|
||||
|
||||
if not SpeechRegistry.contains(key):
|
||||
return None
|
||||
|
||||
try:
|
||||
backend_cls = SpeechRegistry.get(key)
|
||||
|
||||
if key == "faster-whisper":
|
||||
return backend_cls(
|
||||
model_size=config.speech.model,
|
||||
device=config.speech.device,
|
||||
compute_type=config.speech.compute_type,
|
||||
)
|
||||
elif key == "openai":
|
||||
api_key = os.environ.get("OPENAI_API_KEY", "")
|
||||
if not api_key:
|
||||
return None
|
||||
return backend_cls(api_key=api_key)
|
||||
elif key == "deepgram":
|
||||
api_key = os.environ.get("DEEPGRAM_API_KEY", "")
|
||||
if not api_key:
|
||||
return None
|
||||
return backend_cls(api_key=api_key)
|
||||
else:
|
||||
return backend_cls()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def get_speech_backend(config: "JarvisConfig") -> Optional["SpeechBackend"]:
|
||||
"""Resolve the speech backend from config.
|
||||
|
||||
If ``config.speech.backend`` is ``"auto"``, tries backends in
|
||||
priority order and returns the first healthy one.
|
||||
"""
|
||||
# Trigger registration of built-in backends
|
||||
import openjarvis.speech # noqa: F401
|
||||
|
||||
backend_key = config.speech.backend
|
||||
|
||||
if backend_key != "auto":
|
||||
return _create_backend(backend_key, config)
|
||||
|
||||
# Auto-discovery: try each in priority order
|
||||
for key in DISCOVERY_ORDER:
|
||||
backend = _create_backend(key, config)
|
||||
if backend is not None:
|
||||
return backend
|
||||
|
||||
return None
|
||||
@@ -2,13 +2,21 @@
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from openjarvis.core.registry import SpeechRegistry
|
||||
from openjarvis.speech._stubs import TranscriptionResult
|
||||
from openjarvis.speech.deepgram import DeepgramSpeechBackend
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _register_deepgram():
|
||||
"""Re-register after any registry clear."""
|
||||
if not SpeechRegistry.contains("deepgram"):
|
||||
SpeechRegistry.register_value("deepgram", DeepgramSpeechBackend)
|
||||
|
||||
|
||||
def test_deepgram_registers():
|
||||
import openjarvis.speech.deepgram # noqa: F401
|
||||
from openjarvis.core.registry import SpeechRegistry
|
||||
|
||||
assert SpeechRegistry.contains("deepgram")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Tests for speech backend auto-discovery."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from openjarvis.core.config import JarvisConfig
|
||||
|
||||
|
||||
def test_get_speech_backend_explicit():
|
||||
"""Explicit backend selection works."""
|
||||
from openjarvis.speech._discovery import get_speech_backend
|
||||
|
||||
config = JarvisConfig()
|
||||
config.speech.backend = "faster-whisper"
|
||||
|
||||
with patch("openjarvis.speech._discovery._create_backend") as mock_create:
|
||||
mock_backend = type("MockBackend", (), {
|
||||
"backend_id": "faster-whisper",
|
||||
"health": lambda self: True,
|
||||
})()
|
||||
mock_create.return_value = mock_backend
|
||||
|
||||
result = get_speech_backend(config)
|
||||
assert result is not None
|
||||
assert result.backend_id == "faster-whisper"
|
||||
|
||||
|
||||
def test_get_speech_backend_returns_none_if_nothing_available():
|
||||
"""Returns None when no backend can be created."""
|
||||
from openjarvis.speech._discovery import get_speech_backend
|
||||
|
||||
config = JarvisConfig()
|
||||
config.speech.backend = "nonexistent"
|
||||
|
||||
result = get_speech_backend(config)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_auto_discovery_priority():
|
||||
"""Auto mode tries backends in priority order."""
|
||||
from openjarvis.speech._discovery import DISCOVERY_ORDER
|
||||
|
||||
assert DISCOVERY_ORDER[0] == "faster-whisper"
|
||||
assert "openai" in DISCOVERY_ORDER
|
||||
assert "deepgram" in DISCOVERY_ORDER
|
||||
@@ -2,12 +2,21 @@
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from openjarvis.core.registry import SpeechRegistry
|
||||
from openjarvis.speech.faster_whisper import FasterWhisperBackend
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _register_faster_whisper():
|
||||
"""Re-register after any registry clear."""
|
||||
if not SpeechRegistry.contains("faster-whisper"):
|
||||
SpeechRegistry.register_value("faster-whisper", FasterWhisperBackend)
|
||||
|
||||
|
||||
def test_faster_whisper_backend_registers():
|
||||
"""Backend registers itself in SpeechRegistry."""
|
||||
import openjarvis.speech.faster_whisper # noqa: F401
|
||||
from openjarvis.core.registry import SpeechRegistry
|
||||
|
||||
assert SpeechRegistry.contains("faster-whisper")
|
||||
|
||||
|
||||
|
||||
@@ -2,13 +2,21 @@
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from openjarvis.core.registry import SpeechRegistry
|
||||
from openjarvis.speech._stubs import TranscriptionResult
|
||||
from openjarvis.speech.openai_whisper import OpenAIWhisperBackend
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _register_openai_whisper():
|
||||
"""Re-register after any registry clear."""
|
||||
if not SpeechRegistry.contains("openai"):
|
||||
SpeechRegistry.register_value("openai", OpenAIWhisperBackend)
|
||||
|
||||
|
||||
def test_openai_whisper_registers():
|
||||
import openjarvis.speech.openai_whisper # noqa: F401
|
||||
from openjarvis.core.registry import SpeechRegistry
|
||||
|
||||
assert SpeechRegistry.contains("openai")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user