From c32c12adc8f2a1767fc8998f8b1157fb6256996c Mon Sep 17 00:00:00 2001 From: "partha.mukherjee" Date: Wed, 26 Aug 2026 13:44:02 -0700 Subject: [PATCH] Deepgram Flux TTS in EVA --- .env.example | 5 +- pyproject.toml | 2 +- src/eva/assistant/pipeline/services.py | 17 +++- tests/unit/assistant/test_services.py | 15 ++++ uv.lock | 110 ++++++++++++++----------- 5 files changed, 97 insertions(+), 52 deletions(-) diff --git a/.env.example b/.env.example index 85b9da4d..5afdbf30 100644 --- a/.env.example +++ b/.env.example @@ -90,7 +90,7 @@ EVA_MODEL__STT_PARAMS='{"api_key": "your_cartesia_api_key", "model": "ink-2"}' # --- TTS (LLM and AudioLLM modes) --- #i TTS provider for the voice pipeline. #d enum -#e cartesia,chatterbox,elevenlabs,gemini,kokoro,nvidia-baseten,openai,smallest,soniox,xtts +#e cartesia,chatterbox,deepgram,deepgram-flux,elevenlabs,gemini,kokoro,nvidia-baseten,openai,smallest,soniox,xtts #x pipeline_mode=LLM,AudioLLM EVA_MODEL__TTS=cartesia @@ -98,6 +98,9 @@ EVA_MODEL__TTS=cartesia #i Some providers also accept extra provider-specific tuning parameters here. #i Soniox example — omit "voice_id" to use pipecat's default voice ("Adrian"): #i EVA_MODEL__TTS_PARAMS='{"api_key": "your_soniox_api_key", "model": "tts-rt-v2", "voice": "Sarah"}' +#i Deepgram Flux example (EVA_MODEL__TTS=deepgram-flux) — "voice" is the full flux-{voice}-{lang} model +#i string (defaults to "flux-alexis-en" if omitted); "model" is not used for Flux: +#i EVA_MODEL__TTS_PARAMS='{"api_key": "your_deepgram_api_key", "voice": "flux-haley-en"}' #d json_object #x pipeline_mode=LLM,AudioLLM EVA_MODEL__TTS_PARAMS='{"api_key": "your_cartesia_api_key", "model": "sonic"}' diff --git a/pyproject.toml b/pyproject.toml index f7d79a95..f53486cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ license = "MIT" dependencies = [ "pydantic>=2.0", "pydantic-settings>=2.0", - "pipecat-ai>=1.4.0", + "pipecat-ai>=1.6.0", "elevenlabs>=2.53.0", "openai>=2.36.0", "anthropic>=0.83.0", diff --git a/src/eva/assistant/pipeline/services.py b/src/eva/assistant/pipeline/services.py index da0959e2..a15478ee 100644 --- a/src/eva/assistant/pipeline/services.py +++ b/src/eva/assistant/pipeline/services.py @@ -21,6 +21,7 @@ from pipecat.services.cartesia.tts import CartesiaTTSService from pipecat.services.cartesia.turns.stt import CartesiaTurnsSTTService from pipecat.services.deepgram.flux.stt import DeepgramFluxSTTService, DeepgramFluxSTTSettings +from pipecat.services.deepgram.flux.tts import DeepgramFluxTTSService from pipecat.services.deepgram.stt import DeepgramSTTService from pipecat.services.deepgram.tts import DeepgramTTSService from pipecat.services.elevenlabs.stt import CommitStrategy, ElevenLabsRealtimeSTTService @@ -375,7 +376,7 @@ def create_tts_service( Based on create_tts_service() from chatbot.py. Args: - model: TTS model identifier (cartesia, elevenlabs, openai, gemini) + model: TTS model identifier (cartesia, deepgram, deepgram-flux, elevenlabs, openai, gemini) params: Model-specific parameters (may include 'alias' key which is ignored here) language_code: Language code for speech synthesis @@ -430,7 +431,17 @@ def create_tts_service( chatterbox_tts._settings.language = language_code return chatterbox_tts - elif model_lower == "deepgram": + elif model_lower.startswith("deepgram"): + if "flux" in model_lower: + # Flux TTS (/v2/speak) has no separate "model" field: the flux-{voice}-{lang} + # string IS the voice, and pipecat keeps Settings.model in sync with it. + voice = params.get("voice", "flux-alexis-en") + logger.info(f"Using Deepgram Flux TTS: {voice}") + return DeepgramFluxTTSService( + api_key=api_key, + sample_rate=SAMPLE_RATE, + settings=DeepgramFluxTTSService.Settings(voice=voice), + ) logger.info(f"Using Deepgram TTS: {params['model']}") return DeepgramTTSService( api_key=api_key, @@ -646,7 +657,7 @@ def create_tts_service( else: raise ValueError( - f"Unknown TTS model: {model}. Available: cartesia, chatterbox, deepgram, elevenlabs, gemini, kokoro, nvidia-baseten, openai, smallest, soniox, voxtral, xai, xtts" + f"Unknown TTS model: {model}. Available: cartesia, chatterbox, deepgram, deepgram-flux, elevenlabs, gemini, kokoro, nvidia-baseten, openai, smallest, soniox, voxtral, xai, xtts" ) diff --git a/tests/unit/assistant/test_services.py b/tests/unit/assistant/test_services.py index b87d26fb..5d9fcf1e 100644 --- a/tests/unit/assistant/test_services.py +++ b/tests/unit/assistant/test_services.py @@ -321,6 +321,21 @@ def test_soniox_defaults_voice_when_unset(self): svc = create_tts_service("soniox", params={"api_key": "k", "model": "tts-rt-v1"}) assert svc._settings.voice == "Adrian" + def test_deepgram_returns_deepgram_service(self): + svc = create_tts_service("deepgram", params={"api_key": "k", "model": "aura-2-helena-en"}) + assert "Deepgram" in type(svc).__name__ + assert "Flux" not in type(svc).__name__ + assert svc._settings.voice == "aura-2-helena-en" + + def test_deepgram_flux_returns_flux_variant(self): + svc = create_tts_service("deepgram-flux", params={"api_key": "k", "voice": "flux-haley-en"}) + assert "Flux" in type(svc).__name__ + assert svc._settings.voice == "flux-haley-en" + + def test_deepgram_flux_defaults_voice_when_unset(self): + svc = create_tts_service("deepgram-flux", params={"api_key": "k"}) + assert svc._settings.voice == "flux-alexis-en" + def test_openai_respects_voice_param(self): svc = create_tts_service("openai", params={"api_key": "k", "model": "tts-1", "voice": "nova"}) assert svc._settings.voice == "nova" diff --git a/uv.lock b/uv.lock index 4eae4cdb..8af956ef 100644 --- a/uv.lock +++ b/uv.lock @@ -674,6 +674,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/99/dbac65ef9db7041127fe9170a567c3a0e1c1879708b49c793f9aed4e9150/deepgram_sdk-7.3.0-py3-none-any.whl", hash = "sha256:32070574142354f698aa53a8d80b6b056f3a8256cb81a0fd111a854251e699b3", size = 560127, upload-time = "2026-06-01T13:41:22.062Z" }, ] +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -692,6 +701,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "docopt" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } + [[package]] name = "docstring-parser" version = "0.17.0" @@ -804,7 +819,7 @@ requires-dist = [ { name = "openai", specifier = ">=2.36.0" }, { name = "pandas", specifier = ">=2.0" }, { name = "pandas", marker = "extra == 'apps'", specifier = ">=2.0" }, - { name = "pipecat-ai", specifier = ">=1.4.0" }, + { name = "pipecat-ai", specifier = ">=1.6.0" }, { name = "plotly", marker = "extra == 'apps'", specifier = ">=5.0" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.0" }, { name = "pydantic", specifier = ">=2.0" }, @@ -1239,14 +1254,14 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "filelock", marker = "python_full_version < '3.13'" }, - { name = "fsspec", marker = "python_full_version < '3.13'" }, - { name = "hf-xet", marker = "(python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'arm64') or (python_full_version < '3.13' and platform_machine == 'x86_64')" }, - { name = "packaging", marker = "python_full_version < '3.13'" }, - { name = "pyyaml", marker = "python_full_version < '3.13'" }, - { name = "requests", marker = "python_full_version < '3.13'" }, - { name = "tqdm", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } wheels = [ @@ -1261,15 +1276,15 @@ resolution-markers = [ "python_full_version >= '3.13'", ] dependencies = [ - { name = "filelock", marker = "python_full_version >= '3.13'" }, - { name = "fsspec", marker = "python_full_version >= '3.13'" }, - { name = "hf-xet", marker = "(python_full_version >= '3.13' and platform_machine == 'AMD64') or (python_full_version >= '3.13' and platform_machine == 'aarch64') or (python_full_version >= '3.13' and platform_machine == 'amd64') or (python_full_version >= '3.13' and platform_machine == 'arm64') or (python_full_version >= '3.13' and platform_machine == 'x86_64')" }, - { name = "httpx", marker = "python_full_version >= '3.13'" }, - { name = "packaging", marker = "python_full_version >= '3.13'" }, - { name = "pyyaml", marker = "python_full_version >= '3.13'" }, - { name = "tqdm", marker = "python_full_version >= '3.13'" }, - { name = "typer", marker = "python_full_version >= '3.13'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.13'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ae/76/b5efb3033d8499b17f9386beaf60f64c461798e1ee16d10bc9c0077beba5/huggingface_hub-1.5.0.tar.gz", hash = "sha256:f281838db29265880fb543de7a23b0f81d3504675de82044307ea3c6c62f799d", size = 695872, upload-time = "2026-02-26T15:35:32.745Z" } wheels = [ @@ -1612,7 +1627,7 @@ name = "markdown-it-py" version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mdurl", marker = "python_full_version >= '3.13'" }, + { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ @@ -1867,17 +1882,18 @@ wheels = [ [[package]] name = "nltk" -version = "3.9.4" +version = "3.10.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, + { name = "defusedxml" }, { name = "joblib" }, { name = "regex" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864, upload-time = "2026-03-24T06:13:40.641Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/e6/fe51d2bb1a3b446f59c5c8165999a9fee208bc346af90a7cbf7657bc0d75/nltk-3.10.3.tar.gz", hash = "sha256:bb9327a461c3811c2fa4900e03840401f2126adfb30c0072827c433bd2444ea4", size = 5137152, upload-time = "2026-08-12T23:46:37.258Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087, upload-time = "2026-03-24T06:13:38.47Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6d/ebd2af4640b12168fdf0cb74b6118df2f32a2f62ec7e0c06fbfd80706639/nltk-3.10.3-py3-none-any.whl", hash = "sha256:ff9598a8e20518ee0d557745890cc4435b9578489e2dcbc69c4f81fa060caf7c", size = 1798643, upload-time = "2026-08-12T23:44:13.478Z" }, ] [[package]] @@ -1889,6 +1905,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] +[[package]] +name = "num2words" +version = "0.5.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docopt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/58/ad645bd38b4b648eb2fc2ba1b909398e54eb0cbb6a7dbd2b4953e38c9621/num2words-0.5.14.tar.gz", hash = "sha256:b066ec18e56b6616a3b38086b5747daafbaa8868b226a36127e0451c0cf379c6", size = 218213, upload-time = "2024-12-17T20:17:10.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/5b/545e9267a1cc080c8a1be2746113a063e34bcdd0f5173fd665a5c13cb234/num2words-0.5.14-py3-none-any.whl", hash = "sha256:1c8e5b00142fc2966fd8d685001e36c4a9911e070d1b120e1beb721fa1edb33d", size = 163525, upload-time = "2024-12-17T20:17:06.074Z" }, +] + [[package]] name = "numba" version = "0.61.2" @@ -2149,7 +2177,7 @@ wheels = [ [[package]] name = "pipecat-ai" -version = "1.4.0" +version = "1.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiofiles" }, @@ -2159,6 +2187,7 @@ dependencies = [ { name = "loguru" }, { name = "markdown" }, { name = "nltk" }, + { name = "num2words" }, { name = "numba" }, { name = "numpy" }, { name = "onnxruntime" }, @@ -2168,16 +2197,15 @@ dependencies = [ { name = "pydantic" }, { name = "pyloudnorm" }, { name = "pyyaml" }, - { name = "pyyaml-include" }, { name = "resampy" }, { name = "soxr" }, { name = "typing-extensions" }, { name = "wait-for2", marker = "python_full_version < '3.12'" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/ed/4bcf67703996ac6a0896dc64232e79fdfcb23eccff36929a7a234587a085/pipecat_ai-1.4.0.tar.gz", hash = "sha256:4d1e68da79e0632dcdaf66581fbf07e840249dfb715dc6e57b423d20fc3520d7", size = 11505429, upload-time = "2026-06-17T03:27:48.83Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/bc/01e7d51462ab11f71bdd70469cad7aef654c92f2d9294101520bc528c291/pipecat_ai-1.7.0.tar.gz", hash = "sha256:d6cd4c7a55e8d2b5e9ff0c263f9a7c0de47b0ce6e7ffa81f85c6d5eece034b88", size = 11732539, upload-time = "2026-08-01T16:14:38.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/ab/9acf81effd93f6f6d1ededbe5db8fedb50d196363d75188dc21d8aa71d5a/pipecat_ai-1.4.0-py3-none-any.whl", hash = "sha256:d7e1f2ee6f2646720daed3a028835d0e26e5e93787d0424ab00f4c7b6034a170", size = 11171447, upload-time = "2026-06-17T03:27:46.367Z" }, + { url = "https://files.pythonhosted.org/packages/41/c8/9615e801905f6c6a3cbcb9d72dd81c90d5f5a9cc14110d30ade0ab2b7600/pipecat_ai-1.7.0-py3-none-any.whl", hash = "sha256:15bfc523899cf2f590e2a0c0f06dcd6a65d3c2700b8b29c930ca33ceca4073e5", size = 11343558, upload-time = "2026-08-01T16:14:36.299Z" }, ] [[package]] @@ -2668,18 +2696,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, ] -[[package]] -name = "pyyaml-include" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7f/be/2d07ad85e3d593d69640876a8686eae2c533db8cb7bf298d25c421b4d2d5/pyyaml-include-1.4.1.tar.gz", hash = "sha256:1a96e33a99a3e56235f5221273832464025f02ff3d8539309a3bf00dec624471", size = 20592, upload-time = "2024-03-25T14:56:43.748Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/ca/6a2cc3a73170d10b5af1f1613baa2ed1f8f46f62dd0bfab2bffd2c2fe260/pyyaml_include-1.4.1-py3-none-any.whl", hash = "sha256:323c7f3a19c82fbc4d73abbaab7ef4f793e146a13383866831631b26ccc7fb00", size = 19079, upload-time = "2024-03-25T14:56:41.274Z" }, -] - [[package]] name = "rapidfuzz" version = "3.14.3" @@ -2856,8 +2872,8 @@ name = "rich" version = "14.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py", marker = "python_full_version >= '3.13'" }, - { name = "pygments", marker = "python_full_version >= '3.13'" }, + { name = "markdown-it-py" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } wheels = [ @@ -3159,8 +3175,8 @@ name = "standard-aifc" version = "3.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, - { name = "standard-chunk", marker = "python_full_version >= '3.13'" }, + { name = "audioop-lts" }, + { name = "standard-chunk" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/53/6050dc3dde1671eb3db592c13b55a8005e5040131f7509cef0215212cb84/standard_aifc-3.13.0.tar.gz", hash = "sha256:64e249c7cb4b3daf2fdba4e95721f811bde8bdfc43ad9f936589b7bb2fae2e43", size = 15240, upload-time = "2024-10-30T16:01:31.772Z" } wheels = [ @@ -3181,7 +3197,7 @@ name = "standard-sunau" version = "3.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, + { name = "audioop-lts" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/e3/ce8d38cb2d70e05ffeddc28bb09bad77cfef979eb0a299c9117f7ed4e6a9/standard_sunau-3.13.0.tar.gz", hash = "sha256:b319a1ac95a09a2378a8442f403c66f4fd4b36616d6df6ae82b8e536ee790908", size = 9368, upload-time = "2024-10-30T16:01:41.626Z" } wheels = [ @@ -3462,10 +3478,10 @@ name = "typer" version = "0.24.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-doc", marker = "python_full_version >= '3.13'" }, - { name = "click", marker = "python_full_version >= '3.13'" }, - { name = "rich", marker = "python_full_version >= '3.13'" }, - { name = "shellingham", marker = "python_full_version >= '3.13'" }, + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } wheels = [