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
10 changes: 4 additions & 6 deletions .fern/metadata.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"cliVersion": "4.92.0",
"cliVersion": "4.107.0",
"generatorName": "fernapi/fern-python-sdk",
"generatorVersion": "5.5.11",
"generatorVersion": "5.8.3",
"generatorConfig": {
"client": {
"class_name": "BaseClient",
Expand All @@ -16,10 +16,8 @@
"skip_validation": true
}
},
"originGitCommit": "a11286b37e09a565312275eb0ee6b111fbdc5360",
"originGitCommit": "05bd7add608b322a6278fa20da22f2ed501d50ef",
"originGitCommitIsDirty": true,
"invokedBy": "manual",
"requestedVersion": null,
"ciProvider": null,
"sdkVersion": "6.1.2"
"sdkVersion": "7.0.1"
}
14 changes: 14 additions & 0 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,22 @@ src/deepgram/listen/v1/socket_client.py
src/deepgram/listen/v2/socket_client.py
src/deepgram/speak/v1/socket_client.py

# Backward-compatibility alias shims for renamed generated public types/params.
# These are hand-written wrappers around the current generated names and must not be regenerated.
src/deepgram/agent/v1/types/agent_v1history_content.py
src/deepgram/agent/v1/types/agent_v1history_function_calls.py
src/deepgram/agent/v1/types/agent_v1settings_agent_context_messages_item_content.py
src/deepgram/agent/v1/types/agent_v1settings_agent_context_messages_item_function_calls.py
src/deepgram/agent/v1/requests/agent_v1history_content.py
src/deepgram/agent/v1/requests/agent_v1history_function_calls.py
src/deepgram/agent/v1/requests/agent_v1settings_agent_context_messages_item_content.py
src/deepgram/agent/v1/requests/agent_v1settings_agent_context_messages_item_function_calls.py
src/deepgram/types/create_key_v1request_one.py
src/deepgram/requests/create_key_v1request_one.py

# Hand-written custom tests
tests/custom/test_agent_history.py
tests/custom/test_compat_aliases.py
tests/custom/test_text_builder.py
tests/custom/test_transport.py

Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ How to identify:
Current permanently frozen files:
- `src/deepgram/client.py` — entirely custom (Bearer auth, session ID); no Fern equivalent
- `src/deepgram/helpers/` — hand-written TextBuilder helpers
- `src/deepgram/agent/v1/types/agent_v1history_content.py`, `src/deepgram/agent/v1/types/agent_v1history_function_calls.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_context_messages_item_content.py`, `src/deepgram/agent/v1/types/agent_v1settings_agent_context_messages_item_function_calls.py` — hand-written compatibility aliases preserving old public Agent History type imports after regen renames
- `src/deepgram/agent/v1/requests/agent_v1history_content.py`, `src/deepgram/agent/v1/requests/agent_v1history_function_calls.py`, `src/deepgram/agent/v1/requests/agent_v1settings_agent_context_messages_item_content.py`, `src/deepgram/agent/v1/requests/agent_v1settings_agent_context_messages_item_function_calls.py` — hand-written compatibility aliases preserving old public Agent History request-param imports after regen renames
- `src/deepgram/types/create_key_v1request_one.py`, `src/deepgram/requests/create_key_v1request_one.py` — hand-written compatibility aliases preserving the old public create-key request imports after the regen rename to `CreateKeyV1Request`
- `src/deepgram/transport_interface.py`, `src/deepgram/transport.py`, `src/deepgram/transports/` — custom transport layer
- `tests/custom/test_agent_history.py` — hand-written regression test for Agent History websocket payload parsing
- `tests/custom/test_compat_aliases.py` — hand-written regression test for backward-compatible alias imports after regen renames
- `tests/custom/test_text_builder.py`, `tests/custom/test_transport.py` — hand-written tests
- `tests/manual/` — manual standalone tests
- `README.md`, `CHANGELOG.md`, `CONTRIBUTING.md`, `reference.md` — docs
Expand Down
5 changes: 3 additions & 2 deletions examples/14-transcription-live-websocket-v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

This example shows how to use Listen V2 for advanced conversational speech recognition
with contextual turn detection.

It streams the bundled WAV fixture directly, so the example lets Deepgram infer the
audio format from the container instead of forcing raw linear16 settings.
"""

import os
Expand Down Expand Up @@ -31,8 +34,6 @@
try:
with client.listen.v2.connect(
model="flux-general-en",
encoding="linear16",
sample_rate="16000",
) as connection:

def on_message(message: ListenV2SocketClientResponse) -> None:
Expand Down
43 changes: 29 additions & 14 deletions examples/24-text-builder-streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@

This example demonstrates using TextBuilder with streaming text-to-speech
over WebSocket for real-time audio generation.

The streaming API path shown here focuses on pronunciation controls. Inline
pause tokens are omitted because the WebSocket endpoint may reject them with
DATA-0002 policy violations.
"""

