diff --git a/ai-hub/app/api/routes/stt.py b/ai-hub/app/api/routes/stt.py index 2072c78..369618f 100644 --- a/ai-hub/app/api/routes/stt.py +++ b/ai-hub/app/api/routes/stt.py @@ -57,11 +57,17 @@ from app.config import settings active_provider = provider_name or prefs.get("active_provider") or system_prefs.get("stt", {}).get("active_provider") or settings.STT_PROVIDER - active_prefs = prefs.get("providers", {}).get(active_provider, {}) + + # M6: Resolve base provider for lookup (e.g. 'google_gemini_personal' -> 'google_gemini') + base_provider = active_provider + if active_provider.endswith("_personal"): + base_provider = active_provider.replace("_personal", "") + + active_prefs = prefs.get("providers", {}).get(base_provider, {}) # --- Fallback to System Settings if personal key is missing --- if not active_prefs or not active_prefs.get("api_key") or "*" in str(active_prefs.get("api_key")): - system_provider_prefs = system_prefs.get("stt", {}).get("providers", {}).get(active_provider, {}) + system_provider_prefs = system_prefs.get("stt", {}).get("providers", {}).get(base_provider, {}) if system_provider_prefs: # Merge but prioritize system key if personal is masked/empty merged = system_provider_prefs.copy() diff --git a/ai-hub/app/api/routes/tts.py b/ai-hub/app/api/routes/tts.py index 4689a42..8572e9e 100644 --- a/ai-hub/app/api/routes/tts.py +++ b/ai-hub/app/api/routes/tts.py @@ -45,11 +45,17 @@ from app.config import settings active_provider = provider_name or prefs.get("active_provider") or system_prefs.get("tts", {}).get("active_provider") or settings.TTS_PROVIDER - active_prefs = prefs.get("providers", {}).get(active_provider, {}) + + # M6: Resolve base provider for lookup (e.g. 'google_gemini_personal' -> 'google_gemini') + base_provider = active_provider + if active_provider.endswith("_personal"): + base_provider = active_provider.replace("_personal", "") + + active_prefs = prefs.get("providers", {}).get(base_provider, {}) # --- Fallback to System Settings if personal key is missing --- if not active_prefs or not active_prefs.get("api_key") or "*" in str(active_prefs.get("api_key")): - system_provider_prefs = system_prefs.get("tts", {}).get("providers", {}).get(active_provider, {}) + system_provider_prefs = system_prefs.get("tts", {}).get("providers", {}).get(base_provider, {}) if system_provider_prefs: # Merge but prioritize system key if personal is masked/empty merged = system_provider_prefs.copy() diff --git a/ai-hub/app/core/providers/stt/gemini.py b/ai-hub/app/core/providers/stt/gemini.py index 9937942..6abfc7a 100644 --- a/ai-hub/app/core/providers/stt/gemini.py +++ b/ai-hub/app/core/providers/stt/gemini.py @@ -6,10 +6,24 @@ from fastapi import HTTPException from app.core.providers.base import STTProvider +import httpx +from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception # Configure logging logger = logging.getLogger(__name__) +def is_retryable_exception(exception): + """Check if the exception is one we should retry on.""" + if isinstance(exception, httpx.TimeoutException): + return True + if isinstance(exception, httpx.NetworkError): + return True + if isinstance(exception, HTTPException): + # Retry on 429 (Too Many Requests) or 5xx (Server Errors) + return exception.status_code == 429 or 500 <= exception.status_code < 600 + return False + + class GoogleSTTProvider(STTProvider): """Concrete STT provider for Google Gemini API using inline audio data.""" @@ -55,6 +69,13 @@ # Default: browsers record as webm return 'audio/webm' + @retry( + retry=retry_if_exception(is_retryable_exception), + wait=wait_exponential(multiplier=1, min=2, max=10), + stop=stop_after_attempt(5), + reraise=True, + before_sleep=lambda retry_state: logger.warning(f"Retrying Gemini STT after error: {retry_state.outcome.exception()}. Attempt {retry_state.attempt_number}") + ) async def transcribe_audio(self, audio_data: bytes) -> str: """Transcribes audio using Gemini's inline_data approach (no Files API needed).""" if not self.api_key: diff --git a/ai-hub/app/core/providers/tts/gemini.py b/ai-hub/app/core/providers/tts/gemini.py index f87fa37..34356ce 100644 --- a/ai-hub/app/core/providers/tts/gemini.py +++ b/ai-hub/app/core/providers/tts/gemini.py @@ -73,11 +73,11 @@ logger.debug(f"GeminiTTSProvider: initialized for {self.model_name} (Vertex={self.is_vertex})") @retry( - retry=retry_if_exception_type((httpx.TimeoutException, httpx.NetworkError)) | retry_if_exception(is_retryable_exception), - stop=stop_after_attempt(3), - wait=wait_exponential(multiplier=1, min=1, max=10), + retry=retry_if_exception(is_retryable_exception), + wait=wait_exponential(multiplier=1, min=2, max=10), + stop=stop_after_attempt(5), reraise=True, - before_sleep=lambda retry_state: logger.warning(f"Retrying Gemini TTS request (attempt {retry_state.attempt_number})...") + before_sleep=lambda retry_state: logger.warning(f"Retrying Gemini TTS after error: {retry_state.outcome.exception()}. Attempt {retry_state.attempt_number}") ) async def generate_speech(self, text: str) -> bytes: logger.info(f"TTS request [model={self.model_name}, vertex={self.is_vertex}]: '{text}'")