596 lines
22 KiB
Python
596 lines
22 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import queue
|
|
import time
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from services.ai_voice_runtime_service.audiosocket import pcm16le_to_wav_bytes
|
|
from services.ai_voice_runtime_service.providers import asr as asr_module
|
|
|
|
|
|
class _DummyResponse:
|
|
def __init__(self, json_payload: dict | None = None) -> None:
|
|
self.content = b"{}"
|
|
self._json_payload = json_payload or {}
|
|
|
|
def raise_for_status(self) -> None:
|
|
return None
|
|
|
|
def json(self) -> dict:
|
|
return self._json_payload
|
|
|
|
|
|
def test_yandex_asr_provider_posts_lpcm_with_tts_credential_fallback(monkeypatch):
|
|
calls: list[dict] = []
|
|
pcm = b"\x01\x00" * 160
|
|
wav_bytes = pcm16le_to_wav_bytes(pcm, sample_rate_hz=8000)
|
|
|
|
class _DummyClient:
|
|
def __init__(self, *, timeout: float) -> None:
|
|
self.timeout = timeout
|
|
|
|
def __enter__(self) -> "_DummyClient":
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc, tb) -> None:
|
|
return None
|
|
|
|
def post(
|
|
self,
|
|
url: str,
|
|
*,
|
|
headers: dict[str, str],
|
|
params: dict[str, str],
|
|
content: bytes,
|
|
) -> _DummyResponse:
|
|
calls.append(
|
|
{
|
|
"url": url,
|
|
"headers": headers,
|
|
"params": params,
|
|
"content": content,
|
|
"timeout": self.timeout,
|
|
}
|
|
)
|
|
return _DummyResponse({"result": "almaty schedule"})
|
|
|
|
monkeypatch.delenv("AI_VOICE_ASR_YANDEX_API_KEY", raising=False)
|
|
monkeypatch.delenv("AI_VOICE_ASR_YANDEX_IAM_TOKEN", raising=False)
|
|
monkeypatch.setenv("AI_VOICE_TTS_YANDEX_API_KEY", "tts-yandex-key")
|
|
monkeypatch.setenv("AI_VOICE_ASR_YANDEX_API_BASE", "https://stt.example.test")
|
|
monkeypatch.setenv("AI_VOICE_ASR_YANDEX_FOLDER_ID", "folder-test")
|
|
monkeypatch.setenv("AI_VOICE_ASR_YANDEX_TIMEOUT_SECONDS", "5")
|
|
monkeypatch.setattr(asr_module.httpx, "Client", _DummyClient)
|
|
|
|
provider = asr_module.YandexSpeechKitASRProvider()
|
|
result = provider.transcribe(wav_bytes, language_hint="ru")
|
|
|
|
assert result.text == "almaty schedule"
|
|
assert result.language == "ru-RU"
|
|
assert len(calls) == 1
|
|
assert calls[0]["url"] == "https://stt.example.test/speech/v1/stt:recognize"
|
|
assert calls[0]["headers"]["Authorization"] == "Api-Key tts-yandex-key"
|
|
assert calls[0]["headers"]["Content-Type"] == "application/octet-stream"
|
|
assert calls[0]["params"]["lang"] == "ru-RU"
|
|
assert calls[0]["params"]["format"] == "lpcm"
|
|
assert calls[0]["params"]["sampleRateHertz"] == "8000"
|
|
assert calls[0]["params"]["topic"] == "general"
|
|
assert calls[0]["params"]["folderId"] == "folder-test"
|
|
assert calls[0]["content"] == pcm
|
|
assert calls[0]["timeout"] == 5.0
|
|
|
|
|
|
def test_elevenlabs_asr_provider_posts_wav_with_tts_credential_fallback(monkeypatch):
|
|
calls: list[dict] = []
|
|
pcm = b"\x01\x00" * 160
|
|
wav_bytes = pcm16le_to_wav_bytes(pcm, sample_rate_hz=8000)
|
|
|
|
class _DummyClient:
|
|
def __init__(self, *, timeout: float) -> None:
|
|
self.timeout = timeout
|
|
|
|
def __enter__(self) -> "_DummyClient":
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc, tb) -> None:
|
|
return None
|
|
|
|
def post(
|
|
self,
|
|
url: str,
|
|
*,
|
|
headers: dict[str, str],
|
|
data: dict[str, str],
|
|
files: dict[str, tuple[str, bytes, str]],
|
|
) -> _DummyResponse:
|
|
calls.append(
|
|
{
|
|
"url": url,
|
|
"headers": headers,
|
|
"data": data,
|
|
"files": files,
|
|
"timeout": self.timeout,
|
|
}
|
|
)
|
|
return _DummyResponse({"text": "schedule", "language_code": "rus"})
|
|
|
|
monkeypatch.delenv("AI_VOICE_ASR_ELEVENLABS_API_KEY", raising=False)
|
|
monkeypatch.setenv("AI_VOICE_TTS_ELEVENLABS_API_KEY", "tts-elevenlabs-key")
|
|
monkeypatch.setenv("AI_VOICE_ASR_ELEVENLABS_API_BASE", "https://api.elevenlabs.example")
|
|
monkeypatch.setenv("AI_VOICE_ASR_ELEVENLABS_MODEL_ID", "scribe_v1")
|
|
monkeypatch.setattr(asr_module.httpx, "Client", _DummyClient)
|
|
|
|
provider = asr_module.ElevenLabsASRProvider()
|
|
result = provider.transcribe(wav_bytes, language_hint="ru")
|
|
|
|
assert result.text == "schedule"
|
|
assert result.language == "ru"
|
|
assert len(calls) == 1
|
|
assert calls[0]["url"] == "https://api.elevenlabs.example/v1/speech-to-text"
|
|
assert calls[0]["headers"]["xi-api-key"] == "tts-elevenlabs-key"
|
|
assert calls[0]["data"]["model_id"] == "scribe_v1"
|
|
assert calls[0]["data"]["language_code"] == "rus"
|
|
assert calls[0]["files"]["file"][0] == "turn.wav"
|
|
assert calls[0]["files"]["file"][2] == "audio/wav"
|
|
assert calls[0]["timeout"] == 20.0
|
|
|
|
|
|
def test_elevenlabs_asr_provider_surfaces_http_auth_errors(monkeypatch):
|
|
request = httpx.Request("POST", "https://api.elevenlabs.example/v1/speech-to-text")
|
|
response = httpx.Response(401, request=request)
|
|
|
|
class _DummyClient:
|
|
def __init__(self, *, timeout: float) -> None:
|
|
self.timeout = timeout
|
|
|
|
def __enter__(self) -> "_DummyClient":
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc, tb) -> None:
|
|
return None
|
|
|
|
def post(
|
|
self,
|
|
url: str,
|
|
*,
|
|
headers: dict[str, str],
|
|
data: dict[str, str],
|
|
files: dict[str, tuple[str, bytes, str]],
|
|
) -> _DummyResponse:
|
|
del url, headers, data, files
|
|
class _FailingResponse(_DummyResponse):
|
|
def raise_for_status(self_nonlocal) -> None:
|
|
raise httpx.HTTPStatusError("401 Unauthorized", request=request, response=response)
|
|
|
|
return _FailingResponse()
|
|
|
|
monkeypatch.setenv("AI_VOICE_ASR_ELEVENLABS_API_KEY", "asr-elevenlabs-key")
|
|
monkeypatch.setattr(asr_module.httpx, "Client", _DummyClient)
|
|
|
|
provider = asr_module.ElevenLabsASRProvider()
|
|
|
|
with pytest.raises(httpx.HTTPStatusError):
|
|
provider.transcribe(b"\x01\x00" * 160, language_hint="ru")
|
|
|
|
|
|
def test_yandex_asr_provider_uses_iam_token(monkeypatch):
|
|
calls: list[dict] = []
|
|
|
|
class _DummyClient:
|
|
def __init__(self, *, timeout: float) -> None:
|
|
self.timeout = timeout
|
|
|
|
def __enter__(self) -> "_DummyClient":
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc, tb) -> None:
|
|
return None
|
|
|
|
def post(
|
|
self,
|
|
url: str,
|
|
*,
|
|
headers: dict[str, str],
|
|
params: dict[str, str],
|
|
content: bytes,
|
|
) -> _DummyResponse:
|
|
calls.append({"url": url, "headers": headers, "params": params, "content": content})
|
|
return _DummyResponse({"result": "operator"})
|
|
|
|
monkeypatch.delenv("AI_VOICE_ASR_YANDEX_API_KEY", raising=False)
|
|
monkeypatch.delenv("AI_VOICE_TTS_YANDEX_API_KEY", raising=False)
|
|
monkeypatch.setenv("AI_VOICE_ASR_YANDEX_IAM_TOKEN", "iam-token")
|
|
monkeypatch.setenv("AI_VOICE_ASR_YANDEX_API_BASE", "https://stt.example.test")
|
|
monkeypatch.setattr(asr_module.httpx, "Client", _DummyClient)
|
|
|
|
provider = asr_module.YandexSpeechKitASRProvider()
|
|
result = provider.transcribe(b"\x02\x00" * 80, language_hint="kk")
|
|
|
|
assert result.text == "operator"
|
|
assert result.language == "kk-KZ"
|
|
assert calls[0]["headers"]["Authorization"] == "Bearer iam-token"
|
|
assert calls[0]["params"]["lang"] == "kk-KZ"
|
|
|
|
|
|
def test_yandex_asr_provider_supports_kz_v3_async_rest(monkeypatch):
|
|
calls: list[dict] = []
|
|
|
|
class _DummyClient:
|
|
def __init__(self, *, timeout: float) -> None:
|
|
self.timeout = timeout
|
|
|
|
def __enter__(self) -> "_DummyClient":
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc, tb) -> None:
|
|
return None
|
|
|
|
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": False})
|
|
|
|
def get(
|
|
self,
|
|
url: str,
|
|
*,
|
|
headers: dict[str, str],
|
|
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": {
|
|
"normalizedText": {
|
|
"alternatives": [
|
|
{
|
|
"text": "almaty schedule",
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
)
|
|
|
|
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)
|
|
|
|
provider = asr_module.YandexSpeechKitASRProvider()
|
|
result = provider.transcribe(b"\x03\x00" * 80, language_hint="ru")
|
|
|
|
assert result.text == "almaty schedule"
|
|
assert result.language == "ru-RU"
|
|
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://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():
|
|
calls: list[dict] = []
|
|
|
|
class _FakeYandexASR:
|
|
def transcribe(self, audio_bytes: bytes, *, language_hint: str | None = None):
|
|
calls.append({"audio_bytes": audio_bytes, "language_hint": language_hint})
|
|
return asr_module.ASRTranscription(text="need schedule", language=language_hint, confidence=None)
|
|
|
|
provider = asr_module.YandexSpeechKitBufferedStreamingASRProvider(asr_provider=_FakeYandexASR()) # type: ignore[arg-type]
|
|
stream_id = provider.open_stream("session-1", language_hint="ru")
|
|
|
|
provider.push_pcm(stream_id, b"\x10\x00")
|
|
provider.push_pcm(stream_id, b"\x20\x00")
|
|
|
|
assert provider.poll_partial(stream_id) is None
|
|
|
|
result = provider.finalize(stream_id)
|
|
provider.close_stream(stream_id)
|
|
|
|
assert result.text == "need schedule"
|
|
assert calls == [{"audio_bytes": b"\x10\x00\x20\x00", "language_hint": "ru"}]
|
|
|
|
|
|
def test_elevenlabs_realtime_streaming_provider_returns_partial_and_final():
|
|
calls: list[dict] = []
|
|
|
|
class _FakeRealtimeWebSocket:
|
|
def __init__(self) -> None:
|
|
self.sent_payloads: list[dict] = []
|
|
self.incoming: queue.Queue[str | None] = queue.Queue()
|
|
self.incoming.put(
|
|
json.dumps(
|
|
{
|
|
"message_type": "session_started",
|
|
"session_id": "session-test",
|
|
"config": {"sample_rate": 16000, "audio_format": "pcm_16000"},
|
|
}
|
|
)
|
|
)
|
|
|
|
def send(self, raw_payload: str) -> None:
|
|
payload = json.loads(raw_payload)
|
|
self.sent_payloads.append(payload)
|
|
if payload.get("commit"):
|
|
self.incoming.put(
|
|
json.dumps(
|
|
{
|
|
"message_type": "committed_transcript",
|
|
"text": "нужно узнать график работы",
|
|
"language_code": "ru",
|
|
}
|
|
)
|
|
)
|
|
else:
|
|
self.incoming.put(
|
|
json.dumps(
|
|
{
|
|
"message_type": "partial_transcript",
|
|
"text": "нужно узнать график",
|
|
"language_code": "ru",
|
|
}
|
|
)
|
|
)
|
|
|
|
def recv(self) -> str:
|
|
item = self.incoming.get(timeout=1)
|
|
if item is None:
|
|
raise RuntimeError("closed")
|
|
return item
|
|
|
|
def close(self) -> None:
|
|
self.incoming.put(None)
|
|
|
|
websocket = _FakeRealtimeWebSocket()
|
|
|
|
def _factory(url: str, *, header: list[str], timeout: float):
|
|
calls.append({"url": url, "header": header, "timeout": timeout})
|
|
return websocket
|
|
|
|
provider = asr_module.ElevenLabsRealtimeStreamingASRProvider(
|
|
api_base="https://api.elevenlabs.example",
|
|
api_key="asr-key",
|
|
timeout_seconds=3,
|
|
finalize_timeout_seconds=1,
|
|
websocket_factory=_factory,
|
|
)
|
|
stream_id = provider.open_stream("session-1", language_hint="ru")
|
|
provider.push_pcm(stream_id, b"\x01\x00" * 160)
|
|
|
|
partial = None
|
|
for _ in range(20):
|
|
partial = provider.poll_partial(stream_id)
|
|
if partial is not None:
|
|
break
|
|
time.sleep(0.02)
|
|
|
|
assert partial is not None
|
|
assert partial.text == "нужно узнать график"
|
|
assert partial.is_final is False
|
|
|
|
final = provider.finalize(stream_id)
|
|
provider.close_stream(stream_id)
|
|
|
|
assert final.text == "нужно узнать график работы"
|
|
assert final.language == "ru"
|
|
assert calls[0]["url"].startswith("wss://api.elevenlabs.example/v1/speech-to-text/realtime?")
|
|
assert "model_id=scribe_v2_realtime" in calls[0]["url"]
|
|
assert "audio_format=pcm_16000" in calls[0]["url"]
|
|
assert "language_code=ru" in calls[0]["url"]
|
|
assert calls[0]["header"] == ["xi-api-key: asr-key"]
|
|
assert calls[0]["timeout"] == 3.0
|
|
assert len(websocket.sent_payloads) == 2
|
|
assert websocket.sent_payloads[0]["message_type"] == "input_audio_chunk"
|
|
assert websocket.sent_payloads[0]["commit"] is False
|
|
assert websocket.sent_payloads[0]["sample_rate"] == 16000
|
|
assert websocket.sent_payloads[0]["audio_base_64"]
|
|
assert websocket.sent_payloads[1]["commit"] is True
|
|
|
|
|
|
def test_elevenlabs_realtime_streaming_provider_finalizes_from_stable_partial_on_timeout():
|
|
class _FakeRealtimeWebSocket:
|
|
def __init__(self) -> None:
|
|
self.sent_payloads: list[dict] = []
|
|
self.incoming: queue.Queue[str | None] = queue.Queue()
|
|
|
|
def send(self, raw_payload: str) -> None:
|
|
payload = json.loads(raw_payload)
|
|
self.sent_payloads.append(payload)
|
|
if not payload.get("commit"):
|
|
partial = json.dumps(
|
|
{
|
|
"message_type": "partial_transcript",
|
|
"text": "need schedule",
|
|
"language_code": "ru",
|
|
}
|
|
)
|
|
self.incoming.put(partial)
|
|
self.incoming.put(partial)
|
|
|
|
def recv(self) -> str:
|
|
item = self.incoming.get(timeout=1)
|
|
if item is None:
|
|
raise RuntimeError("closed")
|
|
return item
|
|
|
|
def close(self) -> None:
|
|
self.incoming.put(None)
|
|
|
|
websocket = _FakeRealtimeWebSocket()
|
|
|
|
provider = asr_module.ElevenLabsRealtimeStreamingASRProvider(
|
|
api_base="https://api.elevenlabs.example",
|
|
api_key="asr-key",
|
|
timeout_seconds=1,
|
|
finalize_timeout_seconds=0.25,
|
|
websocket_factory=lambda url, *, header, timeout: websocket,
|
|
)
|
|
stream_id = provider.open_stream("session-1", language_hint="ru")
|
|
provider.push_pcm(stream_id, b"\x01\x00" * 160)
|
|
|
|
partial = None
|
|
for _ in range(20):
|
|
partial = provider.poll_partial(stream_id)
|
|
if partial is not None and partial.is_stable:
|
|
break
|
|
time.sleep(0.02)
|
|
|
|
assert partial is not None
|
|
assert partial.is_stable is True
|
|
|
|
final = provider.finalize(stream_id)
|
|
provider.close_stream(stream_id)
|
|
|
|
assert final.text == "need schedule"
|
|
assert final.language == "ru"
|
|
assert websocket.sent_payloads[-1]["commit"] is True
|
|
|
|
|
|
def test_yandex_grpc_streaming_provider_returns_partial_and_final():
|
|
calls: list[dict] = []
|
|
|
|
class _Message:
|
|
def __init__(self, **kwargs) -> None:
|
|
self.__dict__.update(kwargs)
|
|
|
|
def HasField(self, name: str) -> bool:
|
|
return getattr(self, name, None) is not None
|
|
|
|
class _RawAudio(_Message):
|
|
LINEAR16_PCM = 1
|
|
|
|
class _TextNormalizationOptions(_Message):
|
|
TEXT_NORMALIZATION_ENABLED = 1
|
|
PHONE_FORMATTING_MODE_DISABLED = 1
|
|
|
|
class _LanguageRestrictionOptions(_Message):
|
|
WHITELIST = 1
|
|
|
|
class _RecognitionModelOptions(_Message):
|
|
REAL_TIME = 1
|
|
|
|
class _DefaultEouClassifier(_Message):
|
|
HIGH = 2
|
|
|
|
class _FakeSttPb2:
|
|
RawAudio = _RawAudio
|
|
AudioFormatOptions = _Message
|
|
TextNormalizationOptions = _TextNormalizationOptions
|
|
LanguageRestrictionOptions = _LanguageRestrictionOptions
|
|
RecognitionModelOptions = _RecognitionModelOptions
|
|
DefaultEouClassifier = _DefaultEouClassifier
|
|
EouClassifierOptions = _Message
|
|
StreamingOptions = _Message
|
|
StreamingRequest = _Message
|
|
AudioChunk = _Message
|
|
Eou = _Message
|
|
|
|
class _FakeChannel:
|
|
def close(self) -> None:
|
|
calls.append({"method": "close"})
|
|
|
|
class _FakeGrpc:
|
|
@staticmethod
|
|
def ssl_channel_credentials():
|
|
return "ssl"
|
|
|
|
@staticmethod
|
|
def secure_channel(target: str, credentials):
|
|
calls.append({"method": "secure_channel", "target": target, "credentials": credentials})
|
|
return _FakeChannel()
|
|
|
|
class _RecognizerStub:
|
|
def __init__(self, channel) -> None:
|
|
calls.append({"method": "stub_init", "channel": channel})
|
|
|
|
def RecognizeStreaming(self, request_iterator, *, metadata, timeout):
|
|
calls.append({"method": "recognize", "metadata": metadata, "timeout": timeout})
|
|
for request in request_iterator:
|
|
calls.append({"method": "request", "request": request})
|
|
if getattr(request, "chunk", None) is not None:
|
|
yield _Message(
|
|
partial=_Message(
|
|
alternatives=[
|
|
_Message(text="need schedule", confidence=0.7, languages=[]),
|
|
]
|
|
)
|
|
)
|
|
continue
|
|
if getattr(request, "eou", None) is not None:
|
|
yield _Message(
|
|
final_refinement=_Message(
|
|
normalized_text=_Message(
|
|
alternatives=[
|
|
_Message(text="need schedule in almaty", confidence=0.91, languages=[]),
|
|
]
|
|
)
|
|
)
|
|
)
|
|
return
|
|
|
|
class _FakeGrpcPb2:
|
|
RecognizerStub = _RecognizerStub
|
|
|
|
provider = asr_module.YandexSpeechKitGrpcStreamingASRProvider(
|
|
api_key="asr-key",
|
|
grpc_target="stt.test:443",
|
|
timeout_seconds=3,
|
|
grpc_module=_FakeGrpc,
|
|
stt_pb2_module=_FakeSttPb2,
|
|
stt_service_pb2_grpc_module=_FakeGrpcPb2,
|
|
)
|
|
stream_id = provider.open_stream("session-1", language_hint="ru")
|
|
provider.push_pcm(stream_id, b"\x10\x00\x20\x00")
|
|
|
|
partial = None
|
|
for _ in range(20):
|
|
partial = provider.poll_partial(stream_id)
|
|
if partial is not None:
|
|
break
|
|
time.sleep(0.02)
|
|
|
|
assert partial is not None
|
|
assert partial.text == "need schedule"
|
|
assert partial.is_final is False
|
|
|
|
final = provider.finalize(stream_id)
|
|
provider.close_stream(stream_id)
|
|
|
|
assert final.text == "need schedule in almaty"
|
|
recognize_call = next(call for call in calls if call.get("method") == "recognize")
|
|
assert ("authorization", "Api-Key asr-key") in recognize_call["metadata"]
|
|
requests = [call["request"] for call in calls if call.get("method") == "request"]
|
|
assert getattr(requests[0], "session_options", None) is not None
|
|
assert getattr(requests[1], "chunk", None).data == b"\x10\x00\x20\x00"
|
|
assert getattr(requests[-1], "eou", None) is not None
|
|
|
|
|
|
def test_yandex_asr_builders():
|
|
assert isinstance(asr_module.build_asr_provider("elevenlabs"), asr_module.ElevenLabsASRProvider)
|
|
assert isinstance(asr_module.build_asr_provider("yandex"), asr_module.YandexSpeechKitASRProvider)
|
|
assert isinstance(asr_module.build_asr_provider("speechkit"), asr_module.YandexSpeechKitASRProvider)
|
|
assert isinstance(
|
|
asr_module.build_streaming_asr_provider("elevenlabs_realtime"),
|
|
asr_module.ElevenLabsRealtimeStreamingASRProvider,
|
|
)
|
|
assert isinstance(
|
|
asr_module.build_streaming_asr_provider("yandex_speechkit"),
|
|
asr_module.YandexSpeechKitGrpcStreamingASRProvider,
|
|
)
|
|
assert isinstance(
|
|
asr_module.build_streaming_asr_provider("yandex_speechkit_buffered"),
|
|
asr_module.YandexSpeechKitBufferedStreamingASRProvider,
|
|
)
|