import os
import threading
from typing import Union

from deepgram import DeepgramClient
from deepgram.helpers import TextBuilder
from deepgram.core.events import EventType
from deepgram.speak.v1.types import SpeakV1Close, SpeakV1Flush, SpeakV1Text
from deepgram.speak.v1.types import SpeakV1Text

SpeakV1SocketClientResponse = Union[str, bytes]

Expand All @@ -30,7 +35,6 @@ def example_streaming_with_textbuilder():
.text(" twice daily with ")
.pronunciation("dupilumab", "duːˈpɪljuːmæb")
.text(" injections.")
.pause(500)
.text(" Do not exceed prescribed dosage.")
.build()
)
Expand All @@ -46,8 +50,9 @@ def example_streaming_with_textbuilder():

try:
with client.speak.v1.connect(
model="aura-asteria-en", encoding="linear16", sample_rate=24000
model="aura-2-asteria-en", encoding="linear16", sample_rate=24000
) as connection:
closed_event = threading.Event()

def on_message(message: SpeakV1SocketClientResponse) -> None:
if isinstance(message, bytes):
Expand All @@ -58,10 +63,12 @@ def on_message(message: SpeakV1SocketClientResponse) -> None:
else:
msg_type = getattr(message, "type", "Unknown")
print(f"Received {msg_type} event")
if msg_type == "Flushed":
connection.send_close()

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.CLOSE, lambda _: (print("✓ Connection closed"), closed_event.set()))
connection.on(EventType.ERROR, lambda error: print(f"✗ Error: {error}"))

# Send the TextBuilder-generated text
Expand All @@ -70,11 +77,9 @@ def on_message(message: SpeakV1SocketClientResponse) -> None:
# 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
connection.start_listening()
listener = threading.Thread(target=connection.start_listening, daemon=True)
listener.start()
closed_event.wait(30)

print("\n✓ Audio saved to streaming_output.raw")
print(" Convert to WAV: ffmpeg -f s16le -ar 24000 -ac 1 -i streaming_output.raw output.wav")
Expand Down Expand Up @@ -118,29 +123,35 @@ def example_multiple_messages():

try:
with client.speak.v1.connect(
model="aura-asteria-en", encoding="linear16", sample_rate=24000
model="aura-2-asteria-en", encoding="linear16", sample_rate=24000
) as connection:

audio_chunks = []
closed_event = threading.Event()

def on_message(message: SpeakV1SocketClientResponse) -> None:
if isinstance(message, bytes):
audio_chunks.append(message)
print(f"Received {len(message)} bytes")
else:
msg_type = getattr(message, "type", "Unknown")
if msg_type == "Flushed":
connection.send_close()

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.CLOSE, lambda _: (print("✓ Connection closed"), closed_event.set()))

# Send multiple messages
for i, text in enumerate([intro, instruction1, instruction2, closing], 1):
print(f"Sending message {i}: {text[:50]}...")
connection.send_text(SpeakV1Text(text=text))

connection.send_flush()
connection.send_close()

connection.start_listening()
listener = threading.Thread(target=connection.start_listening, daemon=True)
listener.start()
closed_event.wait(30)

# Save all audio
with open("streaming_multi.raw", "wb") as f:
Expand All @@ -155,6 +166,11 @@ def on_message(message: SpeakV1SocketClientResponse) -> None:

def main():
"""Run all streaming examples"""
if os.path.exists("streaming_output.raw"):
os.remove("streaming_output.raw")
if os.path.exists("streaming_multi.raw"):
os.remove("streaming_multi.raw")

example_streaming_with_textbuilder()
example_multiple_messages()

Expand All @@ -165,4 +181,3 @@ def main():

if __name__ == "__main__":
main()

55 changes: 31 additions & 24 deletions examples/30-voice-agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
Example: Voice Agent (Agent V1)

