Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1603580
chore: initialize SDK regeneration branch
GregHolmes Sep 3, 2026
df731f7
chore: unfreeze files pending regen
GregHolmes Sep 3, 2026
9c5e1ef
docs: use gh prefix for regeneration branches
GregHolmes Sep 3, 2026
0183cb6
SDK regeneration
fern-api[bot] Sep 4, 2026
d003d42
chore: re-apply manual patches after regen
GregHolmes Sep 4, 2026
2e7d3de
test: cover regenerated Agent and Flux controls
GregHolmes Sep 4, 2026
e4bfa05
docs(examples): document Agent ForceEndTurn requirements
GregHolmes Sep 4, 2026
acb99f6
docs: narrow Agent ForceEndTurn requirement
GregHolmes Sep 4, 2026
3c211f6
docs: add Agent ForceEndTurn skill guidance
GregHolmes Sep 4, 2026
f676bfe
docs: clarify Flux manual turn control
GregHolmes Sep 4, 2026
7ab7745
fix(examples): require callback URL configuration
GregHolmes Sep 7, 2026
43f4704
fix: address SDK regeneration review feedback
GregHolmes Sep 8, 2026
74c5c4e
test: cover Flux speed boundaries
GregHolmes Sep 11, 2026
5efed31
docs: clarify Flux expressivity limits
GregHolmes Sep 11, 2026
e37d44d
fix: validate Agent TTS expressivity
GregHolmes Sep 11, 2026
58ca4d0
fix: remove unsupported Flux turn control claim
GregHolmes Sep 11, 2026
1d94bba
fix: validate all Agent TTS expressivity
GregHolmes Sep 11, 2026
99cba18
fix: restore Flux turn control contract
GregHolmes Sep 11, 2026
0dc9e6b
fix: document Flux EOT timeout requirement
GregHolmes Sep 11, 2026
17927af
fix: clarify Flux EOT confidence scope
GregHolmes Sep 11, 2026
38f84d4
docs: record shared Flux EOT patch
GregHolmes Sep 11, 2026
8bd34b5
test: expand Flux speed matrix
GregHolmes Sep 14, 2026
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
8 changes: 5 additions & 3 deletions .agents/skills/deepgram-python-conversational-stt/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,23 +82,25 @@ with client.listen.v2.connect(
| `encoding` | `linear16`, `mulaw`, etc. Omit for containerized audio |
| `sample_rate` | String in the SDK signature, e.g. `"16000"` |
| `eager_eot_threshold` | Fire end-of-turn early at this confidence |
| `eot_threshold` | Primary end-of-turn confidence |
| `eot_timeout_ms` | Time-based fallback turn end |
| `eot_threshold` | Primary end-of-turn confidence; set to `"1.0"` to suppress confidence-based endings |
| `eot_timeout_ms` | Time-based fallback turn end; still applies when `eot_threshold="1.0"` |
| `keyterm` | Bias for domain keywords |
| `mip_opt_out`, `tag` | Metadata / privacy flags |
| `language_hint` | **ONLY for `flux-general-multi`** |
| `authorization`, `request_options` | Override auth or request options |

**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`.

## Events (server → client)

- `ListenV2Connected` — connection established
- `ListenV2ConfigureSuccess` / `ListenV2ConfigureFailure` — mid-session config changes
- `ListenV2TurnInfo` — per-turn transcript + event (`Update`, `EndOfTurn`, `EagerEndOfTurn`, ...) + `turn_index`
- `ListenV2FatalError` — terminal error

Client messages: `ListenV2Media`, `ListenV2Configure`, `ListenV2CloseStream`.
Client messages: `ListenV2Media`, `ListenV2Configure`, `ListenV2ForceEndTurn`, `ListenV2CloseStream`.

## Async equivalent

Expand Down
7 changes: 7 additions & 0 deletions .agents/skills/deepgram-python-voice-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ with client.agent.v1.connect() as agent:
- Prompt / think / speak update messages (change mid-session)
- User / assistant text injection
- Function call response (reply to `FunctionCallRequest`)
- `ForceEndTurn` (end an active user turn; requires a Deepgram V2/Flux listen provider)

## Reusable agent configurations

Expand Down Expand Up @@ -190,13 +191,18 @@ agent.send_inject_user_message(
# 6. Idle-period keep-alive (no payload required; the SDK fills in the type literal)
agent.send_keep_alive(AgentV1KeepAlive())
# Or simply: agent.send_keep_alive() — the message arg is optional.

# 7. End an active user turn immediately (for example, on push-to-talk release).
# Requires a Deepgram V2/Flux listen provider; V1 returns FORCE_END_TURN_UNSUPPORTED.
agent.send_force_end_turn()
```

Async client equivalents are identical but `await`-prefixed:

```python
await agent.send_update_prompt(AgentV1UpdatePrompt(prompt="..."))
await agent.send_inject_agent_message(AgentV1InjectAgentMessage(message="..."))
await agent.send_force_end_turn()
```

## Stream lifecycle & recovery
Expand Down Expand Up @@ -288,6 +294,7 @@ The server emits a `History` message on connect when the SDK has captured prior
## Example files in this repo

- `examples/30-voice-agent.py`
- `examples/32-voice-agent-force-end-turn.py` — Force an active turn to end with a Flux listen provider
- `tests/manual/agent/v1/connect/main.py` — live connection test

## Central product skills
Expand Down
4 changes: 2 additions & 2 deletions .fern/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"skip_validation": true
}
},
"originGitCommit": "068de888501fcc3b792086aab45de975587b89e1",
"originGitCommit": "0825695d67503a2d95eda2ecb88672d7ee6aa4d6",
"originGitCommitIsDirty": true,
"invokedBy": "manual",
"sdkVersion": "7.8.1"
}
}
30 changes: 19 additions & 11 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ src/deepgram/listen/v2/socket_client.py
src/deepgram/speak/v1/socket_client.py
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.
# [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]
src/deepgram/types/speak_settings_v1provider.py
src/deepgram/types/deepgram.py

