Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions tests/tools/test_tts_lazy_import_fallback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Regression tests for TTS lazy import fallback behavior."""

import sys
import types


def _make_ensure_failure(*_args, **_kwargs):
raise RuntimeError("lazy install unavailable")


def test_edge_tts_raw_import_still_runs_when_lazy_deps_ensure_fails(monkeypatch):
"""edge-tts may be importable from PYTHONPATH even when lazy install fails."""
import tools.lazy_deps as lazy_deps
from tools.tts_tool import _import_edge_tts

fake_edge_tts = types.ModuleType("edge_tts")
monkeypatch.setitem(sys.modules, "edge_tts", fake_edge_tts)
monkeypatch.setattr(lazy_deps, "ensure", _make_ensure_failure)

assert _import_edge_tts() is fake_edge_tts


def test_elevenlabs_raw_import_still_runs_when_lazy_deps_ensure_fails(monkeypatch):
"""ElevenLabs may be importable from PYTHONPATH even when lazy install fails."""
import tools.lazy_deps as lazy_deps
from tools.tts_tool import _import_elevenlabs

class FakeElevenLabs:
pass

fake_elevenlabs = types.ModuleType("elevenlabs")
fake_client = types.ModuleType("elevenlabs.client")
setattr(fake_client, "ElevenLabs", FakeElevenLabs)
setattr(fake_elevenlabs, "client", fake_client)
monkeypatch.setitem(sys.modules, "elevenlabs", fake_elevenlabs)
monkeypatch.setitem(sys.modules, "elevenlabs.client", fake_client)
monkeypatch.setattr(lazy_deps, "ensure", _make_ensure_failure)

assert _import_elevenlabs() is FakeElevenLabs


def test_mistral_raw_import_still_runs_when_lazy_deps_ensure_fails(monkeypatch):
"""Mistral uses the same lazy import pattern and should preserve fallback."""
import tools.lazy_deps as lazy_deps
from tools.tts_tool import _import_mistral_client

class FakeMistral:
pass

fake_mistralai = types.ModuleType("mistralai")
fake_client = types.ModuleType("mistralai.client")
setattr(fake_client, "Mistral", FakeMistral)
setattr(fake_mistralai, "client", fake_client)
monkeypatch.setitem(sys.modules, "mistralai", fake_mistralai)
monkeypatch.setitem(sys.modules, "mistralai.client", fake_client)
monkeypatch.setattr(lazy_deps, "ensure", _make_ensure_failure)

assert _import_mistral_client() is FakeMistral


def test_stt_mistral_raw_import_still_runs_when_lazy_deps_ensure_fails(monkeypatch):
"""STT Mistral must fall through to the raw import when lazy install fails.

Regression for the sweeper finding on PR #53289: a non-ImportError from
lazy_deps.ensure("stt.mistral") escaped to _transcribe_mistral()'s outer
error handler before the raw import could see PYTHONPATH-supplied
packages.
"""
import tools.lazy_deps as lazy_deps
import tools.transcription_tools as transcription_tools

class FakeTranscription:
text = "hello world"

class FakeTranscriptions:
@staticmethod
def complete(**_kwargs):
return FakeTranscription()

class FakeAudio:
transcriptions = FakeTranscriptions()

class FakeMistral:
def __init__(self, api_key=None, **_kwargs):
self.audio = FakeAudio()

def __enter__(self):
return self

def __exit__(self, *exc):
return False

fake_mistralai = types.ModuleType("mistralai")
fake_client = types.ModuleType("mistralai.client")
fake_client.Mistral = FakeMistral
fake_mistralai.client = fake_client
monkeypatch.setitem(sys.modules, "mistralai", fake_mistralai)
monkeypatch.setitem(sys.modules, "mistralai.client", fake_client)
monkeypatch.setattr(lazy_deps, "ensure", _make_ensure_failure)
monkeypatch.setattr(
transcription_tools, "get_env_value", lambda _k: "test-key"
)

result = transcription_tools._transcribe_mistral(
__file__, "voxtral-mini-latest"
)

assert result["success"] is True
assert result["transcript"] == "hello world"
4 changes: 4 additions & 0 deletions tools/transcription_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,10 @@ def _transcribe_mistral(file_path: str, model_name: str) -> Dict[str, Any]:
_lazy_ensure("stt.mistral", prompt=False)
except ImportError:
pass
except Exception as e: # FeatureUnavailable or any unexpected error
logger.debug(
"lazy_deps.ensure(stt.mistral) failed: %s. Attempting raw import.", e
)
from mistralai.client import Mistral

with Mistral(api_key=api_key) as client:
Expand Down
22 changes: 15 additions & 7 deletions tools/tts_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ def _import_edge_tts():
except ImportError:
pass
except Exception as e:
raise ImportError(str(e))
logger.debug(
"lazy_deps.ensure(tts.edge) failed: %s. Attempting raw import.", e
)
import edge_tts
return edge_tts

Expand All @@ -101,18 +103,21 @@ def _import_elevenlabs():
Calls :func:`tools.lazy_deps.ensure` first so the SDK gets installed on
demand if the user picked ElevenLabs as their TTS provider but never ran
the post-setup hook (e.g. enabled it by editing config.yaml directly).
Raises ``ImportError`` on lazy-install failure so existing callers'
error-handling paths keep working.
If lazy installation fails, fall through to the raw import so packages
supplied by ``PYTHONPATH`` or a container image layer still work.
"""
try:
from tools.lazy_deps import FeatureUnavailable, ensure
from tools.lazy_deps import ensure
ensure("tts.elevenlabs", prompt=False)
except ImportError:
# lazy_deps module itself missing — fall through to the raw import
# so older code paths still get a clean ImportError.
pass
except Exception as e: # FeatureUnavailable or any unexpected error
raise ImportError(str(e))
logger.debug(
"lazy_deps.ensure(tts.elevenlabs) failed: %s. Attempting raw import.",
e,
)
from elevenlabs.client import ElevenLabs
return ElevenLabs

Expand All @@ -127,15 +132,18 @@ def _import_mistral_client():
Calls :func:`tools.lazy_deps.ensure` first so the ``mistralai`` SDK gets
installed on demand if the user picked Mistral as their STT/TTS provider
but never ran the post-setup hook (e.g. enabled it by editing config.yaml
directly). Mirrors the ElevenLabs lazy-import path.
directly). If lazy installation fails, fall through to the raw import so
packages supplied by ``PYTHONPATH`` or a container image layer still work.
"""
try:
from tools.lazy_deps import ensure
ensure("tts.mistral", prompt=False)
except ImportError:
pass
except Exception as e: # FeatureUnavailable or any unexpected error
raise ImportError(str(e))
logger.debug(
"lazy_deps.ensure(tts.mistral) failed: %s. Attempting raw import.", e
)
from mistralai.client import Mistral
return Mistral

Expand Down