friday-framework-speech 0.1.0a0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- friday_framework_speech-0.1.0a0/.gitignore +24 -0
- friday_framework_speech-0.1.0a0/PKG-INFO +56 -0
- friday_framework_speech-0.1.0a0/README.md +25 -0
- friday_framework_speech-0.1.0a0/pyproject.toml +55 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/__init__.py +44 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/adapters/__init__.py +43 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/adapters/elevenlabs.py +183 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/adapters/picovoice.py +277 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/chunking.py +75 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/config.py +364 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/errors.py +34 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/harness.py +100 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/interfaces.py +71 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/playback.py +245 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/selection.py +149 -0
- friday_framework_speech-0.1.0a0/src/friday_speech/service.py +123 -0
- friday_framework_speech-0.1.0a0/tests_speech/test_chunking.py +18 -0
- friday_framework_speech-0.1.0a0/tests_speech/test_harness.py +54 -0
- friday_framework_speech-0.1.0a0/tests_speech/test_orca_sanitizer.py +13 -0
- friday_framework_speech-0.1.0a0/tests_speech/test_selection.py +36 -0
- friday_framework_speech-0.1.0a0/tests_speech/test_speech_config.py +53 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
.env
|
|
4
|
+
.venv/
|
|
5
|
+
.idea/
|
|
6
|
+
.vscode/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
chroma_db/
|
|
11
|
+
.friday/
|
|
12
|
+
/vector/
|
|
13
|
+
/exports/
|
|
14
|
+
tests/integration/memory/vector/*/
|
|
15
|
+
tests/integration/memory/vector/backups/*/
|
|
16
|
+
tests/integration/memory/vector/reorganization_logs/*
|
|
17
|
+
*.gpickle
|
|
18
|
+
keys.txt
|
|
19
|
+
experiments/*/config/runtime.local.yaml
|
|
20
|
+
experiments/*/artifacts/*
|
|
21
|
+
!experiments/*/artifacts/.gitkeep
|
|
22
|
+
scripts/source-friday-chat-example-env.sh
|
|
23
|
+
scripts/pypi.sh
|
|
24
|
+
/docs/reference/PyPI-Recovery-Codes-cichuck-2026-07-20T15_36_49.709334.txt
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: friday-framework-speech
|
|
3
|
+
Version: 0.1.0a0
|
|
4
|
+
Summary: Speech services (TTS, wake word) for Friday
|
|
5
|
+
Project-URL: Homepage, https://github.com/CIChuck/agent-framework
|
|
6
|
+
Project-URL: Repository, https://github.com/CIChuck/agent-framework
|
|
7
|
+
Project-URL: Issues, https://github.com/CIChuck/agent-framework/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/CIChuck/agent-framework/tree/main/docs
|
|
9
|
+
Project-URL: Source, https://github.com/CIChuck/agent-framework/tree/main/packages/friday-speech
|
|
10
|
+
Author: Friday Team
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
Keywords: assistant,audio,speech,tts,wakeword
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: friday-framework-core==0.1.0a0
|
|
15
|
+
Requires-Dist: pydantic>=2.0.0
|
|
16
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
20
|
+
Provides-Extra: elevenlabs
|
|
21
|
+
Requires-Dist: elevenlabs>=1.0.0; extra == 'elevenlabs'
|
|
22
|
+
Provides-Extra: picovoice
|
|
23
|
+
Requires-Dist: pvcobra>=2.0.0; extra == 'picovoice'
|
|
24
|
+
Requires-Dist: pvleopard>=2.0.0; extra == 'picovoice'
|
|
25
|
+
Requires-Dist: pvorca>=0.1.0; extra == 'picovoice'
|
|
26
|
+
Requires-Dist: pvporcupine>=3.0.0; extra == 'picovoice'
|
|
27
|
+
Requires-Dist: pvrecorder>=1.2.0; extra == 'picovoice'
|
|
28
|
+
Requires-Dist: pvrhino>=2.0.0; extra == 'picovoice'
|
|
29
|
+
Requires-Dist: pvspeaker>=0.1.0; extra == 'picovoice'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Friday Framework Speech
|
|
33
|
+
|
|
34
|
+
Speech service package for the Friday Framework.
|
|
35
|
+
|
|
36
|
+
This package provides speech service abstractions and optional text-to-speech,
|
|
37
|
+
wake-word, recording, playback, and speech-provider integrations.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install friday-framework-speech
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Optional speech providers are available through extras such as `elevenlabs` and
|
|
46
|
+
`picovoice`.
|
|
47
|
+
|
|
48
|
+
## Import Package
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import friday_speech
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Friday Framework Speech
|
|
2
|
+
|
|
3
|
+
Speech service package for the Friday Framework.
|
|
4
|
+
|
|
5
|
+
This package provides speech service abstractions and optional text-to-speech,
|
|
6
|
+
wake-word, recording, playback, and speech-provider integrations.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install friday-framework-speech
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Optional speech providers are available through extras such as `elevenlabs` and
|
|
15
|
+
`picovoice`.
|
|
16
|
+
|
|
17
|
+
## Import Package
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import friday_speech
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## License
|
|
24
|
+
|
|
25
|
+
MIT
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "friday-framework-speech"
|
|
3
|
+
version = "0.1.0a0"
|
|
4
|
+
description = "Speech services (TTS, wake word) for Friday"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [{ name = "Friday Team" }]
|
|
9
|
+
keywords = ["speech", "tts", "wakeword", "audio", "assistant"]
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pydantic>=2.0.0",
|
|
13
|
+
"friday-framework-core==0.1.0a0",
|
|
14
|
+
"pyyaml>=6.0.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/CIChuck/agent-framework"
|
|
19
|
+
Repository = "https://github.com/CIChuck/agent-framework"
|
|
20
|
+
Issues = "https://github.com/CIChuck/agent-framework/issues"
|
|
21
|
+
Documentation = "https://github.com/CIChuck/agent-framework/tree/main/docs"
|
|
22
|
+
Source = "https://github.com/CIChuck/agent-framework/tree/main/packages/friday-speech"
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
elevenlabs = [
|
|
26
|
+
"elevenlabs>=1.0.0",
|
|
27
|
+
]
|
|
28
|
+
picovoice = [
|
|
29
|
+
"pvporcupine>=3.0.0",
|
|
30
|
+
"pvrecorder>=1.2.0",
|
|
31
|
+
"pvorca>=0.1.0",
|
|
32
|
+
"pvspeaker>=0.1.0",
|
|
33
|
+
"pvcobra>=2.0.0",
|
|
34
|
+
"pvleopard>=2.0.0",
|
|
35
|
+
"pvrhino>=2.0.0",
|
|
36
|
+
]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=8.0.0",
|
|
39
|
+
"pytest-asyncio>=0.23.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["hatchling"]
|
|
44
|
+
build-backend = "hatchling.build"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/friday_speech"]
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
testpaths = ["tests_speech"]
|
|
51
|
+
asyncio_mode = "auto"
|
|
52
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
53
|
+
|
|
54
|
+
[tool.uv.sources]
|
|
55
|
+
friday-framework-core = { workspace = true }
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
"""Friday speech package."""
|
|
3
|
+
|
|
4
|
+
from .config import SpeechConfig
|
|
5
|
+
from .errors import (
|
|
6
|
+
AdapterError,
|
|
7
|
+
DependencyError,
|
|
8
|
+
PlaybackError,
|
|
9
|
+
SelectionError,
|
|
10
|
+
SpeechConfigError,
|
|
11
|
+
SpeechError,
|
|
12
|
+
STTError,
|
|
13
|
+
WakeWordError,
|
|
14
|
+
)
|
|
15
|
+
from .harness import SpeechHarness
|
|
16
|
+
from .interfaces import (
|
|
17
|
+
AdapterRegistry,
|
|
18
|
+
AudioFormat,
|
|
19
|
+
AudioStream,
|
|
20
|
+
STTAdapter,
|
|
21
|
+
TTSAdapter,
|
|
22
|
+
WakeWordAdapter,
|
|
23
|
+
)
|
|
24
|
+
from .service import SpeechService
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"AdapterError",
|
|
28
|
+
"AdapterRegistry",
|
|
29
|
+
"AudioFormat",
|
|
30
|
+
"AudioStream",
|
|
31
|
+
"DependencyError",
|
|
32
|
+
"PlaybackError",
|
|
33
|
+
"SelectionError",
|
|
34
|
+
"SpeechConfig",
|
|
35
|
+
"SpeechConfigError",
|
|
36
|
+
"SpeechError",
|
|
37
|
+
"SpeechHarness",
|
|
38
|
+
"SpeechService",
|
|
39
|
+
"STTAdapter",
|
|
40
|
+
"STTError",
|
|
41
|
+
"TTSAdapter",
|
|
42
|
+
"WakeWordAdapter",
|
|
43
|
+
"WakeWordError",
|
|
44
|
+
]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
"""Adapter factory helpers for friday-speech."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from ..config import SpeechConfig, TTSProvider, WakeWordProvider
|
|
7
|
+
from ..errors import AdapterError
|
|
8
|
+
from ..interfaces import STTAdapter, TTSAdapter, WakeWordAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build_tts_adapter(config: SpeechConfig) -> TTSAdapter:
|
|
12
|
+
"""Create a TTS adapter for the configured provider."""
|
|
13
|
+
if config.tts.provider == TTSProvider.ELEVENLABS:
|
|
14
|
+
from .elevenlabs import ElevenLabsAdapter
|
|
15
|
+
|
|
16
|
+
return ElevenLabsAdapter(config.elevenlabs)
|
|
17
|
+
if config.tts.provider == TTSProvider.PICOVOICE:
|
|
18
|
+
from .picovoice import PicovoiceOrcaAdapter
|
|
19
|
+
|
|
20
|
+
return PicovoiceOrcaAdapter(config.picovoice)
|
|
21
|
+
raise AdapterError(f"Unsupported TTS provider: {config.tts.provider}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build_wakeword_adapter(config: SpeechConfig) -> WakeWordAdapter | None:
|
|
25
|
+
"""Create a wake word adapter if enabled."""
|
|
26
|
+
if not config.wakeword.enabled:
|
|
27
|
+
return None
|
|
28
|
+
if config.wakeword.provider == WakeWordProvider.PICOVOICE:
|
|
29
|
+
from .picovoice import PicovoicePorcupineAdapter
|
|
30
|
+
|
|
31
|
+
return PicovoicePorcupineAdapter(config.picovoice)
|
|
32
|
+
raise AdapterError(f"Unsupported wake word provider: {config.wakeword.provider}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_stt_adapter(config: SpeechConfig) -> STTAdapter | None:
|
|
36
|
+
"""Create a speech-to-text adapter if enabled."""
|
|
37
|
+
if not config.stt.enabled:
|
|
38
|
+
return None
|
|
39
|
+
if config.stt.provider.value == "picovoice":
|
|
40
|
+
from .picovoice import PicovoiceLeopardAdapter
|
|
41
|
+
|
|
42
|
+
return PicovoiceLeopardAdapter(config.picovoice)
|
|
43
|
+
raise AdapterError(f"Unsupported STT provider: {config.stt.provider}")
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
"""ElevenLabs TTS adapter (best-effort streaming)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import re
|
|
8
|
+
import threading
|
|
9
|
+
from collections.abc import AsyncIterator, Iterable
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from ..config import ElevenLabsConfig, VoiceSettingsConfig
|
|
13
|
+
from ..errors import AdapterError, DependencyError
|
|
14
|
+
from ..interfaces import AudioFormat, AudioStream, TTSAdapter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ElevenLabsAdapter(TTSAdapter):
|
|
18
|
+
"""ElevenLabs adapter using the official Python SDK when available."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, config: ElevenLabsConfig) -> None:
|
|
21
|
+
self._config = config
|
|
22
|
+
self.output_format = _infer_audio_format(config.output_format)
|
|
23
|
+
self.sample_rate = _infer_sample_rate(config.output_format)
|
|
24
|
+
self.channels = None
|
|
25
|
+
|
|
26
|
+
async def stream(
|
|
27
|
+
self,
|
|
28
|
+
text: str,
|
|
29
|
+
voice_id: str | None,
|
|
30
|
+
settings: VoiceSettingsConfig,
|
|
31
|
+
) -> AudioStream:
|
|
32
|
+
if not self._config.api_key:
|
|
33
|
+
raise AdapterError("ElevenLabs API key is required")
|
|
34
|
+
|
|
35
|
+
elevenlabs = _import_elevenlabs()
|
|
36
|
+
client = _build_client(elevenlabs, self._config.api_key)
|
|
37
|
+
payload = _build_voice_settings(settings)
|
|
38
|
+
|
|
39
|
+
text_to_speech = getattr(client, "text_to_speech", None)
|
|
40
|
+
if text_to_speech is None:
|
|
41
|
+
raise AdapterError("ElevenLabs client missing text_to_speech interface")
|
|
42
|
+
|
|
43
|
+
if hasattr(text_to_speech, "convert_as_stream"):
|
|
44
|
+
sync_stream = text_to_speech.convert_as_stream(
|
|
45
|
+
text=text,
|
|
46
|
+
voice_id=voice_id or self._config.voice_id,
|
|
47
|
+
model_id=self._config.model_id,
|
|
48
|
+
output_format=self._config.output_format,
|
|
49
|
+
voice_settings=payload or None,
|
|
50
|
+
)
|
|
51
|
+
chunks = _async_from_sync_iterator(sync_stream)
|
|
52
|
+
return AudioStream(
|
|
53
|
+
format=self.output_format,
|
|
54
|
+
chunks=chunks,
|
|
55
|
+
sample_rate=self.sample_rate,
|
|
56
|
+
channels=self.channels,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if hasattr(text_to_speech, "convert"):
|
|
60
|
+
audio = await asyncio.to_thread(
|
|
61
|
+
text_to_speech.convert,
|
|
62
|
+
text=text,
|
|
63
|
+
voice_id=voice_id or self._config.voice_id,
|
|
64
|
+
model_id=self._config.model_id,
|
|
65
|
+
output_format=self._config.output_format,
|
|
66
|
+
voice_settings=payload or None,
|
|
67
|
+
)
|
|
68
|
+
chunks = _async_single_chunk(audio)
|
|
69
|
+
return AudioStream(
|
|
70
|
+
format=self.output_format,
|
|
71
|
+
chunks=chunks,
|
|
72
|
+
sample_rate=self.sample_rate,
|
|
73
|
+
channels=self.channels,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
raise AdapterError(
|
|
77
|
+
"Unsupported ElevenLabs SDK: expected text_to_speech.convert[_as_stream]"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _import_elevenlabs() -> Any:
|
|
82
|
+
try:
|
|
83
|
+
import elevenlabs # type: ignore
|
|
84
|
+
|
|
85
|
+
return elevenlabs
|
|
86
|
+
except Exception as exc: # pragma: no cover - dependency gate
|
|
87
|
+
raise DependencyError(
|
|
88
|
+
"ElevenLabs SDK not installed. Install with: pip install elevenlabs"
|
|
89
|
+
) from exc
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _build_client(module: Any, api_key: str) -> Any:
|
|
93
|
+
for path in ("client", None):
|
|
94
|
+
try:
|
|
95
|
+
if path:
|
|
96
|
+
client_mod = __import__(f"elevenlabs.{path}", fromlist=["ElevenLabs"])
|
|
97
|
+
client_cls = getattr(client_mod, "ElevenLabs", None)
|
|
98
|
+
else:
|
|
99
|
+
client_cls = getattr(module, "ElevenLabs", None)
|
|
100
|
+
if client_cls:
|
|
101
|
+
return client_cls(api_key=api_key)
|
|
102
|
+
except Exception:
|
|
103
|
+
continue
|
|
104
|
+
raise AdapterError("Unable to initialize ElevenLabs client")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _build_voice_settings(settings: VoiceSettingsConfig) -> dict[str, Any]:
|
|
108
|
+
payload: dict[str, Any] = {}
|
|
109
|
+
if settings.stability is not None:
|
|
110
|
+
payload["stability"] = settings.stability
|
|
111
|
+
if settings.similarity_boost is not None:
|
|
112
|
+
payload["similarity_boost"] = settings.similarity_boost
|
|
113
|
+
if settings.style is not None:
|
|
114
|
+
payload["style"] = settings.style
|
|
115
|
+
if settings.speed is not None:
|
|
116
|
+
payload["speed"] = settings.speed
|
|
117
|
+
return payload
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _infer_audio_format(output_format: str) -> AudioFormat:
|
|
121
|
+
value = output_format.lower()
|
|
122
|
+
if value.startswith("mp3"):
|
|
123
|
+
return AudioFormat.MP3
|
|
124
|
+
if value.startswith("pcm"):
|
|
125
|
+
return AudioFormat.PCM
|
|
126
|
+
raise AdapterError(f"Unsupported ElevenLabs output_format: {output_format}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _infer_sample_rate(output_format: str) -> int | None:
|
|
130
|
+
match = re.search(r"(\d{4,6})", output_format)
|
|
131
|
+
if not match:
|
|
132
|
+
return None
|
|
133
|
+
try:
|
|
134
|
+
return int(match.group(1))
|
|
135
|
+
except ValueError:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _async_single_chunk(audio: Any) -> AsyncIterator[bytes]:
|
|
140
|
+
async def _generator() -> AsyncIterator[bytes]:
|
|
141
|
+
if isinstance(audio, (bytes, bytearray)):
|
|
142
|
+
yield bytes(audio)
|
|
143
|
+
return
|
|
144
|
+
if isinstance(audio, Iterable):
|
|
145
|
+
for chunk in audio:
|
|
146
|
+
if isinstance(chunk, (bytes, bytearray)):
|
|
147
|
+
yield bytes(chunk)
|
|
148
|
+
else:
|
|
149
|
+
raise AdapterError("ElevenLabs stream yielded non-bytes chunk")
|
|
150
|
+
return
|
|
151
|
+
raise AdapterError("ElevenLabs audio payload is not bytes or iterable")
|
|
152
|
+
|
|
153
|
+
return _generator()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _async_from_sync_iterator(stream: Iterable[Any]) -> AsyncIterator[bytes]:
|
|
157
|
+
async def _generator() -> AsyncIterator[bytes]:
|
|
158
|
+
queue: asyncio.Queue[Any] = asyncio.Queue()
|
|
159
|
+
sentinel = object()
|
|
160
|
+
loop = asyncio.get_running_loop()
|
|
161
|
+
|
|
162
|
+
def _worker() -> None:
|
|
163
|
+
try:
|
|
164
|
+
for chunk in stream:
|
|
165
|
+
loop.call_soon_threadsafe(queue.put_nowait, chunk)
|
|
166
|
+
except Exception as exc:
|
|
167
|
+
loop.call_soon_threadsafe(queue.put_nowait, exc)
|
|
168
|
+
finally:
|
|
169
|
+
loop.call_soon_threadsafe(queue.put_nowait, sentinel)
|
|
170
|
+
|
|
171
|
+
threading.Thread(target=_worker, daemon=True).start()
|
|
172
|
+
|
|
173
|
+
while True:
|
|
174
|
+
item = await queue.get()
|
|
175
|
+
if item is sentinel:
|
|
176
|
+
break
|
|
177
|
+
if isinstance(item, Exception):
|
|
178
|
+
raise item
|
|
179
|
+
if not isinstance(item, (bytes, bytearray)):
|
|
180
|
+
raise AdapterError("ElevenLabs stream yielded non-bytes chunk")
|
|
181
|
+
yield bytes(item)
|
|
182
|
+
|
|
183
|
+
return _generator()
|