From 51178b8a06d536803cbc63871104f88a6ea54dba Mon Sep 17 00:00:00 2001 From: John Date: Fri, 25 Jul 2025 17:44:43 -0700 Subject: [PATCH 1/4] Was benchmarking multiple audio providers --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 4 +- fastloop/integrations/conversation.py | 189 +++++++ fastloop/integrations/plugins/Deepgram.py | 87 +++ fastloop/integrations/plugins/ElevenLabs.py | 52 ++ fastloop/integrations/plugins/SpeechMatics.py | 54 ++ fastloop/integrations/plugins/utils.py | 15 + fastloop/types.py | 1 + pyproject.toml | 14 +- uv.lock | 526 ++++++++++++++---- 10 files changed, 845 insertions(+), 97 deletions(-) create mode 100644 .DS_Store create mode 100644 fastloop/integrations/conversation.py create mode 100644 fastloop/integrations/plugins/Deepgram.py create mode 100644 fastloop/integrations/plugins/ElevenLabs.py create mode 100644 fastloop/integrations/plugins/SpeechMatics.py create mode 100644 fastloop/integrations/plugins/utils.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 IntegrationType: + return IntegrationType.CONVERSATION + + def register(self, fastloop: "FastLoop", loop_name: str) -> None: + fastloop.register_events( + [ + StartConversationEvent, + OnUserAudioDataEvent, + GenerateResponseEvent, + GenerationationInterruptedEvent, + AudioStreamResponseEvent, + ] + ) + + self._fastloop: FastLoop = fastloop + self._fastloop.app.add_api_websocket_route( + path=f"/{loop_name}/conversation/start", + endpoint=self._handle_start_conversation, + ) + self.loop_name: str = loop_name + + + async def _handle_websocket_event(self, websocket: WebSocket, request_id: str): + audio_buffer: list[bytes] = [] + while True: + try: + data = await websocket.receive_json() + match data.get("type"): + case "on_user_audio_data": + audio_data = base64.b64decode(data.get("audio").encode("utf-8")) + audio_buffer.append(audio_data) + self.queue.put_nowait(OnUserAudioDataEvent( + loop_id=request_id or None, + audio=audio_data, + )) + case "on_user_stop_speaking": + self.queue.put_nowait(GenerateResponseEvent( + loop_id=request_id or None, + )) + case _: + logger.error(f"Unknown event: {data}") + continue + except WebSocketDisconnect: + logger.info("Client disconnected") + self.is_running = False + break + except Exception as e: + logger.error(f"Error receiving data: {e}") + self.is_running = False + break + + async def _handle_start_conversation(self, websocket: WebSocket ): + await websocket.accept() + self.is_running = True + request_id = str(uuid.uuid4()) + + stt_manager = SpeechmaticsSpeechToTextManager(request_id, websocket) + await stt_manager.start() + print("starting conversation") + asyncio.create_task(self._handle_websocket_event(websocket, request_id)) + # llm_manager = LLMManager(self._fastloop, request_id) + # stt_task = self.executor.submit(stt_manager.on_voice_stream, websocket) + # tts_manager = TextToSpeechManager(self._fastloop, request_id) + while self.is_running: + loop_event: Any = await self.queue.get() + + if isinstance(loop_event, OnUserAudioDataEvent): + await stt_manager.send_audio(loop_event.audio) + elif isinstance(loop_event, GenerateResponseEvent): + pass + # llm_manager.generate_response(stt_manager.get_text()) + elif isinstance(loop_event, GenerationationInterruptedEvent): + pass + elif isinstance(loop_event, AudioStreamResponseEvent): + pass + # case "on_agent_audio_data": + # pass + # case "on_agent_stop_speaking": + # pass + # case "on_user_generation_interrupted": + # pass + # case "on_generation_completed": + # pass + # case "on_error": + # pass + + # # loop_state: LoopState = await loop_event_handler(mapped_request) + # mapped_request: dict[str, Any] = loop_event.to_dict() if loop_event else {} + + # loop_event_handler = self._fastloop.loop_event_handlers.get(self.loop_name) + # if not loop_event_handler: + # continue + + # loop: LoopState = await loop_event_handler(mapped_request) + # if loop.loop_id: + # await self._fastloop.state_manager.set_loop_mapping( + # f"conversation:{loop.loop_id}", loop.loop_id + # ) + + + + # if loop_state.loop_id: + # await self._fastloop.state_manager.set_loop_mapping( + # f"conversation:{loop_state.loop_id}", loop_state.loop_id + # ) + + + async def emit(self, event: LoopEvent): + pass + + def events(self) -> list[Any]: + return [StartConversationEvent] + + +class TextToSpeechManager: + def __init__(self, fastloop: "FastLoop", request_id: str): + self.fastloop = fastloop + + def synthesize(self, text: str): + pass + +class LLMManager: + def __init__(self, fastloop: "FastLoop", request_id: str): + self.fastloop = fastloop + + def generate_response(self, text: str): + pass + + +# class ConversationManager: +# def __init__(self, fastloop: "FastLoop"): +# self.fastloop = fastloop + +# def start_conversation(self, loop_id: str): +# pass + +# def generate_response(self, loop_id: str): +# pass + + diff --git a/fastloop/integrations/plugins/Deepgram.py b/fastloop/integrations/plugins/Deepgram.py new file mode 100644 index 0000000..b3513c3 --- /dev/null +++ b/fastloop/integrations/plugins/Deepgram.py @@ -0,0 +1,87 @@ +import os +from typing import Any +from fastapi import WebSocket +from deepgram import DeepgramClient, DeepgramClientOptions, LiveTranscriptionEvents, LiveOptions +import asyncio +import dotenv + +dotenv.load_dotenv() + +class DeepgramSpeechToTextManager: + def __init__(self, request_id: str , websocket: WebSocket): + self.request_id = request_id + self.websocket = websocket + + async def start(self): + # self.dg = DeepgramClient(api_key=os.getenv("DEEPGRAM_API_KEY", "")) + config = DeepgramClientOptions(options={"keepalive": "true"}) + deepgram: DeepgramClient = DeepgramClient(os.getenv("DEEPGRAM_API_KEY", ""), config) + self.dg_connection = deepgram.listen.asyncwebsocket.v("1") + + async def on_message(*_, result: Any, **__): + sentence = result.channel.alternatives[0].transcript + print(f"Transcript: {sentence}") + if len(sentence) > 0: + # Send transcript back to client`` + response = { + "type": "transcript", + "text": sentence, + "is_final": result.speech_final + } + await self.websocket.send_json(response) + + async def on_error(error: Any, *_, **__): + print(f"Deepgram error: {error}") + error_response = { + "type": "error", + "message": str(error) + } + # await self.websocket.send_json(error_response) + + async def on_close(error: Any, *d, **k): + print(f"Deepgram connection closed: {error}") + # await self.websocket.send_json(error_response) + + async def on_speech_started(result: Any, *_, **__): + print(f"Speech started: {result}") + + async def on_utterance_end(result: Any, *_, **__): + print(f"Utterance end: {result}") + + self.dg_connection.on(LiveTranscriptionEvents.Transcript, on_message) # type: ignore + self.dg_connection.on(LiveTranscriptionEvents.Error, on_error) # type: ignore + self.dg_connection.on(LiveTranscriptionEvents.Close, on_close) # type: ignore + self.dg_connection.on(LiveTranscriptionEvents.SpeechStarted, on_speech_started) # type: ignore + self.dg_connection.on(LiveTranscriptionEvents.UtteranceEnd, on_utterance_end) # type: ignore + + print("starting deepgram connection") + + if not await self.dg_connection.start( + LiveOptions( + model="nova-3", + punctuate=True, + encoding="linear16", + channels=1, + sample_rate=16000, + vad_events=True, + endpointing=False, + interim_results=False, + ) + ): # type: ignore + print("Failed to start Deepgram connection") + + print("deepgram connection started") + + while not await self.dg_connection.is_connected(): + await asyncio.sleep(0.1) + + print("deepgram connection connected") + + async def send_audio(self, audio: bytes): + try: + if await self.dg_connection.is_connected(): + await self.dg_connection.send(audio) + else: + return + except Exception as e: + print(f"Error sending audio: {e}") diff --git a/fastloop/integrations/plugins/ElevenLabs.py b/fastloop/integrations/plugins/ElevenLabs.py new file mode 100644 index 0000000..9ef650d --- /dev/null +++ b/fastloop/integrations/plugins/ElevenLabs.py @@ -0,0 +1,52 @@ +import os +import dotenv +import asyncio +import time + + +from elevenlabs.client import AsyncElevenLabs +from fastapi import WebSocket +from .utils import bytes_to_wav + +dotenv.load_dotenv() +TIME_BETWEEN_TRANSCRIPTIONS = 5 + +# Elevenlabs does not support realtime transcription so we +# we hack together a solution by sending audio in chunks and +# waiting for the transcription to be generated before sending the next chunk. + + +class ElevenLabsSpeechToTextManager: + def __init__(self, request_id: str, websocket: WebSocket): + self.request_id = request_id + self.websocket = websocket + self.client = AsyncElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY")) + self.transcripts: list[str | None] = [] + self.lock = asyncio.Lock() + self.audio_buffer: list[bytes] = [] + self.last_transcription_time = time.time() + + + async def start(self): + pass + + async def _generate_transcript_async(self, index: int, audio: bytes): + print(f"Generating transcript for {index}") + transcript = await self.client.speech_to_text.convert( + model_id="scribe_v1", + file=bytes_to_wav(audio), + language_code="eng", + tag_audio_events=False, + ) + self.transcripts[index] = transcript.text + print(f"Transcript: {self.transcripts[index]}") + + async def send_audio(self, audio: bytes): + self.audio_buffer.append(audio) + if time.time() - self.last_transcription_time > TIME_BETWEEN_TRANSCRIPTIONS: + index = len(self.transcripts) + self.transcripts.append(None) + + asyncio.create_task(self._generate_transcript_async(index, b"".join(self.audio_buffer))) + self.audio_buffer = [] + self.last_transcription_time = time.time() \ No newline at end of file diff --git a/fastloop/integrations/plugins/SpeechMatics.py b/fastloop/integrations/plugins/SpeechMatics.py new file mode 100644 index 0000000..30d6e08 --- /dev/null +++ b/fastloop/integrations/plugins/SpeechMatics.py @@ -0,0 +1,54 @@ +import asyncio +import time +from typing import Any +from speechmatics.rt import AsyncClient, TranscriptionConfig, ServerMessageType, AudioFormat, AudioEncoding +from fastapi import WebSocket +import os +import dotenv + +dotenv.load_dotenv() + +TIME_BETWEEN_TRANSCRIPTIONS = 5 + +class SpeechmaticsSpeechToTextManager: + def __init__(self, request_id: str, websocket: WebSocket): + self.request_id = request_id + self.websocket = websocket + self.client = AsyncClient(api_key=os.getenv("SPEECHMATICS_API_KEY")) + self.session = None + self.lock = asyncio.Lock() + self.audio_buffer: list[bytes] = [] + self.last_transcription_time = time.time() + + async def start(self): + await self.client.start_session( + transcription_config=TranscriptionConfig( + language="en", + enable_partials=True, + max_delay=1, + ), + audio_format=AudioFormat( + encoding=AudioEncoding.PCM_S16LE, + sample_rate=16000, + chunk_size=1024, + ), + ) + + def on_message(message: dict[str, Any]): + results = message["results"] + for result in results: + print(result) + + def on_recognition_started(*_, **__): + print("Recognition started") + + self.client.on(ServerMessageType.RECOGNITION_STARTED, on_recognition_started) + self.client.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT, on_message) + + async def send_audio(self, audio: bytes): + self.audio_buffer.append(audio) + if time.time() - self.last_transcription_time > TIME_BETWEEN_TRANSCRIPTIONS: + await self.client.send_audio(b"".join(self.audio_buffer)) + self.audio_buffer = [] + self.last_transcription_time = time.time() + \ No newline at end of file diff --git a/fastloop/integrations/plugins/utils.py b/fastloop/integrations/plugins/utils.py new file mode 100644 index 0000000..1adfff9 --- /dev/null +++ b/fastloop/integrations/plugins/utils.py @@ -0,0 +1,15 @@ +import io +import wave + +def bytes_to_wav(audio_bytes: bytes, sample_rate: int = 16000, channels: int = 1, sample_width: int = 2) -> io.BytesIO: + # Create an in-memory WAV file + wav_buffer = io.BytesIO() + + with wave.open(wav_buffer, 'wb') as wav_file: + wav_file.setnchannels(channels) # mono or stereo + wav_file.setsampwidth(sample_width) # 2 bytes = 16-bit + wav_file.setframerate(sample_rate) # sample rate + wav_file.writeframes(audio_bytes) + + wav_buffer.seek(0) + return wav_buffer \ No newline at end of file diff --git a/fastloop/types.py b/fastloop/types.py index 18657d3..b6981e9 100644 --- a/fastloop/types.py +++ b/fastloop/types.py @@ -66,6 +66,7 @@ class SlackConfig(BaseModel): class IntegrationType(StrEnum): SLACK = "slack" + CONVERSATION = "conversation" class BaseConfig(BaseModel): diff --git a/pyproject.toml b/pyproject.toml index 7693686..610c9c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "fastloop" version = "0.1.52" description = "A Python package for deploying stateful loops" readme = "README.md" -requires-python = ">=3.12" +requires-python = "<4.0,>=3.12" license = {text = "MIT"} authors = [ {name = "Luke Lombardi", email = "luke@beaml.cloud"} @@ -26,6 +26,13 @@ dependencies = [ "aioboto3>=15.0.0", "slack-sdk>=3.27.0", "aiohttp>=3.9.0", + "numpy>=2.3.1", + "websockets>=15.0.1", + "deepgram-sdk>=4.7.0", + "pyaudio>=0.2.14", + "scipy>=1.16.0", + "elevenlabs>=2.8.1", + "speechmatics-rt>=0.4.0", ] [project.optional-dependencies] @@ -112,3 +119,8 @@ line-ending = "auto" # Enable auto-formatting of code examples in docstrings. Markdown, # reStructuredText code/literal blocks and doctests are all supported. docstring-code-format = true + +[dependency-groups] +dev = [ + "mypy>=1.17.0", +] diff --git a/uv.lock b/uv.lock index eda1b34..ae1cd92 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,14 @@ version = 1 revision = 1 -requires-python = ">=3.12" +requires-python = ">=3.12, <4.0" + +[[package]] +name = "aenum" +version = "3.1.16" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/52/6ad8f63ec8da1bf40f96996d25d5b650fdd38f5975f8c813732c47388f18/aenum-3.1.16-py3-none-any.whl", hash = "sha256:9035092855a98e41b66e3d0998bd7b96280e85ceb3a04cc035636138a1943eaf", size = 165627 }, +] [[package]] name = "aioboto3" @@ -58,7 +66,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.12.13" +version = "3.12.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -69,42 +77,42 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/6a/ce40e329788013cd190b1d62bbabb2b6a9673ecb6d836298635b939562ef/aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73", size = 700491 }, - { url = "https://files.pythonhosted.org/packages/28/d9/7150d5cf9163e05081f1c5c64a0cdf3c32d2f56e2ac95db2a28fe90eca69/aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347", size = 475104 }, - { url = "https://files.pythonhosted.org/packages/f8/91/d42ba4aed039ce6e449b3e2db694328756c152a79804e64e3da5bc19dffc/aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f", size = 467948 }, - { url = "https://files.pythonhosted.org/packages/99/3b/06f0a632775946981d7c4e5a865cddb6e8dfdbaed2f56f9ade7bb4a1039b/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6", size = 1714742 }, - { url = "https://files.pythonhosted.org/packages/92/a6/2552eebad9ec5e3581a89256276009e6a974dc0793632796af144df8b740/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5", size = 1697393 }, - { url = "https://files.pythonhosted.org/packages/d8/9f/bd08fdde114b3fec7a021381b537b21920cdd2aa29ad48c5dffd8ee314f1/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b", size = 1752486 }, - { url = "https://files.pythonhosted.org/packages/f7/e1/affdea8723aec5bd0959171b5490dccd9a91fcc505c8c26c9f1dca73474d/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75", size = 1798643 }, - { url = "https://files.pythonhosted.org/packages/f3/9d/666d856cc3af3a62ae86393baa3074cc1d591a47d89dc3bf16f6eb2c8d32/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6", size = 1718082 }, - { url = "https://files.pythonhosted.org/packages/f3/ce/3c185293843d17be063dada45efd2712bb6bf6370b37104b4eda908ffdbd/aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8", size = 1633884 }, - { url = "https://files.pythonhosted.org/packages/3a/5b/f3413f4b238113be35dfd6794e65029250d4b93caa0974ca572217745bdb/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710", size = 1694943 }, - { url = "https://files.pythonhosted.org/packages/82/c8/0e56e8bf12081faca85d14a6929ad5c1263c146149cd66caa7bc12255b6d/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462", size = 1716398 }, - { url = "https://files.pythonhosted.org/packages/ea/f3/33192b4761f7f9b2f7f4281365d925d663629cfaea093a64b658b94fc8e1/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae", size = 1657051 }, - { url = "https://files.pythonhosted.org/packages/5e/0b/26ddd91ca8f84c48452431cb4c5dd9523b13bc0c9766bda468e072ac9e29/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e", size = 1736611 }, - { url = "https://files.pythonhosted.org/packages/c3/8d/e04569aae853302648e2c138a680a6a2f02e374c5b6711732b29f1e129cc/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a", size = 1764586 }, - { url = "https://files.pythonhosted.org/packages/ac/98/c193c1d1198571d988454e4ed75adc21c55af247a9fda08236602921c8c8/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5", size = 1724197 }, - { url = "https://files.pythonhosted.org/packages/e7/9e/07bb8aa11eec762c6b1ff61575eeeb2657df11ab3d3abfa528d95f3e9337/aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf", size = 421771 }, - { url = "https://files.pythonhosted.org/packages/52/66/3ce877e56ec0813069cdc9607cd979575859c597b6fb9b4182c6d5f31886/aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e", size = 447869 }, - { url = "https://files.pythonhosted.org/packages/11/0f/db19abdf2d86aa1deec3c1e0e5ea46a587b97c07a16516b6438428b3a3f8/aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938", size = 694910 }, - { url = "https://files.pythonhosted.org/packages/d5/81/0ab551e1b5d7f1339e2d6eb482456ccbe9025605b28eed2b1c0203aaaade/aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace", size = 472566 }, - { url = "https://files.pythonhosted.org/packages/34/3f/6b7d336663337672d29b1f82d1f252ec1a040fe2d548f709d3f90fa2218a/aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb", size = 464856 }, - { url = "https://files.pythonhosted.org/packages/26/7f/32ca0f170496aa2ab9b812630fac0c2372c531b797e1deb3deb4cea904bd/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7", size = 1703683 }, - { url = "https://files.pythonhosted.org/packages/ec/53/d5513624b33a811c0abea8461e30a732294112318276ce3dbf047dbd9d8b/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b", size = 1684946 }, - { url = "https://files.pythonhosted.org/packages/37/72/4c237dd127827b0247dc138d3ebd49c2ded6114c6991bbe969058575f25f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177", size = 1737017 }, - { url = "https://files.pythonhosted.org/packages/0d/67/8a7eb3afa01e9d0acc26e1ef847c1a9111f8b42b82955fcd9faeb84edeb4/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef", size = 1786390 }, - { url = "https://files.pythonhosted.org/packages/48/19/0377df97dd0176ad23cd8cad4fd4232cfeadcec6c1b7f036315305c98e3f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103", size = 1708719 }, - { url = "https://files.pythonhosted.org/packages/61/97/ade1982a5c642b45f3622255173e40c3eed289c169f89d00eeac29a89906/aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da", size = 1622424 }, - { url = "https://files.pythonhosted.org/packages/99/ab/00ad3eea004e1d07ccc406e44cfe2b8da5acb72f8c66aeeb11a096798868/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d", size = 1675447 }, - { url = "https://files.pythonhosted.org/packages/3f/fe/74e5ce8b2ccaba445fe0087abc201bfd7259431d92ae608f684fcac5d143/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041", size = 1707110 }, - { url = "https://files.pythonhosted.org/packages/ef/c4/39af17807f694f7a267bd8ab1fbacf16ad66740862192a6c8abac2bff813/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1", size = 1649706 }, - { url = "https://files.pythonhosted.org/packages/38/e8/f5a0a5f44f19f171d8477059aa5f28a158d7d57fe1a46c553e231f698435/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1", size = 1725839 }, - { url = "https://files.pythonhosted.org/packages/fd/ac/81acc594c7f529ef4419d3866913f628cd4fa9cab17f7bf410a5c3c04c53/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911", size = 1759311 }, - { url = "https://files.pythonhosted.org/packages/38/0d/aabe636bd25c6ab7b18825e5a97d40024da75152bec39aa6ac8b7a677630/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3", size = 1708202 }, - { url = "https://files.pythonhosted.org/packages/1f/ab/561ef2d8a223261683fb95a6283ad0d36cb66c87503f3a7dde7afe208bb2/aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd", size = 420794 }, - { url = "https://files.pythonhosted.org/packages/9d/47/b11d0089875a23bff0abd3edb5516bcd454db3fefab8604f5e4b07bd6210/aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706", size = 446735 }, +sdist = { url = "https://files.pythonhosted.org/packages/e6/0b/e39ad954107ebf213a2325038a3e7a506be3d98e1435e1f82086eec4cde2/aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2", size = 7822921 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/0d/29026524e9336e33d9767a1e593ae2b24c2b8b09af7c2bd8193762f76b3e/aiohttp-3.12.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a0ecbb32fc3e69bc25efcda7d28d38e987d007096cbbeed04f14a6662d0eee22", size = 701055 }, + { url = "https://files.pythonhosted.org/packages/0a/b8/a5e8e583e6c8c1056f4b012b50a03c77a669c2e9bf012b7cf33d6bc4b141/aiohttp-3.12.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0400f0ca9bb3e0b02f6466421f253797f6384e9845820c8b05e976398ac1d81a", size = 475670 }, + { url = "https://files.pythonhosted.org/packages/29/e8/5202890c9e81a4ec2c2808dd90ffe024952e72c061729e1d49917677952f/aiohttp-3.12.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a56809fed4c8a830b5cae18454b7464e1529dbf66f71c4772e3cfa9cbec0a1ff", size = 468513 }, + { url = "https://files.pythonhosted.org/packages/23/e5/d11db8c23d8923d3484a27468a40737d50f05b05eebbb6288bafcb467356/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f2e373276e4755691a963e5d11756d093e346119f0627c2d6518208483fb6d", size = 1715309 }, + { url = "https://files.pythonhosted.org/packages/53/44/af6879ca0eff7a16b1b650b7ea4a827301737a350a464239e58aa7c387ef/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ca39e433630e9a16281125ef57ece6817afd1d54c9f1bf32e901f38f16035869", size = 1697961 }, + { url = "https://files.pythonhosted.org/packages/bb/94/18457f043399e1ec0e59ad8674c0372f925363059c276a45a1459e17f423/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c748b3f8b14c77720132b2510a7d9907a03c20ba80f469e58d5dfd90c079a1c", size = 1753055 }, + { url = "https://files.pythonhosted.org/packages/26/d9/1d3744dc588fafb50ff8a6226d58f484a2242b5dd93d8038882f55474d41/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a568abe1b15ce69d4cc37e23020720423f0728e3cb1f9bcd3f53420ec3bfe7", size = 1799211 }, + { url = "https://files.pythonhosted.org/packages/73/12/2530fb2b08773f717ab2d249ca7a982ac66e32187c62d49e2c86c9bba9b4/aiohttp-3.12.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9888e60c2c54eaf56704b17feb558c7ed6b7439bca1e07d4818ab878f2083660", size = 1718649 }, + { url = "https://files.pythonhosted.org/packages/b9/34/8d6015a729f6571341a311061b578e8b8072ea3656b3d72329fa0faa2c7c/aiohttp-3.12.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3006a1dc579b9156de01e7916d38c63dc1ea0679b14627a37edf6151bc530088", size = 1634452 }, + { url = "https://files.pythonhosted.org/packages/ff/4b/08b83ea02595a582447aeb0c1986792d0de35fe7a22fb2125d65091cbaf3/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa8ec5c15ab80e5501a26719eb48a55f3c567da45c6ea5bb78c52c036b2655c7", size = 1695511 }, + { url = "https://files.pythonhosted.org/packages/b5/66/9c7c31037a063eec13ecf1976185c65d1394ded4a5120dd5965e3473cb21/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:39b94e50959aa07844c7fe2206b9f75d63cc3ad1c648aaa755aa257f6f2498a9", size = 1716967 }, + { url = "https://files.pythonhosted.org/packages/ba/02/84406e0ad1acb0fb61fd617651ab6de760b2d6a31700904bc0b33bd0894d/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04c11907492f416dad9885d503fbfc5dcb6768d90cad8639a771922d584609d3", size = 1657620 }, + { url = "https://files.pythonhosted.org/packages/07/53/da018f4013a7a179017b9a274b46b9a12cbeb387570f116964f498a6f211/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:88167bd9ab69bb46cee91bd9761db6dfd45b6e76a0438c7e884c3f8160ff21eb", size = 1737179 }, + { url = "https://files.pythonhosted.org/packages/49/e8/ca01c5ccfeaafb026d85fa4f43ceb23eb80ea9c1385688db0ef322c751e9/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:791504763f25e8f9f251e4688195e8b455f8820274320204f7eafc467e609425", size = 1765156 }, + { url = "https://files.pythonhosted.org/packages/22/32/5501ab525a47ba23c20613e568174d6c63aa09e2caa22cded5c6ea8e3ada/aiohttp-3.12.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2785b112346e435dd3a1a67f67713a3fe692d288542f1347ad255683f066d8e0", size = 1724766 }, + { url = "https://files.pythonhosted.org/packages/06/af/28e24574801fcf1657945347ee10df3892311c2829b41232be6089e461e7/aiohttp-3.12.14-cp312-cp312-win32.whl", hash = "sha256:15f5f4792c9c999a31d8decf444e79fcfd98497bf98e94284bf390a7bb8c1729", size = 422641 }, + { url = "https://files.pythonhosted.org/packages/98/d5/7ac2464aebd2eecac38dbe96148c9eb487679c512449ba5215d233755582/aiohttp-3.12.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b66e1a182879f579b105a80d5c4bd448b91a57e8933564bf41665064796a338", size = 449316 }, + { url = "https://files.pythonhosted.org/packages/06/48/e0d2fa8ac778008071e7b79b93ab31ef14ab88804d7ba71b5c964a7c844e/aiohttp-3.12.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3143a7893d94dc82bc409f7308bc10d60285a3cd831a68faf1aa0836c5c3c767", size = 695471 }, + { url = "https://files.pythonhosted.org/packages/8d/e7/f73206afa33100804f790b71092888f47df65fd9a4cd0e6800d7c6826441/aiohttp-3.12.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d62ac3d506cef54b355bd34c2a7c230eb693880001dfcda0bf88b38f5d7af7e", size = 473128 }, + { url = "https://files.pythonhosted.org/packages/df/e2/4dd00180be551a6e7ee979c20fc7c32727f4889ee3fd5b0586e0d47f30e1/aiohttp-3.12.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48e43e075c6a438937c4de48ec30fa8ad8e6dfef122a038847456bfe7b947b63", size = 465426 }, + { url = "https://files.pythonhosted.org/packages/de/dd/525ed198a0bb674a323e93e4d928443a680860802c44fa7922d39436b48b/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:077b4488411a9724cecc436cbc8c133e0d61e694995b8de51aaf351c7578949d", size = 1704252 }, + { url = "https://files.pythonhosted.org/packages/d8/b1/01e542aed560a968f692ab4fc4323286e8bc4daae83348cd63588e4f33e3/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d8c35632575653f297dcbc9546305b2c1133391089ab925a6a3706dfa775ccab", size = 1685514 }, + { url = "https://files.pythonhosted.org/packages/b3/06/93669694dc5fdabdc01338791e70452d60ce21ea0946a878715688d5a191/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b8ce87963f0035c6834b28f061df90cf525ff7c9b6283a8ac23acee6502afd4", size = 1737586 }, + { url = "https://files.pythonhosted.org/packages/a5/3a/18991048ffc1407ca51efb49ba8bcc1645961f97f563a6c480cdf0286310/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a2cf66e32a2563bb0766eb24eae7e9a269ac0dc48db0aae90b575dc9583026", size = 1786958 }, + { url = "https://files.pythonhosted.org/packages/30/a8/81e237f89a32029f9b4a805af6dffc378f8459c7b9942712c809ff9e76e5/aiohttp-3.12.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdea089caf6d5cde975084a884c72d901e36ef9c2fd972c9f51efbbc64e96fbd", size = 1709287 }, + { url = "https://files.pythonhosted.org/packages/8c/e3/bd67a11b0fe7fc12c6030473afd9e44223d456f500f7cf526dbaa259ae46/aiohttp-3.12.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7865f27db67d49e81d463da64a59365ebd6b826e0e4847aa111056dcb9dc88", size = 1622990 }, + { url = "https://files.pythonhosted.org/packages/83/ba/e0cc8e0f0d9ce0904e3cf2d6fa41904e379e718a013c721b781d53dcbcca/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ab5b38a6a39781d77713ad930cb5e7feea6f253de656a5f9f281a8f5931b086", size = 1676015 }, + { url = "https://files.pythonhosted.org/packages/d8/b3/1e6c960520bda094c48b56de29a3d978254637ace7168dd97ddc273d0d6c/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b3b15acee5c17e8848d90a4ebc27853f37077ba6aec4d8cb4dbbea56d156933", size = 1707678 }, + { url = "https://files.pythonhosted.org/packages/0a/19/929a3eb8c35b7f9f076a462eaa9830b32c7f27d3395397665caa5e975614/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4c972b0bdaac167c1e53e16a16101b17c6d0ed7eac178e653a07b9f7fad7151", size = 1650274 }, + { url = "https://files.pythonhosted.org/packages/22/e5/81682a6f20dd1b18ce3d747de8eba11cbef9b270f567426ff7880b096b48/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7442488b0039257a3bdbc55f7209587911f143fca11df9869578db6c26feeeb8", size = 1726408 }, + { url = "https://files.pythonhosted.org/packages/8c/17/884938dffaa4048302985483f77dfce5ac18339aad9b04ad4aaa5e32b028/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f68d3067eecb64c5e9bab4a26aa11bd676f4c70eea9ef6536b0a4e490639add3", size = 1759879 }, + { url = "https://files.pythonhosted.org/packages/95/78/53b081980f50b5cf874359bde707a6eacd6c4be3f5f5c93937e48c9d0025/aiohttp-3.12.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f88d3704c8b3d598a08ad17d06006cb1ca52a1182291f04979e305c8be6c9758", size = 1708770 }, + { url = "https://files.pythonhosted.org/packages/ed/91/228eeddb008ecbe3ffa6c77b440597fdf640307162f0c6488e72c5a2d112/aiohttp-3.12.14-cp313-cp313-win32.whl", hash = "sha256:a3c99ab19c7bf375c4ae3debd91ca5d394b98b6089a03231d4c580ef3c2ae4c5", size = 421688 }, + { url = "https://files.pythonhosted.org/packages/66/5f/8427618903343402fdafe2850738f735fd1d9409d2a8f9bcaae5e630d3ba/aiohttp-3.12.14-cp313-cp313-win_amd64.whl", hash = "sha256:3f8aad695e12edc9d571f878c62bedc91adf30c760c8632f09663e5f564f4baa", size = 448098 }, ] [[package]] @@ -163,14 +171,14 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.0" +version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/9d/b1e08d36899c12c8b894a44a5583ee157789f26fc4b176f8e4b6217b56e1/authlib-1.6.0.tar.gz", hash = "sha256:4367d32031b7af175ad3a323d571dc7257b7099d55978087ceae4a0d88cd3210", size = 158371 } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/29/587c189bbab1ccc8c86a03a5d0e13873df916380ef1be461ebe6acebf48d/authlib-1.6.0-py2.py3-none-any.whl", hash = "sha256:91685589498f79e8655e8a8947431ad6288831d643f11c55c2143ffcc738048d", size = 239981 }, + { url = "https://files.pythonhosted.org/packages/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299 }, ] [[package]] @@ -217,11 +225,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.6.15" +version = "2025.7.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753 } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650 }, + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722 }, ] [[package]] @@ -357,6 +365,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016 }, ] +[[package]] +name = "dataclasses-json" +version = "0.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 }, +] + +[[package]] +name = "deepgram-sdk" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aenum" }, + { name = "aiofiles" }, + { name = "aiohttp" }, + { name = "dataclasses-json" }, + { name = "deprecation" }, + { name = "httpx" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/9b/ddbcb74375c0329594010c2eb51f9ccb6b3b7ac44d2bb3effb6b4064d6d6/deepgram_sdk-4.8.0.tar.gz", hash = "sha256:b5cfd16e657121f9db05d9a594c4f4dfab604bb3885d17e2ae12a7923cd11ba3", size = 100072 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/f1/655ddffdef4b738c0ae4fe647bbb21d6ba5da49ee1d9e44d6860fdd986a6/deepgram_sdk-4.8.0-py3-none-any.whl", hash = "sha256:ba30873c5438eab269d88bd059cf6b147c55a61cb8d71c91c30a51a3a6c99608", size = 157929 }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, +] + [[package]] name = "docutils" version = "0.21.2" @@ -366,6 +418,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, ] +[[package]] +name = "elevenlabs" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "requests" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/e0/227a979245baf10c9ed056fc9b3d00c7b353c2027243bfae8d715bb9264e/elevenlabs-2.8.1.tar.gz", hash = "sha256:ace2d780e3d58f8408f57f70358286c0cc8581d2f8ac5c1cfacab384dc76f411", size = 271262 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/48/2fc6e53c0181c8b51262b2424c261b14108f4e6360a70b562718b3d2c205/elevenlabs-2.8.1-py3-none-any.whl", hash = "sha256:f0aa8d02595edf5a38b72f9d032293a0820062786849f6ef9e2e9231c98d3e27", size = 767310 }, +] + [[package]] name = "exceptiongroup" version = "1.3.0" @@ -380,16 +449,16 @@ wheels = [ [[package]] name = "fastapi" -version = "0.115.14" +version = "0.116.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/53/8c38a874844a8b0fa10dd8adf3836ac154082cf88d3f22b544e9ceea0a15/fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739", size = 296263 } +sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485 } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/50/b1222562c6d270fea83e9c9075b8e8600b8479150a18e4516a6138b980d1/fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca", size = 95514 }, + { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631 }, ] [[package]] @@ -400,12 +469,19 @@ dependencies = [ { name = "aioboto3" }, { name = "aiohttp" }, { name = "cloudpickle" }, + { name = "deepgram-sdk" }, + { name = "elevenlabs" }, { name = "fastapi" }, { name = "fastmcp" }, + { name = "numpy" }, + { name = "pyaudio" }, { name = "pydantic" }, { name = "pyyaml" }, { name = "redis" }, + { name = "scipy" }, { name = "slack-sdk" }, + { name = "speechmatics-rt" }, + { name = "websockets" }, ] [package.optional-dependencies] @@ -415,23 +491,38 @@ dev = [ { name = "twine" }, ] +[package.dev-dependencies] +dev = [ + { name = "mypy" }, +] + [package.metadata] requires-dist = [ { name = "aioboto3", specifier = ">=15.0.0" }, { name = "aiohttp", specifier = ">=3.9.0" }, { name = "build", marker = "extra == 'dev'", specifier = ">=1.0.0" }, { name = "cloudpickle", specifier = ">=3.1.1" }, + { name = "deepgram-sdk", specifier = ">=4.7.0" }, + { name = "elevenlabs", specifier = ">=2.8.1" }, { name = "fastapi", specifier = ">=0.115.14" }, { name = "fastmcp", specifier = "==2.9.0" }, + { name = "numpy", specifier = ">=2.3.1" }, + { name = "pyaudio", specifier = ">=0.2.14" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, { name = "pyyaml", specifier = ">=6.0" }, { name = "redis", specifier = ">=6.2.0" }, + { name = "scipy", specifier = ">=1.16.0" }, { name = "slack-sdk", specifier = ">=3.27.0" }, + { name = "speechmatics-rt", specifier = ">=0.4.0" }, { name = "twine", marker = "extra == 'dev'", specifier = ">=4.0.0" }, + { name = "websockets", specifier = ">=15.0.1" }, ] provides-extras = ["dev"] +[package.metadata.requires-dev] +dev = [{ name = "mypy", specifier = ">=1.17.0" }] + [[package]] name = "fastmcp" version = "2.9.0" @@ -640,7 +731,7 @@ wheels = [ [[package]] name = "jsonschema" -version = "4.24.0" +version = "4.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -648,9 +739,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480 } +sdist = { url = "https://files.pythonhosted.org/packages/d5/00/a297a868e9d0784450faa7365c2172a7d6110c763e30ba861867c32ae6a9/jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f", size = 356830 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709 }, + { url = "https://files.pythonhosted.org/packages/fe/54/c86cd8e011fe98803d7e382fd67c0df5ceab8d2b7ad8c5a81524f791551c/jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716", size = 89184 }, ] [[package]] @@ -694,9 +785,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, ] +[[package]] +name = "marshmallow" +version = "3.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878 }, +] + [[package]] name = "mcp" -version = "1.10.1" +version = "1.12.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -706,13 +809,14 @@ dependencies = [ { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/68/63045305f29ff680a9cd5be360c755270109e6b76f696ea6824547ddbc30/mcp-1.10.1.tar.gz", hash = "sha256:aaa0957d8307feeff180da2d9d359f2b801f35c0c67f1882136239055ef034c2", size = 392969 } +sdist = { url = "https://files.pythonhosted.org/packages/66/85/f36d538b1286b7758f35c1b69d93f2719d2df90c01bd074eadd35f6afc35/mcp-1.12.2.tar.gz", hash = "sha256:a4b7c742c50ce6ed6d6a6c096cca0e3893f5aecc89a59ed06d47c4e6ba41edcc", size = 426202 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/3f/435a5b3d10ae242a9d6c2b33175551173c3c61fe637dc893be05c4ed0aaf/mcp-1.10.1-py3-none-any.whl", hash = "sha256:4d08301aefe906dce0fa482289db55ce1db831e3e67212e65b5e23ad8454b3c5", size = 150878 }, + { url = "https://files.pythonhosted.org/packages/2f/cf/3fd38cfe43962452e4bfadc6966b2ea0afaf8e0286cb3991c247c8c33ebd/mcp-1.12.2-py3-none-any.whl", hash = "sha256:b86d584bb60193a42bd78aef01882c5c42d614e416cbf0480149839377ab5a5f", size = 158473 }, ] [[package]] @@ -796,35 +900,135 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313 }, ] +[[package]] +name = "mypy" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395 }, + { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052 }, + { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806 }, + { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371 }, + { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558 }, + { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447 }, + { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019 }, + { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457 }, + { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838 }, + { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358 }, + { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480 }, + { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666 }, + { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, +] + [[package]] name = "nh3" -version = "0.2.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/30/2f81466f250eb7f591d4d193930df661c8c23e9056bdc78e365b646054d8/nh3-0.2.21.tar.gz", hash = "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e", size = 16581 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/81/b83775687fcf00e08ade6d4605f0be9c4584cb44c4973d9f27b7456a31c9/nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286", size = 1297678 }, - { url = "https://files.pythonhosted.org/packages/22/ee/d0ad8fb4b5769f073b2df6807f69a5e57ca9cea504b78809921aef460d20/nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde", size = 733774 }, - { url = "https://files.pythonhosted.org/packages/ea/76/b450141e2d384ede43fe53953552f1c6741a499a8c20955ad049555cabc8/nh3-0.2.21-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243", size = 760012 }, - { url = "https://files.pythonhosted.org/packages/97/90/1182275db76cd8fbb1f6bf84c770107fafee0cb7da3e66e416bcb9633da2/nh3-0.2.21-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b", size = 923619 }, - { url = "https://files.pythonhosted.org/packages/29/c7/269a7cfbec9693fad8d767c34a755c25ccb8d048fc1dfc7a7d86bc99375c/nh3-0.2.21-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251", size = 1000384 }, - { url = "https://files.pythonhosted.org/packages/68/a9/48479dbf5f49ad93f0badd73fbb48b3d769189f04c6c69b0df261978b009/nh3-0.2.21-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b", size = 918908 }, - { url = "https://files.pythonhosted.org/packages/d7/da/0279c118f8be2dc306e56819880b19a1cf2379472e3b79fc8eab44e267e3/nh3-0.2.21-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9", size = 909180 }, - { url = "https://files.pythonhosted.org/packages/26/16/93309693f8abcb1088ae143a9c8dbcece9c8f7fb297d492d3918340c41f1/nh3-0.2.21-cp313-cp313t-win32.whl", hash = "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d", size = 532747 }, - { url = "https://files.pythonhosted.org/packages/a2/3a/96eb26c56cbb733c0b4a6a907fab8408ddf3ead5d1b065830a8f6a9c3557/nh3-0.2.21-cp313-cp313t-win_amd64.whl", hash = "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82", size = 528908 }, - { url = "https://files.pythonhosted.org/packages/ba/1d/b1ef74121fe325a69601270f276021908392081f4953d50b03cbb38b395f/nh3-0.2.21-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967", size = 1316133 }, - { url = "https://files.pythonhosted.org/packages/b8/f2/2c7f79ce6de55b41e7715f7f59b159fd59f6cdb66223c05b42adaee2b645/nh3-0.2.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759", size = 758328 }, - { url = "https://files.pythonhosted.org/packages/6d/ad/07bd706fcf2b7979c51b83d8b8def28f413b090cf0cb0035ee6b425e9de5/nh3-0.2.21-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab", size = 747020 }, - { url = "https://files.pythonhosted.org/packages/75/99/06a6ba0b8a0d79c3d35496f19accc58199a1fb2dce5e711a31be7e2c1426/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42", size = 944878 }, - { url = "https://files.pythonhosted.org/packages/79/d4/dc76f5dc50018cdaf161d436449181557373869aacf38a826885192fc587/nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f", size = 903460 }, - { url = "https://files.pythonhosted.org/packages/cd/c3/d4f8037b2ab02ebf5a2e8637bd54736ed3d0e6a2869e10341f8d9085f00e/nh3-0.2.21-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578", size = 839369 }, - { url = "https://files.pythonhosted.org/packages/11/a9/1cd3c6964ec51daed7b01ca4686a5c793581bf4492cbd7274b3f544c9abe/nh3-0.2.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585", size = 739036 }, - { url = "https://files.pythonhosted.org/packages/fd/04/bfb3ff08d17a8a96325010ae6c53ba41de6248e63cdb1b88ef6369a6cdfc/nh3-0.2.21-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293", size = 768712 }, - { url = "https://files.pythonhosted.org/packages/9e/aa/cfc0bf545d668b97d9adea4f8b4598667d2b21b725d83396c343ad12bba7/nh3-0.2.21-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431", size = 930559 }, - { url = "https://files.pythonhosted.org/packages/78/9d/6f5369a801d3a1b02e6a9a097d56bcc2f6ef98cffebf03c4bb3850d8e0f0/nh3-0.2.21-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa", size = 1008591 }, - { url = "https://files.pythonhosted.org/packages/a6/df/01b05299f68c69e480edff608248313cbb5dbd7595c5e048abe8972a57f9/nh3-0.2.21-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1", size = 925670 }, - { url = "https://files.pythonhosted.org/packages/3d/79/bdba276f58d15386a3387fe8d54e980fb47557c915f5448d8c6ac6f7ea9b/nh3-0.2.21-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283", size = 917093 }, - { url = "https://files.pythonhosted.org/packages/e7/d8/c6f977a5cd4011c914fb58f5ae573b071d736187ccab31bfb1d539f4af9f/nh3-0.2.21-cp38-abi3-win32.whl", hash = "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a", size = 537623 }, - { url = "https://files.pythonhosted.org/packages/23/fc/8ce756c032c70ae3dd1d48a3552577a325475af2a2f629604b44f571165c/nh3-0.2.21-cp38-abi3-win_amd64.whl", hash = "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629", size = 535283 }, +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/96cff0977357f60f06ec4368c4c7a7a26cccfe7c9fcd54f5378bf0428fd3/nh3-0.3.0.tar.gz", hash = "sha256:d8ba24cb31525492ea71b6aac11a4adac91d828aadeff7c4586541bf5dc34d2f", size = 19655 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/11/340b7a551916a4b2b68c54799d710f86cf3838a4abaad8e74d35360343bb/nh3-0.3.0-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a537ece1bf513e5a88d8cff8a872e12fe8d0f42ef71dd15a5e7520fecd191bbb", size = 1427992 }, + { url = "https://files.pythonhosted.org/packages/ad/7f/7c6b8358cf1222921747844ab0eef81129e9970b952fcb814df417159fb9/nh3-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c915060a2c8131bef6a29f78debc29ba40859b6dbe2362ef9e5fd44f11487c2", size = 798194 }, + { url = "https://files.pythonhosted.org/packages/63/da/c5fd472b700ba37d2df630a9e0d8cc156033551ceb8b4c49cc8a5f606b68/nh3-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba0caa8aa184196daa6e574d997a33867d6d10234018012d35f86d46024a2a95", size = 837884 }, + { url = "https://files.pythonhosted.org/packages/4c/3c/cba7b26ccc0ef150c81646478aa32f9c9535234f54845603c838a1dc955c/nh3-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:80fe20171c6da69c7978ecba33b638e951b85fb92059259edd285ff108b82a6d", size = 996365 }, + { url = "https://files.pythonhosted.org/packages/f3/ba/59e204d90727c25b253856e456ea61265ca810cda8ee802c35f3fadaab00/nh3-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e90883f9f85288f423c77b3f5a6f4486375636f25f793165112679a7b6363b35", size = 1071042 }, + { url = "https://files.pythonhosted.org/packages/10/71/2fb1834c10fab6d9291d62c95192ea2f4c7518bd32ad6c46aab5d095cb87/nh3-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0649464ac8eee018644aacbc103874ccbfac80e3035643c3acaab4287e36e7f5", size = 995737 }, + { url = "https://files.pythonhosted.org/packages/33/c1/8f8ccc2492a000b6156dce68a43253fcff8b4ce70ab4216d08f90a2ac998/nh3-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1adeb1062a1c2974bc75b8d1ecb014c5fd4daf2df646bbe2831f7c23659793f9", size = 980552 }, + { url = "https://files.pythonhosted.org/packages/2f/d6/f1c6e091cbe8700401c736c2bc3980c46dca770a2cf6a3b48a175114058e/nh3-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:7275fdffaab10cc5801bf026e3c089d8de40a997afc9e41b981f7ac48c5aa7d5", size = 593618 }, + { url = "https://files.pythonhosted.org/packages/23/1e/80a8c517655dd40bb13363fc4d9e66b2f13245763faab1a20f1df67165a7/nh3-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:423201bbdf3164a9e09aa01e540adbb94c9962cc177d5b1cbb385f5e1e79216e", size = 598948 }, + { url = "https://files.pythonhosted.org/packages/9a/e0/af86d2a974c87a4ba7f19bc3b44a8eaa3da480de264138fec82fe17b340b/nh3-0.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:16f8670201f7e8e0e05ed1a590eb84bfa51b01a69dd5caf1d3ea57733de6a52f", size = 580479 }, + { url = "https://files.pythonhosted.org/packages/0c/e0/cf1543e798ba86d838952e8be4cb8d18e22999be2a24b112a671f1c04fd6/nh3-0.3.0-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ec6cfdd2e0399cb79ba4dcffb2332b94d9696c52272ff9d48a630c5dca5e325a", size = 1442218 }, + { url = "https://files.pythonhosted.org/packages/5c/86/a96b1453c107b815f9ab8fac5412407c33cc5c7580a4daf57aabeb41b774/nh3-0.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5e7185599f89b0e391e2f29cc12dc2e206167380cea49b33beda4891be2fe1", size = 823791 }, + { url = "https://files.pythonhosted.org/packages/97/33/11e7273b663839626f714cb68f6eb49899da5a0d9b6bc47b41fe870259c2/nh3-0.3.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:389d93d59b8214d51c400fb5b07866c2a4f79e4e14b071ad66c92184fec3a392", size = 811143 }, + { url = "https://files.pythonhosted.org/packages/6a/1b/b15bd1ce201a1a610aeb44afd478d55ac018b4475920a3118ffd806e2483/nh3-0.3.0-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e9e6a7e4d38f7e8dda9edd1433af5170c597336c1a74b4693c5cb75ab2b30f2a", size = 1064661 }, + { url = "https://files.pythonhosted.org/packages/8f/14/079670fb2e848c4ba2476c5a7a2d1319826053f4f0368f61fca9bb4227ae/nh3-0.3.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7852f038a054e0096dac12b8141191e02e93e0b4608c4b993ec7d4ffafea4e49", size = 997061 }, + { url = "https://files.pythonhosted.org/packages/a3/e5/ac7fc565f5d8bce7f979d1afd68e8cb415020d62fa6507133281c7d49f91/nh3-0.3.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af5aa8127f62bbf03d68f67a956627b1bd0469703a35b3dad28d0c1195e6c7fb", size = 924761 }, + { url = "https://files.pythonhosted.org/packages/39/2c/6394301428b2017a9d5644af25f487fa557d06bc8a491769accec7524d9a/nh3-0.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f416c35efee3e6a6c9ab7716d9e57aa0a49981be915963a82697952cba1353e1", size = 803959 }, + { url = "https://files.pythonhosted.org/packages/4e/9a/344b9f9c4bd1c2413a397f38ee6a3d5db30f1a507d4976e046226f12b297/nh3-0.3.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37d3003d98dedca6cd762bf88f2e70b67f05100f6b949ffe540e189cc06887f9", size = 844073 }, + { url = "https://files.pythonhosted.org/packages/66/3f/cd37f76c8ca277b02a84aa20d7bd60fbac85b4e2cbdae77cb759b22de58b/nh3-0.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:634e34e6162e0408e14fb61d5e69dbaea32f59e847cfcfa41b66100a6b796f62", size = 1000680 }, + { url = "https://files.pythonhosted.org/packages/ee/db/7aa11b44bae4e7474feb1201d8dee04fabe5651c7cb51409ebda94a4ed67/nh3-0.3.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:b0612ccf5de8a480cf08f047b08f9d3fecc12e63d2ee91769cb19d7290614c23", size = 1076613 }, + { url = "https://files.pythonhosted.org/packages/97/03/03f79f7e5178eb1ad5083af84faff471e866801beb980cc72943a4397368/nh3-0.3.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c7a32a7f0d89f7d30cb8f4a84bdbd56d1eb88b78a2434534f62c71dac538c450", size = 1001418 }, + { url = "https://files.pythonhosted.org/packages/ce/55/1974bcc16884a397ee699cebd3914e1f59be64ab305533347ca2d983756f/nh3-0.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3f1b4f8a264a0c86ea01da0d0c390fe295ea0bcacc52c2103aca286f6884f518", size = 986499 }, + { url = "https://files.pythonhosted.org/packages/c9/50/76936ec021fe1f3270c03278b8af5f2079038116b5d0bfe8538ffe699d69/nh3-0.3.0-cp38-abi3-win32.whl", hash = "sha256:6d68fa277b4a3cf04e5c4b84dd0c6149ff7d56c12b3e3fab304c525b850f613d", size = 599000 }, + { url = "https://files.pythonhosted.org/packages/8c/ae/324b165d904dc1672eee5f5661c0a68d4bab5b59fbb07afb6d8d19a30b45/nh3-0.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:bae63772408fd63ad836ec569a7c8f444dd32863d0c67f6e0b25ebbd606afa95", size = 604530 }, + { url = "https://files.pythonhosted.org/packages/5b/76/3165e84e5266d146d967a6cc784ff2fbf6ddd00985a55ec006b72bc39d5d/nh3-0.3.0-cp38-abi3-win_arm64.whl", hash = "sha256:d97d3efd61404af7e5721a0e74d81cdbfc6e5f97e11e731bb6d090e30a7b62b2", size = 585971 }, +] + +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420 }, + { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660 }, + { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382 }, + { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258 }, + { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409 }, + { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317 }, + { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262 }, + { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342 }, + { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610 }, + { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292 }, + { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071 }, + { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074 }, + { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311 }, + { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022 }, + { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135 }, + { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147 }, + { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989 }, + { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052 }, + { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955 }, + { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843 }, + { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876 }, + { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786 }, + { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395 }, + { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374 }, + { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864 }, + { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533 }, + { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007 }, + { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914 }, + { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708 }, + { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678 }, + { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832 }, + { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049 }, + { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935 }, + { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906 }, + { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607 }, + { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110 }, + { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050 }, + { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292 }, + { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913 }, + { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180 }, + { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809 }, + { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410 }, + { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821 }, + { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303 }, + { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524 }, + { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519 }, + { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972 }, + { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439 }, + { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479 }, + { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805 }, + { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830 }, + { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665 }, + { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777 }, + { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856 }, + { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226 }, ] [[package]] @@ -848,6 +1052,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, ] +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -914,6 +1127,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663 }, ] +[[package]] +name = "pyaudio" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/1d/8878c7752febb0f6716a7e1a52cb92ac98871c5aa522cba181878091607c/PyAudio-0.2.14.tar.gz", hash = "sha256:78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87", size = 47066 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/45/8d2b76e8f6db783f9326c1305f3f816d4a12c8eda5edc6a2e1d03c097c3b/PyAudio-0.2.14-cp312-cp312-win32.whl", hash = "sha256:5fce4bcdd2e0e8c063d835dbe2860dac46437506af509353c7f8114d4bacbd5b", size = 144750 }, + { url = "https://files.pythonhosted.org/packages/b0/6a/d25812e5f79f06285767ec607b39149d02aa3b31d50c2269768f48768930/PyAudio-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3", size = 164126 }, + { url = "https://files.pythonhosted.org/packages/3a/77/66cd37111a87c1589b63524f3d3c848011d21ca97828422c7fde7665ff0d/PyAudio-0.2.14-cp313-cp313-win32.whl", hash = "sha256:95328285b4dab57ea8c52a4a996cb52be6d629353315be5bfda403d15932a497", size = 150982 }, + { url = "https://files.pythonhosted.org/packages/a5/8b/7f9a061c1cc2b230f9ac02a6003fcd14c85ce1828013aecbaf45aa988d20/PyAudio-0.2.14-cp313-cp313-win_amd64.whl", hash = "sha256:692d8c1446f52ed2662120bcd9ddcb5aa2b71f38bda31e58b19fb4672fffba69", size = 173655 }, +] + [[package]] name = "pycparser" version = "2.22" @@ -1058,6 +1283,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, ] +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543 }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040 }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102 }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700 }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700 }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318 }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714 }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800 }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540 }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -1168,15 +1409,15 @@ wheels = [ [[package]] name = "rich" -version = "13.9.4" +version = "14.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368 }, ] [[package]] @@ -1257,14 +1498,52 @@ wheels = [ [[package]] name = "s3transfer" -version = "0.13.0" +version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/5d/9dcc100abc6711e8247af5aa561fc07c4a046f72f659c3adea9a449e191a/s3transfer-0.13.0.tar.gz", hash = "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177", size = 150232 } +sdist = { url = "https://files.pythonhosted.org/packages/6d/05/d52bf1e65044b4e5e27d4e63e8d1579dbdec54fce685908ae09bc3720030/s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf", size = 150589 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/17/22bf8155aa0ea2305eefa3a6402e040df7ebe512d1310165eda1e233c3f8/s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", size = 85152 }, + { url = "https://files.pythonhosted.org/packages/6d/4f/d073e09df851cfa251ef7840007d04db3293a0482ce607d2b993926089be/s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724", size = 85308 }, +] + +[[package]] +name = "scipy" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/18/b06a83f0c5ee8cddbde5e3f3d0bb9b702abfa5136ef6d4620ff67df7eee5/scipy-1.16.0.tar.gz", hash = "sha256:b5ef54021e832869c8cfb03bc3bf20366cbcd426e02a58e8a58d7584dfbb8f62", size = 30581216 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/c0/c943bc8d2bbd28123ad0f4f1eef62525fa1723e84d136b32965dcb6bad3a/scipy-1.16.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:7eb6bd33cef4afb9fa5f1fb25df8feeb1e52d94f21a44f1d17805b41b1da3180", size = 36459071 }, + { url = "https://files.pythonhosted.org/packages/99/0d/270e2e9f1a4db6ffbf84c9a0b648499842046e4e0d9b2275d150711b3aba/scipy-1.16.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:1dbc8fdba23e4d80394ddfab7a56808e3e6489176d559c6c71935b11a2d59db1", size = 28490500 }, + { url = "https://files.pythonhosted.org/packages/1c/22/01d7ddb07cff937d4326198ec8d10831367a708c3da72dfd9b7ceaf13028/scipy-1.16.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7dcf42c380e1e3737b343dec21095c9a9ad3f9cbe06f9c05830b44b1786c9e90", size = 20762345 }, + { url = "https://files.pythonhosted.org/packages/34/7f/87fd69856569ccdd2a5873fe5d7b5bbf2ad9289d7311d6a3605ebde3a94b/scipy-1.16.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26ec28675f4a9d41587266084c626b02899db373717d9312fa96ab17ca1ae94d", size = 23418563 }, + { url = "https://files.pythonhosted.org/packages/f6/f1/e4f4324fef7f54160ab749efbab6a4bf43678a9eb2e9817ed71a0a2fd8de/scipy-1.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:952358b7e58bd3197cfbd2f2f2ba829f258404bdf5db59514b515a8fe7a36c52", size = 33203951 }, + { url = "https://files.pythonhosted.org/packages/6d/f0/b6ac354a956384fd8abee2debbb624648125b298f2c4a7b4f0d6248048a5/scipy-1.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03931b4e870c6fef5b5c0970d52c9f6ddd8c8d3e934a98f09308377eba6f3824", size = 35070225 }, + { url = "https://files.pythonhosted.org/packages/e5/73/5cbe4a3fd4bc3e2d67ffad02c88b83edc88f381b73ab982f48f3df1a7790/scipy-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:512c4f4f85912767c351a0306824ccca6fd91307a9f4318efe8fdbd9d30562ef", size = 35389070 }, + { url = "https://files.pythonhosted.org/packages/86/e8/a60da80ab9ed68b31ea5a9c6dfd3c2f199347429f229bf7f939a90d96383/scipy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e69f798847e9add03d512eaf5081a9a5c9a98757d12e52e6186ed9681247a1ac", size = 37825287 }, + { url = "https://files.pythonhosted.org/packages/ea/b5/29fece1a74c6a94247f8a6fb93f5b28b533338e9c34fdcc9cfe7a939a767/scipy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:adf9b1999323ba335adc5d1dc7add4781cb5a4b0ef1e98b79768c05c796c4e49", size = 38431929 }, + { url = "https://files.pythonhosted.org/packages/46/95/0746417bc24be0c2a7b7563946d61f670a3b491b76adede420e9d173841f/scipy-1.16.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:e9f414cbe9ca289a73e0cc92e33a6a791469b6619c240aa32ee18abdce8ab451", size = 36418162 }, + { url = "https://files.pythonhosted.org/packages/19/5a/914355a74481b8e4bbccf67259bbde171348a3f160b67b4945fbc5f5c1e5/scipy-1.16.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:bbba55fb97ba3cdef9b1ee973f06b09d518c0c7c66a009c729c7d1592be1935e", size = 28465985 }, + { url = "https://files.pythonhosted.org/packages/58/46/63477fc1246063855969cbefdcee8c648ba4b17f67370bd542ba56368d0b/scipy-1.16.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:58e0d4354eacb6004e7aa1cd350e5514bd0270acaa8d5b36c0627bb3bb486974", size = 20737961 }, + { url = "https://files.pythonhosted.org/packages/93/86/0fbb5588b73555e40f9d3d6dde24ee6fac7d8e301a27f6f0cab9d8f66ff2/scipy-1.16.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:75b2094ec975c80efc273567436e16bb794660509c12c6a31eb5c195cbf4b6dc", size = 23377941 }, + { url = "https://files.pythonhosted.org/packages/ca/80/a561f2bf4c2da89fa631b3cbf31d120e21ea95db71fd9ec00cb0247c7a93/scipy-1.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b65d232157a380fdd11a560e7e21cde34fdb69d65c09cb87f6cc024ee376351", size = 33196703 }, + { url = "https://files.pythonhosted.org/packages/11/6b/3443abcd0707d52e48eb315e33cc669a95e29fc102229919646f5a501171/scipy-1.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d8747f7736accd39289943f7fe53a8333be7f15a82eea08e4afe47d79568c32", size = 35083410 }, + { url = "https://files.pythonhosted.org/packages/20/ab/eb0fc00e1e48961f1bd69b7ad7e7266896fe5bad4ead91b5fc6b3561bba4/scipy-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb9f147a1b8529bb7fec2a85cf4cf42bdfadf9e83535c309a11fdae598c88e8b", size = 35387829 }, + { url = "https://files.pythonhosted.org/packages/57/9e/d6fc64e41fad5d481c029ee5a49eefc17f0b8071d636a02ceee44d4a0de2/scipy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2b83c37edbfa837a8923d19c749c1935ad3d41cf196006a24ed44dba2ec4358", size = 37841356 }, + { url = "https://files.pythonhosted.org/packages/7c/a7/4c94bbe91f12126b8bf6709b2471900577b7373a4fd1f431f28ba6f81115/scipy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:79a3c13d43c95aa80b87328a46031cf52508cf5f4df2767602c984ed1d3c6bbe", size = 38403710 }, + { url = "https://files.pythonhosted.org/packages/47/20/965da8497f6226e8fa90ad3447b82ed0e28d942532e92dd8b91b43f100d4/scipy-1.16.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:f91b87e1689f0370690e8470916fe1b2308e5b2061317ff76977c8f836452a47", size = 36813833 }, + { url = "https://files.pythonhosted.org/packages/28/f4/197580c3dac2d234e948806e164601c2df6f0078ed9f5ad4a62685b7c331/scipy-1.16.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:88a6ca658fb94640079e7a50b2ad3b67e33ef0f40e70bdb7dc22017dae73ac08", size = 28974431 }, + { url = "https://files.pythonhosted.org/packages/8a/fc/e18b8550048d9224426e76906694c60028dbdb65d28b1372b5503914b89d/scipy-1.16.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ae902626972f1bd7e4e86f58fd72322d7f4ec7b0cfc17b15d4b7006efc385176", size = 21246454 }, + { url = "https://files.pythonhosted.org/packages/8c/48/07b97d167e0d6a324bfd7484cd0c209cc27338b67e5deadae578cf48e809/scipy-1.16.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:8cb824c1fc75ef29893bc32b3ddd7b11cf9ab13c1127fe26413a05953b8c32ed", size = 23772979 }, + { url = "https://files.pythonhosted.org/packages/4c/4f/9efbd3f70baf9582edf271db3002b7882c875ddd37dc97f0f675ad68679f/scipy-1.16.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:de2db7250ff6514366a9709c2cba35cb6d08498e961cba20d7cff98a7ee88938", size = 33341972 }, + { url = "https://files.pythonhosted.org/packages/3f/dc/9e496a3c5dbe24e76ee24525155ab7f659c20180bab058ef2c5fa7d9119c/scipy-1.16.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e85800274edf4db8dd2e4e93034f92d1b05c9421220e7ded9988b16976f849c1", size = 35185476 }, + { url = "https://files.pythonhosted.org/packages/ce/b3/21001cff985a122ba434c33f2c9d7d1dc3b669827e94f4fc4e1fe8b9dfd8/scipy-1.16.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4f720300a3024c237ace1cb11f9a84c38beb19616ba7c4cdcd771047a10a1706", size = 35570990 }, + { url = "https://files.pythonhosted.org/packages/e5/d3/7ba42647d6709251cdf97043d0c107e0317e152fa2f76873b656b509ff55/scipy-1.16.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aad603e9339ddb676409b104c48a027e9916ce0d2838830691f39552b38a352e", size = 37950262 }, + { url = "https://files.pythonhosted.org/packages/eb/c4/231cac7a8385394ebbbb4f1ca662203e9d8c332825ab4f36ffc3ead09a42/scipy-1.16.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f56296fefca67ba605fd74d12f7bd23636267731a72cb3947963e76b8c0a25db", size = 38515076 }, ] [[package]] @@ -1316,28 +1595,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] +[[package]] +name = "speechmatics-rt" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/80/b375a061b11683b9969f21fae1c2043f048694caeb20d8d2cd3f924f2de8/speechmatics_rt-0.4.0.tar.gz", hash = "sha256:33b94834923e739c9a97197b4cb5b62678879f838707d24f8724474dd42ea6fd", size = 25073 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/d3/29c2408b02c8568a805a5255f2dfad894f4adde2b69c22bae06ed3b9a7e2/speechmatics_rt-0.4.0-py3-none-any.whl", hash = "sha256:d51ed416dbdbb3b2bfbf2868e3c8bde4fd023376f5202fb725f1bc0dd6af5bb7", size = 31107 }, +] + [[package]] name = "sse-starlette" -version = "2.3.6" +version = "2.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/f4/989bc70cb8091eda43a9034ef969b25145291f3601703b82766e5172dfed/sse_starlette-2.3.6.tar.gz", hash = "sha256:0382336f7d4ec30160cf9ca0518962905e1b69b72d6c1c995131e0a703b436e3", size = 18284 } +sdist = { url = "https://files.pythonhosted.org/packages/07/3e/eae74d8d33e3262bae0a7e023bb43d8bdd27980aa3557333f4632611151f/sse_starlette-2.4.1.tar.gz", hash = "sha256:7c8a800a1ca343e9165fc06bbda45c78e4c6166320707ae30b416c42da070926", size = 18635 } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/05/78850ac6e79af5b9508f8841b0f26aa9fd329a1ba00bf65453c2d312bcc8/sse_starlette-2.3.6-py3-none-any.whl", hash = "sha256:d49a8285b182f6e2228e2609c350398b2ca2c36216c2675d875f81e93548f760", size = 10606 }, + { url = "https://files.pythonhosted.org/packages/e4/f1/6c7eaa8187ba789a6dd6d74430307478d2a91c23a5452ab339b6fbe15a08/sse_starlette-2.4.1-py3-none-any.whl", hash = "sha256:08b77ea898ab1a13a428b2b6f73cfe6d0e607a7b4e15b9bb23e4a37b087fd39a", size = 10824 }, ] [[package]] name = "starlette" -version = "0.46.2" +version = "0.47.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 } +sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, + { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984 }, ] [[package]] @@ -1377,11 +1669,24 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.14.0" +version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423 } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839 }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, ] [[package]] @@ -1418,6 +1723,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 }, ] +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, +] + [[package]] name = "wrapt" version = "1.17.2" From 78c461fabacb67dd9a06284d9b4c6a1f3cf46d40 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 29 Jul 2025 10:34:30 -0700 Subject: [PATCH 2/4] Conversational Init --- examples/conversational/client.py | 316 ++++++++++++++++++ examples/conversational/main.py | 27 ++ fastloop/integrations/conversation.py | 314 +++++++++-------- fastloop/integrations/plugins/Deepgram.py | 183 +++++----- fastloop/integrations/plugins/ElevenLabs.py | 82 ++--- fastloop/integrations/plugins/SpeechMatics.py | 102 +++--- fastloop/integrations/plugins/base.py | 20 ++ pyproject.toml | 2 +- recorded_audio.wav | Bin 0 -> 163884 bytes uv.lock | 21 +- 10 files changed, 758 insertions(+), 309 deletions(-) create mode 100644 examples/conversational/client.py create mode 100644 examples/conversational/main.py create mode 100644 fastloop/integrations/plugins/base.py create mode 100644 recorded_audio.wav diff --git a/examples/conversational/client.py b/examples/conversational/client.py new file mode 100644 index 0000000..a7cac28 --- /dev/null +++ b/examples/conversational/client.py @@ -0,0 +1,316 @@ +import asyncio +import json +import queue +import time +import wave +from typing import Optional + +import numpy as np +import pyaudio +import webrtcvad +import websockets +from scipy.io.wavfile import write + + +class SpeechMicrophoneStreamer: + def __init__( + self, + websocket_manager: "WebsocketManager", + chunk_size: int = 16 * 1024, + sample_rate: int = 16000, + channels: int = 1, + # Simple optimization parameters + silence_threshold: float = 0.01, # RMS threshold for silence + silence_timeout: float = 2.0, # Stop sending after 2s of silence + send_interval: float = 0.5, # Send audio every 500ms instead of immediately + ): + """ + Initialize the microphone streamer. + + Args: + websocket_url: WebSocket server URL + chunk_size: Audio chunk size in frames + sample_rate: Sample rate in Hz + channels: Number of audio channels (1 for mono, 2 for stereo) + silence_threshold: RMS threshold below which audio is considered silence + silence_timeout: Stop transmission after this much continuous silence + send_interval: Batch audio data for this duration before sending + """ + self.chunk_size = chunk_size + self.sample_rate = sample_rate + self.channels = channels + self.format = pyaudio.paInt16 # 16-bit audio + + # Simple optimization parameters + self.silence_threshold = silence_threshold + self.silence_timeout = silence_timeout + self.send_interval = send_interval + + # Audio components + self.audio = pyaudio.PyAudio() + self.stream: Optional[pyaudio.Stream] = None + + # Threading components + self.audio_queue: queue.Queue[bytes] = queue.Queue() + self.is_recording = False + self.websocket = None + self.stored_audio: list[bytes] = [] + + self.vad = webrtcvad.Vad() + self.vad.set_mode(1) + + # Simple state tracking + self.last_speech_time = None + self.audio_buffer: list[bytes] = [] + self.last_send_time = time.time() + + self.processing_task = None + # Websocket manager + self.websocket_manager = websocket_manager + + def calculate_rms(self, audio_data: bytes) -> float: + """Calculate RMS (Root Mean Square) of audio data.""" + try: + audio_array = np.frombuffer(audio_data, dtype=np.int16) + audio_float = audio_array.astype(np.float32) / 32768.0 + rms = np.sqrt(np.mean(audio_float**2)) + return rms + except Exception: + return 0.0 + + def is_speech(self, frame: bytes, sample_rate: int) -> bool: + """Check if frame contains speech using both RMS and WebRTC VAD""" + # First check RMS - if too quiet, definitely not speech + rms = self.calculate_rms(frame) + if rms < self.silence_threshold: + return False + + # If loud enough, use WebRTC VAD for better detection + try: + # WebRTC VAD requires specific frame lengths + frame_size = 480 * 2 # 30ms at 16kHz, 16-bit + if len(frame) >= frame_size: + chunk = frame[:frame_size] + return self.vad.is_speech(chunk, sample_rate) + else: + # If frame too small, pad it + padded_frame = frame + b"\x00" * (frame_size - len(frame)) + return self.vad.is_speech(padded_frame[:frame_size], sample_rate) + except Exception: + # Fallback to RMS-based detection + return rms > self.silence_threshold * 2 + + def should_send_audio(self) -> bool: + """Simple logic: send if we've heard speech recently""" + if self.last_speech_time is None: + return False + + time_since_speech = time.time() - self.last_speech_time + return time_since_speech < self.silence_timeout + + def list_audio_devices(self): + """List available audio input devices.""" + print("Available audio devices:") + for i in range(self.audio.get_device_count()): + info = self.audio.get_device_info_by_index(i) + if info["maxInputChannels"] > 0: # type: ignore + print(f" {i}: {info['name']} - {info['maxInputChannels']} channels") + + async def send_stop_speaking(self): + await self.websocket_manager.send_stop_speaking() + + def audio_callback(self, in_data: bytes, *_, **__): + if not self.is_recording: + return (None, pyaudio.paContinue) + + current_time = time.time() + + # Check for speech + if self.is_speech(in_data, self.sample_rate): + self.last_speech_time = current_time + + # Always buffer audio if we've heard speech recently + if self.should_send_audio(): + self.audio_buffer.append(in_data) + + # Send batched audio periodically + if ( + current_time - self.last_send_time >= self.send_interval + and self.audio_buffer + and self.should_send_audio() + ): + # Combine buffered audio and send + combined_audio = b"".join(self.audio_buffer) + self.audio_queue.put(combined_audio) + + # Reset buffer and timer + self.audio_buffer = [] + self.last_send_time = current_time + + return (None, pyaudio.paContinue) + + async def process_audio_buffer(self): + while self.is_recording: + try: + # Get audio data with timeout + audio_data: bytes = self.audio_queue.get(timeout=0.1) + self.stored_audio.append(audio_data) + + # Send to WebSocket + await self.websocket_manager.send_audio_data(audio_data) + print(f"Sent {len(audio_data)} bytes of audio") + await asyncio.sleep(1) + + except queue.Empty: + # No audio data available, continue + continue + except websockets.exceptions.ConnectionClosed: + print("WebSocket connection closed") + break + except Exception as e: + print(f"Error sending audio data: {e}") + + def start_recording(self, device_index: Optional[int] = None): + """Start recording audio from microphone.""" + try: + self.stream = self.audio.open( + format=self.format, + channels=self.channels, + rate=self.sample_rate, + input=True, + input_device_index=device_index, + frames_per_buffer=self.chunk_size, + stream_callback=self.audio_callback, + ) + + self.is_recording = True + self.stream.start_stream() + print(f"Started recording from microphone (device: {device_index or 'default'})") + print(f"Silence threshold: {self.silence_threshold}, Timeout: {self.silence_timeout}s") + + except Exception as e: + print(f"Error starting recording: {e}") + raise + + def stop_recording(self): + """Stop recording audio.""" + self.is_recording = False + + if self.stream: + self.stream.stop_stream() + self.stream.close() + self.stream = None + + print("Stopped recording") + + def save_audio_to_file(self, filename: str = "recorded_audio.wav"): + """Save recorded audio chunks to a WAV file.""" + if self.stored_audio: + try: + combined_audio = np.frombuffer(b"".join(self.stored_audio), dtype=np.int16) + write(filename, self.sample_rate, combined_audio) + print(f"Saved {len(self.stored_audio)} audio chunks to {filename}") + return True + except Exception as e: + print(f"Error saving audio file: {e}") + return False + return False + + def start_streaming(self, device_index: Optional[int] = None, start_recording: bool = True): + """Start streaming audio to WebSocket.""" + if start_recording: + self.start_recording(device_index) + self.processing_task = asyncio.create_task(self.process_audio_buffer()) + + def cleanup(self): + """Clean up resources.""" + self.stop_recording() + self.audio.terminate() + + +class WebsocketManager: + def __init__(self, websocket_url: str): + self.websocket_url = websocket_url + self.websocket = None + self.websocket_receive_task = None + self.running = False + + async def connect(self): + if self.websocket: + return + + self.websocket = await websockets.connect(self.websocket_url) + self.running = True + + async def handle_websocket_receive(self): + if not self.websocket: + return + + while self.running: + try: + message = await self.websocket.recv() + print(message) + await asyncio.sleep(0.1) + except websockets.exceptions.ConnectionClosed: + break + + async def send_audio_data(self, audio_data: bytes): + if self.websocket: + await self.websocket.send(audio_data) + + async def send_stop_speaking(self): + if self.websocket: + await self.websocket.send(json.dumps({"type": "on_user_stop_speaking"})) + + +class ConversationManager: + def __init__(self, websocket_url: str): + self.websocket_url = websocket_url + self.websocket_manager = WebsocketManager(websocket_url) + self.streamer = SpeechMicrophoneStreamer( + websocket_manager=self.websocket_manager, + chunk_size=1024, + ) + self.audio_player = None + + async def start_conversation(self): + await self.websocket_manager.connect() + self.streamer.start_streaming() + await self.websocket_manager.handle_websocket_receive() + + # This is used to test the server with an audio file + async def pipe_in_audio_file(self, audio_file_path: str): + await self.websocket_manager.connect() + self.streamer.start_streaming(start_recording=False) + self.streamer.is_recording = True + + with wave.open(audio_file_path, "rb") as wf: + while True: + data = wf.readframes(self.streamer.chunk_size * 16) + if not data: + break + self.streamer.audio_queue.put(data) + await asyncio.sleep(30) + + +# Usage example +async def main(): + # WebSocket server URL (replace with your server) + websocket_url = "ws://localhost:8000/chat/conversation/start" + + # Create streamer instance with simple optimizations + streamer = ConversationManager( + websocket_url=websocket_url, + ) + + print("Starting optimized audio streaming... Press Ctrl+C to stop") + try: + await streamer.pipe_in_audio_file("./_sandbox/input.wav") + except BaseException as e: + print(f"Stopping recording: {e}") + streamer.streamer.stop_recording() + streamer.streamer.save_audio_to_file() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/conversational/main.py b/examples/conversational/main.py new file mode 100644 index 0000000..a41086a --- /dev/null +++ b/examples/conversational/main.py @@ -0,0 +1,27 @@ +from fastloop import FastLoop, LoopContext +from fastloop.integrations.conversation import ( + ConversationIntegration, + GenerateResponseEvent, + StartConversationEvent, +) + +app = FastLoop(name="my-app") + + +# @app.loop(name="start_conversation"]) +async def start_conversation(context: LoopContext): + print("Conversation started") + + +@app.loop(name="chat", start_event=StartConversationEvent, integrations=[ConversationIntegration()]) +async def chat_loop(context: LoopContext): + # User can trigger something here, for example a call or audio stream + # await context.wait_for(StartConversationEvent, timeout=5.0) + await context.wait_for(GenerateResponseEvent) + print("Conversation started") + + # context. + + +if __name__ == "__main__": + app.run(port=8000) diff --git a/fastloop/integrations/conversation.py b/fastloop/integrations/conversation.py index 3d38429..5259eb6 100644 --- a/fastloop/integrations/conversation.py +++ b/fastloop/integrations/conversation.py @@ -1,189 +1,219 @@ -from typing import TYPE_CHECKING, Any -from fastapi import WebSocket, WebSocketDisconnect -import base64 -from .plugins.SpeechMatics import SpeechmaticsSpeechToTextManager -from concurrent.futures import ProcessPoolExecutor import asyncio -import uuid -import dotenv import logging +import uuid +from concurrent.futures import ProcessPoolExecutor +from typing import TYPE_CHECKING, Any +import dotenv +import numpy as np +from fastapi import WebSocket, WebSocketDisconnect +from scipy.io.wavfile import write from ..integrations import Integration from ..logging import setup_logger from ..loop import LoopEvent from ..types import IntegrationType +from .plugins.Deepgram import DeepgramSpeechToTextManager + dotenv.load_dotenv() if TYPE_CHECKING: from ..fastloop import FastLoop logger = setup_logger(__name__) -logger.setLevel(logging.DEBUG ) +logger.setLevel(logging.DEBUG) + class StartConversationEvent(LoopEvent): type: str = "start_conversation" - - + + class OnUserAudioDataEvent(LoopEvent): type: str = "on_user_audio_data" audio: bytes + class GenerateResponseEvent(LoopEvent): type: str = "generate_response" + class GenerationationInterruptedEvent(LoopEvent): type: str = "generationation_interrupted" - + + class AudioStreamResponseEvent(LoopEvent): type: str = "audio_stream_response" audio: bytes - class ConversationIntegration(Integration): - def __init__(self): - self.queue: asyncio.Queue[Any] = asyncio.Queue() - self.executor = ProcessPoolExecutor() - self.is_running: bool = False - - def type(self) -> IntegrationType: - return IntegrationType.CONVERSATION - - def register(self, fastloop: "FastLoop", loop_name: str) -> None: - fastloop.register_events( - [ - StartConversationEvent, - OnUserAudioDataEvent, - GenerateResponseEvent, - GenerationationInterruptedEvent, - AudioStreamResponseEvent, - ] - ) - - self._fastloop: FastLoop = fastloop - self._fastloop.app.add_api_websocket_route( - path=f"/{loop_name}/conversation/start", - endpoint=self._handle_start_conversation, - ) - self.loop_name: str = loop_name - - - async def _handle_websocket_event(self, websocket: WebSocket, request_id: str): - audio_buffer: list[bytes] = [] - while True: - try: - data = await websocket.receive_json() - match data.get("type"): - case "on_user_audio_data": - audio_data = base64.b64decode(data.get("audio").encode("utf-8")) - audio_buffer.append(audio_data) - self.queue.put_nowait(OnUserAudioDataEvent( - loop_id=request_id or None, - audio=audio_data, - )) - case "on_user_stop_speaking": - self.queue.put_nowait(GenerateResponseEvent( - loop_id=request_id or None, - )) - case _: - logger.error(f"Unknown event: {data}") - continue - except WebSocketDisconnect: - logger.info("Client disconnected") - self.is_running = False - break - except Exception as e: - logger.error(f"Error receiving data: {e}") - self.is_running = False - break - - async def _handle_start_conversation(self, websocket: WebSocket ): - await websocket.accept() - self.is_running = True - request_id = str(uuid.uuid4()) - - stt_manager = SpeechmaticsSpeechToTextManager(request_id, websocket) - await stt_manager.start() - print("starting conversation") - asyncio.create_task(self._handle_websocket_event(websocket, request_id)) - # llm_manager = LLMManager(self._fastloop, request_id) - # stt_task = self.executor.submit(stt_manager.on_voice_stream, websocket) - # tts_manager = TextToSpeechManager(self._fastloop, request_id) - while self.is_running: - loop_event: Any = await self.queue.get() - - if isinstance(loop_event, OnUserAudioDataEvent): - await stt_manager.send_audio(loop_event.audio) - elif isinstance(loop_event, GenerateResponseEvent): + def __init__(self): + self.queue: asyncio.Queue[Any] = asyncio.Queue() + self.executor = ProcessPoolExecutor() + self.is_running: bool = False + + def type(self) -> IntegrationType: + return IntegrationType.CONVERSATION + + def register(self, fastloop: "FastLoop", loop_name: str) -> None: + fastloop.register_events( + [ + StartConversationEvent, + OnUserAudioDataEvent, + GenerateResponseEvent, + GenerationationInterruptedEvent, + AudioStreamResponseEvent, + ] + ) + + self._fastloop: FastLoop = fastloop + self._fastloop.app.add_api_websocket_route( + path=f"/{loop_name}/conversation/start", + endpoint=self._handle_start_conversation, + ) + self.loop_name: str = loop_name + + def save_audio_to_file(self, audio_chunks: list[bytes], filename: str = "recorded_audio.wav"): + """Save recorded audio chunks to a WAV file.""" + if audio_chunks: + try: + combined_audio = np.frombuffer(b"".join(audio_chunks), dtype=np.int16) + write(filename, 16000, combined_audio) + print(f"Saved {len(audio_chunks)} audio chunks to {filename}") + return True + except Exception as e: + print(f"Error saving audio file: {e}") + return False + return False + + async def _handle_websocket_event(self, websocket: WebSocket, request_id: str): + audio_buffer: list[bytes] = [] + while True: + try: + data = await websocket.receive() + if "bytes" in data: + audio_buffer.append(data["bytes"]) + self.queue.put_nowait( + OnUserAudioDataEvent( + loop_id=request_id or None, + audio=data["bytes"], + ) + ) + continue + + match data.get("type"): + case "on_user_stop_speaking": + self.queue.put_nowait( + GenerateResponseEvent( + loop_id=request_id or None, + ) + ) + case _: + # logger.error(f"Unknown event: {data}") + continue + except WebSocketDisconnect: + logger.info("Client disconnected") + self.is_running = False + break + except BaseException as e: + logger.error(f"Error receiving data: {e}") + self.is_running = False + break + + async def _handle_start_conversation(self, websocket: WebSocket): + await websocket.accept() + self.is_running = True + request_id = str(uuid.uuid4()) + + # stt_manager = SpeechmaticsSpeechToTextManager(request_id, websocket) + stt_manager = DeepgramSpeechToTextManager(request_id, websocket) + await stt_manager.start() + print("starting conversation") + asyncio.create_task(self._handle_websocket_event(websocket, request_id)) + # llm_manager = LLMManager(self._fastloop, request_id) + # stt_task = self.executor.submit(stt_manager.on_voice_stream, websocket) + # tts_manager = TextToSpeechManager(self._fastloop, request_id) + + while self.is_running: + try: + loop_event: Any = self.queue.get_nowait() + except asyncio.QueueEmpty: + await asyncio.sleep(0.1) + continue + except BaseException as e: + logger.error(f"Error processing event: {e}") + self.is_running = False + break + + if isinstance(loop_event, OnUserAudioDataEvent): + await stt_manager.send_audio(loop_event.audio) + elif isinstance(loop_event, GenerateResponseEvent): + pass + # llm_manager.generate_response(stt_manager.get_text()) + elif isinstance(loop_event, GenerationationInterruptedEvent): + pass + elif isinstance(loop_event, AudioStreamResponseEvent): + pass + # case "on_agent_audio_data": + # pass + # case "on_agent_stop_speaking": + # pass + # case "on_user_generation_interrupted": + # pass + # case "on_generation_completed": + # pass + # case "on_error": + # pass + + # # loop_state: LoopState = await loop_event_handler(mapped_request) + # mapped_request: dict[str, Any] = loop_event.to_dict() if loop_event else {} + + # loop_event_handler = self._fastloop.loop_event_handlers.get(self.loop_name) + # if not loop_event_handler: + # continue + + # loop: LoopState = await loop_event_handler(mapped_request) + # if loop.loop_id: + # await self._fastloop.state_manager.set_loop_mapping( + # f"conversation:{loop.loop_id}", loop.loop_id + # ) + + # if loop_state.loop_id: + # await self._fastloop.state_manager.set_loop_mapping( + # f"conversation:{loop_state.loop_id}", loop_state.loop_id + # ) + + await stt_manager.stop() + + async def emit(self, event: LoopEvent): pass - # llm_manager.generate_response(stt_manager.get_text()) - elif isinstance(loop_event, GenerationationInterruptedEvent): - pass - elif isinstance(loop_event, AudioStreamResponseEvent): - pass - # case "on_agent_audio_data": - # pass - # case "on_agent_stop_speaking": - # pass - # case "on_user_generation_interrupted": - # pass - # case "on_generation_completed": - # pass - # case "on_error": - # pass - - # # loop_state: LoopState = await loop_event_handler(mapped_request) - # mapped_request: dict[str, Any] = loop_event.to_dict() if loop_event else {} - - # loop_event_handler = self._fastloop.loop_event_handlers.get(self.loop_name) - # if not loop_event_handler: - # continue - - # loop: LoopState = await loop_event_handler(mapped_request) - # if loop.loop_id: - # await self._fastloop.state_manager.set_loop_mapping( - # f"conversation:{loop.loop_id}", loop.loop_id - # ) - - - - # if loop_state.loop_id: - # await self._fastloop.state_manager.set_loop_mapping( - # f"conversation:{loop_state.loop_id}", loop_state.loop_id - # ) - - - async def emit(self, event: LoopEvent): - pass - - def events(self) -> list[Any]: - return [StartConversationEvent] - + + def events(self) -> list[Any]: + return [StartConversationEvent] + class TextToSpeechManager: - def __init__(self, fastloop: "FastLoop", request_id: str): - self.fastloop = fastloop - - def synthesize(self, text: str): - pass + def __init__(self, fastloop: "FastLoop", request_id: str): + self.fastloop = fastloop + + def synthesize(self, text: str): + pass + class LLMManager: - def __init__(self, fastloop: "FastLoop", request_id: str): - self.fastloop = fastloop + def __init__(self, fastloop: "FastLoop", request_id: str): + self.fastloop = fastloop - def generate_response(self, text: str): - pass + def generate_response(self, text: str): + pass # class ConversationManager: # def __init__(self, fastloop: "FastLoop"): # self.fastloop = fastloop - + # def start_conversation(self, loop_id: str): # pass - + # def generate_response(self, loop_id: str): # pass - - diff --git a/fastloop/integrations/plugins/Deepgram.py b/fastloop/integrations/plugins/Deepgram.py index b3513c3..e6ec12d 100644 --- a/fastloop/integrations/plugins/Deepgram.py +++ b/fastloop/integrations/plugins/Deepgram.py @@ -1,87 +1,108 @@ +import asyncio import os from typing import Any -from fastapi import WebSocket -from deepgram import DeepgramClient, DeepgramClientOptions, LiveTranscriptionEvents, LiveOptions -import asyncio + import dotenv +from deepgram import ( + DeepgramClient, + DeepgramClientOptions, + LiveOptions, + LiveTranscriptionEvents, +) +from fastapi import WebSocket + +from fastloop.integrations.plugins.base import BaseSpeechToTextManager dotenv.load_dotenv() -class DeepgramSpeechToTextManager: - def __init__(self, request_id: str , websocket: WebSocket): - self.request_id = request_id - self.websocket = websocket - - async def start(self): - # self.dg = DeepgramClient(api_key=os.getenv("DEEPGRAM_API_KEY", "")) - config = DeepgramClientOptions(options={"keepalive": "true"}) - deepgram: DeepgramClient = DeepgramClient(os.getenv("DEEPGRAM_API_KEY", ""), config) - self.dg_connection = deepgram.listen.asyncwebsocket.v("1") - - async def on_message(*_, result: Any, **__): - sentence = result.channel.alternatives[0].transcript - print(f"Transcript: {sentence}") - if len(sentence) > 0: - # Send transcript back to client`` - response = { - "type": "transcript", - "text": sentence, - "is_final": result.speech_final - } - await self.websocket.send_json(response) - - async def on_error(error: Any, *_, **__): - print(f"Deepgram error: {error}") - error_response = { - "type": "error", - "message": str(error) - } - # await self.websocket.send_json(error_response) - - async def on_close(error: Any, *d, **k): - print(f"Deepgram connection closed: {error}") - # await self.websocket.send_json(error_response) - - async def on_speech_started(result: Any, *_, **__): - print(f"Speech started: {result}") - - async def on_utterance_end(result: Any, *_, **__): - print(f"Utterance end: {result}") - - self.dg_connection.on(LiveTranscriptionEvents.Transcript, on_message) # type: ignore - self.dg_connection.on(LiveTranscriptionEvents.Error, on_error) # type: ignore - self.dg_connection.on(LiveTranscriptionEvents.Close, on_close) # type: ignore - self.dg_connection.on(LiveTranscriptionEvents.SpeechStarted, on_speech_started) # type: ignore - self.dg_connection.on(LiveTranscriptionEvents.UtteranceEnd, on_utterance_end) # type: ignore - - print("starting deepgram connection") - - if not await self.dg_connection.start( - LiveOptions( - model="nova-3", - punctuate=True, - encoding="linear16", - channels=1, - sample_rate=16000, - vad_events=True, - endpointing=False, - interim_results=False, - ) - ): # type: ignore - print("Failed to start Deepgram connection") - - print("deepgram connection started") - - while not await self.dg_connection.is_connected(): - await asyncio.sleep(0.1) - - print("deepgram connection connected") - - async def send_audio(self, audio: bytes): - try: - if await self.dg_connection.is_connected(): - await self.dg_connection.send(audio) - else: - return - except Exception as e: - print(f"Error sending audio: {e}") + +class DeepgramSpeechToTextManager(BaseSpeechToTextManager): + def __init__(self, request_id: str, websocket: WebSocket): + self.request_id = request_id + self.websocket = websocket + self.buffer = b"" + self.buffer_lock = asyncio.Lock() + self.is_running = False + + async def start(self): + # self.dg = DeepgramClient(api_key=os.getenv("DEEPGRAM_API_KEY", "")) + config = DeepgramClientOptions(options={"keepalive": "true"}) + deepgram: DeepgramClient = DeepgramClient(os.getenv("DEEPGRAM_API_KEY", ""), config) + self.dg_connection = deepgram.listen.asyncwebsocket.v("1") + + async def on_message(*_, result: Any, **__): + sentence = result.channel.alternatives[0].transcript + print(f"Transcript: {sentence}") + if len(sentence) > 0: + # Send transcript back to client`` + response = {"type": "transcript", "text": sentence, "is_final": result.speech_final} + await self.websocket.send_json(response) + + async def on_error(error: Any, *_, **__): + print(f"Deepgram error: {error}") + # await self.websocket.send_json(error_response) + + async def on_close(error: Any, *d, **k): + print(f"Deepgram connection closed: {error}") + # await self.websocket.send_json(error_response) + + async def on_speech_started(result: Any, *_, **__): + print(f"Speech started: {result}") + + async def on_utterance_end(result: Any, *_, **__): + print(f"Utterance end: {result}") + + self.dg_connection.on(LiveTranscriptionEvents.Transcript, on_message) # type: ignore + self.dg_connection.on(LiveTranscriptionEvents.Error, on_error) # type: ignore + self.dg_connection.on(LiveTranscriptionEvents.Close, on_close) # type: ignore + self.dg_connection.on(LiveTranscriptionEvents.SpeechStarted, on_speech_started) # type: ignore + self.dg_connection.on(LiveTranscriptionEvents.UtteranceEnd, on_utterance_end) # type: ignore + + print("starting deepgram connection") + + if not await self.dg_connection.start( + LiveOptions( + model="nova-3", + smart_format=True, + encoding="linear16", + channels=1, + sample_rate=16000, + ) + ): # type: ignore + print("Failed to start Deepgram connection") + + print("deepgram connection started") + + while not await self.dg_connection.is_connected(): + await asyncio.sleep(0.1) + + print("deepgram connection connected") + self.is_running = True + self.process_audio_task = asyncio.create_task(self.process_audio()) + + async def process_audio(self): + while self.is_running: + async with self.buffer_lock: + audio = self.buffer + print("sending audio", len(audio) / 1024, "kb") + if len(audio) > 0: + await self.dg_connection.send(audio) + self.buffer = b"" + await asyncio.sleep(0.5) + + async def send_audio(self, audio: bytes): + try: + if await self.dg_connection.is_connected(): + # await self.dg_connection.send(audio) + async with self.buffer_lock: + self.buffer += audio + else: + return + except Exception as e: + print(f"Error sending audio: {e}") + + async def stop(self): + print("stopping deepgram connection") + self.is_running = False + self.process_audio_task.cancel() + await self.dg_connection.finish() # type: ignore diff --git a/fastloop/integrations/plugins/ElevenLabs.py b/fastloop/integrations/plugins/ElevenLabs.py index 9ef650d..b2a41af 100644 --- a/fastloop/integrations/plugins/ElevenLabs.py +++ b/fastloop/integrations/plugins/ElevenLabs.py @@ -1,52 +1,56 @@ -import os -import dotenv import asyncio +import os import time - +import dotenv from elevenlabs.client import AsyncElevenLabs from fastapi import WebSocket + +from fastloop.integrations.plugins.base import BaseSpeechToTextManager + from .utils import bytes_to_wav dotenv.load_dotenv() TIME_BETWEEN_TRANSCRIPTIONS = 5 -# Elevenlabs does not support realtime transcription so we -# we hack together a solution by sending audio in chunks and +# Elevenlabs does not support realtime transcription so we +# we hack together a solution by sending audio in chunks and # waiting for the transcription to be generated before sending the next chunk. -class ElevenLabsSpeechToTextManager: - def __init__(self, request_id: str, websocket: WebSocket): - self.request_id = request_id - self.websocket = websocket - self.client = AsyncElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY")) - self.transcripts: list[str | None] = [] - self.lock = asyncio.Lock() - self.audio_buffer: list[bytes] = [] - self.last_transcription_time = time.time() - - - async def start(self): - pass - - async def _generate_transcript_async(self, index: int, audio: bytes): - print(f"Generating transcript for {index}") - transcript = await self.client.speech_to_text.convert( - model_id="scribe_v1", - file=bytes_to_wav(audio), - language_code="eng", - tag_audio_events=False, - ) - self.transcripts[index] = transcript.text - print(f"Transcript: {self.transcripts[index]}") - - async def send_audio(self, audio: bytes): - self.audio_buffer.append(audio) - if time.time() - self.last_transcription_time > TIME_BETWEEN_TRANSCRIPTIONS: - index = len(self.transcripts) - self.transcripts.append(None) - - asyncio.create_task(self._generate_transcript_async(index, b"".join(self.audio_buffer))) - self.audio_buffer = [] - self.last_transcription_time = time.time() \ No newline at end of file +class ElevenLabsSpeechToTextManager(BaseSpeechToTextManager): + def __init__(self, request_id: str, websocket: WebSocket): + self.request_id = request_id + self.websocket = websocket + self.client = AsyncElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY")) + self.transcripts: list[str | None] = [] + self.lock = asyncio.Lock() + self.audio_buffer: list[bytes] = [] + self.last_transcription_time = time.time() + + async def start(self): + pass + + async def stop(self): + pass + + async def _generate_transcript_async(self, index: int, audio: bytes): + print(f"Generating transcript for {index}") + transcript = await self.client.speech_to_text.convert( + model_id="scribe_v1", + file=bytes_to_wav(audio), + language_code="eng", + tag_audio_events=False, + ) + self.transcripts[index] = transcript.text + print(f"Transcript: {self.transcripts[index]}") + + async def send_audio(self, audio: bytes): + self.audio_buffer.append(audio) + if time.time() - self.last_transcription_time > TIME_BETWEEN_TRANSCRIPTIONS: + index = len(self.transcripts) + self.transcripts.append(None) + + asyncio.create_task(self._generate_transcript_async(index, b"".join(self.audio_buffer))) + self.audio_buffer = [] + self.last_transcription_time = time.time() diff --git a/fastloop/integrations/plugins/SpeechMatics.py b/fastloop/integrations/plugins/SpeechMatics.py index 30d6e08..fd46427 100644 --- a/fastloop/integrations/plugins/SpeechMatics.py +++ b/fastloop/integrations/plugins/SpeechMatics.py @@ -1,54 +1,66 @@ import asyncio +import os import time from typing import Any -from speechmatics.rt import AsyncClient, TranscriptionConfig, ServerMessageType, AudioFormat, AudioEncoding -from fastapi import WebSocket -import os + import dotenv +from fastapi import WebSocket +from speechmatics.rt import ( + AsyncClient, + AudioEncoding, + AudioFormat, + ServerMessageType, + TranscriptionConfig, +) + +from fastloop.integrations.plugins.base import BaseSpeechToTextManager dotenv.load_dotenv() TIME_BETWEEN_TRANSCRIPTIONS = 5 -class SpeechmaticsSpeechToTextManager: - def __init__(self, request_id: str, websocket: WebSocket): - self.request_id = request_id - self.websocket = websocket - self.client = AsyncClient(api_key=os.getenv("SPEECHMATICS_API_KEY")) - self.session = None - self.lock = asyncio.Lock() - self.audio_buffer: list[bytes] = [] - self.last_transcription_time = time.time() - - async def start(self): - await self.client.start_session( - transcription_config=TranscriptionConfig( - language="en", - enable_partials=True, - max_delay=1, - ), - audio_format=AudioFormat( - encoding=AudioEncoding.PCM_S16LE, - sample_rate=16000, - chunk_size=1024, - ), - ) - - def on_message(message: dict[str, Any]): - results = message["results"] - for result in results: - print(result) - - def on_recognition_started(*_, **__): - print("Recognition started") - - self.client.on(ServerMessageType.RECOGNITION_STARTED, on_recognition_started) - self.client.on(ServerMessageType.ADD_PARTIAL_TRANSCRIPT, on_message) - - async def send_audio(self, audio: bytes): - self.audio_buffer.append(audio) - if time.time() - self.last_transcription_time > TIME_BETWEEN_TRANSCRIPTIONS: - await self.client.send_audio(b"".join(self.audio_buffer)) - self.audio_buffer = [] - self.last_transcription_time = time.time() - \ No newline at end of file + +class SpeechmaticsSpeechToTextManager(BaseSpeechToTextManager): + def __init__(self, request_id: str, websocket: WebSocket): + self.request_id = request_id + self.websocket = websocket + self.client = AsyncClient(api_key=os.getenv("SPEECHMATICS_API_KEY")) + self.session = None + self.lock = asyncio.Lock() + self.audio_buffer: list[bytes] = [] + self.last_transcription_time = time.time() + + async def start(self): + await self.client.start_session( + transcription_config=TranscriptionConfig( + language="en", + enable_partials=True, + max_delay=1, + ), + audio_format=AudioFormat( + encoding=AudioEncoding.PCM_S16LE, + sample_rate=16000, + chunk_size=1024, + ), + ) + + def on_message(message: dict[str, Any]): + results = message["results"] + for result in results: + print(result) + + def on_recognition_started(*_, **__): + print("Recognition started") + + self.client.on(ServerMessageType.RECOGNITION_STARTED, on_recognition_started) + self.client.on(ServerMessageType.ADD_TRANSCRIPT, on_message) + + async def send_audio(self, audio: bytes): + # self.audio_buffer.append(audio) + # if time.time() - self.last_transcription_time > TIME_BETWEEN_TRANSCRIPTIONS: + await self.client.send_audio(audio) + # self.audio_buffer = [] + # self.last_transcription_time = time.time() + + async def stop(self): + await self.client.close() diff --git a/fastloop/integrations/plugins/base.py b/fastloop/integrations/plugins/base.py new file mode 100644 index 0000000..09e94fd --- /dev/null +++ b/fastloop/integrations/plugins/base.py @@ -0,0 +1,20 @@ +from abc import ABC, abstractmethod + +from fastapi import WebSocket + + +class BaseSpeechToTextManager(ABC): + def __init__(self, request_id: str, websocket: WebSocket): + raise NotImplementedError + + @abstractmethod + async def start(self): + raise NotImplementedError + + @abstractmethod + async def stop(self): + raise NotImplementedError + + @abstractmethod + async def send_audio(self, audio: bytes): + raise NotImplementedError diff --git a/pyproject.toml b/pyproject.toml index 610c9c0..110a3c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,5 +122,5 @@ docstring-code-format = true [dependency-groups] dev = [ - "mypy>=1.17.0", + "mypy", ] diff --git a/recorded_audio.wav b/recorded_audio.wav new file mode 100644 index 0000000000000000000000000000000000000000..17e270ad412478dd9b1193ab3d2260686a04a217 GIT binary patch literal 163884 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 literal 0 HcmV?d00001 diff --git a/uv.lock b/uv.lock index ae1cd92..705c552 100644 --- a/uv.lock +++ b/uv.lock @@ -479,8 +479,10 @@ dependencies = [ { name = "pyyaml" }, { name = "redis" }, { name = "scipy" }, + { name = "setuptools" }, { name = "slack-sdk" }, { name = "speechmatics-rt" }, + { name = "webrtcvad" }, { name = "websockets" }, ] @@ -513,15 +515,17 @@ requires-dist = [ { name = "pyyaml", specifier = ">=6.0" }, { name = "redis", specifier = ">=6.2.0" }, { name = "scipy", specifier = ">=1.16.0" }, + { name = "setuptools", specifier = ">=80.9.0" }, { name = "slack-sdk", specifier = ">=3.27.0" }, { name = "speechmatics-rt", specifier = ">=0.4.0" }, { name = "twine", marker = "extra == 'dev'", specifier = ">=4.0.0" }, + { name = "webrtcvad", specifier = ">=2.0.10" }, { name = "websockets", specifier = ">=15.0.1" }, ] provides-extras = ["dev"] [package.metadata.requires-dev] -dev = [{ name = "mypy", specifier = ">=1.17.0" }] +dev = [{ name = "mypy" }] [[package]] name = "fastmcp" @@ -1559,6 +1563,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221 }, ] +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -1723,6 +1736,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 }, ] +[[package]] +name = "webrtcvad" +version = "2.0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/34/e2de2d97f3288512b9ea56f92e7452f8207eb5a0096500badf9dfd48f5e6/webrtcvad-2.0.10.tar.gz", hash = "sha256:f1bed2fb25b63fb7b1a55d64090c993c9c9167b28485ae0bcdd81cf6ede96aea", size = 66156 } + [[package]] name = "websockets" version = "15.0.1" From 6e92ae8f0106aacf89079abdd55f7875d4229f6d Mon Sep 17 00:00:00 2001 From: John Date: Thu, 31 Jul 2025 17:34:05 -0700 Subject: [PATCH 3/4] Basic full cnversation --- docker-compose.yml | 0 examples/conversational/client.py | 35 ++- fastloop/baml_client/__init__.py | 58 ++++ fastloop/baml_client/async_client.py | 136 ++++++++++ fastloop/baml_client/config.py | 94 +++++++ fastloop/baml_client/globals.py | 35 +++ fastloop/baml_client/inlinedbaml.py | 21 ++ fastloop/baml_client/parser.py | 45 +++ fastloop/baml_client/runtime.py | 256 ++++++++++++++++++ fastloop/baml_client/stream_types.py | 40 +++ fastloop/baml_client/sync_client.py | 148 ++++++++++ fastloop/baml_client/tracing.py | 22 ++ fastloop/baml_client/type_builder.py | 142 ++++++++++ fastloop/baml_client/type_map.py | 26 ++ fastloop/baml_client/types.py | 58 ++++ fastloop/baml_src/clients.baml | 75 +++++ fastloop/baml_src/conversational.baml | 35 +++ fastloop/baml_src/generators.baml | 18 ++ fastloop/integrations/conversation.py | 252 ++++++++++------- .../plugins/{ => stt}/Deepgram.py | 31 ++- .../plugins/{ => stt}/ElevenLabs.py | 0 .../plugins/{ => stt}/SpeechMatics.py | 0 fastloop/integrations/plugins/stt/__init__.py | 0 .../integrations/plugins/tts/DeepgramTTS.py | 112 ++++++++ fastloop/integrations/plugins/types.py | 29 ++ fastloop/integrations/plugins/utils.py | 27 +- pyproject.toml | 2 + recorded_audio.wav | Bin 163884 -> 143404 bytes uv.lock | 53 ++-- 29 files changed, 1612 insertions(+), 138 deletions(-) create mode 100644 docker-compose.yml create mode 100644 fastloop/baml_client/__init__.py create mode 100644 fastloop/baml_client/async_client.py create mode 100644 fastloop/baml_client/config.py create mode 100644 fastloop/baml_client/globals.py create mode 100644 fastloop/baml_client/inlinedbaml.py create mode 100644 fastloop/baml_client/parser.py create mode 100644 fastloop/baml_client/runtime.py create mode 100644 fastloop/baml_client/stream_types.py create mode 100644 fastloop/baml_client/sync_client.py create mode 100644 fastloop/baml_client/tracing.py create mode 100644 fastloop/baml_client/type_builder.py create mode 100644 fastloop/baml_client/type_map.py create mode 100644 fastloop/baml_client/types.py create mode 100644 fastloop/baml_src/clients.baml create mode 100644 fastloop/baml_src/conversational.baml create mode 100644 fastloop/baml_src/generators.baml rename fastloop/integrations/plugins/{ => stt}/Deepgram.py (77%) rename fastloop/integrations/plugins/{ => stt}/ElevenLabs.py (100%) rename fastloop/integrations/plugins/{ => stt}/SpeechMatics.py (100%) create mode 100644 fastloop/integrations/plugins/stt/__init__.py create mode 100644 fastloop/integrations/plugins/tts/DeepgramTTS.py create mode 100644 fastloop/integrations/plugins/types.py diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e69de29 diff --git a/examples/conversational/client.py b/examples/conversational/client.py index a7cac28..54ddd3b 100644 --- a/examples/conversational/client.py +++ b/examples/conversational/client.py @@ -7,6 +7,7 @@ import numpy as np import pyaudio +import sounddevice as sd import webrtcvad import websockets from scipy.io.wavfile import write @@ -228,12 +229,26 @@ def cleanup(self): self.audio.terminate() +class AudioPlayer: + def __init__(self, sample_rate: int = 16000, channels: int = 1): + self.sample_rate = sample_rate + + def play_audio(self, audio_data: bytes): + try: + array = np.frombuffer(audio_data, dtype=np.int16) + print(f"Playing audio: {len(array)} samples") + sd.play(array, self.sample_rate, blocking=True) + except Exception as e: + print(f"Error playing audio: {e}") + + class WebsocketManager: - def __init__(self, websocket_url: str): + def __init__(self, websocket_url: str, audio_player: Optional["AudioPlayer"] = None): self.websocket_url = websocket_url self.websocket = None self.websocket_receive_task = None self.running = False + self.audio_player = audio_player async def connect(self): if self.websocket: @@ -246,10 +261,19 @@ async def handle_websocket_receive(self): if not self.websocket: return + buffer_audio = b"" + while self.running: try: message = await self.websocket.recv() - print(message) + if isinstance(message, bytes) and self.audio_player: + buffer_audio += message + if len(buffer_audio) > 1024 * 16: + self.audio_player.play_audio(buffer_audio) + buffer_audio = b"" + print(f"Playing {len(message)} bytes of audio") + else: + print(message) await asyncio.sleep(0.1) except websockets.exceptions.ConnectionClosed: break @@ -266,12 +290,12 @@ async def send_stop_speaking(self): class ConversationManager: def __init__(self, websocket_url: str): self.websocket_url = websocket_url - self.websocket_manager = WebsocketManager(websocket_url) + self.audio_player = AudioPlayer(48000) + self.websocket_manager = WebsocketManager(websocket_url, self.audio_player) self.streamer = SpeechMicrophoneStreamer( websocket_manager=self.websocket_manager, chunk_size=1024, ) - self.audio_player = None async def start_conversation(self): await self.websocket_manager.connect() @@ -305,7 +329,8 @@ async def main(): print("Starting optimized audio streaming... Press Ctrl+C to stop") try: - await streamer.pipe_in_audio_file("./_sandbox/input.wav") + # await streamer.pipe_in_audio_file("./_sandbox/input.wav") + await streamer.start_conversation() except BaseException as e: print(f"Stopping recording: {e}") streamer.streamer.stop_recording() diff --git a/fastloop/baml_client/__init__.py b/fastloop/baml_client/__init__.py new file mode 100644 index 0000000..a1dddaa --- /dev/null +++ b/fastloop/baml_client/__init__.py @@ -0,0 +1,58 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +__version__ = "0.202.1" + +try: + from baml_py.safe_import import EnsureBamlPyImport +except ImportError: + raise ImportError(f"""Update to baml-py required. +Version of baml_client generator (see generators.baml): {__version__} + +Please upgrade baml-py to version "{__version__}". + +$ pip install baml-py=={__version__} +$ uv add baml-py=={__version__} + +If nothing else works, please ask for help: + +https://github.com/boundaryml/baml/issues +https://boundaryml.com/discord +""") from None + + +with EnsureBamlPyImport(__version__) as e: + e.raise_if_incompatible_version(__version__) + + from . import types + from . import tracing + from . import stream_types + from . import config + from .config import reset_baml_env_vars + + from .sync_client import b + + + +# FOR LEGACY COMPATIBILITY, expose "partial_types" as an alias for "stream_types" +# WE RECOMMEND USERS TO USE "stream_types" INSTEAD +partial_types = stream_types + +__all__ = [ + "b", + "stream_types", + "partial_types", + "tracing", + "types", + "reset_baml_env_vars", + "config", +] \ No newline at end of file diff --git a/fastloop/baml_client/async_client.py b/fastloop/baml_client/async_client.py new file mode 100644 index 0000000..20eb19d --- /dev/null +++ b/fastloop/baml_client/async_client.py @@ -0,0 +1,136 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +import typing +import typing_extensions +import baml_py + +from . import stream_types, types, type_builder +from .parser import LlmResponseParser, LlmStreamParser +from .runtime import DoNotUseDirectlyCallManager, BamlCallOptions +from .globals import DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME as __runtime__ + + +class BamlAsyncClient: + __options: DoNotUseDirectlyCallManager + __stream_client: "BamlStreamClient" + __http_request: "BamlHttpRequestClient" + __http_stream_request: "BamlHttpStreamRequestClient" + __llm_response_parser: LlmResponseParser + __llm_stream_parser: LlmStreamParser + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + self.__stream_client = BamlStreamClient(options) + self.__http_request = BamlHttpRequestClient(options) + self.__http_stream_request = BamlHttpStreamRequestClient(options) + self.__llm_response_parser = LlmResponseParser(options) + self.__llm_stream_parser = LlmStreamParser(options) + + def with_options(self, + tb: typing.Optional[type_builder.TypeBuilder] = None, + client_registry: typing.Optional[baml_py.baml_py.ClientRegistry] = None, + collector: typing.Optional[typing.Union[baml_py.baml_py.Collector, typing.List[baml_py.baml_py.Collector]]] = None, + env: typing.Optional[typing.Dict[str, typing.Optional[str]]] = None, + ) -> "BamlAsyncClient": + options: BamlCallOptions = {} + if tb is not None: + options["tb"] = tb + if client_registry is not None: + options["client_registry"] = client_registry + if collector is not None: + options["collector"] = collector + if env is not None: + options["env"] = env + return BamlAsyncClient(self.__options.merge_options(options)) + + @property + def stream(self): + return self.__stream_client + + @property + def request(self): + return self.__http_request + + @property + def stream_request(self): + return self.__http_stream_request + + @property + def parse(self): + return self.__llm_response_parser + + @property + def parse_stream(self): + return self.__llm_stream_parser + + async def GenerateResponse(self, input: types.ConversationalAgentInput, + baml_options: BamlCallOptions = {}, + ) -> str: + result = await self.__options.merge_options(baml_options).call_function_async(function_name="GenerateResponse", args={ + "input": input, + }) + return typing.cast(str, result.cast_to(types, types, stream_types, False, __runtime__)) + + + +class BamlStreamClient: + __options: DoNotUseDirectlyCallManager + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + + def GenerateResponse(self, input: types.ConversationalAgentInput, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlStream[str, str]: + ctx, result = self.__options.merge_options(baml_options).create_async_stream(function_name="GenerateResponse", args={ + "input": input, + }) + return baml_py.BamlStream[str, str]( + result, + lambda x: typing.cast(str, x.cast_to(types, types, stream_types, True, __runtime__)), + lambda x: typing.cast(str, x.cast_to(types, types, stream_types, False, __runtime__)), + ctx, + ) + + +class BamlHttpRequestClient: + __options: DoNotUseDirectlyCallManager + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + + async def GenerateResponse(self, input: types.ConversationalAgentInput, + baml_options: BamlCallOptions = {}, + ) -> baml_py.baml_py.HTTPRequest: + result = await self.__options.merge_options(baml_options).create_http_request_async(function_name="GenerateResponse", args={ + "input": input, + }, mode="request") + return result + + +class BamlHttpStreamRequestClient: + __options: DoNotUseDirectlyCallManager + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + + async def GenerateResponse(self, input: types.ConversationalAgentInput, + baml_options: BamlCallOptions = {}, + ) -> baml_py.baml_py.HTTPRequest: + result = await self.__options.merge_options(baml_options).create_http_request_async(function_name="GenerateResponse", args={ + "input": input, + }, mode="stream") + return result + + +b = BamlAsyncClient(DoNotUseDirectlyCallManager({})) \ No newline at end of file diff --git a/fastloop/baml_client/config.py b/fastloop/baml_client/config.py new file mode 100644 index 0000000..998bd61 --- /dev/null +++ b/fastloop/baml_client/config.py @@ -0,0 +1,94 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +from __future__ import annotations + +import os +import warnings +import typing_extensions +import typing +import functools + +from baml_py.logging import ( + get_log_level as baml_get_log_level, + set_log_level as baml_set_log_level, +) +from .globals import reset_baml_env_vars + +rT = typing_extensions.TypeVar("rT") # return type +pT = typing_extensions.ParamSpec("pT") # parameters type + + +def _deprecated(message: str): + def decorator(func: typing.Callable[pT, rT]) -> typing.Callable[pT, rT]: + """Use this decorator to mark functions as deprecated. + Every time the decorated function runs, it will emit + a "deprecation" warning.""" + + @functools.wraps(func) + def new_func(*args: pT.args, **kwargs: pT.kwargs): + warnings.simplefilter("always", DeprecationWarning) # turn off filter + warnings.warn( + "Call to a deprecated function {}.".format(func.__name__) + message, + category=DeprecationWarning, + stacklevel=2, + ) + warnings.simplefilter("default", DeprecationWarning) # reset filter + return func(*args, **kwargs) + + return new_func + + return decorator + + +@_deprecated("Use os.environ['BAML_LOG'] instead") +def get_log_level(): + """ + Get the log level for the BAML Python client. + """ + return baml_get_log_level() + + +@_deprecated("Use os.environ['BAML_LOG'] instead") +def set_log_level( + level: typing_extensions.Literal["DEBUG", "INFO", "WARN", "ERROR", "OFF"] | str, +): + """ + Set the log level for the BAML Python client + """ + baml_set_log_level(level) + os.environ["BAML_LOG"] = level + + +@_deprecated("Use os.environ['BAML_LOG_JSON_MODE'] instead") +def set_log_json_mode(): + """ + Set the log JSON mode for the BAML Python client. + """ + os.environ["BAML_LOG_JSON_MODE"] = "true" + + +@_deprecated("Use os.environ['BAML_LOG_MAX_CHUNK_LENGTH'] instead") +def set_log_max_chunk_length(): + """ + Set the maximum log chunk length for the BAML Python client. + """ + os.environ["BAML_LOG_MAX_CHUNK_LENGTH"] = "1000" + + +__all__ = [ + "set_log_level", + "get_log_level", + "set_log_json_mode", + "reset_baml_env_vars", + "set_log_max_chunk_length", +] diff --git a/fastloop/baml_client/globals.py b/fastloop/baml_client/globals.py new file mode 100644 index 0000000..769e055 --- /dev/null +++ b/fastloop/baml_client/globals.py @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +from __future__ import annotations +import os +import warnings + +from baml_py import BamlCtxManager, BamlRuntime +from .inlinedbaml import get_baml_files +from typing import Dict + +DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME = BamlRuntime.from_files( + "baml_src", + get_baml_files(), + os.environ.copy() +) +DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX = BamlCtxManager(DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME) + +def reset_baml_env_vars(env_vars: Dict[str, str]): + warnings.warn( + "reset_baml_env_vars is deprecated and should be removed. Environment variables are now lazily loaded on each function call", + DeprecationWarning, + stacklevel=2 + ) + +__all__ = [] diff --git a/fastloop/baml_client/inlinedbaml.py b/fastloop/baml_client/inlinedbaml.py new file mode 100644 index 0000000..dffabd5 --- /dev/null +++ b/fastloop/baml_client/inlinedbaml.py @@ -0,0 +1,21 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +_file_map = { + + "clients.baml": "// Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview\n\nclient CustomGPT4o {\n provider openai\n options {\n model \"gpt-4o\"\n api_key env.OPENAI_API_KEY\n }\n}\n\nclient CustomGPT4oMini {\n provider openai\n retry_policy Exponential\n options {\n model \"gpt-4o-mini\"\n api_key env.OPENAI_API_KEY\n }\n}\n\nclient CustomSonnet {\n provider anthropic\n options {\n model \"claude-3-5-sonnet-20241022\"\n api_key env.ANTHROPIC_API_KEY\n }\n}\n\n\nclient CustomHaiku {\n provider anthropic\n retry_policy Constant\n options {\n model \"claude-3-haiku-20240307\"\n api_key env.ANTHROPIC_API_KEY\n }\n}\n\n// https://docs.boundaryml.com/docs/snippets/clients/round-robin\nclient CustomFast {\n provider round-robin\n options {\n // This will alternate between the two clients\n strategy [CustomGPT4oMini, CustomHaiku]\n }\n}\n\n// https://docs.boundaryml.com/docs/snippets/clients/fallback\nclient OpenaiFallback {\n provider fallback\n options {\n // This will try the clients in order until one succeeds\n strategy [CustomGPT4oMini, CustomGPT4oMini]\n }\n}\n\n// https://docs.boundaryml.com/docs/snippets/clients/retry\nretry_policy Constant {\n max_retries 3\n // Strategy is optional\n strategy {\n type constant_delay\n delay_ms 200\n }\n}\n\nretry_policy Exponential {\n max_retries 2\n // Strategy is optional\n strategy {\n type exponential_backoff\n delay_ms 300\n multiplier 1.5\n max_delay_ms 10000\n }\n}", + "conversational.baml": "class ConversationHistory {\n role string\n content string\n}\n\nclass ConversationalAgentInput {\n conversation_history ConversationHistory[]\n system_prompt string\n user_message string\n}\n\nfunction GenerateResponse(input: ConversationalAgentInput) -> string {\n client \"openai/gpt-4o\"\n prompt #\"\n {{ input.system_prompt }}\n {{ input.conversation_history }}\n {{ input.user_message }}\n \"#\n}\n\ntest GenerateResponseTest {\n functions [GenerateResponse]\n args {\n input {\n conversation_history [\n { role \"user\", content \"Question, have you seen the movie 'The Matrix'?\" },\n { role \"assistant\", content \"I'm sorry, I haven't seen it.\" },\n { role \"user\", content \"That's too bad. Do you like sci-fi movies?\" },\n { role \"assistant\", content \"Yes, I do. My favorite sci-fi movie is 3-body problem.\" },\n ]\n system_prompt \"You are a general conversational agent.\"\n user_message \"Oh! Who is your favorite character?\"\n }\n }\n}", + "generators.baml": "// This helps use auto generate libraries you can use in the language of\n// your choice. You can have multiple generators if you use multiple languages.\n// Just ensure that the output_dir is different for each generator.\ngenerator target {\n // Valid values: \"python/pydantic\", \"typescript\", \"ruby/sorbet\", \"rest/openapi\"\n output_type \"python/pydantic\"\n\n // Where the generated code will be saved (relative to baml_src/)\n output_dir \"../\"\n\n // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml).\n // The BAML VSCode extension version should also match this version.\n version \"0.202.1\"\n\n // Valid values: \"sync\", \"async\"\n // This controls what `b.FunctionName()` will be (sync or async).\n default_client_mode sync\n}\n", +} + +def get_baml_files(): + return _file_map \ No newline at end of file diff --git a/fastloop/baml_client/parser.py b/fastloop/baml_client/parser.py new file mode 100644 index 0000000..2d8933e --- /dev/null +++ b/fastloop/baml_client/parser.py @@ -0,0 +1,45 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +import typing +import typing_extensions + +from . import stream_types, types +from .runtime import DoNotUseDirectlyCallManager, BamlCallOptions + +class LlmResponseParser: + __options: DoNotUseDirectlyCallManager + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + + def GenerateResponse( + self, llm_response: str, baml_options: BamlCallOptions = {}, + ) -> str: + result = self.__options.merge_options(baml_options).parse_response(function_name="GenerateResponse", llm_response=llm_response, mode="request") + return typing.cast(str, result) + + + +class LlmStreamParser: + __options: DoNotUseDirectlyCallManager + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + + def GenerateResponse( + self, llm_response: str, baml_options: BamlCallOptions = {}, + ) -> str: + result = self.__options.merge_options(baml_options).parse_response(function_name="GenerateResponse", llm_response=llm_response, mode="stream") + return typing.cast(str, result) + + \ No newline at end of file diff --git a/fastloop/baml_client/runtime.py b/fastloop/baml_client/runtime.py new file mode 100644 index 0000000..c94bb95 --- /dev/null +++ b/fastloop/baml_client/runtime.py @@ -0,0 +1,256 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +import os +import typing +import typing_extensions + +import baml_py + +from . import types, stream_types, type_builder +from .globals import DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME as __runtime__, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX as __ctx__manager__ + + +class BamlCallOptions(typing.TypedDict, total=False): + tb: typing_extensions.NotRequired[type_builder.TypeBuilder] + client_registry: typing_extensions.NotRequired[baml_py.baml_py.ClientRegistry] + env: typing_extensions.NotRequired[typing.Dict[str, typing.Optional[str]]] + collector: typing_extensions.NotRequired[ + typing.Union[baml_py.baml_py.Collector, typing.List[baml_py.baml_py.Collector]] + ] + + +class _ResolvedBamlOptions: + tb: typing.Optional[baml_py.baml_py.TypeBuilder] + client_registry: typing.Optional[baml_py.baml_py.ClientRegistry] + collectors: typing.List[baml_py.baml_py.Collector] + env_vars: typing.Dict[str, str] + + def __init__( + self, + tb: typing.Optional[baml_py.baml_py.TypeBuilder], + client_registry: typing.Optional[baml_py.baml_py.ClientRegistry], + collectors: typing.List[baml_py.baml_py.Collector], + env_vars: typing.Dict[str, str], + ): + self.tb = tb + self.client_registry = client_registry + self.collectors = collectors + self.env_vars = env_vars + + + + + +class DoNotUseDirectlyCallManager: + def __init__(self, baml_options: BamlCallOptions): + self.__baml_options = baml_options + + def __getstate__(self): + # Return state needed for pickling + return {"baml_options": self.__baml_options} + + def __setstate__(self, state): + # Restore state from pickling + self.__baml_options = state["baml_options"] + + def __resolve(self) -> _ResolvedBamlOptions: + tb = self.__baml_options.get("tb") + if tb is not None: + baml_tb = tb._tb # type: ignore (we know how to use this private attribute) + else: + baml_tb = None + client_registry = self.__baml_options.get("client_registry") + collector = self.__baml_options.get("collector") + collectors_as_list = ( + collector + if isinstance(collector, list) + else [collector] if collector is not None else [] + ) + env_vars = os.environ.copy() + for k, v in self.__baml_options.get("env", {}).items(): + if v is not None: + env_vars[k] = v + else: + env_vars.pop(k, None) + + return _ResolvedBamlOptions( + baml_tb, + client_registry, + collectors_as_list, + env_vars, + ) + + def merge_options(self, options: BamlCallOptions) -> "DoNotUseDirectlyCallManager": + return DoNotUseDirectlyCallManager({**self.__baml_options, **options}) + + async def call_function_async( + self, *, function_name: str, args: typing.Dict[str, typing.Any] + ) -> baml_py.baml_py.FunctionResult: + resolved_options = self.__resolve() + return await __runtime__.call_function( + function_name, + args, + # ctx + __ctx__manager__.clone_context(), + # tb + resolved_options.tb, + # cr + resolved_options.client_registry, + # collectors + resolved_options.collectors, + # env_vars + resolved_options.env_vars, + ) + + def call_function_sync( + self, *, function_name: str, args: typing.Dict[str, typing.Any] + ) -> baml_py.baml_py.FunctionResult: + resolved_options = self.__resolve() + ctx = __ctx__manager__.get() + return __runtime__.call_function_sync( + function_name, + args, + # ctx + ctx, + # tb + resolved_options.tb, + # cr + resolved_options.client_registry, + # collectors + resolved_options.collectors, + # env_vars + resolved_options.env_vars, + ) + + def create_async_stream( + self, + *, + function_name: str, + args: typing.Dict[str, typing.Any], + ) -> typing.Tuple[baml_py.baml_py.RuntimeContextManager, baml_py.baml_py.FunctionResultStream]: + resolved_options = self.__resolve() + ctx = __ctx__manager__.clone_context() + result = __runtime__.stream_function( + function_name, + args, + # this is always None, we set this later! + # on_event + None, + # ctx + ctx, + # tb + resolved_options.tb, + # cr + resolved_options.client_registry, + # collectors + resolved_options.collectors, + # env_vars + resolved_options.env_vars, + ) + return ctx, result + + def create_sync_stream( + self, + *, + function_name: str, + args: typing.Dict[str, typing.Any], + ) -> typing.Tuple[baml_py.baml_py.RuntimeContextManager, baml_py.baml_py.SyncFunctionResultStream]: + resolved_options = self.__resolve() + ctx = __ctx__manager__.get() + result = __runtime__.stream_function_sync( + function_name, + args, + # this is always None, we set this later! + # on_event + None, + # ctx + ctx, + # tb + resolved_options.tb, + # cr + resolved_options.client_registry, + # collectors + resolved_options.collectors, + # env_vars + resolved_options.env_vars, + ) + return ctx, result + + async def create_http_request_async( + self, + *, + function_name: str, + args: typing.Dict[str, typing.Any], + mode: typing_extensions.Literal["stream", "request"], + ) -> baml_py.baml_py.HTTPRequest: + resolved_options = self.__resolve() + return await __runtime__.build_request( + function_name, + args, + # ctx + __ctx__manager__.clone_context(), + # tb + resolved_options.tb, + # cr + resolved_options.client_registry, + # env_vars + resolved_options.env_vars, + # is_stream + mode == "stream", + ) + + def create_http_request_sync( + self, + *, + function_name: str, + args: typing.Dict[str, typing.Any], + mode: typing_extensions.Literal["stream", "request"], + ) -> baml_py.baml_py.HTTPRequest: + resolved_options = self.__resolve() + return __runtime__.build_request_sync( + function_name, + args, + # ctx + __ctx__manager__.get(), + # tb + resolved_options.tb, + # cr + resolved_options.client_registry, + # env_vars + resolved_options.env_vars, + # is_stream + mode == "stream", + ) + + def parse_response(self, *, function_name: str, llm_response: str, mode: typing_extensions.Literal["stream", "request"]) -> typing.Any: + resolved_options = self.__resolve() + return __runtime__.parse_llm_response( + function_name, + llm_response, + # enum_module + types, + # cls_module + types, + # partial_cls_module + stream_types, + # allow_partials + mode == "stream", + # ctx + __ctx__manager__.get(), + # tb + resolved_options.tb, + # cr + resolved_options.client_registry, + # env_vars + resolved_options.env_vars, + ) \ No newline at end of file diff --git a/fastloop/baml_client/stream_types.py b/fastloop/baml_client/stream_types.py new file mode 100644 index 0000000..879b02a --- /dev/null +++ b/fastloop/baml_client/stream_types.py @@ -0,0 +1,40 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +import typing +import typing_extensions +from pydantic import BaseModel, ConfigDict + +import baml_py + +from . import types + +StreamStateValueT = typing.TypeVar('StreamStateValueT') +class StreamState(BaseModel, typing.Generic[StreamStateValueT]): + value: StreamStateValueT + state: typing_extensions.Literal["Pending", "Incomplete", "Complete"] +# ######################################################################### +# Generated classes (2) +# ######################################################################### + +class ConversationHistory(BaseModel): + role: typing.Optional[str] = None + content: typing.Optional[str] = None + +class ConversationalAgentInput(BaseModel): + conversation_history: typing.List["ConversationHistory"] + system_prompt: typing.Optional[str] = None + user_message: typing.Optional[str] = None + +# ######################################################################### +# Generated type aliases (0) +# ######################################################################### diff --git a/fastloop/baml_client/sync_client.py b/fastloop/baml_client/sync_client.py new file mode 100644 index 0000000..b1c1319 --- /dev/null +++ b/fastloop/baml_client/sync_client.py @@ -0,0 +1,148 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +import typing +import typing_extensions +import baml_py + +from . import stream_types, types, type_builder +from .parser import LlmResponseParser, LlmStreamParser +from .runtime import DoNotUseDirectlyCallManager, BamlCallOptions +from .globals import DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME as __runtime__ + +class BamlSyncClient: + __options: DoNotUseDirectlyCallManager + __stream_client: "BamlStreamClient" + __http_request: "BamlHttpRequestClient" + __http_stream_request: "BamlHttpStreamRequestClient" + __llm_response_parser: LlmResponseParser + __llm_stream_parser: LlmStreamParser + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + self.__stream_client = BamlStreamClient(options) + self.__http_request = BamlHttpRequestClient(options) + self.__http_stream_request = BamlHttpStreamRequestClient(options) + self.__llm_response_parser = LlmResponseParser(options) + self.__llm_stream_parser = LlmStreamParser(options) + + def __getstate__(self): + # Return state needed for pickling + return {"options": self.__options} + + def __setstate__(self, state): + # Restore state from pickling + self.__options = state["options"] + self.__stream_client = BamlStreamClient(self.__options) + self.__http_request = BamlHttpRequestClient(self.__options) + self.__http_stream_request = BamlHttpStreamRequestClient(self.__options) + self.__llm_response_parser = LlmResponseParser(self.__options) + self.__llm_stream_parser = LlmStreamParser(self.__options) + + def with_options(self, + tb: typing.Optional[type_builder.TypeBuilder] = None, + client_registry: typing.Optional[baml_py.baml_py.ClientRegistry] = None, + collector: typing.Optional[typing.Union[baml_py.baml_py.Collector, typing.List[baml_py.baml_py.Collector]]] = None, + env: typing.Optional[typing.Dict[str, typing.Optional[str]]] = None, + ) -> "BamlSyncClient": + options: BamlCallOptions = {} + if tb is not None: + options["tb"] = tb + if client_registry is not None: + options["client_registry"] = client_registry + if collector is not None: + options["collector"] = collector + if env is not None: + options["env"] = env + return BamlSyncClient(self.__options.merge_options(options)) + + @property + def stream(self): + return self.__stream_client + + @property + def request(self): + return self.__http_request + + @property + def stream_request(self): + return self.__http_stream_request + + @property + def parse(self): + return self.__llm_response_parser + + @property + def parse_stream(self): + return self.__llm_stream_parser + + def GenerateResponse(self, input: types.ConversationalAgentInput, + baml_options: BamlCallOptions = {}, + ) -> str: + result = self.__options.merge_options(baml_options).call_function_sync(function_name="GenerateResponse", args={ + "input": input, + }) + return typing.cast(str, result.cast_to(types, types, stream_types, False, __runtime__)) + + + +class BamlStreamClient: + __options: DoNotUseDirectlyCallManager + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + + def GenerateResponse(self, input: types.ConversationalAgentInput, + baml_options: BamlCallOptions = {}, + ) -> baml_py.BamlSyncStream[str, str]: + ctx, result = self.__options.merge_options(baml_options).create_sync_stream(function_name="GenerateResponse", args={ + "input": input, + }) + return baml_py.BamlSyncStream[str, str]( + result, + lambda x: typing.cast(str, x.cast_to(types, types, stream_types, True, __runtime__)), + lambda x: typing.cast(str, x.cast_to(types, types, stream_types, False, __runtime__)), + ctx, + ) + + +class BamlHttpRequestClient: + __options: DoNotUseDirectlyCallManager + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + + def GenerateResponse(self, input: types.ConversationalAgentInput, + baml_options: BamlCallOptions = {}, + ) -> baml_py.baml_py.HTTPRequest: + result = self.__options.merge_options(baml_options).create_http_request_sync(function_name="GenerateResponse", args={ + "input": input, + }, mode="request") + return result + + +class BamlHttpStreamRequestClient: + __options: DoNotUseDirectlyCallManager + + def __init__(self, options: DoNotUseDirectlyCallManager): + self.__options = options + + def GenerateResponse(self, input: types.ConversationalAgentInput, + baml_options: BamlCallOptions = {}, + ) -> baml_py.baml_py.HTTPRequest: + result = self.__options.merge_options(baml_options).create_http_request_sync(function_name="GenerateResponse", args={ + "input": input, + }, mode="stream") + return result + + +b = BamlSyncClient(DoNotUseDirectlyCallManager({})) \ No newline at end of file diff --git a/fastloop/baml_client/tracing.py b/fastloop/baml_client/tracing.py new file mode 100644 index 0000000..0672559 --- /dev/null +++ b/fastloop/baml_client/tracing.py @@ -0,0 +1,22 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +from .globals import DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX + +trace = DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX.trace_fn +set_tags = DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX.upsert_tags +def flush(): + DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX.flush() +on_log_event = DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX.on_log_event + + +__all__ = ['trace', 'set_tags', "flush", "on_log_event"] diff --git a/fastloop/baml_client/type_builder.py b/fastloop/baml_client/type_builder.py new file mode 100644 index 0000000..0854a6b --- /dev/null +++ b/fastloop/baml_client/type_builder.py @@ -0,0 +1,142 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +import typing +from baml_py import type_builder +from baml_py import baml_py +from .globals import DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME + +class TypeBuilder(type_builder.TypeBuilder): + def __init__(self): + super().__init__(classes=set( + ["ConversationHistory","ConversationalAgentInput",] + ), enums=set( + [] + ), runtime=DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME) + + # ######################################################################### + # Generated enums 0 + # ######################################################################### + + + # ######################################################################### + # Generated classes 2 + # ######################################################################### + + @property + def ConversationHistory(self) -> "ConversationHistoryViewer": + return ConversationHistoryViewer(self) + + @property + def ConversationalAgentInput(self) -> "ConversationalAgentInputViewer": + return ConversationalAgentInputViewer(self) + + + +# ######################################################################### +# Generated enums 0 +# ######################################################################### + + +# ######################################################################### +# Generated classes 2 +# ######################################################################### + +class ConversationHistoryAst: + def __init__(self, tb: type_builder.TypeBuilder): + _tb = tb._tb # type: ignore (we know how to use this private attribute) + self._bldr = _tb.class_("ConversationHistory") + self._properties: typing.Set[str] = set([ "role", "content", ]) + self._props = ConversationHistoryProperties(self._bldr, self._properties) + + def type(self) -> baml_py.FieldType: + return self._bldr.field() + + @property + def props(self) -> "ConversationHistoryProperties": + return self._props + + +class ConversationHistoryViewer(ConversationHistoryAst): + def __init__(self, tb: type_builder.TypeBuilder): + super().__init__(tb) + + + def list_properties(self) -> typing.List[typing.Tuple[str, type_builder.ClassPropertyViewer]]: + return [(name, type_builder.ClassPropertyViewer(self._bldr.property(name))) for name in self._properties] + + + +class ConversationHistoryProperties: + def __init__(self, bldr: baml_py.ClassBuilder, properties: typing.Set[str]): + self.__bldr = bldr + self.__properties = properties # type: ignore (we know how to use this private attribute) # noqa: F821 + + + + @property + def role(self) -> type_builder.ClassPropertyViewer: + return type_builder.ClassPropertyViewer(self.__bldr.property("role")) + + @property + def content(self) -> type_builder.ClassPropertyViewer: + return type_builder.ClassPropertyViewer(self.__bldr.property("content")) + + + + +class ConversationalAgentInputAst: + def __init__(self, tb: type_builder.TypeBuilder): + _tb = tb._tb # type: ignore (we know how to use this private attribute) + self._bldr = _tb.class_("ConversationalAgentInput") + self._properties: typing.Set[str] = set([ "conversation_history", "system_prompt", "user_message", ]) + self._props = ConversationalAgentInputProperties(self._bldr, self._properties) + + def type(self) -> baml_py.FieldType: + return self._bldr.field() + + @property + def props(self) -> "ConversationalAgentInputProperties": + return self._props + + +class ConversationalAgentInputViewer(ConversationalAgentInputAst): + def __init__(self, tb: type_builder.TypeBuilder): + super().__init__(tb) + + + def list_properties(self) -> typing.List[typing.Tuple[str, type_builder.ClassPropertyViewer]]: + return [(name, type_builder.ClassPropertyViewer(self._bldr.property(name))) for name in self._properties] + + + +class ConversationalAgentInputProperties: + def __init__(self, bldr: baml_py.ClassBuilder, properties: typing.Set[str]): + self.__bldr = bldr + self.__properties = properties # type: ignore (we know how to use this private attribute) # noqa: F821 + + + + @property + def conversation_history(self) -> type_builder.ClassPropertyViewer: + return type_builder.ClassPropertyViewer(self.__bldr.property("conversation_history")) + + @property + def system_prompt(self) -> type_builder.ClassPropertyViewer: + return type_builder.ClassPropertyViewer(self.__bldr.property("system_prompt")) + + @property + def user_message(self) -> type_builder.ClassPropertyViewer: + return type_builder.ClassPropertyViewer(self.__bldr.property("user_message")) + + + diff --git a/fastloop/baml_client/type_map.py b/fastloop/baml_client/type_map.py new file mode 100644 index 0000000..08b5513 --- /dev/null +++ b/fastloop/baml_client/type_map.py @@ -0,0 +1,26 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +from . import types +from . import stream_types + + +type_map = { + + "types.ConversationHistory": types.ConversationHistory, + "stream_types.ConversationHistory": stream_types.ConversationHistory, + + "types.ConversationalAgentInput": types.ConversationalAgentInput, + "stream_types.ConversationalAgentInput": stream_types.ConversationalAgentInput, + + +} \ No newline at end of file diff --git a/fastloop/baml_client/types.py b/fastloop/baml_client/types.py new file mode 100644 index 0000000..002b4f4 --- /dev/null +++ b/fastloop/baml_client/types.py @@ -0,0 +1,58 @@ +# ---------------------------------------------------------------------------- +# +# Welcome to Baml! To use this generated code, please run the following: +# +# $ pip install baml +# +# ---------------------------------------------------------------------------- + +# This file was generated by BAML: please do not edit it. Instead, edit the +# BAML files and re-generate this code using: baml-cli generate +# baml-cli is available with the baml package. + +import typing +import typing_extensions +from enum import Enum + + +from pydantic import BaseModel, ConfigDict + + +import baml_py + +CheckT = typing_extensions.TypeVar('CheckT') +CheckName = typing_extensions.TypeVar('CheckName', bound=str) + +class Check(BaseModel): + name: str + expression: str + status: str +class Checked(BaseModel, typing.Generic[CheckT, CheckName]): + value: CheckT + checks: typing.Dict[CheckName, Check] + +def get_checks(checks: typing.Dict[CheckName, Check]) -> typing.List[Check]: + return list(checks.values()) + +def all_succeeded(checks: typing.Dict[CheckName, Check]) -> bool: + return all(check.status == "succeeded" for check in get_checks(checks)) +# ######################################################################### +# Generated enums (0) +# ######################################################################### + +# ######################################################################### +# Generated classes (2) +# ######################################################################### + +class ConversationHistory(BaseModel): + role: str + content: str + +class ConversationalAgentInput(BaseModel): + conversation_history: typing.List["ConversationHistory"] + system_prompt: str + user_message: str + +# ######################################################################### +# Generated type aliases (0) +# ######################################################################### diff --git a/fastloop/baml_src/clients.baml b/fastloop/baml_src/clients.baml new file mode 100644 index 0000000..f12893b --- /dev/null +++ b/fastloop/baml_src/clients.baml @@ -0,0 +1,75 @@ +// Learn more about clients at https://docs.boundaryml.com/docs/snippets/clients/overview + +client CustomGPT4o { + provider openai + options { + model "gpt-4o" + api_key env.OPENAI_API_KEY + } +} + +client CustomGPT4oMini { + provider openai + retry_policy Exponential + options { + model "gpt-4o-mini" + api_key env.OPENAI_API_KEY + } +} + +client CustomSonnet { + provider anthropic + options { + model "claude-3-5-sonnet-20241022" + api_key env.ANTHROPIC_API_KEY + } +} + + +client CustomHaiku { + provider anthropic + retry_policy Constant + options { + model "claude-3-haiku-20240307" + api_key env.ANTHROPIC_API_KEY + } +} + +// https://docs.boundaryml.com/docs/snippets/clients/round-robin +client CustomFast { + provider round-robin + options { + // This will alternate between the two clients + strategy [CustomGPT4oMini, CustomHaiku] + } +} + +// https://docs.boundaryml.com/docs/snippets/clients/fallback +client OpenaiFallback { + provider fallback + options { + // This will try the clients in order until one succeeds + strategy [CustomGPT4oMini, CustomGPT4oMini] + } +} + +// https://docs.boundaryml.com/docs/snippets/clients/retry +retry_policy Constant { + max_retries 3 + // Strategy is optional + strategy { + type constant_delay + delay_ms 200 + } +} + +retry_policy Exponential { + max_retries 2 + // Strategy is optional + strategy { + type exponential_backoff + delay_ms 300 + multiplier 1.5 + max_delay_ms 10000 + } +} \ No newline at end of file diff --git a/fastloop/baml_src/conversational.baml b/fastloop/baml_src/conversational.baml new file mode 100644 index 0000000..693a531 --- /dev/null +++ b/fastloop/baml_src/conversational.baml @@ -0,0 +1,35 @@ +class ConversationHistory { + role string + content string +} + +class ConversationalAgentInput { + conversation_history ConversationHistory[] + system_prompt string + user_message string +} + +function GenerateResponse(input: ConversationalAgentInput) -> string { + client "openai/gpt-4o" + prompt #" + {{ input.system_prompt }} + {{ input.conversation_history }} + {{ input.user_message }} + "# +} + +test GenerateResponseTest { + functions [GenerateResponse] + args { + input { + conversation_history [ + { role "user", content "Question, have you seen the movie 'The Matrix'?" }, + { role "assistant", content "I'm sorry, I haven't seen it." }, + { role "user", content "That's too bad. Do you like sci-fi movies?" }, + { role "assistant", content "Yes, I do. My favorite sci-fi movie is 3-body problem." }, + ] + system_prompt "You are a general conversational agent." + user_message "Oh! Who is your favorite character?" + } + } +} \ No newline at end of file diff --git a/fastloop/baml_src/generators.baml b/fastloop/baml_src/generators.baml new file mode 100644 index 0000000..dbe8279 --- /dev/null +++ b/fastloop/baml_src/generators.baml @@ -0,0 +1,18 @@ +// This helps use auto generate libraries you can use in the language of +// your choice. You can have multiple generators if you use multiple languages. +// Just ensure that the output_dir is different for each generator. +generator target { + // Valid values: "python/pydantic", "typescript", "ruby/sorbet", "rest/openapi" + output_type "python/pydantic" + + // Where the generated code will be saved (relative to baml_src/) + output_dir "../" + + // The version of the BAML package you have installed (e.g. same version as your baml-py or @boundaryml/baml). + // The BAML VSCode extension version should also match this version. + version "0.202.1" + + // Valid values: "sync", "async" + // This controls what `b.FunctionName()` will be (sync or async). + default_client_mode sync +} diff --git a/fastloop/integrations/conversation.py b/fastloop/integrations/conversation.py index 5259eb6..7184d73 100644 --- a/fastloop/integrations/conversation.py +++ b/fastloop/integrations/conversation.py @@ -1,19 +1,28 @@ import asyncio import logging import uuid -from concurrent.futures import ProcessPoolExecutor from typing import TYPE_CHECKING, Any import dotenv import numpy as np -from fastapi import WebSocket, WebSocketDisconnect +from fastapi import WebSocket, WebSocketException from scipy.io.wavfile import write -from ..integrations import Integration +from ..baml_client import b +from ..baml_client.types import ConversationalAgentInput, ConversationHistory +from ..integrations import Integration, IntegrationType from ..logging import setup_logger from ..loop import LoopEvent -from ..types import IntegrationType -from .plugins.Deepgram import DeepgramSpeechToTextManager +from .plugins.stt.Deepgram import DeepgramSpeechToTextManager +from .plugins.tts.DeepgramTTS import TextToSpeechManager +from .plugins.types import ( + AudioStreamResponseEvent, + GenerateResponseEvent, + GenerationationInterruptedEvent, + OnUserAudioDataEvent, + StartConversationEvent, + StreamLLMResponseEvent, +) dotenv.load_dotenv() @@ -24,32 +33,102 @@ logger.setLevel(logging.DEBUG) -class StartConversationEvent(LoopEvent): - type: str = "start_conversation" - - -class OnUserAudioDataEvent(LoopEvent): - type: str = "on_user_audio_data" - audio: bytes - - -class GenerateResponseEvent(LoopEvent): - type: str = "generate_response" - +class LLMManager: + def __init__(self, conversation_integration: "ConversationIntegration", request_id: str): + self.conversation_integration = conversation_integration + self.request_id = request_id + self.prompt = "Only answer in one short coherent sentence." + self.conversation_history: list[ConversationHistory] = [] + + async def add_to_conversation_history(self, text: str, role: str): + self.conversation_history.append(ConversationHistory(role=role, content=text)) + + async def generate_response(self, text: str): + stream = b.stream.GenerateResponse( + input=ConversationalAgentInput( + conversation_history=self.conversation_history, + system_prompt=self.prompt, + user_message=text, + ) + ) + current_index = 0 + for chunk in stream: + end = len(chunk) + if current_index == end: + continue + self.conversation_integration.queue.put_nowait( + StreamLLMResponseEvent( + loop_id=self.request_id or None, + text=chunk[current_index:].strip(), + ) + ) + current_index = end + + final = stream.get_final_response() + self.conversation_history.append(ConversationHistory(role="assistant", content=final)) + # self.conversation_integration.queue.put_nowait( + # StreamLLMResponseEvent( + # loop_id=self.request_id or None, + # text=final, + # ) + # ) + + +class WebsocketManager: + def __init__( + self, + conversation_integration: "ConversationIntegration", + request_id: str, + websocket: WebSocket, + ): + self.conversation_integration = conversation_integration + self.request_id = request_id + self.websocket = websocket + + async def start(self): + audio_buffer: list[bytes] = [] + while True: + try: + data = await self.websocket.receive() + if "bytes" in data: + audio_buffer.append(data["bytes"]) + await self.conversation_integration.emit( + OnUserAudioDataEvent( + loop_id=self.request_id or None, + audio=data["bytes"], + ) + ) + continue -class GenerationationInterruptedEvent(LoopEvent): - type: str = "generationation_interrupted" + # match data.get("type"): + # case "on_user_stop_speaking": + # await self.emit( + # GenerateResponseEvent( + # loop_id=request_id or None, + # ) + # ) + # case _: + # # logger.error(f"Unknown event: {data}") + # continue + except WebSocketException: + logger.info("Client disconnected") + self.is_running = False + break + except BaseException as e: + logger.error(f"Error receiving data: {e}") + self.is_running = False + break + async def stop(self): + await self.websocket.close() -class AudioStreamResponseEvent(LoopEvent): - type: str = "audio_stream_response" - audio: bytes + async def send_audio(self, audio: bytes): + await self.websocket.send_bytes(audio) class ConversationIntegration(Integration): def __init__(self): self.queue: asyncio.Queue[Any] = asyncio.Queue() - self.executor = ProcessPoolExecutor() self.is_running: bool = False def type(self) -> IntegrationType: @@ -86,53 +165,54 @@ def save_audio_to_file(self, audio_chunks: list[bytes], filename: str = "recorde return False return False - async def _handle_websocket_event(self, websocket: WebSocket, request_id: str): - audio_buffer: list[bytes] = [] - while True: - try: - data = await websocket.receive() - if "bytes" in data: - audio_buffer.append(data["bytes"]) - self.queue.put_nowait( - OnUserAudioDataEvent( - loop_id=request_id or None, - audio=data["bytes"], - ) - ) - continue - - match data.get("type"): - case "on_user_stop_speaking": - self.queue.put_nowait( - GenerateResponseEvent( - loop_id=request_id or None, - ) - ) - case _: - # logger.error(f"Unknown event: {data}") - continue - except WebSocketDisconnect: - logger.info("Client disconnected") - self.is_running = False - break - except BaseException as e: - logger.error(f"Error receiving data: {e}") - self.is_running = False - break + # async def _handle_websocket_event(self, websocket: WebSocket, request_id: str): + # audio_buffer: list[bytes] = [] + # while True: + # try: + # data = await websocket.receive() + # if "bytes" in data: + # audio_buffer.append(data["bytes"]) + # await self.emit( + # OnUserAudioDataEvent( + # loop_id=request_id or None, + # audio=data["bytes"], + # ) + # ) + # continue + + # # match data.get("type"): + # # case "on_user_stop_speaking": + # # await self.emit( + # # GenerateResponseEvent( + # # loop_id=request_id or None, + # # ) + # # ) + # # case _: + # # # logger.error(f"Unknown event: {data}") + # # continue + # except WebSocketDisconnect: + # logger.info("Client disconnected") + # self.is_running = False + # break + # except BaseException as e: + # logger.error(f"Error receiving data: {e}") + # self.is_running = False + # break async def _handle_start_conversation(self, websocket: WebSocket): await websocket.accept() self.is_running = True request_id = str(uuid.uuid4()) + current_generation_task: asyncio.Task[Any] | None = None # stt_manager = SpeechmaticsSpeechToTextManager(request_id, websocket) - stt_manager = DeepgramSpeechToTextManager(request_id, websocket) + stt_manager = DeepgramSpeechToTextManager(self.queue, request_id) await stt_manager.start() - print("starting conversation") - asyncio.create_task(self._handle_websocket_event(websocket, request_id)) - # llm_manager = LLMManager(self._fastloop, request_id) - # stt_task = self.executor.submit(stt_manager.on_voice_stream, websocket) - # tts_manager = TextToSpeechManager(self._fastloop, request_id) + websocket_manager = WebsocketManager(self, request_id, websocket) + websocket_task = asyncio.create_task(websocket_manager.start()) + llm_manager = LLMManager(self, request_id) + tts_manager = TextToSpeechManager(self.queue, request_id) + await tts_manager.start() while self.is_running: try: @@ -148,12 +228,20 @@ async def _handle_start_conversation(self, websocket: WebSocket): if isinstance(loop_event, OnUserAudioDataEvent): await stt_manager.send_audio(loop_event.audio) elif isinstance(loop_event, GenerateResponseEvent): - pass - # llm_manager.generate_response(stt_manager.get_text()) + print("Generating response", loop_event.text) + current_generation_task = asyncio.create_task( + llm_manager.generate_response(loop_event.text) + ) + elif isinstance(loop_event, StreamLLMResponseEvent): + print(loop_event.text) + # Send text to TTS for synthesis + await tts_manager.synthesize(loop_event.text) elif isinstance(loop_event, GenerationationInterruptedEvent): - pass + if current_generation_task: + current_generation_task.cancel() + # await websocket_manager.send_json(loop_event.to_dict()) elif isinstance(loop_event, AudioStreamResponseEvent): - pass + await websocket_manager.send_audio(loop_event.audio) # case "on_agent_audio_data": # pass # case "on_agent_stop_speaking": @@ -184,36 +272,12 @@ async def _handle_start_conversation(self, websocket: WebSocket): # ) await stt_manager.stop() + await tts_manager.stop() + if websocket_task: + websocket_task.cancel() async def emit(self, event: LoopEvent): - pass + await self.queue.put(event) def events(self) -> list[Any]: return [StartConversationEvent] - - -class TextToSpeechManager: - def __init__(self, fastloop: "FastLoop", request_id: str): - self.fastloop = fastloop - - def synthesize(self, text: str): - pass - - -class LLMManager: - def __init__(self, fastloop: "FastLoop", request_id: str): - self.fastloop = fastloop - - def generate_response(self, text: str): - pass - - -# class ConversationManager: -# def __init__(self, fastloop: "FastLoop"): -# self.fastloop = fastloop - -# def start_conversation(self, loop_id: str): -# pass - -# def generate_response(self, loop_id: str): -# pass diff --git a/fastloop/integrations/plugins/Deepgram.py b/fastloop/integrations/plugins/stt/Deepgram.py similarity index 77% rename from fastloop/integrations/plugins/Deepgram.py rename to fastloop/integrations/plugins/stt/Deepgram.py index e6ec12d..11f2901 100644 --- a/fastloop/integrations/plugins/Deepgram.py +++ b/fastloop/integrations/plugins/stt/Deepgram.py @@ -9,20 +9,21 @@ LiveOptions, LiveTranscriptionEvents, ) -from fastapi import WebSocket from fastloop.integrations.plugins.base import BaseSpeechToTextManager +from fastloop.integrations.plugins.types import GenerateResponseEvent dotenv.load_dotenv() class DeepgramSpeechToTextManager(BaseSpeechToTextManager): - def __init__(self, request_id: str, websocket: WebSocket): + def __init__(self, queue: asyncio.Queue[Any], request_id: str): + self.queue = queue self.request_id = request_id - self.websocket = websocket self.buffer = b"" self.buffer_lock = asyncio.Lock() self.is_running = False + self.current_sentence = "" async def start(self): # self.dg = DeepgramClient(api_key=os.getenv("DEEPGRAM_API_KEY", "")) @@ -33,10 +34,13 @@ async def start(self): async def on_message(*_, result: Any, **__): sentence = result.channel.alternatives[0].transcript print(f"Transcript: {sentence}") - if len(sentence) > 0: - # Send transcript back to client`` - response = {"type": "transcript", "text": sentence, "is_final": result.speech_final} - await self.websocket.send_json(response) + if result.speech_final: + self.current_sentence += sentence + # # Send transcript back to client`` + # response = {"type": "transcript", "text": sentence, "is_final": result.speech_final} + # await self.websocket.send_json( + # {"type": "transcript", "text": sentence, "is_final": result.speech_final} + # ) async def on_error(error: Any, *_, **__): print(f"Deepgram error: {error}") @@ -51,6 +55,13 @@ async def on_speech_started(result: Any, *_, **__): async def on_utterance_end(result: Any, *_, **__): print(f"Utterance end: {result}") + self.queue.put_nowait( + GenerateResponseEvent( + loop_id=self.request_id or None, + text=self.current_sentence, + ) + ) + self.current_sentence = "" self.dg_connection.on(LiveTranscriptionEvents.Transcript, on_message) # type: ignore self.dg_connection.on(LiveTranscriptionEvents.Error, on_error) # type: ignore @@ -67,6 +78,9 @@ async def on_utterance_end(result: Any, *_, **__): encoding="linear16", channels=1, sample_rate=16000, + # endpointing=True, + interim_results=True, + utterance_end_ms="1000", ) ): # type: ignore print("Failed to start Deepgram connection") @@ -84,8 +98,9 @@ async def process_audio(self): while self.is_running: async with self.buffer_lock: audio = self.buffer - print("sending audio", len(audio) / 1024, "kb") + if len(audio) > 0: + print("sending audio", len(audio) / 1024, "kb") await self.dg_connection.send(audio) self.buffer = b"" await asyncio.sleep(0.5) diff --git a/fastloop/integrations/plugins/ElevenLabs.py b/fastloop/integrations/plugins/stt/ElevenLabs.py similarity index 100% rename from fastloop/integrations/plugins/ElevenLabs.py rename to fastloop/integrations/plugins/stt/ElevenLabs.py diff --git a/fastloop/integrations/plugins/SpeechMatics.py b/fastloop/integrations/plugins/stt/SpeechMatics.py similarity index 100% rename from fastloop/integrations/plugins/SpeechMatics.py rename to fastloop/integrations/plugins/stt/SpeechMatics.py diff --git a/fastloop/integrations/plugins/stt/__init__.py b/fastloop/integrations/plugins/stt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fastloop/integrations/plugins/tts/DeepgramTTS.py b/fastloop/integrations/plugins/tts/DeepgramTTS.py new file mode 100644 index 0000000..1732493 --- /dev/null +++ b/fastloop/integrations/plugins/tts/DeepgramTTS.py @@ -0,0 +1,112 @@ +import asyncio +import os +from typing import Any + +import dotenv +from deepgram import ( + DeepgramClient, + SpeakWebSocketEvents, +) + +from fastloop.integrations.plugins.types import AudioStreamResponseEvent + +dotenv.load_dotenv() + + +class TextToSpeechManager: + def __init__(self, queue: asyncio.Queue[Any], request_id: str): + self.queue = queue + self.request_id = request_id + self.is_running = False + self.dg_connection = None + self.text_buffer = "" + self.text_buffer_lock = asyncio.Lock() + + async def start(self): + deepgram: DeepgramClient = DeepgramClient( + os.getenv("DEEPGRAM_API_KEY", ""), + ) + self.dg_connection = deepgram.speak.websocket.v("1") + + def on_binary_data(cls, data: Any, **kwargs): + print("Received audio chunk") + # Convert binary data to audio format playback devices understand + # array = np.frombuffer(data, dtype=np.int16) + # Play the audio immediately upon receiving each chunk + # sd.play(array, 48000, blocking=True) + self.queue.put_nowait( + AudioStreamResponseEvent( + loop_id=self.request_id or None, + audio=data, + ) + ) + + def on_error(cls, *args, **kwargs): + """Handle TTS errors""" + print(f"Deepgram TTS error: {args} {kwargs}") + + def on_close(cls, error: Any, *d, **k): + """Handle TTS connection close""" + print(f"Deepgram TTS connection closed: {error}") + + # Register event handlers - using string events similar to STT + self.dg_connection.on(SpeakWebSocketEvents.AudioData, on_binary_data) # type: ignore + self.dg_connection.on(SpeakWebSocketEvents.Error, on_error) # type: ignore + self.dg_connection.on(SpeakWebSocketEvents.Close, on_close) # type: ignore + print("Starting Deepgram TTS connection") + + if not self.dg_connection.start( + { + "model": "aura-2-thalia-en", + "encoding": "linear16", + "sample_rate": 48000, + } + ): # type: ignore + print("Failed to start Deepgram TTS connection") + raise Exception("Failed to start Deepgram TTS connection") + + print("Deepgram TTS connection started") + + while not self.dg_connection.is_connected(): + await asyncio.sleep(0.1) + + print("Deepgram TTS connection connected") + self.is_running = True + self.process_text_task = asyncio.create_task(self.process_text()) + + async def process_text(self): + """Process text buffer and send to Deepgram TTS""" + while self.is_running: + async with self.text_buffer_lock: + text = self.text_buffer + self.text_buffer = "" + + if text.strip(): + try: + if self.dg_connection: + self.dg_connection.send_text(text) + self.dg_connection.flush() + except Exception as e: + print(f"Error sending text to TTS: {e}") + + await asyncio.sleep(0.1) + + async def synthesize(self, text: str): + """Add text to the buffer for TTS synthesis""" + try: + if self.dg_connection and self.dg_connection.is_connected(): + async with self.text_buffer_lock: + self.text_buffer += text + " " + else: + print("TTS connection not connected") + except Exception as e: + print(f"Error in synthesize: {e}") + + async def stop(self): + """Stop the TTS connection""" + print("Stopping Deepgram TTS connection") + self.is_running = False + if hasattr(self, "process_text_task"): + self.process_text_task.cancel() + if self.dg_connection: + self.dg_connection.finish() # type: ignore diff --git a/fastloop/integrations/plugins/types.py b/fastloop/integrations/plugins/types.py new file mode 100644 index 0000000..3360431 --- /dev/null +++ b/fastloop/integrations/plugins/types.py @@ -0,0 +1,29 @@ +from fastloop.loop import LoopEvent + + +class GenerateResponseEvent(LoopEvent): + type: str = "generate_response" + text: str + + +class StartConversationEvent(LoopEvent): + type: str = "start_conversation" + + +class OnUserAudioDataEvent(LoopEvent): + type: str = "on_user_audio_data" + audio: bytes + + +class StreamLLMResponseEvent(LoopEvent): + type: str = "stream_llm_response" + text: str + + +class GenerationationInterruptedEvent(LoopEvent): + type: str = "generationation_interrupted" + + +class AudioStreamResponseEvent(LoopEvent): + type: str = "audio_stream_response" + audio: bytes diff --git a/fastloop/integrations/plugins/utils.py b/fastloop/integrations/plugins/utils.py index 1adfff9..6bc2247 100644 --- a/fastloop/integrations/plugins/utils.py +++ b/fastloop/integrations/plugins/utils.py @@ -1,15 +1,18 @@ import io import wave -def bytes_to_wav(audio_bytes: bytes, sample_rate: int = 16000, channels: int = 1, sample_width: int = 2) -> io.BytesIO: - # Create an in-memory WAV file - wav_buffer = io.BytesIO() - - with wave.open(wav_buffer, 'wb') as wav_file: - wav_file.setnchannels(channels) # mono or stereo - wav_file.setsampwidth(sample_width) # 2 bytes = 16-bit - wav_file.setframerate(sample_rate) # sample rate - wav_file.writeframes(audio_bytes) - - wav_buffer.seek(0) - return wav_buffer \ No newline at end of file + +def bytes_to_wav( + audio_bytes: bytes, sample_rate: int = 16000, channels: int = 1, sample_width: int = 2 +) -> io.BytesIO: + # Create an in-memory WAV file + wav_buffer = io.BytesIO() + + with wave.open(wav_buffer, "wb") as wav_file: + wav_file.setnchannels(channels) # mono or stereo + wav_file.setsampwidth(sample_width) # 2 bytes = 16-bit + wav_file.setframerate(sample_rate) # sample rate + wav_file.writeframes(audio_bytes) + + wav_buffer.seek(0) + return wav_buffer diff --git a/pyproject.toml b/pyproject.toml index 86cd3d9..ffdcf76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,11 @@ dependencies = [ "websockets>=15.0.1", "deepgram-sdk>=4.7.0", "pyaudio>=0.2.14", + "sounddevice>=0.4.6", "scipy>=1.16.0", "elevenlabs>=2.8.1", "speechmatics-rt>=0.4.0", + "baml-py>=0.202.1", ] [project.optional-dependencies] diff --git a/recorded_audio.wav b/recorded_audio.wav index 17e270ad412478dd9b1193ab3d2260686a04a217..030680a935ea400427603b338d23a08517a4cc02 100644 GIT binary patch literal 143404 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

Y>;v@7m+S@l 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%0DoqPZSP)B{Z> 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