# Backward-compat patch: AgentV1SettingsAgentContext schema restructure as of
# 2026-05-05. The new schema nests messages under .context.messages; this file
# carries a model_validator(mode='before') that accepts the legacy `messages=`
Expand Down Expand Up @@ -117,8 +131,11 @@ src/deepgram/requests/deepgram_listen_provider_v2language_hint.py
# deny_unknown_fields so the singular key is rejected on the wire). These three generated
# models carry a hand-added model_validator(mode='before') / root_validator(pre=True) that
# remaps a legacy `language_hint=` kwarg to `language_hints` and drops the dead singular
# key. Frozen so Fern won't strip the validator on regen. Remove the shim and unfreeze when
# the singular alias is intentionally retired in a future major release.
# key. This DeepgramListenProviderV2 model also carries the EOT description correction:
# eot_threshold=1.0 suppresses confidence-based detection only; eot_timeout_ms still ends
# idle turns and must be increased for manual ForceEndTurn control. Remove that correction
# when the API definition provides it; remove the shim and unfreeze when the singular alias
# is intentionally retired in a future major release.
src/deepgram/types/deepgram_listen_provider_v2.py
src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py
src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py
Expand All @@ -132,15 +149,6 @@ src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py
# when the old provider payloads are retired in a future major.
src/deepgram/agent/v1/types/agent_v1update_listen_listen.py

# Backward-compat patch for the 2026-08-18 regen SpeakV2Speed retype. The generator
# changed this from `float` to `Union[Literal["0.85"..."1.15"], Any]` -- a string-literal
# enum that contradicts the API contract (a numeric multiplier; cf. SpeakV2SpeedValue =
# float, used by the Configure message) and silently changed the documented domain of the
# `speak.v2.connect(speed=...)` param from numeric to string. Restored to `float` so the
# connect param stays exactly what it was on main and consistent with Configure. Unfreeze
# when the spec types the connect `speed` as a number.
src/deepgram/types/speak_v2speed.py

