diff --git a/.fern/metadata.json b/.fern/metadata.json index f810fd87..7799ad6e 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -16,8 +16,8 @@ "skip_validation": true } }, - "originGitCommit": "8dd6f48a06eee8c3985894f68ba3b554e5564d21", + "originGitCommit": "335af96251466afae7a9f71badb65c630a48626e", "originGitCommitIsDirty": true, "invokedBy": "manual", - "sdkVersion": "7.3.2" + "sdkVersion": "7.4.1" } \ No newline at end of file diff --git a/.fernignore b/.fernignore index 3c065285..d868646d 100644 --- a/.fernignore +++ b/.fernignore @@ -26,6 +26,7 @@ src/deepgram/agent/v1/socket_client.py src/deepgram/listen/v1/socket_client.py src/deepgram/listen/v2/socket_client.py src/deepgram/speak/v1/socket_client.py +src/deepgram/speak/v2/socket_client.py # Backward-compat patch: AgentV1SettingsAgentContext schema restructure as of # 2026-05-05. The new schema nests messages under .context.messages; this file @@ -125,13 +126,16 @@ src/deepgram/core/query_encoder.py # Hand-written custom tests tests/custom/test_agent_history.py +tests/custom/test_agent_update_listen.py tests/custom/test_compat_aliases.py +tests/custom/test_eot_thresholds_feature.py tests/custom/test_language_hint_compat.py tests/custom/test_language_hints_feature.py tests/custom/test_listen_v2_regen_constraints.py tests/custom/test_query_encoder.py tests/custom/test_secure_logging.py tests/custom/test_socket_client_shims.py +tests/custom/test_speak_v2_socket.py tests/custom/test_text_builder.py tests/custom/test_transport.py tests/typecheck/compat_aliases.py diff --git a/AGENTS.md b/AGENTS.md index fd3febac..8e7f3afc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -53,6 +53,7 @@ How to identify: Current temporarily frozen files: - `src/deepgram/speak/v1/socket_client.py` — optional message param defaults, broad exception catch +- `src/deepgram/speak/v2/socket_client.py` — same (optional `send_flush`/`send_close` defaults, broad exception catch); new websocket TTS client added in the 2026-07-08 regen - `src/deepgram/listen/v1/socket_client.py` — same - `src/deepgram/listen/v2/socket_client.py` — same + `send_configure` typing.Any/raw shim, response Union uses typing.Any instead of `ListenV2ConfigureSuccess` - `src/deepgram/agent/v1/socket_client.py` — same + `_sanitize_numeric_types` diff --git a/examples/25-text-to-speech-streaming-v2.py b/examples/25-text-to-speech-streaming-v2.py new file mode 100644 index 00000000..cd5fcd7f --- /dev/null +++ b/examples/25-text-to-speech-streaming-v2.py @@ -0,0 +1,66 @@ +""" +Example: Text-to-Speech Streaming with the Speak V2 (Flux) WebSocket + +This example shows how to stream text-to-speech conversion using the Speak V2 +WebSocket. Speak V2 uses Flux voices (model strings of the form +`flux-{voice}-{language}`, e.g. `flux-alexis-en`); use Speak V1 for Aura voices. +""" + +from typing import Union + +from dotenv import load_dotenv + +load_dotenv() + +from deepgram import DeepgramClient +from deepgram.core.events import EventType +from deepgram.speak.v2.types import SpeakV2Speak + +SpeakV2SocketClientResponse = Union[str, bytes] + +client = DeepgramClient() + +try: + with client.speak.v2.connect(model="flux-alexis-en", encoding="linear16", sample_rate="24000") as connection: + + def on_message(message: SpeakV2SocketClientResponse) -> None: + if isinstance(message, bytes): + print("Received audio data") + # In production, you would write this audio data to a file or play it + # with open("output.raw", "ab") as audio_file: + # audio_file.write(message) + else: + msg_type = getattr(message, "type", "Unknown") + print(f"Received {msg_type} event") + + connection.on(EventType.OPEN, lambda _: print("Connection opened")) + connection.on(EventType.MESSAGE, on_message) + connection.on(EventType.CLOSE, lambda _: print("Connection closed")) + connection.on(EventType.ERROR, lambda error: print(f"Error: {error}")) + + # For sync version: Send messages before starting to listen + # Note: start_listening() blocks, so send all messages first + # For better control with bidirectional communication, use the async version + connection.send_speak(SpeakV2Speak(text="Hello, this is a text to speech example.")) + + # Flush to ensure all text is processed + connection.send_flush() + + # Close the connection when done + connection.send_close() + + # Start listening - this blocks until the connection closes + # All messages should be sent before calling this in sync mode + connection.start_listening() + + # For async version: + # from deepgram import AsyncDeepgramClient + # async with client.speak.v2.connect(...) as connection: + # listen_task = asyncio.create_task(connection.start_listening()) + # await connection.send_speak(SpeakV2Speak(text="...")) + # await connection.send_flush() + # await connection.send_close() + # await listen_task + +except Exception as e: + print(f"Error: {e}") diff --git a/examples/README.md b/examples/README.md index 61cf030c..819e723f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -25,6 +25,7 @@ This directory contains comprehensive examples demonstrating how to use the Deep - **22-text-builder-demo.py** - TextBuilder demo (no API key required) - **23-text-builder-helper.py** - TextBuilder with REST API generation - **24-text-builder-streaming.py** - TextBuilder with streaming TTS (WebSocket) +- **25-text-to-speech-streaming-v2.py** - Streaming TTS via the Speak V2 (Flux) WebSocket ### 30-39: Voice Agent diff --git a/poetry.lock b/poetry.lock index 8f46bcc6..534ebf4f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.2" +version = "2.7.1" description = "Happy Eyeballs for asyncio" optional = true python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, - {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, + {file = "aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472"}, + {file = "aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d"}, ] [[package]] @@ -188,14 +188,14 @@ files = [ [[package]] name = "anyio" -version = "4.14.0" +version = "4.14.1" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9"}, - {file = "anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89"}, + {file = "anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72"}, + {file = "anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e"}, ] [package.dependencies] @@ -259,141 +259,105 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.7" +version = "3.4.9" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, - {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, - {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win32.whl", hash = "sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win_arm64.whl", hash = "sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe"}, + {file = "charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5"}, + {file = "charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b"}, ] [[package]] @@ -1245,14 +1209,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "9.1.0" +version = "9.1.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32"}, - {file = "pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c"}, + {file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"}, + {file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"}, ] [package.dependencies] @@ -1473,14 +1437,14 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, + {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, + {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 114b57e3..cce103b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "deepgram-sdk" -version = "7.4.0" +version = "7.4.1" description = "" readme = "README.md" authors = [] diff --git a/reference.md b/reference.md index 724b3388..2123ca1c 100644 --- a/reference.md +++ b/reference.md @@ -5757,6 +5757,283 @@ asyncio.run(main()) +## Speak V2 Connect + +
client.speak.v2.connect(...) +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Convert text into natural-sounding speech using Deepgram's Flux TTS WebSocket + +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from typing import Union + +from deepgram import DeepgramClient +from deepgram.core.events import EventType +from deepgram.speak.v2.types import ( + SpeakV2Speak, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Warning, + SpeakV2Error, +) + +SpeakV2Response = Union[ + bytes, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Warning, + SpeakV2Error, +] + +client = DeepgramClient( + api_key="YOUR_API_KEY", +) + +with client.speak.v2.connect( + model="flux-alexis-en", + encoding="linear16", + sample_rate="24000" +) as connection: + def on_message(message: SpeakV2Response) -> None: + if isinstance(message, bytes): + print("Received audio event") + else: + msg_type = getattr(message, "type", "Unknown") + print(f"Received {msg_type} event") + + connection.on(EventType.OPEN, lambda _: print("Connection opened")) + connection.on(EventType.MESSAGE, on_message) + connection.on(EventType.CLOSE, lambda _: print("Connection closed")) + connection.on(EventType.ERROR, lambda error: print(f"Caught: {error}")) + + # Start listening + connection.start_listening() + + # Send text to be converted to speech + connection.send_speak(SpeakV2Speak(text="Hello, world!")) + + # Send control messages + connection.send_flush() + connection.send_close() + +``` + +
+
+
+
+ +#### 🔌 Async Usage + +
+
+ +
+
+ +```python +import asyncio +from typing import Union + +from deepgram import AsyncDeepgramClient +from deepgram.core.events import EventType +from deepgram.speak.v2.types import ( + SpeakV2Speak, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Warning, + SpeakV2Error, +) + +SpeakV2Response = Union[ + bytes, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Warning, + SpeakV2Error, +] + +client = AsyncDeepgramClient( + api_key="YOUR_API_KEY", +) + +async def main(): + async with client.speak.v2.connect( + model="flux-alexis-en", + encoding="linear16", + sample_rate="24000" + ) as connection: + def on_message(message: SpeakV2Response) -> None: + if isinstance(message, bytes): + print("Received audio event") + else: + msg_type = getattr(message, "type", "Unknown") + print(f"Received {msg_type} event") + + connection.on(EventType.OPEN, lambda _: print("Connection opened")) + connection.on(EventType.MESSAGE, on_message) + connection.on(EventType.CLOSE, lambda _: print("Connection closed")) + connection.on(EventType.ERROR, lambda error: print(f"Caught: {error}")) + + # Start listening + await connection.start_listening() + + # Send text to be converted to speech + await connection.send_speak(SpeakV2Speak(text="Hello, world!")) + + # Send control messages + await connection.send_flush() + await connection.send_close() + +asyncio.run(main()) + +``` + +
+
+
+
+ +#### 📤 Send Methods + +
+
+ +
+
+ +**`send_speak(message: SpeakV2Speak)`** — Send text to be converted to speech + +- `connection.send_speak(SpeakV2Speak(text="Hello, world!"))` + +
+
+ +
+
+ +**`send_flush()`** — Process all queued text immediately + +- `connection.send_flush()` + +
+
+ +
+
+ +**`send_close()`** — Close the connection + +- `connection.send_close()` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**model:** `SpeakV2Model` — The Flux TTS model used to synthesize speech. Required on every connection. Model strings follow the format `flux-{voice}-{language}` (e.g. `flux-alexis-en`). Aura model strings are rejected on `/v2/speak`; use Speak V1 for Aura voices. + +
+
+ +
+
+ +**encoding:** `typing.Optional[SpeakV2Encoding]` — Specify the expected encoding of your output audio (`linear16`, `mulaw`, `alaw`) + +
+
+ +
+
+ +**sample_rate:** `typing.Optional[SpeakV2SampleRate]` — Sample rate for the output audio (`8000`, `16000`, `24000`, `32000`, `44100`, `48000`) + +
+
+ +
+
+ +**mip_opt_out:** `typing.Optional[SpeakV2MipOptOut]` — Opts out requests from the Deepgram Model Improvement Program + +
+
+ +
+
+ +**tag:** `typing.Optional[SpeakV2Tag]` — Label your requests for the purpose of identification during usage reporting. Repeatable. + +
+
+ +
+
+ +**authorization:** `typing.Optional[str]` — Use your API key for authentication, or alternatively generate a temporary token and pass it via the token query parameter. + +**Example:** `token %DEEPGRAM_API_KEY%` or `bearer %DEEPGRAM_TOKEN%` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
+
+
+ ## Agent V1 Connect
client.agent.v1.connect(...) @@ -6063,6 +6340,16 @@ asyncio.run(main())
+**`send_update_listen(message: AgentV1UpdateListen)`** — Update the agent's speech-to-text settings + +- `AgentV1UpdateListen(listen=AgentV1UpdateListenListen(provider=DeepgramListenProviderV2(...)))` — Modify STT configuration during conversation. The provider identity (type, version, model) must match the current session. + +
+
+ +
+
+ **`send_update_prompt(message: AgentV1UpdatePrompt)`** — Update the agent's system prompt - `AgentV1UpdatePrompt(prompt="...")` — Change the agent's behavior instructions diff --git a/src/deepgram/__init__.py b/src/deepgram/__init__.py index 3b9390d0..8e0e64c3 100644 --- a/src/deepgram/__init__.py +++ b/src/deepgram/__init__.py @@ -222,6 +222,11 @@ SpeakV1Response, SpeakV1SampleRate, SpeakV1Speed, + SpeakV2Encoding, + SpeakV2MipOptOut, + SpeakV2Model, + SpeakV2SampleRate, + SpeakV2Tag, ThinkSettingsV1, ThinkSettingsV1ContextLength, ThinkSettingsV1Endpoint, @@ -791,6 +796,11 @@ "SpeakV1Response": ".types", "SpeakV1SampleRate": ".types", "SpeakV1Speed": ".types", + "SpeakV2Encoding": ".types", + "SpeakV2MipOptOut": ".types", + "SpeakV2Model": ".types", + "SpeakV2SampleRate": ".types", + "SpeakV2Tag": ".types", "ThinkSettingsV1": ".types", "ThinkSettingsV1ContextLength": ".types", "ThinkSettingsV1ContextLengthParams": ".requests", @@ -1236,6 +1246,11 @@ def __dir__(): "SpeakV1Response", "SpeakV1SampleRate", "SpeakV1Speed", + "SpeakV2Encoding", + "SpeakV2MipOptOut", + "SpeakV2Model", + "SpeakV2SampleRate", + "SpeakV2Tag", "ThinkSettingsV1", "ThinkSettingsV1ContextLength", "ThinkSettingsV1ContextLengthParams", diff --git a/src/deepgram/agent/__init__.py b/src/deepgram/agent/__init__.py index 3b753b62..18f12e7d 100644 --- a/src/deepgram/agent/__init__.py +++ b/src/deepgram/agent/__init__.py @@ -41,6 +41,8 @@ AgentV1InjectionRefusedParams, AgentV1KeepAlive, AgentV1KeepAliveParams, + AgentV1ListenUpdated, + AgentV1ListenUpdatedParams, AgentV1PromptUpdated, AgentV1PromptUpdatedParams, AgentV1ReceiveFunctionCallResponse, @@ -120,6 +122,10 @@ AgentV1SpeakUpdatedParams, AgentV1ThinkUpdated, AgentV1ThinkUpdatedParams, + AgentV1UpdateListen, + AgentV1UpdateListenListen, + AgentV1UpdateListenListenParams, + AgentV1UpdateListenParams, AgentV1UpdatePrompt, AgentV1UpdatePromptParams, AgentV1UpdateSpeak, @@ -175,6 +181,8 @@ "AgentV1InjectionRefusedParams": ".v1", "AgentV1KeepAlive": ".v1", "AgentV1KeepAliveParams": ".v1", + "AgentV1ListenUpdated": ".v1", + "AgentV1ListenUpdatedParams": ".v1", "AgentV1PromptUpdated": ".v1", "AgentV1PromptUpdatedParams": ".v1", "AgentV1ReceiveFunctionCallResponse": ".v1", @@ -254,6 +262,10 @@ "AgentV1SpeakUpdatedParams": ".v1", "AgentV1ThinkUpdated": ".v1", "AgentV1ThinkUpdatedParams": ".v1", + "AgentV1UpdateListen": ".v1", + "AgentV1UpdateListenListen": ".v1", + "AgentV1UpdateListenListenParams": ".v1", + "AgentV1UpdateListenParams": ".v1", "AgentV1UpdatePrompt": ".v1", "AgentV1UpdatePromptParams": ".v1", "AgentV1UpdateSpeak": ".v1", @@ -333,6 +345,8 @@ def __dir__(): "AgentV1InjectionRefusedParams", "AgentV1KeepAlive", "AgentV1KeepAliveParams", + "AgentV1ListenUpdated", + "AgentV1ListenUpdatedParams", "AgentV1PromptUpdated", "AgentV1PromptUpdatedParams", "AgentV1ReceiveFunctionCallResponse", @@ -412,6 +426,10 @@ def __dir__(): "AgentV1SpeakUpdatedParams", "AgentV1ThinkUpdated", "AgentV1ThinkUpdatedParams", + "AgentV1UpdateListen", + "AgentV1UpdateListenListen", + "AgentV1UpdateListenListenParams", + "AgentV1UpdateListenParams", "AgentV1UpdatePrompt", "AgentV1UpdatePromptParams", "AgentV1UpdateSpeak", diff --git a/src/deepgram/agent/v1/__init__.py b/src/deepgram/agent/v1/__init__.py index 17be4a0f..80bc28fb 100644 --- a/src/deepgram/agent/v1/__init__.py +++ b/src/deepgram/agent/v1/__init__.py @@ -25,6 +25,7 @@ AgentV1InjectUserMessage, AgentV1InjectionRefused, AgentV1KeepAlive, + AgentV1ListenUpdated, AgentV1PromptUpdated, AgentV1ReceiveFunctionCallResponse, AgentV1SendFunctionCallResponse, @@ -67,6 +68,8 @@ AgentV1SettingsFlags, AgentV1SpeakUpdated, AgentV1ThinkUpdated, + AgentV1UpdateListen, + AgentV1UpdateListenListen, AgentV1UpdatePrompt, AgentV1UpdateSpeak, AgentV1UpdateSpeakSpeak, @@ -95,6 +98,7 @@ AgentV1InjectUserMessageParams, AgentV1InjectionRefusedParams, AgentV1KeepAliveParams, + AgentV1ListenUpdatedParams, AgentV1PromptUpdatedParams, AgentV1ReceiveFunctionCallResponseParams, AgentV1SendFunctionCallResponseParams, @@ -132,6 +136,8 @@ AgentV1SettingsParams, AgentV1SpeakUpdatedParams, AgentV1ThinkUpdatedParams, + AgentV1UpdateListenListenParams, + AgentV1UpdateListenParams, AgentV1UpdatePromptParams, AgentV1UpdateSpeakParams, AgentV1UpdateSpeakSpeakParams, @@ -177,6 +183,8 @@ "AgentV1InjectionRefusedParams": ".requests", "AgentV1KeepAlive": ".types", "AgentV1KeepAliveParams": ".requests", + "AgentV1ListenUpdated": ".types", + "AgentV1ListenUpdatedParams": ".requests", "AgentV1PromptUpdated": ".types", "AgentV1PromptUpdatedParams": ".requests", "AgentV1ReceiveFunctionCallResponse": ".types", @@ -256,6 +264,10 @@ "AgentV1SpeakUpdatedParams": ".requests", "AgentV1ThinkUpdated": ".types", "AgentV1ThinkUpdatedParams": ".requests", + "AgentV1UpdateListen": ".types", + "AgentV1UpdateListenListen": ".types", + "AgentV1UpdateListenListenParams": ".requests", + "AgentV1UpdateListenParams": ".requests", "AgentV1UpdatePrompt": ".types", "AgentV1UpdatePromptParams": ".requests", "AgentV1UpdateSpeak": ".types", @@ -335,6 +347,8 @@ def __dir__(): "AgentV1InjectionRefusedParams", "AgentV1KeepAlive", "AgentV1KeepAliveParams", + "AgentV1ListenUpdated", + "AgentV1ListenUpdatedParams", "AgentV1PromptUpdated", "AgentV1PromptUpdatedParams", "AgentV1ReceiveFunctionCallResponse", @@ -414,6 +428,10 @@ def __dir__(): "AgentV1SpeakUpdatedParams", "AgentV1ThinkUpdated", "AgentV1ThinkUpdatedParams", + "AgentV1UpdateListen", + "AgentV1UpdateListenListen", + "AgentV1UpdateListenListenParams", + "AgentV1UpdateListenParams", "AgentV1UpdatePrompt", "AgentV1UpdatePromptParams", "AgentV1UpdateSpeak", diff --git a/src/deepgram/agent/v1/requests/__init__.py b/src/deepgram/agent/v1/requests/__init__.py index af4de477..eb841695 100644 --- a/src/deepgram/agent/v1/requests/__init__.py +++ b/src/deepgram/agent/v1/requests/__init__.py @@ -21,6 +21,7 @@ from .agent_v1inject_user_message import AgentV1InjectUserMessageParams from .agent_v1injection_refused import AgentV1InjectionRefusedParams from .agent_v1keep_alive import AgentV1KeepAliveParams + from .agent_v1listen_updated import AgentV1ListenUpdatedParams from .agent_v1prompt_updated import AgentV1PromptUpdatedParams from .agent_v1receive_function_call_response import AgentV1ReceiveFunctionCallResponseParams from .agent_v1send_function_call_response import AgentV1SendFunctionCallResponseParams @@ -74,6 +75,8 @@ from .agent_v1settings_flags import AgentV1SettingsFlagsParams from .agent_v1speak_updated import AgentV1SpeakUpdatedParams from .agent_v1think_updated import AgentV1ThinkUpdatedParams + from .agent_v1update_listen import AgentV1UpdateListenParams + from .agent_v1update_listen_listen import AgentV1UpdateListenListenParams from .agent_v1update_prompt import AgentV1UpdatePromptParams from .agent_v1update_speak import AgentV1UpdateSpeakParams from .agent_v1update_speak_speak import AgentV1UpdateSpeakSpeakParams @@ -100,6 +103,7 @@ "AgentV1InjectUserMessageParams": ".agent_v1inject_user_message", "AgentV1InjectionRefusedParams": ".agent_v1injection_refused", "AgentV1KeepAliveParams": ".agent_v1keep_alive", + "AgentV1ListenUpdatedParams": ".agent_v1listen_updated", "AgentV1PromptUpdatedParams": ".agent_v1prompt_updated", "AgentV1ReceiveFunctionCallResponseParams": ".agent_v1receive_function_call_response", "AgentV1SendFunctionCallResponseParams": ".agent_v1send_function_call_response", @@ -137,6 +141,8 @@ "AgentV1SettingsParams": ".agent_v1settings", "AgentV1SpeakUpdatedParams": ".agent_v1speak_updated", "AgentV1ThinkUpdatedParams": ".agent_v1think_updated", + "AgentV1UpdateListenListenParams": ".agent_v1update_listen_listen", + "AgentV1UpdateListenParams": ".agent_v1update_listen", "AgentV1UpdatePromptParams": ".agent_v1update_prompt", "AgentV1UpdateSpeakParams": ".agent_v1update_speak", "AgentV1UpdateSpeakSpeakParams": ".agent_v1update_speak_speak", @@ -187,6 +193,7 @@ def __dir__(): "AgentV1InjectUserMessageParams", "AgentV1InjectionRefusedParams", "AgentV1KeepAliveParams", + "AgentV1ListenUpdatedParams", "AgentV1PromptUpdatedParams", "AgentV1ReceiveFunctionCallResponseParams", "AgentV1SendFunctionCallResponseParams", @@ -224,6 +231,8 @@ def __dir__(): "AgentV1SettingsParams", "AgentV1SpeakUpdatedParams", "AgentV1ThinkUpdatedParams", + "AgentV1UpdateListenListenParams", + "AgentV1UpdateListenParams", "AgentV1UpdatePromptParams", "AgentV1UpdateSpeakParams", "AgentV1UpdateSpeakSpeakParams", diff --git a/src/deepgram/agent/v1/requests/agent_v1listen_updated.py b/src/deepgram/agent/v1/requests/agent_v1listen_updated.py new file mode 100644 index 00000000..de0f80c7 --- /dev/null +++ b/src/deepgram/agent/v1/requests/agent_v1listen_updated.py @@ -0,0 +1,12 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class AgentV1ListenUpdatedParams(typing_extensions.TypedDict): + type: typing.Literal["ListenUpdated"] + """ + Message type identifier for listen update confirmation + """ diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider.py b/src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider.py index 98381492..fa1dabfa 100644 --- a/src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider.py +++ b/src/deepgram/agent/v1/requests/agent_v1settings_agent_context_listen_provider.py @@ -21,6 +21,9 @@ class AgentV1SettingsAgentContextListenProvider_V2Params(typing_extensions.Typed type: typing.Literal["deepgram"] model: str language_hints: typing_extensions.NotRequired[typing.Sequence[str]] + eot_threshold: typing_extensions.NotRequired[float] + eager_eot_threshold: typing_extensions.NotRequired[float] + eot_timeout_ms: typing_extensions.NotRequired[int] keyterms: typing_extensions.NotRequired[typing.Sequence[str]] diff --git a/src/deepgram/agent/v1/requests/agent_v1settings_agent_listen_provider.py b/src/deepgram/agent/v1/requests/agent_v1settings_agent_listen_provider.py index a0ddf06b..cc5ec701 100644 --- a/src/deepgram/agent/v1/requests/agent_v1settings_agent_listen_provider.py +++ b/src/deepgram/agent/v1/requests/agent_v1settings_agent_listen_provider.py @@ -21,6 +21,9 @@ class AgentV1SettingsAgentListenProvider_V2Params(typing_extensions.TypedDict): type: typing.Literal["deepgram"] model: str language_hints: typing_extensions.NotRequired[typing.Sequence[str]] + eot_threshold: typing_extensions.NotRequired[float] + eager_eot_threshold: typing_extensions.NotRequired[float] + eot_timeout_ms: typing_extensions.NotRequired[int] keyterms: typing_extensions.NotRequired[typing.Sequence[str]] diff --git a/src/deepgram/agent/v1/requests/agent_v1update_listen.py b/src/deepgram/agent/v1/requests/agent_v1update_listen.py new file mode 100644 index 00000000..7309bc9e --- /dev/null +++ b/src/deepgram/agent/v1/requests/agent_v1update_listen.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions +from .agent_v1update_listen_listen import AgentV1UpdateListenListenParams + + +class AgentV1UpdateListenParams(typing_extensions.TypedDict): + type: typing.Literal["UpdateListen"] + """ + Message type identifier for updating the listen configuration + """ + + listen: AgentV1UpdateListenListenParams + """ + Listen configuration to update. Contains a provider object with the same schema as Settings. The provider identity (type, version, model) is required and must match the current session. + """ diff --git a/src/deepgram/agent/v1/requests/agent_v1update_listen_listen.py b/src/deepgram/agent/v1/requests/agent_v1update_listen_listen.py new file mode 100644 index 00000000..2a91d9f5 --- /dev/null +++ b/src/deepgram/agent/v1/requests/agent_v1update_listen_listen.py @@ -0,0 +1,12 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +from ....requests.deepgram_listen_provider_v2 import DeepgramListenProviderV2Params + + +class AgentV1UpdateListenListenParams(typing_extensions.TypedDict): + """ + Listen configuration to update. Contains a provider object with the same schema as Settings. The provider identity (type, version, model) is required and must match the current session. + """ + + provider: DeepgramListenProviderV2Params diff --git a/src/deepgram/agent/v1/socket_client.py b/src/deepgram/agent/v1/socket_client.py index 17c3d0d2..cc5437c2 100644 --- a/src/deepgram/agent/v1/socket_client.py +++ b/src/deepgram/agent/v1/socket_client.py @@ -18,6 +18,7 @@ from .types.agent_v1inject_user_message import AgentV1InjectUserMessage from .types.agent_v1injection_refused import AgentV1InjectionRefused from .types.agent_v1keep_alive import AgentV1KeepAlive +from .types.agent_v1listen_updated import AgentV1ListenUpdated from .types.agent_v1prompt_updated import AgentV1PromptUpdated from .types.agent_v1receive_function_call_response import AgentV1ReceiveFunctionCallResponse from .types.agent_v1send_function_call_response import AgentV1SendFunctionCallResponse @@ -25,6 +26,7 @@ from .types.agent_v1settings_applied import AgentV1SettingsApplied from .types.agent_v1speak_updated import AgentV1SpeakUpdated from .types.agent_v1think_updated import AgentV1ThinkUpdated +from .types.agent_v1update_listen import AgentV1UpdateListen from .types.agent_v1update_prompt import AgentV1UpdatePrompt from .types.agent_v1update_speak import AgentV1UpdateSpeak from .types.agent_v1update_think import AgentV1UpdateThink @@ -37,8 +39,6 @@ except ImportError: from websockets import WebSocketClientProtocol # type: ignore -_logger = logging.getLogger(__name__) - def _sanitize_numeric_types(obj: typing.Any) -> typing.Any: """ @@ -60,11 +60,13 @@ def _sanitize_numeric_types(obj: typing.Any) -> typing.Any: return obj +_logger = logging.getLogger(__name__) V1SocketClientResponse = typing.Union[ + AgentV1ListenUpdated, + AgentV1ThinkUpdated, AgentV1ReceiveFunctionCallResponse, AgentV1PromptUpdated, AgentV1SpeakUpdated, - AgentV1ThinkUpdated, AgentV1InjectionRefused, AgentV1Welcome, AgentV1SettingsApplied, @@ -136,6 +138,20 @@ async def send_settings(self, message: AgentV1Settings) -> None: """ await self._send_model(message) + async def send_update_listen(self, message: AgentV1UpdateListen) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateListen. + """ + await self._send_model(message) + + async def send_update_think(self, message: AgentV1UpdateThink) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateThink. + """ + await self._send_model(message) + async def send_update_speak(self, message: AgentV1UpdateSpeak) -> None: """ Send a message to the websocket connection. @@ -178,13 +194,6 @@ async def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ await self._send_model(message) - async def send_update_think(self, message: AgentV1UpdateThink) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdateThink. - """ - await self._send_model(message) - async def send_media(self, message: bytes) -> None: """ Send a message to the websocket connection. @@ -276,6 +285,20 @@ def send_settings(self, message: AgentV1Settings) -> None: """ self._send_model(message) + def send_update_listen(self, message: AgentV1UpdateListen) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateListen. + """ + self._send_model(message) + + def send_update_think(self, message: AgentV1UpdateThink) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a AgentV1UpdateThink. + """ + self._send_model(message) + def send_update_speak(self, message: AgentV1UpdateSpeak) -> None: """ Send a message to the websocket connection. @@ -318,13 +341,6 @@ def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None: """ self._send_model(message) - def send_update_think(self, message: AgentV1UpdateThink) -> None: - """ - Send a message to the websocket connection. - The message will be sent as a AgentV1UpdateThink. - """ - self._send_model(message) - def send_media(self, message: bytes) -> None: """ Send a message to the websocket connection. diff --git a/src/deepgram/agent/v1/types/__init__.py b/src/deepgram/agent/v1/types/__init__.py index d06917e2..91fdd222 100644 --- a/src/deepgram/agent/v1/types/__init__.py +++ b/src/deepgram/agent/v1/types/__init__.py @@ -24,6 +24,7 @@ from .agent_v1inject_user_message import AgentV1InjectUserMessage from .agent_v1injection_refused import AgentV1InjectionRefused from .agent_v1keep_alive import AgentV1KeepAlive + from .agent_v1listen_updated import AgentV1ListenUpdated from .agent_v1prompt_updated import AgentV1PromptUpdated from .agent_v1receive_function_call_response import AgentV1ReceiveFunctionCallResponse from .agent_v1send_function_call_response import AgentV1SendFunctionCallResponse @@ -82,6 +83,8 @@ from .agent_v1settings_flags import AgentV1SettingsFlags from .agent_v1speak_updated import AgentV1SpeakUpdated from .agent_v1think_updated import AgentV1ThinkUpdated + from .agent_v1update_listen import AgentV1UpdateListen + from .agent_v1update_listen_listen import AgentV1UpdateListenListen from .agent_v1update_prompt import AgentV1UpdatePrompt from .agent_v1update_speak import AgentV1UpdateSpeak from .agent_v1update_speak_speak import AgentV1UpdateSpeakSpeak @@ -111,6 +114,7 @@ "AgentV1InjectUserMessage": ".agent_v1inject_user_message", "AgentV1InjectionRefused": ".agent_v1injection_refused", "AgentV1KeepAlive": ".agent_v1keep_alive", + "AgentV1ListenUpdated": ".agent_v1listen_updated", "AgentV1PromptUpdated": ".agent_v1prompt_updated", "AgentV1ReceiveFunctionCallResponse": ".agent_v1receive_function_call_response", "AgentV1SendFunctionCallResponse": ".agent_v1send_function_call_response", @@ -153,6 +157,8 @@ "AgentV1SettingsFlags": ".agent_v1settings_flags", "AgentV1SpeakUpdated": ".agent_v1speak_updated", "AgentV1ThinkUpdated": ".agent_v1think_updated", + "AgentV1UpdateListen": ".agent_v1update_listen", + "AgentV1UpdateListenListen": ".agent_v1update_listen_listen", "AgentV1UpdatePrompt": ".agent_v1update_prompt", "AgentV1UpdateSpeak": ".agent_v1update_speak", "AgentV1UpdateSpeakSpeak": ".agent_v1update_speak_speak", @@ -206,6 +212,7 @@ def __dir__(): "AgentV1InjectUserMessage", "AgentV1InjectionRefused", "AgentV1KeepAlive", + "AgentV1ListenUpdated", "AgentV1PromptUpdated", "AgentV1ReceiveFunctionCallResponse", "AgentV1SendFunctionCallResponse", @@ -248,6 +255,8 @@ def __dir__(): "AgentV1SettingsFlags", "AgentV1SpeakUpdated", "AgentV1ThinkUpdated", + "AgentV1UpdateListen", + "AgentV1UpdateListenListen", "AgentV1UpdatePrompt", "AgentV1UpdateSpeak", "AgentV1UpdateSpeakSpeak", diff --git a/src/deepgram/agent/v1/types/agent_v1listen_updated.py b/src/deepgram/agent/v1/types/agent_v1listen_updated.py new file mode 100644 index 00000000..9c3ea1b7 --- /dev/null +++ b/src/deepgram/agent/v1/types/agent_v1listen_updated.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class AgentV1ListenUpdated(UncheckedBaseModel): + type: typing.Literal["ListenUpdated"] = pydantic.Field(default="ListenUpdated") + """ + Message type identifier for listen update confirmation + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py b/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py index 229a54dd..13d6739a 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py @@ -34,6 +34,9 @@ class AgentV1SettingsAgentContextListenProvider_V2(UncheckedBaseModel): model: str language_hints: typing.Optional[typing.List[str]] = None language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) + eot_threshold: typing.Optional[float] = None + eager_eot_threshold: typing.Optional[float] = None + eot_timeout_ms: typing.Optional[int] = None keyterms: typing.Optional[typing.List[str]] = None # Backward-compat: the public field was historically named `language_hint` diff --git a/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py b/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py index 7fe885cb..9bc63301 100644 --- a/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py +++ b/src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py @@ -34,6 +34,9 @@ class AgentV1SettingsAgentListenProvider_V2(UncheckedBaseModel): model: str language_hints: typing.Optional[typing.List[str]] = None language_hint: typing.Optional[typing.Union[str, typing.List[str]]] = pydantic.Field(default=None, exclude=True) + eot_threshold: typing.Optional[float] = None + eager_eot_threshold: typing.Optional[float] = None + eot_timeout_ms: typing.Optional[int] = None keyterms: typing.Optional[typing.List[str]] = None # Backward-compat: the public field was historically named `language_hint` diff --git a/src/deepgram/agent/v1/types/agent_v1update_listen.py b/src/deepgram/agent/v1/types/agent_v1update_listen.py new file mode 100644 index 00000000..a25a4ae1 --- /dev/null +++ b/src/deepgram/agent/v1/types/agent_v1update_listen.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel +from .agent_v1update_listen_listen import AgentV1UpdateListenListen + + +class AgentV1UpdateListen(UncheckedBaseModel): + type: typing.Literal["UpdateListen"] = pydantic.Field(default="UpdateListen") + """ + Message type identifier for updating the listen configuration + """ + + listen: AgentV1UpdateListenListen = pydantic.Field() + """ + Listen configuration to update. Contains a provider object with the same schema as Settings. The provider identity (type, version, model) is required and must match the current session. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py b/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py new file mode 100644 index 00000000..386483eb --- /dev/null +++ b/src/deepgram/agent/v1/types/agent_v1update_listen_listen.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel +from ....types.deepgram_listen_provider_v2 import DeepgramListenProviderV2 + + +class AgentV1UpdateListenListen(UncheckedBaseModel): + """ + Listen configuration to update. Contains a provider object with the same schema as Settings. The provider identity (type, version, model) is required and must match the current session. + """ + + provider: DeepgramListenProviderV2 + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/core/client_wrapper.py b/src/deepgram/core/client_wrapper.py index 5136a051..ee926880 100644 --- a/src/deepgram/core/client_wrapper.py +++ b/src/deepgram/core/client_wrapper.py @@ -30,12 +30,12 @@ def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { - "User-Agent": "deepgram-sdk/7.3.2", + "User-Agent": "deepgram-sdk/7.4.1", "X-Fern-Language": "Python", "X-Fern-Runtime": f"python/{platform.python_version()}", "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", "X-Fern-SDK-Name": "deepgram-sdk", - "X-Fern-SDK-Version": "7.3.2", + "X-Fern-SDK-Version": "7.4.1", **(self.get_custom_headers() or {}), } headers["Authorization"] = f"Token {self.api_key}" diff --git a/src/deepgram/requests/deepgram_listen_provider_v2.py b/src/deepgram/requests/deepgram_listen_provider_v2.py index 0f094f5a..47a41ed7 100644 --- a/src/deepgram/requests/deepgram_listen_provider_v2.py +++ b/src/deepgram/requests/deepgram_listen_provider_v2.py @@ -26,6 +26,21 @@ class DeepgramListenProviderV2Params(typing_extensions.TypedDict): An array of one or more BCP-47 language codes to bias the model toward specific languages. Only supported when model is flux-general-multi. Without hints, the model auto-detects the spoken language. See the Language Prompting guide for details. """ + eot_threshold: typing_extensions.NotRequired[float] + """ + End-of-turn confidence required to finish a turn. Valid range: 0.5 - 0.9. Defaults to 0.7. + """ + + eager_eot_threshold: typing_extensions.NotRequired[float] + """ + End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid range: 0.3 - 0.9. + """ + + eot_timeout_ms: typing_extensions.NotRequired[int] + """ + A turn will be finished when this much time in milliseconds has passed after speech, regardless of EOT confidence. Defaults to 5000. + """ + keyterms: typing_extensions.NotRequired[typing.Sequence[str]] """ Prompt keyterm recognition to improve Keyword Recall Rate diff --git a/src/deepgram/speak/__init__.py b/src/deepgram/speak/__init__.py index 73eda24c..27653587 100644 --- a/src/deepgram/speak/__init__.py +++ b/src/deepgram/speak/__init__.py @@ -6,7 +6,7 @@ from importlib import import_module if typing.TYPE_CHECKING: - from . import v1 + from . import v1, v2 from .v1 import ( SpeakV1Clear, SpeakV1ClearParams, @@ -30,6 +30,31 @@ SpeakV1Warning, SpeakV1WarningParams, ) + from .v2 import ( + SpeakV2Close, + SpeakV2CloseParams, + SpeakV2Connected, + SpeakV2ConnectedParams, + SpeakV2Error, + SpeakV2ErrorCode, + SpeakV2ErrorParams, + SpeakV2Flush, + SpeakV2FlushParams, + SpeakV2Flushed, + SpeakV2FlushedParams, + SpeakV2SessionMetadata, + SpeakV2SessionMetadataParams, + SpeakV2Speak, + SpeakV2SpeakParams, + SpeakV2SpeechMetadata, + SpeakV2SpeechMetadataControlsApplied, + SpeakV2SpeechMetadataControlsAppliedParams, + SpeakV2SpeechMetadataParams, + SpeakV2SpeechStarted, + SpeakV2SpeechStartedParams, + SpeakV2Warning, + SpeakV2WarningParams, + ) _dynamic_imports: typing.Dict[str, str] = { "SpeakV1Clear": ".v1", "SpeakV1ClearParams": ".v1", @@ -52,7 +77,31 @@ "SpeakV1TextParams": ".v1", "SpeakV1Warning": ".v1", "SpeakV1WarningParams": ".v1", + "SpeakV2Close": ".v2", + "SpeakV2CloseParams": ".v2", + "SpeakV2Connected": ".v2", + "SpeakV2ConnectedParams": ".v2", + "SpeakV2Error": ".v2", + "SpeakV2ErrorCode": ".v2", + "SpeakV2ErrorParams": ".v2", + "SpeakV2Flush": ".v2", + "SpeakV2FlushParams": ".v2", + "SpeakV2Flushed": ".v2", + "SpeakV2FlushedParams": ".v2", + "SpeakV2SessionMetadata": ".v2", + "SpeakV2SessionMetadataParams": ".v2", + "SpeakV2Speak": ".v2", + "SpeakV2SpeakParams": ".v2", + "SpeakV2SpeechMetadata": ".v2", + "SpeakV2SpeechMetadataControlsApplied": ".v2", + "SpeakV2SpeechMetadataControlsAppliedParams": ".v2", + "SpeakV2SpeechMetadataParams": ".v2", + "SpeakV2SpeechStarted": ".v2", + "SpeakV2SpeechStartedParams": ".v2", + "SpeakV2Warning": ".v2", + "SpeakV2WarningParams": ".v2", "v1": ".v1", + "v2": ".v2", } @@ -99,5 +148,29 @@ def __dir__(): "SpeakV1TextParams", "SpeakV1Warning", "SpeakV1WarningParams", + "SpeakV2Close", + "SpeakV2CloseParams", + "SpeakV2Connected", + "SpeakV2ConnectedParams", + "SpeakV2Error", + "SpeakV2ErrorCode", + "SpeakV2ErrorParams", + "SpeakV2Flush", + "SpeakV2FlushParams", + "SpeakV2Flushed", + "SpeakV2FlushedParams", + "SpeakV2SessionMetadata", + "SpeakV2SessionMetadataParams", + "SpeakV2Speak", + "SpeakV2SpeakParams", + "SpeakV2SpeechMetadata", + "SpeakV2SpeechMetadataControlsApplied", + "SpeakV2SpeechMetadataControlsAppliedParams", + "SpeakV2SpeechMetadataParams", + "SpeakV2SpeechStarted", + "SpeakV2SpeechStartedParams", + "SpeakV2Warning", + "SpeakV2WarningParams", "v1", + "v2", ] diff --git a/src/deepgram/speak/client.py b/src/deepgram/speak/client.py index 4efefd5f..7ca366a2 100644 --- a/src/deepgram/speak/client.py +++ b/src/deepgram/speak/client.py @@ -9,6 +9,7 @@ if typing.TYPE_CHECKING: from .v1.client import AsyncV1Client, V1Client + from .v2.client import AsyncV2Client, V2Client class SpeakClient: @@ -16,6 +17,7 @@ def __init__(self, *, client_wrapper: SyncClientWrapper): self._raw_client = RawSpeakClient(client_wrapper=client_wrapper) self._client_wrapper = client_wrapper self._v1: typing.Optional[V1Client] = None + self._v2: typing.Optional[V2Client] = None @property def with_raw_response(self) -> RawSpeakClient: @@ -36,12 +38,21 @@ def v1(self): self._v1 = V1Client(client_wrapper=self._client_wrapper) return self._v1 + @property + def v2(self): + if self._v2 is None: + from .v2.client import V2Client # noqa: E402 + + self._v2 = V2Client(client_wrapper=self._client_wrapper) + return self._v2 + class AsyncSpeakClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._raw_client = AsyncRawSpeakClient(client_wrapper=client_wrapper) self._client_wrapper = client_wrapper self._v1: typing.Optional[AsyncV1Client] = None + self._v2: typing.Optional[AsyncV2Client] = None @property def with_raw_response(self) -> AsyncRawSpeakClient: @@ -61,3 +72,11 @@ def v1(self): self._v1 = AsyncV1Client(client_wrapper=self._client_wrapper) return self._v1 + + @property + def v2(self): + if self._v2 is None: + from .v2.client import AsyncV2Client # noqa: E402 + + self._v2 = AsyncV2Client(client_wrapper=self._client_wrapper) + return self._v2 diff --git a/src/deepgram/speak/v2/__init__.py b/src/deepgram/speak/v2/__init__.py new file mode 100644 index 00000000..707598c6 --- /dev/null +++ b/src/deepgram/speak/v2/__init__.py @@ -0,0 +1,108 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + SpeakV2Close, + SpeakV2Connected, + SpeakV2Error, + SpeakV2ErrorCode, + SpeakV2Flush, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Speak, + SpeakV2SpeechMetadata, + SpeakV2SpeechMetadataControlsApplied, + SpeakV2SpeechStarted, + SpeakV2Warning, + ) + from .requests import ( + SpeakV2CloseParams, + SpeakV2ConnectedParams, + SpeakV2ErrorParams, + SpeakV2FlushParams, + SpeakV2FlushedParams, + SpeakV2SessionMetadataParams, + SpeakV2SpeakParams, + SpeakV2SpeechMetadataControlsAppliedParams, + SpeakV2SpeechMetadataParams, + SpeakV2SpeechStartedParams, + SpeakV2WarningParams, + ) +_dynamic_imports: typing.Dict[str, str] = { + "SpeakV2Close": ".types", + "SpeakV2CloseParams": ".requests", + "SpeakV2Connected": ".types", + "SpeakV2ConnectedParams": ".requests", + "SpeakV2Error": ".types", + "SpeakV2ErrorCode": ".types", + "SpeakV2ErrorParams": ".requests", + "SpeakV2Flush": ".types", + "SpeakV2FlushParams": ".requests", + "SpeakV2Flushed": ".types", + "SpeakV2FlushedParams": ".requests", + "SpeakV2SessionMetadata": ".types", + "SpeakV2SessionMetadataParams": ".requests", + "SpeakV2Speak": ".types", + "SpeakV2SpeakParams": ".requests", + "SpeakV2SpeechMetadata": ".types", + "SpeakV2SpeechMetadataControlsApplied": ".types", + "SpeakV2SpeechMetadataControlsAppliedParams": ".requests", + "SpeakV2SpeechMetadataParams": ".requests", + "SpeakV2SpeechStarted": ".types", + "SpeakV2SpeechStartedParams": ".requests", + "SpeakV2Warning": ".types", + "SpeakV2WarningParams": ".requests", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "SpeakV2Close", + "SpeakV2CloseParams", + "SpeakV2Connected", + "SpeakV2ConnectedParams", + "SpeakV2Error", + "SpeakV2ErrorCode", + "SpeakV2ErrorParams", + "SpeakV2Flush", + "SpeakV2FlushParams", + "SpeakV2Flushed", + "SpeakV2FlushedParams", + "SpeakV2SessionMetadata", + "SpeakV2SessionMetadataParams", + "SpeakV2Speak", + "SpeakV2SpeakParams", + "SpeakV2SpeechMetadata", + "SpeakV2SpeechMetadataControlsApplied", + "SpeakV2SpeechMetadataControlsAppliedParams", + "SpeakV2SpeechMetadataParams", + "SpeakV2SpeechStarted", + "SpeakV2SpeechStartedParams", + "SpeakV2Warning", + "SpeakV2WarningParams", +] diff --git a/src/deepgram/speak/v2/client.py b/src/deepgram/speak/v2/client.py new file mode 100644 index 00000000..ed0c54a7 --- /dev/null +++ b/src/deepgram/speak/v2/client.py @@ -0,0 +1,226 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +import urllib.parse +from contextlib import asynccontextmanager, contextmanager + +import websockets.sync.client as websockets_sync_client +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import jsonable_encoder +from ...core.query_encoder import encode_query +from ...core.remove_none_from_dict import remove_none_from_dict +from ...core.request_options import RequestOptions +from ...core.websocket_compat import InvalidWebSocketStatus, get_status_code +from ...types.speak_v2encoding import SpeakV2Encoding +from ...types.speak_v2mip_opt_out import SpeakV2MipOptOut +from ...types.speak_v2model import SpeakV2Model +from ...types.speak_v2sample_rate import SpeakV2SampleRate +from ...types.speak_v2tag import SpeakV2Tag +from .raw_client import AsyncRawV2Client, RawV2Client +from .socket_client import AsyncV2SocketClient, V2SocketClient + +try: + from websockets.legacy.client import connect as websockets_client_connect # type: ignore +except ImportError: + from websockets import connect as websockets_client_connect # type: ignore + + +class V2Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawV2Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawV2Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawV2Client + """ + return self._raw_client + + @contextmanager + def connect( + self, + *, + model: SpeakV2Model, + encoding: typing.Optional[SpeakV2Encoding] = None, + sample_rate: typing.Optional[SpeakV2SampleRate] = None, + mip_opt_out: typing.Optional[SpeakV2MipOptOut] = None, + tag: typing.Optional[SpeakV2Tag] = None, + authorization: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Iterator[V2SocketClient]: + """ + Streaming, turn-based text-to-speech (Flux TTS) built for voice-agent + pipelines. Stream LLM tokens in, speak them to the user, and report + per-turn billing and timing. + + Parameters + ---------- + model : SpeakV2Model + + encoding : typing.Optional[SpeakV2Encoding] + + sample_rate : typing.Optional[SpeakV2SampleRate] + + mip_opt_out : typing.Optional[SpeakV2MipOptOut] + + tag : typing.Optional[SpeakV2Tag] + + authorization : typing.Optional[str] + Use your API key or a [temporary token](/guides/fundamentals/token-based-authentication) for authentication via the `Authorization` header. In client-side environments where custom headers are not supported, use the [`Sec-WebSocket-Protocol`](/guides/deep-dives/using-the-sec-websocket-protocol) header instead. + + **Example:** `Authorization: Token %DEEPGRAM_API_KEY%` or `Authorization: Bearer %DEEPGRAM_TOKEN%` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + V2SocketClient + """ + ws_url = self._raw_client._client_wrapper.get_environment().production + "/v2/speak" + _encoded_query_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + { + "model": model, + "encoding": encoding, + "sample_rate": sample_rate, + "mip_opt_out": mip_opt_out, + "tag": tag, + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + } + ) + ) + ) + if _encoded_query_params: + ws_url = ws_url + "?" + urllib.parse.urlencode(_encoded_query_params) + headers = self._raw_client._client_wrapper.get_headers() + if authorization is not None: + headers["Authorization"] = str(authorization) + if request_options and "additional_headers" in request_options: + headers.update(request_options["additional_headers"]) + try: + with websockets_sync_client.connect(ws_url, additional_headers=headers) as protocol: + yield V2SocketClient(websocket=protocol) + except InvalidWebSocketStatus as exc: + status_code: int = get_status_code(exc) + if status_code == 401: + raise ApiError( + status_code=status_code, + headers=dict(headers), + body="Websocket initialized with invalid credentials.", + ) + raise ApiError( + status_code=status_code, + headers=dict(headers), + body="Unexpected error when initializing websocket connection.", + ) + + +class AsyncV2Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawV2Client(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawV2Client: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawV2Client + """ + return self._raw_client + + @asynccontextmanager + async def connect( + self, + *, + model: SpeakV2Model, + encoding: typing.Optional[SpeakV2Encoding] = None, + sample_rate: typing.Optional[SpeakV2SampleRate] = None, + mip_opt_out: typing.Optional[SpeakV2MipOptOut] = None, + tag: typing.Optional[SpeakV2Tag] = None, + authorization: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.AsyncIterator[AsyncV2SocketClient]: + """ + Streaming, turn-based text-to-speech (Flux TTS) built for voice-agent + pipelines. Stream LLM tokens in, speak them to the user, and report + per-turn billing and timing. + + Parameters + ---------- + model : SpeakV2Model + + encoding : typing.Optional[SpeakV2Encoding] + + sample_rate : typing.Optional[SpeakV2SampleRate] + + mip_opt_out : typing.Optional[SpeakV2MipOptOut] + + tag : typing.Optional[SpeakV2Tag] + + authorization : typing.Optional[str] + Use your API key or a [temporary token](/guides/fundamentals/token-based-authentication) for authentication via the `Authorization` header. In client-side environments where custom headers are not supported, use the [`Sec-WebSocket-Protocol`](/guides/deep-dives/using-the-sec-websocket-protocol) header instead. + + **Example:** `Authorization: Token %DEEPGRAM_API_KEY%` or `Authorization: Bearer %DEEPGRAM_TOKEN%` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncV2SocketClient + """ + ws_url = self._raw_client._client_wrapper.get_environment().production + "/v2/speak" + _encoded_query_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + { + "model": model, + "encoding": encoding, + "sample_rate": sample_rate, + "mip_opt_out": mip_opt_out, + "tag": tag, + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + } + ) + ) + ) + if _encoded_query_params: + ws_url = ws_url + "?" + urllib.parse.urlencode(_encoded_query_params) + headers = self._raw_client._client_wrapper.get_headers() + if authorization is not None: + headers["Authorization"] = str(authorization) + if request_options and "additional_headers" in request_options: + headers.update(request_options["additional_headers"]) + try: + async with websockets_client_connect(ws_url, extra_headers=headers) as protocol: + yield AsyncV2SocketClient(websocket=protocol) + except InvalidWebSocketStatus as exc: + status_code: int = get_status_code(exc) + if status_code == 401: + raise ApiError( + status_code=status_code, + headers=dict(headers), + body="Websocket initialized with invalid credentials.", + ) + raise ApiError( + status_code=status_code, + headers=dict(headers), + body="Unexpected error when initializing websocket connection.", + ) diff --git a/src/deepgram/speak/v2/raw_client.py b/src/deepgram/speak/v2/raw_client.py new file mode 100644 index 00000000..4854672f --- /dev/null +++ b/src/deepgram/speak/v2/raw_client.py @@ -0,0 +1,203 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +import urllib.parse +from contextlib import asynccontextmanager, contextmanager + +import websockets.sync.client as websockets_sync_client +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.jsonable_encoder import jsonable_encoder +from ...core.query_encoder import encode_query +from ...core.remove_none_from_dict import remove_none_from_dict +from ...core.request_options import RequestOptions +from ...core.websocket_compat import InvalidWebSocketStatus, get_status_code +from ...types.speak_v2encoding import SpeakV2Encoding +from ...types.speak_v2mip_opt_out import SpeakV2MipOptOut +from ...types.speak_v2model import SpeakV2Model +from ...types.speak_v2sample_rate import SpeakV2SampleRate +from ...types.speak_v2tag import SpeakV2Tag +from .socket_client import AsyncV2SocketClient, V2SocketClient + +try: + from websockets.legacy.client import connect as websockets_client_connect # type: ignore +except ImportError: + from websockets import connect as websockets_client_connect # type: ignore + + +class RawV2Client: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + @contextmanager + def connect( + self, + *, + model: SpeakV2Model, + encoding: typing.Optional[SpeakV2Encoding] = None, + sample_rate: typing.Optional[SpeakV2SampleRate] = None, + mip_opt_out: typing.Optional[SpeakV2MipOptOut] = None, + tag: typing.Optional[SpeakV2Tag] = None, + authorization: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.Iterator[V2SocketClient]: + """ + Streaming, turn-based text-to-speech (Flux TTS) built for voice-agent + pipelines. Stream LLM tokens in, speak them to the user, and report + per-turn billing and timing. + + Parameters + ---------- + model : SpeakV2Model + + encoding : typing.Optional[SpeakV2Encoding] + + sample_rate : typing.Optional[SpeakV2SampleRate] + + mip_opt_out : typing.Optional[SpeakV2MipOptOut] + + tag : typing.Optional[SpeakV2Tag] + + authorization : typing.Optional[str] + Use your API key or a [temporary token](/guides/fundamentals/token-based-authentication) for authentication via the `Authorization` header. In client-side environments where custom headers are not supported, use the [`Sec-WebSocket-Protocol`](/guides/deep-dives/using-the-sec-websocket-protocol) header instead. + + **Example:** `Authorization: Token %DEEPGRAM_API_KEY%` or `Authorization: Bearer %DEEPGRAM_TOKEN%` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + V2SocketClient + """ + ws_url = self._client_wrapper.get_environment().production + "/v2/speak" + _encoded_query_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + { + "model": model, + "encoding": encoding, + "sample_rate": sample_rate, + "mip_opt_out": mip_opt_out, + "tag": tag, + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + } + ) + ) + ) + if _encoded_query_params: + ws_url = ws_url + "?" + urllib.parse.urlencode(_encoded_query_params) + headers = self._client_wrapper.get_headers() + if authorization is not None: + headers["Authorization"] = str(authorization) + if request_options and "additional_headers" in request_options: + headers.update(request_options["additional_headers"]) + try: + with websockets_sync_client.connect(ws_url, additional_headers=headers) as protocol: + yield V2SocketClient(websocket=protocol) + except InvalidWebSocketStatus as exc: + status_code: int = get_status_code(exc) + if status_code == 401: + raise ApiError( + status_code=status_code, + headers=dict(headers), + body="Websocket initialized with invalid credentials.", + ) + raise ApiError( + status_code=status_code, + headers=dict(headers), + body="Unexpected error when initializing websocket connection.", + ) + + +class AsyncRawV2Client: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + @asynccontextmanager + async def connect( + self, + *, + model: SpeakV2Model, + encoding: typing.Optional[SpeakV2Encoding] = None, + sample_rate: typing.Optional[SpeakV2SampleRate] = None, + mip_opt_out: typing.Optional[SpeakV2MipOptOut] = None, + tag: typing.Optional[SpeakV2Tag] = None, + authorization: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> typing.AsyncIterator[AsyncV2SocketClient]: + """ + Streaming, turn-based text-to-speech (Flux TTS) built for voice-agent + pipelines. Stream LLM tokens in, speak them to the user, and report + per-turn billing and timing. + + Parameters + ---------- + model : SpeakV2Model + + encoding : typing.Optional[SpeakV2Encoding] + + sample_rate : typing.Optional[SpeakV2SampleRate] + + mip_opt_out : typing.Optional[SpeakV2MipOptOut] + + tag : typing.Optional[SpeakV2Tag] + + authorization : typing.Optional[str] + Use your API key or a [temporary token](/guides/fundamentals/token-based-authentication) for authentication via the `Authorization` header. In client-side environments where custom headers are not supported, use the [`Sec-WebSocket-Protocol`](/guides/deep-dives/using-the-sec-websocket-protocol) header instead. + + **Example:** `Authorization: Token %DEEPGRAM_API_KEY%` or `Authorization: Bearer %DEEPGRAM_TOKEN%` + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncV2SocketClient + """ + ws_url = self._client_wrapper.get_environment().production + "/v2/speak" + _encoded_query_params = encode_query( + jsonable_encoder( + remove_none_from_dict( + { + "model": model, + "encoding": encoding, + "sample_rate": sample_rate, + "mip_opt_out": mip_opt_out, + "tag": tag, + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + } + ) + ) + ) + if _encoded_query_params: + ws_url = ws_url + "?" + urllib.parse.urlencode(_encoded_query_params) + headers = self._client_wrapper.get_headers() + if authorization is not None: + headers["Authorization"] = str(authorization) + if request_options and "additional_headers" in request_options: + headers.update(request_options["additional_headers"]) + try: + async with websockets_client_connect(ws_url, extra_headers=headers) as protocol: + yield AsyncV2SocketClient(websocket=protocol) + except InvalidWebSocketStatus as exc: + status_code: int = get_status_code(exc) + if status_code == 401: + raise ApiError( + status_code=status_code, + headers=dict(headers), + body="Websocket initialized with invalid credentials.", + ) + raise ApiError( + status_code=status_code, + headers=dict(headers), + body="Unexpected error when initializing websocket connection.", + ) diff --git a/src/deepgram/speak/v2/requests/__init__.py b/src/deepgram/speak/v2/requests/__init__.py new file mode 100644 index 00000000..6da073b9 --- /dev/null +++ b/src/deepgram/speak/v2/requests/__init__.py @@ -0,0 +1,68 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .speak_v2close import SpeakV2CloseParams + from .speak_v2connected import SpeakV2ConnectedParams + from .speak_v2error import SpeakV2ErrorParams + from .speak_v2flush import SpeakV2FlushParams + from .speak_v2flushed import SpeakV2FlushedParams + from .speak_v2session_metadata import SpeakV2SessionMetadataParams + from .speak_v2speak import SpeakV2SpeakParams + from .speak_v2speech_metadata import SpeakV2SpeechMetadataParams + from .speak_v2speech_metadata_controls_applied import SpeakV2SpeechMetadataControlsAppliedParams + from .speak_v2speech_started import SpeakV2SpeechStartedParams + from .speak_v2warning import SpeakV2WarningParams +_dynamic_imports: typing.Dict[str, str] = { + "SpeakV2CloseParams": ".speak_v2close", + "SpeakV2ConnectedParams": ".speak_v2connected", + "SpeakV2ErrorParams": ".speak_v2error", + "SpeakV2FlushParams": ".speak_v2flush", + "SpeakV2FlushedParams": ".speak_v2flushed", + "SpeakV2SessionMetadataParams": ".speak_v2session_metadata", + "SpeakV2SpeakParams": ".speak_v2speak", + "SpeakV2SpeechMetadataControlsAppliedParams": ".speak_v2speech_metadata_controls_applied", + "SpeakV2SpeechMetadataParams": ".speak_v2speech_metadata", + "SpeakV2SpeechStartedParams": ".speak_v2speech_started", + "SpeakV2WarningParams": ".speak_v2warning", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "SpeakV2CloseParams", + "SpeakV2ConnectedParams", + "SpeakV2ErrorParams", + "SpeakV2FlushParams", + "SpeakV2FlushedParams", + "SpeakV2SessionMetadataParams", + "SpeakV2SpeakParams", + "SpeakV2SpeechMetadataControlsAppliedParams", + "SpeakV2SpeechMetadataParams", + "SpeakV2SpeechStartedParams", + "SpeakV2WarningParams", +] diff --git a/src/deepgram/speak/v2/requests/speak_v2close.py b/src/deepgram/speak/v2/requests/speak_v2close.py new file mode 100644 index 00000000..c6a94f6b --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2close.py @@ -0,0 +1,12 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class SpeakV2CloseParams(typing_extensions.TypedDict): + type: typing.Literal["Close"] + """ + Message type identifier + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2connected.py b/src/deepgram/speak/v2/requests/speak_v2connected.py new file mode 100644 index 00000000..a6cc5b01 --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2connected.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class SpeakV2ConnectedParams(typing_extensions.TypedDict): + type: typing.Literal["Connected"] + """ + Message type identifier + """ + + request_id: str + """ + The unique identifier of the `/v2/speak` request + """ + + model_name: str + """ + Resolved model name + """ + + model_version: str + """ + Resolved model version + """ + + model_uuids: typing.Sequence[str] + """ + Resolved model UUIDs. A list, because a resolved model may be backed by more than one underlying model. + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2error.py b/src/deepgram/speak/v2/requests/speak_v2error.py new file mode 100644 index 00000000..83ff05b1 --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2error.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions +from ..types.speak_v2error_code import SpeakV2ErrorCode + + +class SpeakV2ErrorParams(typing_extensions.TypedDict): + type: typing.Literal["Error"] + """ + Message type identifier + """ + + code: SpeakV2ErrorCode + """ + A code identifying the error, e.g. `MESSAGE-0000` or `NET-0000`. + """ + + description: str + """ + Prose description of the error + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2flush.py b/src/deepgram/speak/v2/requests/speak_v2flush.py new file mode 100644 index 00000000..b951d310 --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2flush.py @@ -0,0 +1,12 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class SpeakV2FlushParams(typing_extensions.TypedDict): + type: typing.Literal["Flush"] + """ + Message type identifier + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2flushed.py b/src/deepgram/speak/v2/requests/speak_v2flushed.py new file mode 100644 index 00000000..9cf1dd81 --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2flushed.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class SpeakV2FlushedParams(typing_extensions.TypedDict): + type: typing.Literal["Flushed"] + """ + Message type identifier + """ + + speech_id: str + """ + Server-assigned turn identifier + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2session_metadata.py b/src/deepgram/speak/v2/requests/speak_v2session_metadata.py new file mode 100644 index 00000000..9b4b9e03 --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2session_metadata.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class SpeakV2SessionMetadataParams(typing_extensions.TypedDict): + type: typing.Literal["SessionMetadata"] + """ + Message type identifier + """ + + total_audio_duration_ms: int + """ + Cumulative audio duration produced across the session, in milliseconds + """ + + total_input_character_count: int + """ + Cumulative raw input character count across the session + """ + + total_billable_character_count: int + """ + Cumulative billable character count across the session + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2speak.py b/src/deepgram/speak/v2/requests/speak_v2speak.py new file mode 100644 index 00000000..2112ce06 --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2speak.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class SpeakV2SpeakParams(typing_extensions.TypedDict): + type: typing.Literal["Speak"] + """ + Message type identifier + """ + + text: str + """ + The input text to synthesize + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2speech_metadata.py b/src/deepgram/speak/v2/requests/speak_v2speech_metadata.py new file mode 100644 index 00000000..23b20488 --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2speech_metadata.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions +from .speak_v2speech_metadata_controls_applied import SpeakV2SpeechMetadataControlsAppliedParams + + +class SpeakV2SpeechMetadataParams(typing_extensions.TypedDict): + type: typing.Literal["SpeechMetadata"] + """ + Message type identifier + """ + + speech_id: str + """ + Server-assigned turn identifier + """ + + audio_duration_ms: int + """ + Total audio duration produced for this turn, in milliseconds + """ + + input_character_count: int + """ + Raw input character count for this turn, before text normalization + """ + + billable_character_count: int + """ + Billable character count for this turn — the input character count with stripped control characters removed. Always less than or equal to `input_character_count`. + """ + + controls_applied: SpeakV2SpeechMetadataControlsAppliedParams + """ + Controls applied during the turn. Inline pronunciation and pause controls are not available during Early Access, so every count is currently `0`. + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2speech_metadata_controls_applied.py b/src/deepgram/speak/v2/requests/speak_v2speech_metadata_controls_applied.py new file mode 100644 index 00000000..561318ac --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2speech_metadata_controls_applied.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + + +class SpeakV2SpeechMetadataControlsAppliedParams(typing_extensions.TypedDict): + """ + Controls applied during the turn. Inline pronunciation and pause controls are not available during Early Access, so every count is currently `0`. + """ + + pronunciations_applied: int + """ + Pronunciation overrides successfully applied. Mirrors the Aura-2 `dg-pronunciations-applied` REST header. Always `0` during Early Access. + """ + + pronunciation_warnings: int + """ + Pronunciation entries that triggered a warning (invalid IPA, word too long). Mirrors the Aura-2 `dg-pronunciation-warnings` REST header. Always `0` during Early Access. + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2speech_started.py b/src/deepgram/speak/v2/requests/speak_v2speech_started.py new file mode 100644 index 00000000..ad860b0e --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2speech_started.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class SpeakV2SpeechStartedParams(typing_extensions.TypedDict): + type: typing.Literal["SpeechStarted"] + """ + Message type identifier + """ + + speech_id: str + """ + Server-minted identifier for this turn, of the form `dg_sp_<12 hex digits>`. Informational. + """ diff --git a/src/deepgram/speak/v2/requests/speak_v2warning.py b/src/deepgram/speak/v2/requests/speak_v2warning.py new file mode 100644 index 00000000..1cbeb836 --- /dev/null +++ b/src/deepgram/speak/v2/requests/speak_v2warning.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import typing_extensions + + +class SpeakV2WarningParams(typing_extensions.TypedDict): + type: typing.Literal["Warning"] + """ + Message type identifier + """ + + code: str + """ + Warning code identifying the condition, in `SCREAMING_SNAKE_CASE`. Early Access codes are `NO_ACTIVE_SPEECH` (a speech-scoped message arrived with no active turn) and `SYNTHESIS_RETRYING` (a synthesis request failed and is being retried). + """ + + description: str + """ + A human-readable description of the warning + """ diff --git a/src/deepgram/speak/v2/socket_client.py b/src/deepgram/speak/v2/socket_client.py new file mode 100644 index 00000000..a2896a45 --- /dev/null +++ b/src/deepgram/speak/v2/socket_client.py @@ -0,0 +1,232 @@ +# This file was auto-generated by Fern from our API Definition. + +import json +import logging +import typing + +import websockets.sync.connection as websockets_sync_connection +from ...core.events import EventEmitterMixin, EventType +from ...core.unchecked_base_model import construct_type +from .types.speak_v2close import SpeakV2Close +from .types.speak_v2connected import SpeakV2Connected +from .types.speak_v2error import SpeakV2Error +from .types.speak_v2flush import SpeakV2Flush +from .types.speak_v2flushed import SpeakV2Flushed +from .types.speak_v2session_metadata import SpeakV2SessionMetadata +from .types.speak_v2speak import SpeakV2Speak +from .types.speak_v2speech_metadata import SpeakV2SpeechMetadata +from .types.speak_v2speech_started import SpeakV2SpeechStarted +from .types.speak_v2warning import SpeakV2Warning + +try: + from websockets.legacy.client import WebSocketClientProtocol # type: ignore +except ImportError: + from websockets import WebSocketClientProtocol # type: ignore + +_logger = logging.getLogger(__name__) +V2SocketClientResponse = typing.Union[ + bytes, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Warning, + SpeakV2Error, +] + + +class AsyncV2SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: WebSocketClientProtocol): + super().__init__() + self._websocket = websocket + + async def __aiter__(self): + async for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + async def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + await self._emit_async(EventType.OPEN, None) + try: + async for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + await self._emit_async(EventType.MESSAGE, parsed) + except Exception as exc: + await self._emit_async(EventType.ERROR, exc) + finally: + await self._emit_async(EventType.CLOSE, None) + + async def send_speak(self, message: SpeakV2Speak) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Speak. + """ + await self._send_model(message) + + async def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Flush. + """ + await self._send_model(message or SpeakV2Flush(type="Flush")) + + async def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Close. + """ + await self._send_model(message or SpeakV2Close(type="Close")) + + async def recv(self) -> V2SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = await self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + async def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + await self._websocket.send(data) + + async def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + await self._send(data.dict()) + + +class V2SocketClient(EventEmitterMixin): + def __init__(self, *, websocket: websockets_sync_connection.Connection): + super().__init__() + self._websocket = websocket + + def __iter__(self): + for message in self._websocket: + if isinstance(message, bytes): + yield message + else: + try: + yield construct_type(type_=V2SocketClientResponse, object_=json.loads(message)) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + + def start_listening(self): + """ + Start listening for messages on the websocket connection. + + Emits events in the following order: + - EventType.OPEN when connection is established + - EventType.MESSAGE for each message received + - EventType.ERROR if an error occurs + - EventType.CLOSE when connection is closed + """ + self._emit(EventType.OPEN, None) + try: + for raw_message in self._websocket: + if isinstance(raw_message, bytes): + parsed = raw_message + else: + json_data = json.loads(raw_message) + try: + parsed = construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning( + "Skipping unknown WebSocket message; update your SDK version to support new message types." + ) + continue + self._emit(EventType.MESSAGE, parsed) + except Exception as exc: + self._emit(EventType.ERROR, exc) + finally: + self._emit(EventType.CLOSE, None) + + def send_speak(self, message: SpeakV2Speak) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Speak. + """ + self._send_model(message) + + def send_flush(self, message: typing.Optional[SpeakV2Flush] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Flush. + """ + self._send_model(message or SpeakV2Flush(type="Flush")) + + def send_close(self, message: typing.Optional[SpeakV2Close] = None) -> None: + """ + Send a message to the websocket connection. + The message will be sent as a SpeakV2Close. + """ + self._send_model(message or SpeakV2Close(type="Close")) + + def recv(self) -> V2SocketClientResponse: + """ + Receive a message from the websocket connection. + """ + data = self._websocket.recv() + if isinstance(data, bytes): + return data # type: ignore + json_data = json.loads(data) + try: + return construct_type(type_=V2SocketClientResponse, object_=json_data) # type: ignore + except Exception: + _logger.warning("Skipping unknown WebSocket message; update your SDK version to support new message types.") + return json_data # type: ignore + + def _send(self, data: typing.Any) -> None: + """ + Send a message to the websocket connection. + """ + if isinstance(data, dict): + data = json.dumps(data) + self._websocket.send(data) + + def _send_model(self, data: typing.Any) -> None: + """ + Send a Pydantic model to the websocket connection. + """ + self._send(data.dict()) diff --git a/src/deepgram/speak/v2/types/__init__.py b/src/deepgram/speak/v2/types/__init__.py new file mode 100644 index 00000000..b08407f0 --- /dev/null +++ b/src/deepgram/speak/v2/types/__init__.py @@ -0,0 +1,71 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .speak_v2close import SpeakV2Close + from .speak_v2connected import SpeakV2Connected + from .speak_v2error import SpeakV2Error + from .speak_v2error_code import SpeakV2ErrorCode + from .speak_v2flush import SpeakV2Flush + from .speak_v2flushed import SpeakV2Flushed + from .speak_v2session_metadata import SpeakV2SessionMetadata + from .speak_v2speak import SpeakV2Speak + from .speak_v2speech_metadata import SpeakV2SpeechMetadata + from .speak_v2speech_metadata_controls_applied import SpeakV2SpeechMetadataControlsApplied + from .speak_v2speech_started import SpeakV2SpeechStarted + from .speak_v2warning import SpeakV2Warning +_dynamic_imports: typing.Dict[str, str] = { + "SpeakV2Close": ".speak_v2close", + "SpeakV2Connected": ".speak_v2connected", + "SpeakV2Error": ".speak_v2error", + "SpeakV2ErrorCode": ".speak_v2error_code", + "SpeakV2Flush": ".speak_v2flush", + "SpeakV2Flushed": ".speak_v2flushed", + "SpeakV2SessionMetadata": ".speak_v2session_metadata", + "SpeakV2Speak": ".speak_v2speak", + "SpeakV2SpeechMetadata": ".speak_v2speech_metadata", + "SpeakV2SpeechMetadataControlsApplied": ".speak_v2speech_metadata_controls_applied", + "SpeakV2SpeechStarted": ".speak_v2speech_started", + "SpeakV2Warning": ".speak_v2warning", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "SpeakV2Close", + "SpeakV2Connected", + "SpeakV2Error", + "SpeakV2ErrorCode", + "SpeakV2Flush", + "SpeakV2Flushed", + "SpeakV2SessionMetadata", + "SpeakV2Speak", + "SpeakV2SpeechMetadata", + "SpeakV2SpeechMetadataControlsApplied", + "SpeakV2SpeechStarted", + "SpeakV2Warning", +] diff --git a/src/deepgram/speak/v2/types/speak_v2close.py b/src/deepgram/speak/v2/types/speak_v2close.py new file mode 100644 index 00000000..3afd1934 --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2close.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class SpeakV2Close(UncheckedBaseModel): + type: typing.Literal["Close"] = pydantic.Field(default="Close") + """ + Message type identifier + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2connected.py b/src/deepgram/speak/v2/types/speak_v2connected.py new file mode 100644 index 00000000..197c5792 --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2connected.py @@ -0,0 +1,43 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class SpeakV2Connected(UncheckedBaseModel): + type: typing.Literal["Connected"] = pydantic.Field(default="Connected") + """ + Message type identifier + """ + + request_id: str = pydantic.Field() + """ + The unique identifier of the `/v2/speak` request + """ + + model_name: str = pydantic.Field() + """ + Resolved model name + """ + + model_version: str = pydantic.Field() + """ + Resolved model version + """ + + model_uuids: typing.List[str] = pydantic.Field() + """ + Resolved model UUIDs. A list, because a resolved model may be backed by more than one underlying model. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2error.py b/src/deepgram/speak/v2/types/speak_v2error.py new file mode 100644 index 00000000..9bb02c21 --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2error.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel +from .speak_v2error_code import SpeakV2ErrorCode + + +class SpeakV2Error(UncheckedBaseModel): + type: typing.Literal["Error"] = pydantic.Field(default="Error") + """ + Message type identifier + """ + + code: SpeakV2ErrorCode = pydantic.Field() + """ + A code identifying the error, e.g. `MESSAGE-0000` or `NET-0000`. + """ + + description: str = pydantic.Field() + """ + Prose description of the error + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2error_code.py b/src/deepgram/speak/v2/types/speak_v2error_code.py new file mode 100644 index 00000000..8d9e1df1 --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2error_code.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SpeakV2ErrorCode = typing.Union[ + typing.Literal["MESSAGE-0000", "DATA-0000", "BIG-0000", "NET-0000", "NET-0001", "NET-0002", "NET-0003", "NET-0004"], + typing.Any, +] diff --git a/src/deepgram/speak/v2/types/speak_v2flush.py b/src/deepgram/speak/v2/types/speak_v2flush.py new file mode 100644 index 00000000..088df676 --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2flush.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class SpeakV2Flush(UncheckedBaseModel): + type: typing.Literal["Flush"] = pydantic.Field(default="Flush") + """ + Message type identifier + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2flushed.py b/src/deepgram/speak/v2/types/speak_v2flushed.py new file mode 100644 index 00000000..4739cf47 --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2flushed.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class SpeakV2Flushed(UncheckedBaseModel): + type: typing.Literal["Flushed"] = pydantic.Field(default="Flushed") + """ + Message type identifier + """ + + speech_id: str = pydantic.Field() + """ + Server-assigned turn identifier + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2session_metadata.py b/src/deepgram/speak/v2/types/speak_v2session_metadata.py new file mode 100644 index 00000000..cf96e019 --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2session_metadata.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class SpeakV2SessionMetadata(UncheckedBaseModel): + type: typing.Literal["SessionMetadata"] = pydantic.Field(default="SessionMetadata") + """ + Message type identifier + """ + + total_audio_duration_ms: int = pydantic.Field() + """ + Cumulative audio duration produced across the session, in milliseconds + """ + + total_input_character_count: int = pydantic.Field() + """ + Cumulative raw input character count across the session + """ + + total_billable_character_count: int = pydantic.Field() + """ + Cumulative billable character count across the session + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2speak.py b/src/deepgram/speak/v2/types/speak_v2speak.py new file mode 100644 index 00000000..6d327781 --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2speak.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class SpeakV2Speak(UncheckedBaseModel): + type: typing.Literal["Speak"] = pydantic.Field(default="Speak") + """ + Message type identifier + """ + + text: str = pydantic.Field() + """ + The input text to synthesize + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2speech_metadata.py b/src/deepgram/speak/v2/types/speak_v2speech_metadata.py new file mode 100644 index 00000000..9df85f0e --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2speech_metadata.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel +from .speak_v2speech_metadata_controls_applied import SpeakV2SpeechMetadataControlsApplied + + +class SpeakV2SpeechMetadata(UncheckedBaseModel): + type: typing.Literal["SpeechMetadata"] = pydantic.Field(default="SpeechMetadata") + """ + Message type identifier + """ + + speech_id: str = pydantic.Field() + """ + Server-assigned turn identifier + """ + + audio_duration_ms: int = pydantic.Field() + """ + Total audio duration produced for this turn, in milliseconds + """ + + input_character_count: int = pydantic.Field() + """ + Raw input character count for this turn, before text normalization + """ + + billable_character_count: int = pydantic.Field() + """ + Billable character count for this turn — the input character count with stripped control characters removed. Always less than or equal to `input_character_count`. + """ + + controls_applied: SpeakV2SpeechMetadataControlsApplied = pydantic.Field() + """ + Controls applied during the turn. Inline pronunciation and pause controls are not available during Early Access, so every count is currently `0`. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2speech_metadata_controls_applied.py b/src/deepgram/speak/v2/types/speak_v2speech_metadata_controls_applied.py new file mode 100644 index 00000000..e24d03cd --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2speech_metadata_controls_applied.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class SpeakV2SpeechMetadataControlsApplied(UncheckedBaseModel): + """ + Controls applied during the turn. Inline pronunciation and pause controls are not available during Early Access, so every count is currently `0`. + """ + + pronunciations_applied: int = pydantic.Field() + """ + Pronunciation overrides successfully applied. Mirrors the Aura-2 `dg-pronunciations-applied` REST header. Always `0` during Early Access. + """ + + pronunciation_warnings: int = pydantic.Field() + """ + Pronunciation entries that triggered a warning (invalid IPA, word too long). Mirrors the Aura-2 `dg-pronunciation-warnings` REST header. Always `0` during Early Access. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2speech_started.py b/src/deepgram/speak/v2/types/speak_v2speech_started.py new file mode 100644 index 00000000..5dcd35bc --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2speech_started.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class SpeakV2SpeechStarted(UncheckedBaseModel): + type: typing.Literal["SpeechStarted"] = pydantic.Field(default="SpeechStarted") + """ + Message type identifier + """ + + speech_id: str = pydantic.Field() + """ + Server-minted identifier for this turn, of the form `dg_sp_<12 hex digits>`. Informational. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/speak/v2/types/speak_v2warning.py b/src/deepgram/speak/v2/types/speak_v2warning.py new file mode 100644 index 00000000..967b2279 --- /dev/null +++ b/src/deepgram/speak/v2/types/speak_v2warning.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import IS_PYDANTIC_V2 +from ....core.unchecked_base_model import UncheckedBaseModel + + +class SpeakV2Warning(UncheckedBaseModel): + type: typing.Literal["Warning"] = pydantic.Field(default="Warning") + """ + Message type identifier + """ + + code: str = pydantic.Field() + """ + Warning code identifying the condition, in `SCREAMING_SNAKE_CASE`. Early Access codes are `NO_ACTIVE_SPEECH` (a speech-scoped message arrived with no active turn) and `SYNTHESIS_RETRYING` (a synthesis request failed and is being retried). + """ + + description: str = pydantic.Field() + """ + A human-readable description of the warning + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/deepgram/types/__init__.py b/src/deepgram/types/__init__.py index f927e513..eda099b1 100644 --- a/src/deepgram/types/__init__.py +++ b/src/deepgram/types/__init__.py @@ -257,6 +257,11 @@ from .speak_v1response import SpeakV1Response from .speak_v1sample_rate import SpeakV1SampleRate from .speak_v1speed import SpeakV1Speed + from .speak_v2encoding import SpeakV2Encoding + from .speak_v2mip_opt_out import SpeakV2MipOptOut + from .speak_v2model import SpeakV2Model + from .speak_v2sample_rate import SpeakV2SampleRate + from .speak_v2tag import SpeakV2Tag from .think_settings_v1 import ThinkSettingsV1 from .think_settings_v1context_length import ThinkSettingsV1ContextLength from .think_settings_v1endpoint import ThinkSettingsV1Endpoint @@ -496,6 +501,11 @@ "SpeakV1Response": ".speak_v1response", "SpeakV1SampleRate": ".speak_v1sample_rate", "SpeakV1Speed": ".speak_v1speed", + "SpeakV2Encoding": ".speak_v2encoding", + "SpeakV2MipOptOut": ".speak_v2mip_opt_out", + "SpeakV2Model": ".speak_v2model", + "SpeakV2SampleRate": ".speak_v2sample_rate", + "SpeakV2Tag": ".speak_v2tag", "ThinkSettingsV1": ".think_settings_v1", "ThinkSettingsV1ContextLength": ".think_settings_v1context_length", "ThinkSettingsV1Endpoint": ".think_settings_v1endpoint", @@ -757,6 +767,11 @@ def __dir__(): "SpeakV1Response", "SpeakV1SampleRate", "SpeakV1Speed", + "SpeakV2Encoding", + "SpeakV2MipOptOut", + "SpeakV2Model", + "SpeakV2SampleRate", + "SpeakV2Tag", "ThinkSettingsV1", "ThinkSettingsV1ContextLength", "ThinkSettingsV1Endpoint", diff --git a/src/deepgram/types/deepgram_listen_provider_v2.py b/src/deepgram/types/deepgram_listen_provider_v2.py index 7c29c6f1..e55bf684 100644 --- a/src/deepgram/types/deepgram_listen_provider_v2.py +++ b/src/deepgram/types/deepgram_listen_provider_v2.py @@ -35,6 +35,21 @@ class DeepgramListenProviderV2(UncheckedBaseModel): `exclude=True` keeps it off the wire (the API rejects unknown fields). """ + eot_threshold: typing.Optional[float] = pydantic.Field(default=None) + """ + End-of-turn confidence required to finish a turn. Valid range: 0.5 - 0.9. Defaults to 0.7. + """ + + eager_eot_threshold: typing.Optional[float] = pydantic.Field(default=None) + """ + End-of-turn confidence required to fire an eager end-of-turn event. When set, enables EagerEndOfTurn and TurnResumed events. Valid range: 0.3 - 0.9. + """ + + eot_timeout_ms: typing.Optional[int] = pydantic.Field(default=None) + """ + A turn will be finished when this much time in milliseconds has passed after speech, regardless of EOT confidence. Defaults to 5000. + """ + keyterms: typing.Optional[typing.List[str]] = pydantic.Field(default=None) """ Prompt keyterm recognition to improve Keyword Recall Rate diff --git a/src/deepgram/types/speak_v2encoding.py b/src/deepgram/types/speak_v2encoding.py new file mode 100644 index 00000000..3447211f --- /dev/null +++ b/src/deepgram/types/speak_v2encoding.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SpeakV2Encoding = typing.Union[typing.Literal["linear16", "mulaw", "alaw"], typing.Any] diff --git a/src/deepgram/types/speak_v2mip_opt_out.py b/src/deepgram/types/speak_v2mip_opt_out.py new file mode 100644 index 00000000..6165e627 --- /dev/null +++ b/src/deepgram/types/speak_v2mip_opt_out.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SpeakV2MipOptOut = typing.Any +""" +Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip +""" diff --git a/src/deepgram/types/speak_v2model.py b/src/deepgram/types/speak_v2model.py new file mode 100644 index 00000000..98ba0ea9 --- /dev/null +++ b/src/deepgram/types/speak_v2model.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +SpeakV2Model = str +""" +The Flux TTS model used to synthesize speech. Required on every connection. Model strings follow the format `flux-{voice}-{language}` (e.g. `flux-alexis-en`). An Aura model string is rejected on `/v2/speak`; use `/v1/speak` for Aura voices. +""" diff --git a/src/deepgram/types/speak_v2sample_rate.py b/src/deepgram/types/speak_v2sample_rate.py new file mode 100644 index 00000000..988e6af0 --- /dev/null +++ b/src/deepgram/types/speak_v2sample_rate.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SpeakV2SampleRate = typing.Union[typing.Literal["8000", "16000", "24000", "32000", "44100", "48000"], typing.Any] diff --git a/src/deepgram/types/speak_v2tag.py b/src/deepgram/types/speak_v2tag.py new file mode 100644 index 00000000..235bcc73 --- /dev/null +++ b/src/deepgram/types/speak_v2tag.py @@ -0,0 +1,9 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +SpeakV2Tag = typing.Any +""" +Label your requests for the purpose of identification during usage +reporting. Repeatable. +""" diff --git a/tests/custom/test_agent_update_listen.py b/tests/custom/test_agent_update_listen.py new file mode 100644 index 00000000..667f7623 --- /dev/null +++ b/tests/custom/test_agent_update_listen.py @@ -0,0 +1,55 @@ +""" +Coverage for the Voice Agent mid-session listen-reconfigure surface added in the +2026-07-08 regen: + + * ``send_update_listen(AgentV1UpdateListen(...))`` — client-sent control message + that serializes to ``{"type": "UpdateListen", "listen": {"provider": {...}}}``. + * ``AgentV1ListenUpdated`` — the server acknowledgement, which must resolve out + of the agent socket response union via its ``type`` discriminant. + +Driven with a fake websocket / construct_type — no network. +""" + +import json + +from deepgram.agent.v1.socket_client import V1SocketClient, V1SocketClientResponse +from deepgram.agent.v1.types.agent_v1listen_updated import AgentV1ListenUpdated +from deepgram.agent.v1.types.agent_v1update_listen import AgentV1UpdateListen +from deepgram.agent.v1.types.agent_v1update_listen_listen import AgentV1UpdateListenListen +from deepgram.core.unchecked_base_model import construct_type +from deepgram.types.deepgram_listen_provider_v2 import DeepgramListenProviderV2 + + +class _FakeWebSocket: + def __init__(self): + self.sent = [] + + def send(self, data): + self.sent.append(data) + + +def _sent_json(ws): + assert len(ws.sent) == 1 + payload = ws.sent[0] + return json.loads(payload) if isinstance(payload, str) else payload + + +class TestSendUpdateListen: + def test_serializes_provider_on_the_wire(self): + ws = _FakeWebSocket() + message = AgentV1UpdateListen( + listen=AgentV1UpdateListenListen(provider=DeepgramListenProviderV2(model="flux-general-en")) + ) + V1SocketClient(websocket=ws).send_update_listen(message) + + sent = _sent_json(ws) + assert sent["type"] == "UpdateListen" + assert sent["listen"]["provider"]["type"] == "deepgram" + assert sent["listen"]["provider"]["model"] == "flux-general-en" + + +class TestListenUpdatedResponse: + def test_resolves_from_response_union(self): + obj = construct_type(type_=V1SocketClientResponse, object_={"type": "ListenUpdated"}) + assert isinstance(obj, AgentV1ListenUpdated) + assert obj.type == "ListenUpdated" diff --git a/tests/custom/test_eot_thresholds_feature.py b/tests/custom/test_eot_thresholds_feature.py new file mode 100644 index 00000000..7a2c1aa3 --- /dev/null +++ b/tests/custom/test_eot_thresholds_feature.py @@ -0,0 +1,70 @@ +""" +Forward-feature coverage for the Flux end-of-turn tuning fields added in the +2026-07-08 regen: ``eot_threshold``, ``eager_eot_threshold`` (floats) and +``eot_timeout_ms`` (int). These are real wire fields and MUST serialize. + +Covered on all three listen-provider models that carry them — the top-level +``DeepgramListenProviderV2`` plus the two Voice Agent listen-provider variants — +with kwargs and dict, asserting the values land on the wire and are omitted when +absent. The frozen ``language_hint`` back-compat shim on these same models must +not interfere (see test_language_hint_compat.py). +""" + +from deepgram.agent.v1.types.agent_v1settings_agent_context_listen_provider import ( + AgentV1SettingsAgentContextListenProvider_V2, +) +from deepgram.agent.v1.types.agent_v1settings_agent_listen_provider import ( + AgentV1SettingsAgentListenProvider_V2, +) +from deepgram.core.pydantic_utilities import IS_PYDANTIC_V2 +from deepgram.types.deepgram_listen_provider_v2 import DeepgramListenProviderV2 + + +def _from_dict(cls, payload): + return cls.model_validate(payload) if IS_PYDANTIC_V2 else cls.parse_obj(payload) + + +class TestEotThresholdsProvider: + """DeepgramListenProviderV2 — the top-level Flux STT provider.""" + + def test_all_three_serialize(self): + dumped = DeepgramListenProviderV2( + model="flux-general-en", + eot_threshold=0.7, + eager_eot_threshold=0.4, + eot_timeout_ms=5000, + ).dict() + assert dumped["eot_threshold"] == 0.7 + assert dumped["eager_eot_threshold"] == 0.4 + assert dumped["eot_timeout_ms"] == 5000 + + def test_from_dict(self): + provider = _from_dict( + DeepgramListenProviderV2, + {"model": "flux-general-en", "eot_threshold": 0.65, "eot_timeout_ms": 4000}, + ) + assert provider.eot_threshold == 0.65 + assert provider.eot_timeout_ms == 4000 + + def test_omitted_when_absent(self): + dumped = DeepgramListenProviderV2(model="flux-general-en").dict() + assert dumped.get("eot_threshold") is None + assert dumped.get("eager_eot_threshold") is None + assert dumped.get("eot_timeout_ms") is None + + +class TestEotThresholdsAgentProviders: + """The two Voice Agent listen-provider V2 variants carry the same fields.""" + + def test_context_listen_provider_serializes(self): + dumped = AgentV1SettingsAgentContextListenProvider_V2( + model="flux-general-en", eot_threshold=0.8, eot_timeout_ms=6000 + ).dict() + assert dumped["eot_threshold"] == 0.8 + assert dumped["eot_timeout_ms"] == 6000 + + def test_agent_listen_provider_serializes(self): + dumped = AgentV1SettingsAgentListenProvider_V2( + model="flux-general-en", eager_eot_threshold=0.35 + ).dict() + assert dumped["eager_eot_threshold"] == 0.35 diff --git a/tests/custom/test_socket_client_shims.py b/tests/custom/test_socket_client_shims.py index 7670e9f3..22aba5a7 100644 --- a/tests/custom/test_socket_client_shims.py +++ b/tests/custom/test_socket_client_shims.py @@ -21,6 +21,7 @@ from deepgram.agent.v1.socket_client import V1SocketClient, _sanitize_numeric_types from deepgram.listen.v2.socket_client import V2SocketClient from deepgram.listen.v2.types.listen_v2close_stream import ListenV2CloseStream +from deepgram.speak.v2.socket_client import V2SocketClient as SpeakV2SocketClient class _FakeWebSocket: @@ -86,6 +87,16 @@ def test_agent_keep_alive_no_arg(self): V1SocketClient(websocket=ws).send_keep_alive() assert _sent_json(ws)["type"] == "KeepAlive" + def test_speak_v2_flush_no_arg(self): + ws = _FakeWebSocket() + SpeakV2SocketClient(websocket=ws).send_flush() + assert _sent_json(ws)["type"] == "Flush" + + def test_speak_v2_close_no_arg(self): + ws = _FakeWebSocket() + SpeakV2SocketClient(websocket=ws).send_close() + assert _sent_json(ws)["type"] == "Close" + class TestSendConfigureRawShim: def test_passthrough_dict_is_sent_verbatim(self): diff --git a/tests/custom/test_speak_v2_socket.py b/tests/custom/test_speak_v2_socket.py new file mode 100644 index 00000000..fb5dac04 --- /dev/null +++ b/tests/custom/test_speak_v2_socket.py @@ -0,0 +1,123 @@ +""" +Coverage for the Speak V2 (Flux TTS) WebSocket socket client, added in the +2026-07-08 regen. Speak V2 is a public product but shipped with only indirect +coverage; this pins the behavior a future regen must not silently change: + + * send serialization — ``send_speak`` puts ``text`` on the wire; the no-payload + control sends ``send_flush()`` / ``send_close()`` are callable with no + argument and emit the correct default control message (hand-applied shim, + frozen in .fernignore). + * response parsing — inbound JSON resolves to the right ``SpeakV2*`` type via + the ``type`` discriminant, and binary frames pass through as ``bytes``. + * broad ``except`` — the listen loop surfaces *any* exception as an ERROR event + (the generator narrows to ``websockets.WebSocketException``; the patch keeps + the custom-transport error contract, which can raise arbitrary types). + +Driven with a fake websocket — no network. +""" + +import json + +from deepgram.core.events import EventType +from deepgram.speak.v2.socket_client import V2SocketClient +from deepgram.speak.v2.types.speak_v2connected import SpeakV2Connected +from deepgram.speak.v2.types.speak_v2error import SpeakV2Error +from deepgram.speak.v2.types.speak_v2flushed import SpeakV2Flushed +from deepgram.speak.v2.types.speak_v2speak import SpeakV2Speak + + +class _FakeWebSocket: + """Captures ``.send()`` payloads and replays ``incoming`` on iteration / recv.""" + + def __init__(self, incoming=None, raise_on_iter=None): + self.sent = [] + self._incoming = list(incoming or []) + self._raise_on_iter = raise_on_iter + + def send(self, data): + self.sent.append(data) + + def recv(self): + return self._incoming.pop(0) + + def __iter__(self): + if self._raise_on_iter is not None: + raise self._raise_on_iter + yield from self._incoming + + +def _sent_json(ws): + assert len(ws.sent) == 1 + payload = ws.sent[0] + return json.loads(payload) if isinstance(payload, str) else payload + + +class TestSpeakV2Send: + def test_send_speak_serializes_text(self): + ws = _FakeWebSocket() + V2SocketClient(websocket=ws).send_speak(SpeakV2Speak(text="Hello, world!")) + assert _sent_json(ws) == {"type": "Speak", "text": "Hello, world!"} + + def test_send_flush_no_arg_emits_default(self): + ws = _FakeWebSocket() + V2SocketClient(websocket=ws).send_flush() + assert _sent_json(ws)["type"] == "Flush" + + def test_send_close_no_arg_emits_default(self): + ws = _FakeWebSocket() + V2SocketClient(websocket=ws).send_close() + assert _sent_json(ws)["type"] == "Close" + + +class TestSpeakV2ResponseParsing: + def test_recv_parses_connected(self): + ws = _FakeWebSocket( + incoming=[ + json.dumps( + { + "type": "Connected", + "request_id": "req-1", + "model_name": "flux-alexis-en", + "model_version": "1", + "model_uuids": ["uuid-1"], + } + ) + ] + ) + msg = V2SocketClient(websocket=ws).recv() + assert isinstance(msg, SpeakV2Connected) + assert msg.request_id == "req-1" + + def test_recv_parses_flushed_and_error(self): + ws = _FakeWebSocket( + incoming=[ + json.dumps({"type": "Flushed", "speech_id": "s-1"}), + json.dumps({"type": "Error", "code": "SOME_CODE", "description": "boom"}), + ] + ) + client = V2SocketClient(websocket=ws) + assert isinstance(client.recv(), SpeakV2Flushed) + assert isinstance(client.recv(), SpeakV2Error) + + def test_recv_returns_binary_audio_passthrough(self): + audio = b"\x00\x01\x02audio-frame" + ws = _FakeWebSocket(incoming=[audio]) + assert V2SocketClient(websocket=ws).recv() == audio + + +class TestSpeakV2Broadexcept: + def test_listen_loop_surfaces_non_websocket_error(self): + # A custom transport can raise arbitrary exception types from iteration; + # the narrow generated catch would let this escape instead of emitting ERROR. + boom = ValueError("transport blew up") + ws = _FakeWebSocket(raise_on_iter=boom) + client = V2SocketClient(websocket=ws) + + events = [] + client.on(EventType.ERROR, lambda exc: events.append(("error", exc))) + client.on(EventType.CLOSE, lambda _: events.append(("close", None))) + + client.start_listening() + + assert ("error", boom) in events + assert events[-1] == ("close", None) diff --git a/tests/manual/speak/v2/connect/async.py b/tests/manual/speak/v2/connect/async.py new file mode 100644 index 00000000..f1e960ff --- /dev/null +++ b/tests/manual/speak/v2/connect/async.py @@ -0,0 +1,107 @@ +import asyncio + +from dotenv import load_dotenv + +print("Starting async speak v2 connect example script") +load_dotenv() +print("Environment variables loaded") + +from typing import Union + +from deepgram import AsyncDeepgramClient +from deepgram.core.events import EventType +from deepgram.speak.v2.types import ( + SpeakV2Connected, + SpeakV2Error, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Speak, + SpeakV2SpeechMetadata, + SpeakV2SpeechStarted, + SpeakV2Warning, +) + +SpeakV2SocketClientResponse = Union[ + bytes, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Warning, + SpeakV2Error, +] + +print("Initializing AsyncDeepgramClient") +client = AsyncDeepgramClient() +print("AsyncDeepgramClient initialized") + + +async def main() -> None: + try: + model = "flux-alexis-en" + encoding = "linear16" + sample_rate = "24000" + print(f"Establishing async connection - Model: {model}, Encoding: {encoding}, Sample Rate: {sample_rate}") + async with client.speak.v2.connect(model=model, encoding=encoding, sample_rate=sample_rate) as connection: + print("Connection context entered") + + def on_message(message: SpeakV2SocketClientResponse) -> None: + if isinstance(message, bytes): + print("Received audio event") + print(f"Event body (audio data length): {len(message)} bytes") + else: + msg_type = getattr(message, "type", "Unknown") + print(f"Received {msg_type} event") + print(f"Event body: {message}") + + print("Registering event handlers") + connection.on(EventType.OPEN, lambda _: print("Connection opened")) + connection.on(EventType.MESSAGE, on_message) + connection.on(EventType.CLOSE, lambda _: print("Connection closed")) + connection.on(EventType.ERROR, lambda error: print(f"Connection error: {error}")) + print("Event handlers registered") + + # EXAMPLE ONLY: Start listening task and cancel after brief demo + # In production, you would typically await connection.start_listening() directly + # which runs until the connection closes or is interrupted + print("Starting listening task") + listen_task = asyncio.create_task(connection.start_listening()) + + # Send text to be converted to speech + print("Sending Speak message") + await connection.send_speak(SpeakV2Speak(text="Hello, world!")) + + # Send control messages + print("Sending Flush control message") + await connection.send_flush() + print("Sending Close control message") + await connection.send_close() + + print("Waiting 3 seconds for events...") + await asyncio.sleep(3) # EXAMPLE ONLY: Wait briefly to see some events before exiting + print("Cancelling listening task") + listen_task.cancel() + print("Exiting connection context") + except Exception as e: + print(f"Error occurred: {type(e).__name__}") + # Log request headers if available + if hasattr(e, "request_headers"): + print(f"Request headers: {e.request_headers}") + elif hasattr(e, "request") and hasattr(e.request, "headers"): + print(f"Request headers: {e.request.headers}") + # Log response headers if available + if hasattr(e, "headers"): + print(f"Response headers: {e.headers}") + # Log status code if available + if hasattr(e, "status_code"): + print(f"Status code: {e.status_code}") + # Log body if available + if hasattr(e, "body"): + print(f"Response body: {e.body}") + print(f"Caught: {e}") + + +print("Running async main function") +asyncio.run(main()) +print("Script completed") diff --git a/tests/manual/speak/v2/connect/main.py b/tests/manual/speak/v2/connect/main.py new file mode 100644 index 00000000..9f3940d3 --- /dev/null +++ b/tests/manual/speak/v2/connect/main.py @@ -0,0 +1,99 @@ +from dotenv import load_dotenv + +print("Starting speak v2 connect example script") +load_dotenv() +print("Environment variables loaded") + +import threading +import time +from typing import Union + +from deepgram import DeepgramClient +from deepgram.core.events import EventType +from deepgram.speak.v2.types import ( + SpeakV2Connected, + SpeakV2Error, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Speak, + SpeakV2SpeechMetadata, + SpeakV2SpeechStarted, + SpeakV2Warning, +) + +SpeakV2SocketClientResponse = Union[ + bytes, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Warning, + SpeakV2Error, +] + +print("Initializing DeepgramClient") +client = DeepgramClient() +print("DeepgramClient initialized") + +try: + model = "flux-alexis-en" + encoding = "linear16" + sample_rate = "24000" + print(f"Establishing connection - Model: {model}, Encoding: {encoding}, Sample Rate: {sample_rate}") + with client.speak.v2.connect(model=model, encoding=encoding, sample_rate=sample_rate) as connection: + print("Connection context entered") + + def on_message(message: SpeakV2SocketClientResponse) -> None: + if isinstance(message, bytes): + print("Received audio event") + print(f"Event body (audio data length): {len(message)} bytes") + else: + msg_type = getattr(message, "type", "Unknown") + print(f"Received {msg_type} event") + print(f"Event body: {message}") + + print("Registering event handlers") + connection.on(EventType.OPEN, lambda _: print("Connection opened")) + connection.on(EventType.MESSAGE, on_message) + connection.on(EventType.CLOSE, lambda _: print("Connection closed")) + connection.on(EventType.ERROR, lambda error: print(f"Connection error: {error}")) + print("Event handlers registered") + + # EXAMPLE ONLY: Start listening in a background thread for demo purposes + # In production, you would typically call connection.start_listening() directly + # which blocks until the connection closes, or integrate into your async event loop + print("Starting listening thread") + threading.Thread(target=connection.start_listening, daemon=True).start() + + # Send text to be converted to speech + print("Sending Speak message") + connection.send_speak(SpeakV2Speak(text="Hello, world!")) + + # Send control messages + print("Sending Flush control message") + connection.send_flush() + print("Sending Close control message") + connection.send_close() + + print("Waiting 3 seconds for events...") + time.sleep(3) # EXAMPLE ONLY: Wait briefly to see some events before exiting + print("Exiting connection context") +except Exception as e: + print(f"Error occurred: {type(e).__name__}") + # Log request headers if available + if hasattr(e, "request_headers"): + print(f"Request headers: {e.request_headers}") + elif hasattr(e, "request") and hasattr(e.request, "headers"): + print(f"Request headers: {e.request.headers}") + # Log response headers if available + if hasattr(e, "headers"): + print(f"Response headers: {e.headers}") + # Log status code if available + if hasattr(e, "status_code"): + print(f"Status code: {e.status_code}") + # Log body if available + if hasattr(e, "body"): + print(f"Response body: {e.body}") + print(f"Caught: {e}") +print("Script completed") diff --git a/tests/manual/speak/v2/connect/with_auth_token.py b/tests/manual/speak/v2/connect/with_auth_token.py new file mode 100644 index 00000000..ca0d2960 --- /dev/null +++ b/tests/manual/speak/v2/connect/with_auth_token.py @@ -0,0 +1,109 @@ +from dotenv import load_dotenv + +print("Starting speak v2 connect with auth token example script") +load_dotenv() +print("Environment variables loaded") + +import threading +import time +from typing import Union + +from deepgram import DeepgramClient +from deepgram.core.events import EventType +from deepgram.speak.v2.types import ( + SpeakV2Connected, + SpeakV2Error, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Speak, + SpeakV2SpeechMetadata, + SpeakV2SpeechStarted, + SpeakV2Warning, +) + +SpeakV2SocketClientResponse = Union[ + bytes, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Warning, + SpeakV2Error, +] + +try: + # Using access token instead of API key + print("Initializing DeepgramClient for authentication") + authClient = DeepgramClient() + print("Auth client initialized") + + print("Requesting access token") + authResponse = authClient.auth.v1.tokens.grant() + print("Access token received successfully") + print(f"Token type: {type(authResponse.access_token)}") + + print("Initializing DeepgramClient with access token") + client = DeepgramClient(access_token=authResponse.access_token) + print("Client initialized with access token") + + model = "flux-alexis-en" + encoding = "linear16" + sample_rate = "24000" + print(f"Establishing connection - Model: {model}, Encoding: {encoding}, Sample Rate: {sample_rate}") + with client.speak.v2.connect(model=model, encoding=encoding, sample_rate=sample_rate) as connection: + print("Connection context entered") + + def on_message(message: SpeakV2SocketClientResponse) -> None: + if isinstance(message, bytes): + print("Received audio event") + print(f"Event body (audio data length): {len(message)} bytes") + else: + msg_type = getattr(message, "type", "Unknown") + print(f"Received {msg_type} event") + print(f"Event body: {message}") + + print("Registering event handlers") + connection.on(EventType.OPEN, lambda _: print("Connection opened")) + connection.on(EventType.MESSAGE, on_message) + connection.on(EventType.CLOSE, lambda _: print("Connection closed")) + connection.on(EventType.ERROR, lambda error: print(f"Connection error: {error}")) + print("Event handlers registered") + + # EXAMPLE ONLY: Start listening in a background thread for demo purposes + # In production, you would typically call connection.start_listening() directly + # which blocks until the connection closes, or integrate into your async event loop + print("Starting listening thread") + threading.Thread(target=connection.start_listening, daemon=True).start() + + # Send text to be converted to speech + print("Sending Speak message") + connection.send_speak(SpeakV2Speak(text="Hello, world!")) + + # Send control messages + print("Sending Flush control message") + connection.send_flush() + print("Sending Close control message") + connection.send_close() + + print("Waiting 3 seconds for events...") + time.sleep(3) # EXAMPLE ONLY: Wait briefly to see some events before exiting + print("Exiting connection context") +except Exception as e: + print(f"Error occurred: {type(e).__name__}") + # Log request headers if available + if hasattr(e, "request_headers"): + print(f"Request headers: {e.request_headers}") + elif hasattr(e, "request") and hasattr(e.request, "headers"): + print(f"Request headers: {e.request.headers}") + # Log response headers if available + if hasattr(e, "headers"): + print(f"Response headers: {e.headers}") + # Log status code if available + if hasattr(e, "status_code"): + print(f"Status code: {e.status_code}") + # Log body if available + if hasattr(e, "body"): + print(f"Response body: {e.body}") + print(f"Caught: {e}") +print("Script completed") diff --git a/tests/manual/speak/v2/connect/with_raw_response.py b/tests/manual/speak/v2/connect/with_raw_response.py new file mode 100644 index 00000000..310a0f5f --- /dev/null +++ b/tests/manual/speak/v2/connect/with_raw_response.py @@ -0,0 +1,103 @@ +from dotenv import load_dotenv + +print("Starting speak v2 connect with raw response example script") +load_dotenv() +print("Environment variables loaded") + +import threading +import time +from typing import Union + +from deepgram import DeepgramClient +from deepgram.core.events import EventType +from deepgram.speak.v2.types import ( + SpeakV2Connected, + SpeakV2Error, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Speak, + SpeakV2SpeechMetadata, + SpeakV2SpeechStarted, + SpeakV2Warning, +) + +SpeakV2SocketClientResponse = Union[ + bytes, + SpeakV2Connected, + SpeakV2SpeechStarted, + SpeakV2SpeechMetadata, + SpeakV2Flushed, + SpeakV2SessionMetadata, + SpeakV2Warning, + SpeakV2Error, +] + +print("Initializing DeepgramClient") +client = DeepgramClient() +print("DeepgramClient initialized") + +try: + model = "flux-alexis-en" + encoding = "linear16" + sample_rate = "24000" + print( + f"Establishing connection with raw response - Model: {model}, Encoding: {encoding}, Sample Rate: {sample_rate}" + ) + with client.speak.v2.with_raw_response.connect( + model=model, encoding=encoding, sample_rate=sample_rate + ) as connection: + print("Connection context entered") + + def on_message(message: SpeakV2SocketClientResponse) -> None: + if isinstance(message, bytes): + print("Received audio event") + print(f"Event body (audio data length): {len(message)} bytes") + else: + msg_type = getattr(message, "type", "Unknown") + print(f"Received {msg_type} event") + print(f"Event body: {message}") + + print("Registering event handlers") + connection.on(EventType.OPEN, lambda _: print("Connection opened")) + connection.on(EventType.MESSAGE, on_message) + connection.on(EventType.CLOSE, lambda _: print("Connection closed")) + connection.on(EventType.ERROR, lambda error: print(f"Connection error: {error}")) + print("Event handlers registered") + + # EXAMPLE ONLY: Start listening in a background thread for demo purposes + # In production, you would typically call connection.start_listening() directly + # which blocks until the connection closes, or integrate into your async event loop + print("Starting listening thread") + threading.Thread(target=connection.start_listening, daemon=True).start() + + # Send text to be converted to speech + print("Sending Speak message") + connection.send_speak(SpeakV2Speak(text="Hello, world!")) + + # Send control messages + print("Sending Flush control message") + connection.send_flush() + print("Sending Close control message") + connection.send_close() + + print("Waiting 3 seconds for events...") + time.sleep(3) # EXAMPLE ONLY: Wait briefly to see some events before exiting + print("Exiting connection context") +except Exception as e: + print(f"Error occurred: {type(e).__name__}") + # Log request headers if available + if hasattr(e, "request_headers"): + print(f"Request headers: {e.request_headers}") + elif hasattr(e, "request") and hasattr(e.request, "headers"): + print(f"Request headers: {e.request.headers}") + # Log response headers if available + if hasattr(e, "headers"): + print(f"Response headers: {e.headers}") + # Log status code if available + if hasattr(e, "status_code"): + print(f"Status code: {e.status_code}") + # Log body if available + if hasattr(e, "body"): + print(f"Response body: {e.body}") + print(f"Caught: {e}") +print("Script completed")