From ac2269b6a4f0fe0c646604fad19b6bdc13e344a5 Mon Sep 17 00:00:00 2001 From: Yera All Date: Fri, 17 Apr 2026 13:39:26 +0500 Subject: [PATCH] fix(voice): correct yandex asr endpoints --- .env.example | 5 ++-- .../ai_voice_runtime_service/providers/asr.py | 25 +++++++++++++------ tests/test_ai_voice_asr_provider.py | 13 +++++++--- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/.env.example b/.env.example index 66076ed..3355d3d 100644 --- a/.env.example +++ b/.env.example @@ -76,9 +76,10 @@ AI_VOICE_AUDIOSOCKET_ENABLED=0 AI_VOICE_AUDIOSOCKET_HOST=0.0.0.0 AI_VOICE_AUDIOSOCKET_PORT=9019 AI_VOICE_ASR_MODEL=gpt-4o-mini-transcribe -AI_VOICE_ASR_YANDEX_API_BASE=https://stt.api.ml.yandexcloud.kz +AI_VOICE_ASR_YANDEX_API_BASE=https://stt.api.cloud.yandex.net +AI_VOICE_ASR_YANDEX_OPERATIONS_BASE=https://operation.api.cloud.yandex.net AI_VOICE_ASR_YANDEX_API_VERSION=auto -AI_VOICE_ASR_YANDEX_GRPC_TARGET=stt.api.ml.yandexcloud.kz:443 +AI_VOICE_ASR_YANDEX_GRPC_TARGET=stt.api.cloud.yandex.net:443 AI_VOICE_ASR_YANDEX_API_KEY= AI_VOICE_ASR_YANDEX_IAM_TOKEN= AI_VOICE_ASR_YANDEX_FOLDER_ID= diff --git a/services/ai_voice_runtime_service/providers/asr.py b/services/ai_voice_runtime_service/providers/asr.py index c98cf98..7183e85 100644 --- a/services/ai_voice_runtime_service/providers/asr.py +++ b/services/ai_voice_runtime_service/providers/asr.py @@ -39,13 +39,20 @@ def _openai_asr_model() -> str: def _yandex_asr_api_base() -> str: explicit = os.getenv("AI_VOICE_ASR_YANDEX_API_BASE", "").strip() if explicit: - return explicit.rstrip("/") - tts_base = os.getenv("AI_VOICE_TTS_YANDEX_API_BASE", "https://tts.api.ml.yandexcloud.kz").strip().lower() - if "yandexcloud.kz" in tts_base: - return "https://stt.api.ml.yandexcloud.kz" + normalized = explicit.rstrip("/") + if "stt.api.ml.yandexcloud.kz" in normalized.lower(): + return "https://stt.api.cloud.yandex.net" + return normalized return "https://stt.api.cloud.yandex.net" +def _yandex_asr_operations_base() -> str: + explicit = os.getenv("AI_VOICE_ASR_YANDEX_OPERATIONS_BASE", "").strip() + if explicit: + return explicit.rstrip("/") + return "https://operation.api.cloud.yandex.net" + + def _yandex_asr_api_key() -> str: return ( os.getenv("AI_VOICE_ASR_YANDEX_API_KEY", "").strip() @@ -99,7 +106,8 @@ def _yandex_asr_api_version(api_base: str) -> str: return "v1" if raw in {"v3", "v3_async", "async"}: return "v3" - if "yandexcloud.kz" in str(api_base or "").lower(): + normalized_base = str(api_base or "").lower() + if "stt.api.cloud.yandex.net" in normalized_base or "stt.api.ml.yandexcloud.kz" in normalized_base: return "v3" return "v1" @@ -116,9 +124,9 @@ def _yandex_asr_poll_interval_seconds() -> float: def _yandex_asr_grpc_target(api_base: str) -> str: explicit = os.getenv("AI_VOICE_ASR_YANDEX_GRPC_TARGET", "").strip() if explicit: + if "stt.api.ml.yandexcloud.kz" in explicit.lower(): + return "stt.api.cloud.yandex.net:443" return explicit - if "yandexcloud.kz" in str(api_base or "").lower(): - return "stt.api.ml.yandexcloud.kz:443" return "stt.api.cloud.yandex.net:443" @@ -296,6 +304,7 @@ class YandexSpeechKitASRProvider(ASRProvider): self._api_key = str(api_key if api_key is not None else _yandex_asr_api_key()).strip() self._iam_token = str(iam_token if iam_token is not None else _yandex_asr_iam_token()).strip() self._folder_id = str(folder_id if folder_id is not None else _yandex_asr_folder_id()).strip() + self._operations_base = _yandex_asr_operations_base() self._timeout_seconds = max( float(timeout_seconds if timeout_seconds is not None else _yandex_asr_timeout_seconds()), 3.0, @@ -417,7 +426,7 @@ class YandexSpeechKitASRProvider(ASRProvider): raise TimeoutError("Yandex ASR v3 recognition timed out") time.sleep(self._poll_interval_seconds) operation_response = client.get( - f"{self._api_base}/operations/{operation_id}", + f"{self._operations_base}/operations/{operation_id}", headers=headers, ) operation_response.raise_for_status() diff --git a/tests/test_ai_voice_asr_provider.py b/tests/test_ai_voice_asr_provider.py index 2c5be41..9f24585 100644 --- a/tests/test_ai_voice_asr_provider.py +++ b/tests/test_ai_voice_asr_provider.py @@ -132,7 +132,7 @@ def test_yandex_asr_provider_supports_kz_v3_async_rest(monkeypatch): def post(self, url: str, *, headers: dict[str, str], json: dict) -> _DummyResponse: calls.append({"method": "POST", "url": url, "headers": headers, "json": json}) - return _DummyResponse({"id": "operation-1", "done": True}) + return _DummyResponse({"id": "operation-1", "done": False}) def get( self, @@ -142,6 +142,8 @@ def test_yandex_asr_provider_supports_kz_v3_async_rest(monkeypatch): params: dict[str, str] | None = None, ) -> _DummyResponse: calls.append({"method": "GET", "url": url, "headers": headers, "params": params or {}}) + if "operation.api.cloud.yandex.net/operations/" in url: + return _DummyResponse({"id": "operation-1", "done": True}) return _DummyResponse( { "finalRefinement": { @@ -158,6 +160,7 @@ def test_yandex_asr_provider_supports_kz_v3_async_rest(monkeypatch): monkeypatch.setenv("AI_VOICE_ASR_YANDEX_API_KEY", "asr-key") monkeypatch.setenv("AI_VOICE_ASR_YANDEX_API_BASE", "https://stt.api.ml.yandexcloud.kz") + monkeypatch.delenv("AI_VOICE_ASR_YANDEX_OPERATIONS_BASE", raising=False) monkeypatch.setenv("AI_VOICE_ASR_YANDEX_FOLDER_ID", "folder-test") monkeypatch.setenv("AI_VOICE_ASR_YANDEX_POLL_INTERVAL_SECONDS", "0.05") monkeypatch.setattr(asr_module.httpx, "Client", _DummyClient) @@ -167,14 +170,16 @@ def test_yandex_asr_provider_supports_kz_v3_async_rest(monkeypatch): assert result.text == "almaty schedule" assert result.language == "ru-RU" - assert calls[0]["url"] == "https://stt.api.ml.yandexcloud.kz/stt/v3/recognizeFileAsync" + assert calls[0]["url"] == "https://stt.api.cloud.yandex.net/stt/v3/recognizeFileAsync" assert calls[0]["headers"]["Authorization"] == "Api-Key asr-key" assert calls[0]["headers"]["Content-Type"] == "application/json" assert calls[0]["headers"]["x-folder-id"] == "folder-test" assert calls[0]["json"]["recognitionModel"]["audioFormat"]["rawAudio"]["sampleRateHertz"] == "8000" assert calls[0]["json"]["recognitionModel"]["languageRestriction"]["languageCode"] == ["ru-RU"] - assert calls[1]["url"] == "https://stt.api.ml.yandexcloud.kz/stt/v3/getRecognition" - assert calls[1]["params"] == {"operationId": "operation-1"} + assert calls[1]["url"] == "https://operation.api.cloud.yandex.net/operations/operation-1" + assert calls[1]["params"] == {} + assert calls[2]["url"] == "https://stt.api.cloud.yandex.net/stt/v3/getRecognition" + assert calls[2]["params"] == {"operationId": "operation-1"} def test_yandex_buffered_streaming_provider_buffers_pcm_until_finalize():