feat(voice): add elevenlabs realtime streaming asr
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import queue
|
||||
import time
|
||||
|
||||
import httpx
|
||||
@@ -301,6 +303,102 @@ def test_yandex_buffered_streaming_provider_buffers_pcm_until_finalize():
|
||||
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_yandex_grpc_streaming_provider_returns_partial_and_final():
|
||||
calls: list[dict] = []
|
||||
|
||||
@@ -424,6 +522,10 @@ 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,
|
||||
|
||||
Reference in New Issue
Block a user