This example shows how to set up a voice agent that can listen, think, and speak.
It streams a pre-recorded audio file to simulate user speech.
To keep the example deterministic, it injects a user message after the settings are
applied and then prints the agent's text and audio responses.
"""

import os
import threading
import time
from typing import Union
Expand All @@ -16,6 +16,7 @@

from deepgram import DeepgramClient
from deepgram.agent.v1.types import (
AgentV1InjectUserMessage,
AgentV1Settings,
AgentV1SettingsAgent,
AgentV1SettingsAgentListen,
Expand All @@ -30,13 +31,15 @@
from deepgram.types.think_settings_v1provider import ThinkSettingsV1Provider_OpenAi

AgentV1SocketClientResponse = Union[str, bytes]
USER_MESSAGE = "What does the first all-female spacewalk symbolize?"

client = DeepgramClient()

audio_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures", "audio.wav")

try:
with client.agent.v1.connect() as agent:
settings_applied_event = threading.Event()
assistant_response_event = threading.Event()

# Configure the agent settings
settings = AgentV1Settings(
audio=AgentV1SettingsAudio(
Expand Down Expand Up @@ -69,18 +72,20 @@
),
)

print("Sending agent settings...")
agent.send_settings(settings)

def on_message(message: AgentV1SocketClientResponse) -> None:
if isinstance(message, bytes):
print(f"Received agent audio ({len(message)} bytes)")
else:
msg_type = getattr(message, "type", "Unknown")
if msg_type == "ConversationText":
if msg_type == "SettingsApplied":
print("Received SettingsApplied event")
settings_applied_event.set()
elif msg_type == "ConversationText":
role = getattr(message, "role", "unknown")
content = getattr(message, "content", "")
print(f"[{role}] {content}")
if role == "assistant":
assistant_response_event.set()
elif msg_type == "UserStartedSpeaking":
print(">> User started speaking")
elif msg_type == "AgentThinking":
Expand All @@ -89,6 +94,11 @@ def on_message(message: AgentV1SocketClientResponse) -> None:
print(">> Agent started speaking")
elif msg_type == "AgentAudioDone":
print(">> Agent finished speaking")
elif msg_type == "Error":
print(
f">> Agent error: {getattr(message, 'code', 'unknown')} - "
f"{getattr(message, 'description', 'unknown error')}"
)
else:
print(f"Received {msg_type} event")

Expand All @@ -97,26 +107,23 @@ def on_message(message: AgentV1SocketClientResponse) -> None:
agent.on(EventType.CLOSE, lambda _: print("Connection closed"))
agent.on(EventType.ERROR, lambda error: print(f"Error: {error}"))

# Stream audio in a background thread
def send_audio():
with open(audio_path, "rb") as f:
audio_data = f.read()
listener = threading.Thread(target=agent.start_listening, daemon=True)
listener.start()

print("Sending agent settings...")
agent.send_settings(settings)

chunk_size = 8192
for i in range(0, len(audio_data), chunk_size):
chunk = audio_data[i : i + chunk_size]
if chunk:
agent.send_media(chunk)
time.sleep(0.01)
if not settings_applied_event.wait(10):
raise TimeoutError("Timed out waiting for agent settings to apply")

print("Finished streaming audio, waiting for agent response...")
time.sleep(15)
print(f"Sending injected user message: {USER_MESSAGE}")
agent.send_inject_user_message(AgentV1InjectUserMessage(content=USER_MESSAGE))

sender = threading.Thread(target=send_audio, daemon=True)
sender.start()
if not assistant_response_event.wait(30):
raise TimeoutError("Timed out waiting for the agent to respond")

# Start listening - blocks until connection closes
agent.start_listening()
# Give the final audio callbacks a moment to flush before exiting the context.
time.sleep(2)

except Exception as e:
print(f"Error: {e}")
10 changes: 7 additions & 3 deletions examples/50-management-projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@
project = client.manage.v1.projects.get(project_id=project_id)
print(f"Project name: {project.name}")

# Update project name
# Update the project using the current name so the example exercises the
# update endpoint without unexpectedly renaming a live project.
print("\nUpdating project name...")
updated = client.manage.v1.projects.update(project_id=project_id, name="Updated Project Name")
print(f"Updated project name: {updated.name}")
updated = client.manage.v1.projects.update(project_id=project_id, name=project.name)
print(f"Update response: {updated.message}")

refreshed_project = client.manage.v1.projects.get(project_id=project_id)
print(f"Verified project name: {refreshed_project.name}")

# Note: Delete and leave operations are commented out for safety
# Delete a project:
Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ dynamic = ["version"]

[tool.poetry]
name = "deepgram-sdk"
version = "7.0.0"
version = "7.0.1"
description = ""
readme = "README.md"
authors = []
Expand Down Expand Up @@ -53,7 +53,7 @@ pytest-asyncio = "^1.0.0"
pytest-xdist = "^3.6.1"
python-dateutil = "^2.9.0"
types-python-dateutil = "^2.9.0.20240316"
urllib3 = ">=1.26.19,<2.0.0 || >=2.2.2,<3.0.0"
urllib3 = ">=2.6.3,<3.0.0"
requests = "^2.33.0"
types-requests = "^2.33.0"
ruff = "==0.11.5"
Expand Down
Loading
Loading