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
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"
"sdkVersion": "7.8.2"
}
9 changes: 0 additions & 9 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,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
3 changes: 1 addition & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ Current temporarily frozen files:
- `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/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 +86,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
96 changes: 96 additions & 0 deletions examples/32-voice-agent-force-end-turn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Example: Force an active Voice Agent turn to end.

ForceEndTurn requires a Deepgram V2 (Flux) listen provider. It sends the
control message after the server reports UserStartedSpeaking, then waits for
the agent response. The example streams the first two seconds of the bundled
WAV fixture and does not capture microphone audio.
"""

import threading
import time
from pathlib import Path

from dotenv import load_dotenv

from deepgram import DeepgramClient
from deepgram.agent.v1.types import (
AgentV1Settings,
AgentV1SettingsAgent,
AgentV1SettingsAgentListen,
AgentV1SettingsAgentListenProvider_V2,
AgentV1SettingsAudio,
AgentV1SettingsAudioInput,
)
from deepgram.core.events import EventType
from deepgram.types.speak_settings_v1 import SpeakSettingsV1
from deepgram.types.speak_settings_v1provider import SpeakSettingsV1Provider_Deepgram
from deepgram.types.think_settings_v1 import ThinkSettingsV1
from deepgram.types.think_settings_v1provider import ThinkSettingsV1Provider_OpenAi

load_dotenv()

AUDIO_PATH = Path(__file__).parent / "fixtures" / "audio.wav"


def main() -> None:
user_started = threading.Event()
agent_finished = threading.Event()

settings = AgentV1Settings(
audio=AgentV1SettingsAudio(input=AgentV1SettingsAudioInput(encoding="linear16", sample_rate=44100)),
agent=AgentV1SettingsAgent(
listen=AgentV1SettingsAgentListen(
provider=AgentV1SettingsAgentListenProvider_V2(type="deepgram", model="flux-general-en")
),
think=ThinkSettingsV1(
provider=ThinkSettingsV1Provider_OpenAi(type="open_ai", model="gpt-4o-mini"),
prompt="Reply briefly.",
),
speak=SpeakSettingsV1(
provider=SpeakSettingsV1Provider_Deepgram(type="deepgram", model="aura-2-asteria-en")
),
),
)

with DeepgramClient().agent.v1.connect() as agent:

def on_message(message: object) -> None:
message_type = getattr(message, "type", None)
if message_type == "UserStartedSpeaking":
user_started.set()
print("UserStartedSpeaking received")
elif message_type == "ConversationText":
print(f"[{message.role}] {message.content}")
elif message_type == "AgentAudioDone":
agent_finished.set()
print("AgentAudioDone received")
elif message_type in {"Warning", "Error"}:
print(f"{message_type}: {message.code} - {message.description}")

agent.on(EventType.MESSAGE, on_message)
agent.on(EventType.ERROR, lambda error: print(f"Connection error: {error}"))
threading.Thread(target=agent.start_listening, daemon=True).start()

agent.send_settings(settings)
with AUDIO_PATH.open("rb") as audio_file:
audio_file.read(44)
audio = audio_file.read(44100 * 2 * 2)

for start in range(0, len(audio), 44100 // 10 * 2):
agent.send_media(audio[start : start + 44100 // 10 * 2])
time.sleep(0.1)
if user_started.is_set():
break

if not user_started.is_set():
raise TimeoutError("Timed out waiting for UserStartedSpeaking")

print("Sending ForceEndTurn")
agent.send_force_end_turn()
if not agent_finished.wait(15):
raise TimeoutError("Timed out waiting for the agent response")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This directory contains comprehensive examples demonstrating how to use the Deep

- **30-voice-agent.py** - Voice Agent configuration and usage
- **31-voice-agent-session-recording.py** - Record selected Voice Agent events as JSON
- **32-voice-agent-force-end-turn.py** - End an active Voice Agent Flux turn with ForceEndTurn

### 40-49: Text Intelligence (Read)

Expand Down
Loading
Loading