Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ with client.listen.v2.connect(

**No `language` parameter** on v2 — language is implied by model (`flux-general-en`) or hinted via `language_hint` on multi.

For application-controlled turns, use `eot_threshold="1.0"` with a sufficiently large `eot_timeout_ms`, then call `conn.send_force_end_turn()` for the active turn. ForceEndTurn requires deployment enablement; see `examples/16-transcription-force-end-turn.py`.
For application-controlled turns, use `eot_threshold="1.0"` with a sufficiently large `eot_timeout_ms`, then call `conn.send_force_end_turn()` for the active turn. ForceEndTurn is available on Deepgram-hosted deployments, including EU and AU. Self-hosted availability depends on the deployed release; see `examples/16-transcription-force-end-turn.py`.

## Events (server → client)

Expand Down
7 changes: 4 additions & 3 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,16 @@ src/deepgram/speak/v2/socket_client.py

# The generated eot_threshold descriptions omit that eot_timeout_ms still ends
# idle turns. The DeepgramListenProviderV2 model below shares this patch with
# its language_hint compatibility shim. Remove these patches after the API
# definition includes the timeout requirement and Fern regenerates them.
# its language_hint compatibility shim. Remove these patches after
# https://github.com/deepgram/deepgram-docs/pull/1202 is merged and Fern
# regenerates them.
# [temporarily frozen]
src/deepgram/types/listen_v2eot_threshold.py
src/deepgram/requests/deepgram_listen_provider_v2.py

# Agent TTS provider expressivity must reject fractional values rather than
# allowing Pydantic v1 to truncate them before the API sees the request.
# [temporarily frozen — remove when Fern emits a strict integer]
# [temporarily frozen — remove when Pydantic v1 support is dropped]
src/deepgram/types/speak_settings_v1provider.py
src/deepgram/types/deepgram.py

Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ Current temporarily frozen files:
- `.gitignore` — Fern generates a baseline version and has regenerated it before (`fdcce88`, `ad93815`, `4bee463`). We hand-add the local coverage artifact ignores (`.coverage`, `htmlcov/`, `coverage.xml`). Before each regen, unfreeze and re-diff so Fern's own additions are picked up, then re-apply the three coverage lines.
- `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`/`send_interrupt` defaults, broad exception catch); new websocket TTS client added in the 2026-07-08 regen. `send_interrupt` carries no required payload so it takes the same optional-default treatment as the other control sends; `send_configure` deliberately keeps its required argument (a Configure with no settings is meaningless)
- `src/deepgram/types/listen_v2eot_threshold.py`, `src/deepgram/requests/deepgram_listen_provider_v2.py` — complete the generated `eot_threshold=1.0` description: it suppresses confidence-based detection, while `eot_timeout_ms` still ends idle turns. Unfreeze when the API definition provides that qualifier.
- `src/deepgram/types/speak_settings_v1provider.py`, `src/deepgram/types/deepgram.py` — validate Agent TTS `expressivity` as `pydantic.StrictInt` so Pydantic v1 rejects fractional values instead of truncating them before they reach the API. Regression coverage in `tests/custom/test_socket_client_shims.py`. Unfreeze when Fern emits a strict integer.
- `src/deepgram/listen/v1/socket_client.py` — same
- `src/deepgram/listen/v2/socket_client.py` — same (broad except, optional `send_close_stream` default). As of the 2026-08-11 regen the generator properly types `send_configure(ListenV2Configure)` and puts `ListenV2ConfigureSuccess` in the response Union, so those are taken from the generator; the only `send_configure` patch retained is runtime tolerance for a raw dict (sent verbatim) for back-compat with pre-typed-model callers
- `src/deepgram/agent/v1/socket_client.py` — same + `_sanitize_numeric_types`
- `src/deepgram/types/listen_v2eot_threshold.py`, `src/deepgram/requests/deepgram_listen_provider_v2.py` — complete the generated `eot_threshold=1.0` description: it suppresses confidence-based detection, while `eot_timeout_ms` still ends idle turns. Unfreeze after https://github.com/deepgram/deepgram-docs/pull/1202 is merged and Fern regenerates the descriptions.
- `src/deepgram/types/speak_settings_v1provider.py`, `src/deepgram/types/deepgram.py` — validate Agent TTS `expressivity` as `pydantic.StrictInt` so Pydantic v1 rejects fractional values instead of truncating them before they reach the API. Regression coverage in `tests/custom/test_socket_client_shims.py`. Unfreeze when Pydantic v1 support is dropped.
- `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/api_error.py`, `src/deepgram/core/parse_error.py` — credential redaction. Every websocket `connect()` path raises `ApiError(headers=dict(headers), ...)` with the full request headers, and both error types stringify that dict, so an unredacted `Authorization` reached `str(e)`, tracebacks, log aggregators and error trackers (which serialise attributes as well as the message). Both now mask credential values at construction via `_secure_logging.redact_sensitive_headers`, preserving non-sensitive headers (`dg-request-id`) for debugging. This is the same threat `_secure_logging.py` covers for the `websockets` DEBUG handshake logs, via the other path to it. Regression coverage in `tests/custom/test_api_error_redaction.py`. Unfreeze if the generator starts redacting credentials itself.
- `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.
Expand Down
7 changes: 4 additions & 3 deletions examples/16-transcription-force-end-turn.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
push-to-talk button being released. The connection stays open after a forced end: the
turn index advances and transcription continues.

Note: ForceEndTurn requires server-side enablement and is not available on every
deployment. Where it is not enabled the server replies UNPARSABLE_CLIENT_MESSAGE and
closes the connection; this example reports that and exits.
ForceEndTurn is available on Deepgram-hosted deployments, including EU and AU.
Self-hosted availability depends on the deployed release. Unsupported deployments
reply UNPARSABLE_CLIENT_MESSAGE and close the connection; this example reports that
and exits.
"""

import os
Expand Down
16 changes: 14 additions & 2 deletions examples/32-voice-agent-force-end-turn.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
def main() -> None:
user_started = threading.Event()
agent_finished = threading.Event()
force_end_turn_rejected = threading.Event()
force_end_turn_error: str | None = None

settings = AgentV1Settings(
audio=AgentV1SettingsAudio(input=AgentV1SettingsAudioInput(encoding="linear16", sample_rate=44100)),
Expand All @@ -56,6 +58,7 @@ def main() -> None:
with DeepgramClient().agent.v1.connect() as agent:

def on_message(message: object) -> None:
nonlocal force_end_turn_error
message_type = getattr(message, "type", None)
if message_type == "UserStartedSpeaking":
user_started.set()
Expand All @@ -67,6 +70,9 @@ def on_message(message: object) -> None:
print("AgentAudioDone received")
elif message_type in {"Warning", "Error"}:
print(f"{message_type}: {message.code} - {message.description}")
if message.code == "FORCE_END_TURN_UNSUPPORTED":
force_end_turn_error = f"{message.code}: {message.description}"
force_end_turn_rejected.set()

agent.on(EventType.MESSAGE, on_message)
agent.on(EventType.ERROR, lambda error: print(f"Connection error: {error}"))
Expand All @@ -88,8 +94,14 @@ def on_message(message: object) -> None:

print("Sending ForceEndTurn")
agent.send_force_end_turn()
if not agent_finished.wait(15):
raise TimeoutError("Timed out waiting for the agent response")
deadline = time.monotonic() + 15
while not agent_finished.wait(0.1):
if force_end_turn_rejected.is_set():
raise RuntimeError(f"ForceEndTurn failed: {force_end_turn_error}")
if time.monotonic() >= deadline:
raise TimeoutError("Timed out waiting for the agent response")
if force_end_turn_rejected.is_set():
raise RuntimeError(f"ForceEndTurn failed: {force_end_turn_error}")


if __name__ == "__main__":
Expand Down
Loading