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..5b15b191 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 @@ -123,15 +124,27 @@ src/deepgram/listen/v2/types/__init__.py # [temporarily frozen — manual patches listed above] src/deepgram/core/query_encoder.py +# Carries `# x-release-please-version` annotations on the User-Agent and +# X-Fern-SDK-Version header lines so release-please bumps the wire telemetry +# version in lockstep with the published package version (it is a `generic` +# extra-file in .github/release-please-config.json). Fern regenerates this file +# from scratch and would strip the annotations, so it is frozen to protect them. +# [temporarily frozen — manual patches listed above] +src/deepgram/core/client_wrapper.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_connect_wire.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..2d811fc6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -53,11 +53,13 @@ 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` - `src/deepgram/agent/v1/types/agent_v1settings_agent_context.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent.py`, `src/deepgram/agent/v1/types/agent_v1settings.py`, `src/deepgram/agent/v1/requests/agent_v1settings_agent_context.py`, `src/deepgram/agent/v1/requests/agent_v1settings_agent.py`, `src/deepgram/agent/v1/requests/agent_v1settings.py` — backward-compat patches for the 2026-05-05 Agent Settings schema restructure. These preserve callable `AgentV1SettingsAgent(...)`, keep `AgentV1Settings.agent` accepting both that wrapper and `agent_id` strings, restore the legacy request TypedDict shapes, remap legacy `messages=[...]` / nested `context=AgentV1SettingsAgentContext(messages=[...])` usage into the new `context={"messages": [...]}` wire shape, and keep read-side `obj.messages` access working. - `src/deepgram/core/query_encoder.py` — coerces Python bools to lowercase `"true"`/`"false"` before they reach `urllib.parse.urlencode` (which would otherwise produce `"True"`/`"False"` via `str()` and break websocket query strings). Only the four `*/connect()` paths call `urlencode`; HTTP raw clients hand params to httpx, which lowercases bools itself, so the patch is a no-op for the HTTP path. Once Fern's websocket codegen normalizes bools (or the spec types these as `boolean` end-to-end), this can be unfrozen. +- `src/deepgram/core/client_wrapper.py` — carries `# x-release-please-version` annotations on the `User-Agent` and `X-Fern-SDK-Version` header lines. `client_wrapper.py` is listed as a `generic` extra-file in `.github/release-please-config.json`, but the generic updater only rewrites annotated lines, so without these comments release-please never bumps the wire telemetry version and it drifts from the published package version (e.g. main shipping 7.4.0 while sending 7.3.2). Fern regenerates this file from scratch and would strip the annotations, so it is frozen to protect them. On regen, re-apply the two annotation comments to the freshly generated header lines. Unfreeze only if Fern's generator starts emitting the annotations itself. - `tests/wire/test_manage_v1_projects_keys.py` — restored wire coverage for the legacy `CreateKeyV1RequestOneParams` request alias so future regens do not silently drop that compatibility check - `src/deepgram/__init__.py`, `src/deepgram/agent/__init__.py`, `src/deepgram/agent/v1/__init__.py`, `src/deepgram/agent/v1/types/__init__.py`, `src/deepgram/agent/v1/requests/__init__.py`, `src/deepgram/types/__init__.py`, `src/deepgram/requests/__init__.py` — package `__init__.py` files carrying hand-applied legacy alias re-exports for `CreateKeyV1RequestOne`, `AgentV1HistoryContent`, `AgentV1HistoryFunctionCalls`, `AgentV1SettingsAgentContextMessagesItemContent`, `AgentV1SettingsAgentContextMessagesItemFunctionCalls` (and their `*Params` variants). Fern would otherwise regenerate these and strip the legacy entries. After unfreezing for the next regen and reviewing the new generated content, re-apply the legacy re-exports plus any genuine new entries Fern added. - `src/deepgram/listen/__init__.py`, `src/deepgram/listen/v2/__init__.py`, `src/deepgram/listen/v2/types/__init__.py` — `__init__.py` files re-exporting the hand-written `ListenV2CloseStreamType` shim. Frozen so Fern won't strip the re-export on regen. Same handling as the package `__init__.py` files above: after unfreezing, re-apply the `ListenV2CloseStreamType` re-export plus any genuine new entries Fern added. 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..dba1ced3 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-release-please-version "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", # x-release-please-version **(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_connect_wire.py b/tests/custom/test_speak_v2_connect_wire.py new file mode 100644 index 00000000..2cdddb84 --- /dev/null +++ b/tests/custom/test_speak_v2_connect_wire.py @@ -0,0 +1,93 @@ +""" +Wire-shape coverage for the Speak V2 (Flux TTS) ``connect`` request. + +The *outbound message* JSON is pinned in ``test_speak_v2_socket.py``; this pins +the *request* side of the handshake, which otherwise had no automated coverage: + + * the connection targets the ``/v2/speak`` path, and + * query parameters serialize correctly — in particular a Python ``bool`` + ``mip_opt_out=True`` must reach the wire as lowercase ``true`` (not + ``"True"``). That coercion is the whole job of the frozen + ``core/query_encoder.py`` patch, so this test doubles as its wire-level guard. + +No network / WireMock: the public ``connect`` builds the URL and hands it to the +``websockets`` connect entrypoint, so we replace that entrypoint with a capture +shim and assert on the URL it was given. Covers the sync and async clients. + +Hand-written and frozen in ``.fernignore`` — Fern only generates HTTP WireMock +wire tests, never a websocket-connect wire test, so a regen would not reproduce +this file. +""" + +import urllib.parse +from unittest.mock import patch + +import deepgram.speak.v2.client as speak_v2_client +from deepgram import AsyncDeepgramClient, DeepgramClient + + +class _CaptureConnect: + """Stands in for ``websockets(.sync).connect``: records the URL it is called + with, then behaves as both a sync and async context manager yielding a + throwaway protocol object.""" + + def __init__(self): + self.url = None + self.kwargs = None + + def __call__(self, url, **kwargs): + self.url = url + self.kwargs = kwargs + return self + + def __enter__(self): + return object() + + def __exit__(self, *exc): + return False + + async def __aenter__(self): + return object() + + async def __aexit__(self, *exc): + return False + + +def _path_and_query(url): + parts = urllib.parse.urlsplit(url) + return parts.path, urllib.parse.parse_qs(parts.query) + + +def test_sync_connect_targets_v2_speak_and_serializes_query(): + capture = _CaptureConnect() + with patch.object(speak_v2_client.websockets_sync_client, "connect", capture): + with DeepgramClient(api_key="test_api_key").speak.v2.connect( + model="flux-alexis-en", + encoding="linear16", + sample_rate="24000", + mip_opt_out=True, + ): + pass + + path, query = _path_and_query(capture.url) + assert path == "/v2/speak" + assert query["model"] == ["flux-alexis-en"] + assert query["encoding"] == ["linear16"] + assert query["sample_rate"] == ["24000"] + # The load-bearing assertion: Python True must serialize as "true", not "True". + assert query["mip_opt_out"] == ["true"] + + +async def test_async_connect_targets_v2_speak_and_serializes_query(): + capture = _CaptureConnect() + with patch.object(speak_v2_client, "websockets_client_connect", capture): + async with AsyncDeepgramClient(api_key="test_api_key").speak.v2.connect( + model="flux-alexis-en", + mip_opt_out=True, + ): + pass + + path, query = _path_and_query(capture.url) + assert path == "/v2/speak" + assert query["model"] == ["flux-alexis-en"] + assert query["mip_opt_out"] == ["true"] diff --git a/tests/custom/test_speak_v2_socket.py b/tests/custom/test_speak_v2_socket.py new file mode 100644 index 00000000..d122f6b4 --- /dev/null +++ b/tests/custom/test_speak_v2_socket.py @@ -0,0 +1,204 @@ +""" +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``. + * forward compatibility — an unknown/future message type passes through + untouched on every receive path (sync + async, ``recv()`` and the + ``start_listening`` loop) rather than raising. Python was named specifically + in the Mar 2026 breakage, so this is pinned end-to-end here. + * 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 AsyncV2SocketClient, 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 + +# A message type this SDK version has never heard of. construct_type must pass +# it through as a raw dict rather than raise, so newer servers never break an +# older client. +_UNKNOWN_MESSAGE = {"type": "FutureMessage", "brand_new_field": 123} + + +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 + + +class _FakeAsyncWebSocket: + """Async analogue of ``_FakeWebSocket`` for the ``AsyncV2SocketClient`` paths.""" + + def __init__(self, incoming=None): + self.sent = [] + self._incoming = list(incoming or []) + + async def send(self, data): + self.sent.append(data) + + async def recv(self): + return self._incoming.pop(0) + + def __aiter__(self): + incoming = self._incoming + + async def _gen(): + for message in incoming: + yield message + + return _gen() + + +def _sent_json(ws): + assert len(ws.sent) == 1 + payload = ws.sent[0] + return json.loads(payload) if isinstance(payload, str) else payload + + +def _collect(client): + """Register MESSAGE/ERROR handlers and return the (messages, errors) sinks.""" + messages, errors = [], [] + client.on(EventType.MESSAGE, messages.append) + client.on(EventType.ERROR, errors.append) + return messages, errors + + +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) + + +class TestSpeakV2ForwardCompat: + """An unknown/future message type must survive every receive path untouched + (sync + async, ``recv()`` and the ``start_listening`` loop) — no exception, + no dropped connection, payload preserved. Regression guard for the Mar 2026 + breakage where a newly added server message broke older Python clients.""" + + def test_sync_recv_passes_unknown_through(self): + ws = _FakeWebSocket(incoming=[json.dumps(_UNKNOWN_MESSAGE)]) + assert V2SocketClient(websocket=ws).recv() == _UNKNOWN_MESSAGE + + def test_sync_listen_loop_emits_unknown_without_error(self): + ws = _FakeWebSocket( + incoming=[json.dumps(_UNKNOWN_MESSAGE), json.dumps({"type": "Flushed", "speech_id": "s-1"})] + ) + client = V2SocketClient(websocket=ws) + messages, errors = _collect(client) + + client.start_listening() + + assert errors == [] + assert messages[0] == _UNKNOWN_MESSAGE + assert isinstance(messages[1], SpeakV2Flushed) + + async def test_async_recv_passes_unknown_through(self): + ws = _FakeAsyncWebSocket(incoming=[json.dumps(_UNKNOWN_MESSAGE)]) + assert await AsyncV2SocketClient(websocket=ws).recv() == _UNKNOWN_MESSAGE + + async def test_async_listen_loop_emits_unknown_without_error(self): + ws = _FakeAsyncWebSocket( + incoming=[json.dumps(_UNKNOWN_MESSAGE), json.dumps({"type": "Flushed", "speech_id": "s-1"})] + ) + client = AsyncV2SocketClient(websocket=ws) + messages, errors = _collect(client) + + await client.start_listening() + + assert errors == [] + assert messages[0] == _UNKNOWN_MESSAGE + assert isinstance(messages[1], SpeakV2Flushed) diff --git a/tests/manual/speak/v2/connect/async.py b/tests/manual/speak/v2/connect/async.py new file mode 100644 index 00000000..bd28a992 --- /dev/null +++ b/tests/manual/speak/v2/connect/async.py @@ -0,0 +1,122 @@ +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, +] + +import os + +from deepgram.environment import DeepgramClientEnvironment + + +def _client_environment() -> DeepgramClientEnvironment: + """TEST ONLY: target a non-prod host (e.g. staging) by exporting + DEEPGRAM_BASE_URL=wss://api.staging.deepgram.com. Defaults to production.""" + base = os.environ.get("DEEPGRAM_BASE_URL") + if not base: + return DeepgramClientEnvironment.PRODUCTION + https = base.replace("wss://", "https://").replace("ws://", "http://") + return DeepgramClientEnvironment(base=https, production=base, agent=base, agent_rest=https) + + +print("Initializing AsyncDeepgramClient") +client = AsyncDeepgramClient(environment=_client_environment()) +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..ac420720 --- /dev/null +++ b/tests/manual/speak/v2/connect/main.py @@ -0,0 +1,114 @@ +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, +] + +import os + +from deepgram.environment import DeepgramClientEnvironment + + +def _client_environment() -> DeepgramClientEnvironment: + """TEST ONLY: target a non-prod host (e.g. staging) by exporting + DEEPGRAM_BASE_URL=wss://api.staging.deepgram.com. Defaults to production.""" + base = os.environ.get("DEEPGRAM_BASE_URL") + if not base: + return DeepgramClientEnvironment.PRODUCTION + https = base.replace("wss://", "https://").replace("ws://", "http://") + return DeepgramClientEnvironment(base=https, production=base, agent=base, agent_rest=https) + + +print("Initializing DeepgramClient") +client = DeepgramClient(environment=_client_environment()) +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..c5fe8941 --- /dev/null +++ b/tests/manual/speak/v2/connect/with_auth_token.py @@ -0,0 +1,124 @@ +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, +] + +import os + +from deepgram.environment import DeepgramClientEnvironment + + +def _client_environment() -> DeepgramClientEnvironment: + """TEST ONLY: target a non-prod host (e.g. staging) by exporting + DEEPGRAM_BASE_URL=wss://api.staging.deepgram.com. Defaults to production.""" + base = os.environ.get("DEEPGRAM_BASE_URL") + if not base: + return DeepgramClientEnvironment.PRODUCTION + https = base.replace("wss://", "https://").replace("ws://", "http://") + return DeepgramClientEnvironment(base=https, production=base, agent=base, agent_rest=https) + + +try: + # Using access token instead of API key + print("Initializing DeepgramClient for authentication") + authClient = DeepgramClient(environment=_client_environment()) + 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, environment=_client_environment()) + 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..7f2bedcb --- /dev/null +++ b/tests/manual/speak/v2/connect/with_raw_response.py @@ -0,0 +1,118 @@ +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, +] + +import os + +from deepgram.environment import DeepgramClientEnvironment + + +def _client_environment() -> DeepgramClientEnvironment: + """TEST ONLY: target a non-prod host (e.g. staging) by exporting + DEEPGRAM_BASE_URL=wss://api.staging.deepgram.com. Defaults to production.""" + base = os.environ.get("DEEPGRAM_BASE_URL") + if not base: + return DeepgramClientEnvironment.PRODUCTION + https = base.replace("wss://", "https://").replace("ws://", "http://") + return DeepgramClientEnvironment(base=https, production=base, agent=base, agent_rest=https) + + +print("Initializing DeepgramClient") +client = DeepgramClient(environment=_client_environment()) +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")