# Hand-written compat shim recreating ListenV2CloseStreamType, which Fern removed in the
# 2026-06-15 regen (docs #946). The original generated type wrongly allowed
# Union[Literal["Finalize","CloseStream","KeepAlive"], Any] — v2 copied v1's control-message
Expand Down
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,16 @@ 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/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.
- `src/deepgram/listen/v2/types/listen_v2connected.py`, `src/deepgram/listen/v2/types/listen_v2turn_info.py`, `src/deepgram/listen/v2/types/listen_v2turn_info_words_item.py`, `src/deepgram/listen/v2/types/listen_v2configure_success.py`, `src/deepgram/listen/v2/types/listen_v2configure_success_thresholds.py`, `src/deepgram/listen/v2/types/listen_v2configure_failure.py`, `src/deepgram/listen/v2/types/listen_v2fatal_error.py` — read-side compatibility for the SDK 7.7 Listen V2 response retype. Through 7.6, `V2SocketClientResponse` contained `typing.Any`, so every response was returned as a raw dict; fixing the union made responses typed models and broke callers using `response["field"]`. These generated response classes inherit the hand-written base above, preserving read-only wire-key subscript access alongside canonical attribute access. Restore direct `UncheckedBaseModel` inheritance and unfreeze these files in the next major release.
- `src/deepgram/types/deepgram_listen_provider_v2.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py` — behavioural back-compat shim for the `language_hint` -> `language_hints` rename (2026-06-15 regen). The public field was historically (incorrectly) singular and accepted a str or a list; the API field is `language_hints` (a list, and the server uses `deny_unknown_fields` so the singular key is rejected on the wire). Each carries a hand-added `model_validator(mode='before')` / `root_validator(pre=True)` that remaps a legacy `language_hint=` kwarg and drops the dead singular key. Remove and unfreeze when the singular alias is retired in a future major.
- `src/deepgram/types/speak_v2speed.py` — behavioural back-compat patch for the 2026-08-18 regen `SpeakV2Speed` retype. The generator changed it from `float` to `Union[Literal["0.85"…"1.15"], Any]` (a string-literal enum), which silently changed the documented domain of the `speak.v2.connect(speed=...)` parameter from numeric to string and contradicts the actual API contract (`SpeakV2SpeedValue = float`, used by the mid-stream `SpeakV2Configure` message). Only the `Any` fallback kept `speed=1.05` working. Restored to `float` so the connect param stays exactly what it was on `main`, mypy-precise, and consistent with the `Configure` message. Regression coverage in `tests/custom/test_speak_v2_coverage.py`. Unfreeze when the spec types the connect `speed` as a number.
- `src/deepgram/types/deepgram_listen_provider_v2.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_listen_provider.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_context_listen_provider.py` — behavioural back-compat shim for the `language_hint` -> `language_hints` rename (2026-06-15 regen). The public field was historically (incorrectly) singular and accepted a str or a list; the API field is `language_hints` (a list, and the server uses `deny_unknown_fields` so the singular key is rejected on the wire). Each carries a hand-added `model_validator(mode='before')` / `root_validator(pre=True)` that remaps a legacy `language_hint=` kwarg and drops the dead singular key. `deepgram_listen_provider_v2.py` also corrects the EOT description: `eot_threshold=1.0` suppresses confidence-based detection only, while `eot_timeout_ms` still ends idle turns and must be increased for manual ForceEndTurn control. Remove the EOT patch when the API definition provides it; remove and unfreeze the language alias when it is retired in a future major.
- `src/deepgram/agent/v1/types/agent_v1update_listen_listen.py` — backward-compat patch for the 2026-07-31 `AgentV1UpdateListen` provider retype. The `provider` field changed from a bare `DeepgramListenProviderV2` to the required discriminated union `AgentV1UpdateListenListenProvider` (`_V1`/`_V2`, discriminant `version`). Carries a hand-added `model_validator(mode='before')` / `root_validator(pre=True)` that coerces a legacy `DeepgramListenProviderV1`/`V2` (or a dict lacking the `version` discriminant) into the new shape so existing callers keep working. Remove and unfreeze when the old provider payloads are retired in a future major. NOTE: this patch was silently lost once (it was absent from `.fernignore`, so a regen overwrote it) — keep it frozen.
- `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
- `tests/wire/test_manage_v1_projects_requests.py` — restored query-parameter coverage for `manage.v1.projects.requests.list`. The 2026-08-11 regen simplified the upstream spec *example*, and Fern derives the wire test from the example, so all ten optional query params (and the `datetime` → ISO-8601 `Z` encoding) lost their assertions while the client signature still forwarded them. Frozen for the same reason as the `_keys.py` entry above.
Expand All @@ -87,7 +88,7 @@ Files Fern now owns outright, but that carry a caveat worth knowing before the n

### Prepare repo for regeneration

1. **Create a new branch** off `main` named `lo/sdk-gen-<YYYY-MM-DD>`.
1. **Create a new branch** off `main` named `gh/sdk-gen-<YYYY-MM-DD>`.
2. **Push the branch** and create a PR titled `chore: SDK regeneration <YYYY-MM-DD>` (empty commit if needed).
3. **Read `.fernignore`** and classify each entry using the rules above.
4. **For each temporarily frozen file only:**
Expand Down
37 changes: 27 additions & 10 deletions examples/12-transcription-prerecorded-callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,50 @@

This example shows how to transcribe audio asynchronously using a callback URL.
The transcription result will be sent to your callback URL when ready.

Set DEEPGRAM_CALLBACK_URL to a publicly reachable URL before running this
example. The service must be able to resolve and reach the callback host.
"""

import os
import sys

from dotenv import load_dotenv

load_dotenv()

from deepgram import DeepgramClient

client = DeepgramClient()

try:
def main() -> int:
callback_url = os.getenv("DEEPGRAM_CALLBACK_URL")
if not callback_url:
print("Set DEEPGRAM_CALLBACK_URL to a publicly reachable callback URL.", file=sys.stderr)
return 2

client = DeepgramClient()
print("Sending transcription request with callback...")
response = client.listen.v1.media.transcribe_url(
url="https://dpgr.am/spacewalk.wav",
callback="https://your-callback-url.com/webhook",
model="nova-3",
)
try:
response = client.listen.v1.media.transcribe_url(
url="https://dpgr.am/spacewalk.wav",
callback=callback_url,
model="nova-3",
)
except Exception as exc:
print(f"Callback transcription failed: {type(exc).__name__}: {exc}", file=sys.stderr)
return 1

# This returns a "listen accepted" response, not the full transcription
# The actual transcription will be sent to your callback URL
print(f"Request accepted. Request ID: {response.request_id}")
print("Transcription will be sent to your callback URL when ready.")
print(f"Transcription will be sent to {callback_url} when ready.")

# For async version:
# from deepgram import AsyncDeepgramClient
# client = AsyncDeepgramClient()
# response = await client.listen.v1.media.transcribe_url(..., callback="...")
return 0


except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading