Skip to content
Merged
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
7 changes: 5 additions & 2 deletions .fern/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
},
"use_typeddict_requests": true,
"should_generate_websocket_clients": true,
"enable_wire_tests": true
"enable_wire_tests": true,
"pydantic_config": {
"skip_validation": true
}
},
"sdkVersion": "6.0.0-beta.4"
"sdkVersion": "6.0.1"
}
20 changes: 19 additions & 1 deletion .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,30 @@ tests/wire/test_listen_v1_media.py
# WebSocket socket clients:
# - Optional message parameter defaults for send_flush, send_close, send_clear,
# send_finalize, send_close_stream, send_keep_alive
# - Removed unused imports (JSONDecodeError, websockets) flagged by ruff F401
# - construct_type instead of parse_obj_as (skip_validation for unknown WS messages)
# - except Exception (broad catch for custom transports)
# - _sanitize_numeric_types in agent socket client (float→int for API)
src/deepgram/speak/v1/socket_client.py
src/deepgram/listen/v1/socket_client.py
src/deepgram/listen/v2/socket_client.py
src/deepgram/agent/v1/socket_client.py

# Type files with manual int type corrections (Fern generates float for speaker/channel/num_words)
src/deepgram/types/listen_v1response_results_utterances_item.py
src/deepgram/types/listen_v1response_results_utterances_item_words_item.py
src/deepgram/types/listen_v1response_results_channels_item_alternatives_item_paragraphs_paragraphs_item.py

# Redact type with Union[str, Sequence[str]] support (Fern narrows to Union[Literal, Any])
src/deepgram/types/listen_v1redact.py

# Listen client files with Union[str, Sequence[str]] array param support
src/deepgram/listen/v1/client.py
src/deepgram/listen/v2/client.py

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

# Manual standalone tests
tests/manual

Expand Down
55 changes: 38 additions & 17 deletions examples/14-transcription-live-websocket-v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
with contextual turn detection.
"""

import os
import threading
import time
from pathlib import Path
from typing import Union

from dotenv import load_dotenv
Expand All @@ -14,42 +18,59 @@
from deepgram import DeepgramClient
from deepgram.core.events import EventType
from deepgram.listen.v2.types import (
ListenV2CloseStream,
ListenV2Connected,
ListenV2FatalError,
ListenV2TurnInfo,
)

ListenV2SocketClientResponse = Union[ListenV2Connected, ListenV2TurnInfo, ListenV2FatalError]

client = DeepgramClient()
client = DeepgramClient(api_key=os.environ.get("DEEPGRAM_API_KEY"))

try:
with client.listen.v2.connect(model="flux-general-en", encoding="linear16", sample_rate=16000) as connection:
with client.listen.v2.connect(
model="flux-general-en",
encoding="linear16",
sample_rate="16000",
) as connection:

def on_message(message: ListenV2SocketClientResponse) -> None:
msg_type = getattr(message, "type", "Unknown")
print(f"Received {msg_type} event")
msg_type = getattr(message, "type", type(message).__name__)
print(f"Received {msg_type} event ({type(message).__name__})")

# Extract transcription from TurnInfo events
if isinstance(message, ListenV2TurnInfo):
print(f"Turn transcript: {message.transcript}")
print(f"Turn event: {message.event}")
print(f"Turn index: {message.turn_index}")
print(f" transcript: {message.transcript}")
print(f" event: {message.event}")
print(f" turn_index: {message.turn_index}")

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}"))
connection.on(EventType.ERROR, lambda error: print(f"Error: {type(error).__name__}: {error}"))

# Start listening - this blocks until the connection closes
# In production, you would send audio data here using connection.send_media()
connection.start_listening()
# Send audio in a background thread so start_listening can process responses
def send_audio():
audio_path = Path(__file__).parent / "fixtures" / "audio.wav"
with open(audio_path, "rb") as f:
audio = f.read()

# Send in chunks
chunk_size = 4096
for i in range(0, len(audio), chunk_size):
connection.send_media(audio[i : i + chunk_size])
time.sleep(0.01) # pace the sending

# Signal end of audio
time.sleep(2)
connection.send_close_stream(ListenV2CloseStream(type="CloseStream"))

# For async version:
# from deepgram import AsyncDeepgramClient
# async with client.listen.v2.connect(...) as connection:
# # ... same event handlers ...
# await connection.start_listening()
sender = threading.Thread(target=send_audio, daemon=True)
sender.start()

# This blocks until the connection closes
connection.start_listening()

except Exception as e:
print(f"Error: {e}")
print(f"Error: {type(e).__name__}: {e}")
2 changes: 1 addition & 1 deletion 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 = "6.0.0"
version = "6.0.1"
description = ""
readme = "README.md"
authors = []
Expand Down
10 changes: 5 additions & 5 deletions src/deepgram/agent/v1/settings/think/models/raw_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from ......core.api_error import ApiError
from ......core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from ......core.http_response import AsyncHttpResponse, HttpResponse
from ......core.pydantic_utilities import parse_obj_as
from ......core.request_options import RequestOptions
from ......core.unchecked_base_model import construct_type
from ......errors.bad_request_error import BadRequestError
from ......types.agent_think_models_v1response import AgentThinkModelsV1Response

Expand Down Expand Up @@ -42,7 +42,7 @@ def list(
if 200 <= _response.status_code < 300:
_data = typing.cast(
AgentThinkModelsV1Response,
parse_obj_as(
construct_type(
type_=AgentThinkModelsV1Response, # type: ignore
object_=_response.json(),
),
Expand All @@ -53,7 +53,7 @@ def list(
headers=dict(_response.headers),
body=typing.cast(
typing.Any,
parse_obj_as(
construct_type(
type_=typing.Any, # type: ignore
object_=_response.json(),
),
Expand Down Expand Up @@ -95,7 +95,7 @@ async def list(
if 200 <= _response.status_code < 300:
_data = typing.cast(
AgentThinkModelsV1Response,
parse_obj_as(
construct_type(
type_=AgentThinkModelsV1Response, # type: ignore
object_=_response.json(),
),
Expand All @@ -106,7 +106,7 @@ async def list(
headers=dict(_response.headers),
body=typing.cast(
typing.Any,
parse_obj_as(
construct_type(
type_=typing.Any, # type: ignore
object_=_response.json(),
),
Expand Down
16 changes: 9 additions & 7 deletions src/deepgram/agent/v1/socket_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import json
import typing
from json.decoder import JSONDecodeError

import websockets
import websockets.sync.connection as websockets_sync_connection
from ...core.events import EventEmitterMixin, EventType
from ...core.pydantic_utilities import parse_obj_as
from ...core.unchecked_base_model import construct_type
from .types.agent_v1agent_audio_done import AgentV1AgentAudioDone
from .types.agent_v1agent_started_speaking import AgentV1AgentStartedSpeaking
from .types.agent_v1agent_thinking import AgentV1AgentThinking
Expand Down Expand Up @@ -82,7 +84,7 @@ async def __aiter__(self):
if isinstance(message, bytes):
yield message
else:
yield parse_obj_as(V1SocketClientResponse, json.loads(message)) # type: ignore
yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore

async def start_listening(self):
"""
Expand All @@ -101,7 +103,7 @@ async def start_listening(self):
parsed = raw_message
else:
json_data = json.loads(raw_message)
parsed = parse_obj_as(V1SocketClientResponse, json_data) # type: ignore
parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore
await self._emit_async(EventType.MESSAGE, parsed)
except Exception as exc:
await self._emit_async(EventType.ERROR, exc)
Expand Down Expand Up @@ -172,7 +174,7 @@ async def recv(self) -> V1SocketClientResponse:
if isinstance(data, bytes):
return data # type: ignore
json_data = json.loads(data)
return parse_obj_as(V1SocketClientResponse, json_data) # type: ignore
return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore

async def _send(self, data: typing.Any) -> None:
"""
Expand All @@ -199,7 +201,7 @@ def __iter__(self):
if isinstance(message, bytes):
yield message
else:
yield parse_obj_as(V1SocketClientResponse, json.loads(message)) # type: ignore
yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore

def start_listening(self):
"""
Expand All @@ -218,7 +220,7 @@ def start_listening(self):
parsed = raw_message
else:
json_data = json.loads(raw_message)
parsed = parse_obj_as(V1SocketClientResponse, json_data) # type: ignore
parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore
self._emit(EventType.MESSAGE, parsed)
except Exception as exc:
self._emit(EventType.ERROR, exc)
Expand Down Expand Up @@ -289,7 +291,7 @@ def recv(self) -> V1SocketClientResponse:
if isinstance(data, bytes):
return data # type: ignore
json_data = json.loads(data)
return parse_obj_as(V1SocketClientResponse, json_data) # type: ignore
return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore

def _send(self, data: typing.Any) -> None:
"""
Expand Down
5 changes: 3 additions & 2 deletions src/deepgram/agent/v1/types/agent_v1agent_audio_done.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel


class AgentV1AgentAudioDone(UniversalBaseModel):
class AgentV1AgentAudioDone(UncheckedBaseModel):
type: typing.Literal["AgentAudioDone"] = pydantic.Field(default="AgentAudioDone")
"""
Message type identifier indicating the agent has finished sending audio
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel


class AgentV1AgentStartedSpeaking(UniversalBaseModel):
class AgentV1AgentStartedSpeaking(UncheckedBaseModel):
type: typing.Literal["AgentStartedSpeaking"] = pydantic.Field(default="AgentStartedSpeaking")
"""
Message type identifier for agent started speaking
Expand Down
5 changes: 3 additions & 2 deletions src/deepgram/agent/v1/types/agent_v1agent_thinking.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel


class AgentV1AgentThinking(UniversalBaseModel):
class AgentV1AgentThinking(UncheckedBaseModel):
type: typing.Literal["AgentThinking"] = pydantic.Field(default="AgentThinking")
"""
Message type identifier for agent thinking
Expand Down
5 changes: 3 additions & 2 deletions src/deepgram/agent/v1/types/agent_v1conversation_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel
from .agent_v1conversation_text_role import AgentV1ConversationTextRole


class AgentV1ConversationText(UniversalBaseModel):
class AgentV1ConversationText(UncheckedBaseModel):
type: typing.Literal["ConversationText"] = pydantic.Field(default="ConversationText")
"""
Message type identifier for conversation text
Expand Down
5 changes: 3 additions & 2 deletions src/deepgram/agent/v1/types/agent_v1error.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel


class AgentV1Error(UniversalBaseModel):
class AgentV1Error(UncheckedBaseModel):
type: typing.Literal["Error"] = pydantic.Field(default="Error")
"""
Message type identifier for error responses
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel
from .agent_v1function_call_request_functions_item import AgentV1FunctionCallRequestFunctionsItem


class AgentV1FunctionCallRequest(UniversalBaseModel):
class AgentV1FunctionCallRequest(UncheckedBaseModel):
type: typing.Literal["FunctionCallRequest"] = pydantic.Field(default="FunctionCallRequest")
"""
Message type identifier for function call requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel


class AgentV1FunctionCallRequestFunctionsItem(UniversalBaseModel):
class AgentV1FunctionCallRequestFunctionsItem(UncheckedBaseModel):
id: str = pydantic.Field()
"""
Unique identifier for the function call
Expand Down
5 changes: 3 additions & 2 deletions src/deepgram/agent/v1/types/agent_v1inject_agent_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel


class AgentV1InjectAgentMessage(UniversalBaseModel):
class AgentV1InjectAgentMessage(UncheckedBaseModel):
type: typing.Literal["InjectAgentMessage"] = pydantic.Field(default="InjectAgentMessage")
"""
Message type identifier for injecting an agent message
Expand Down
5 changes: 3 additions & 2 deletions src/deepgram/agent/v1/types/agent_v1inject_user_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel


class AgentV1InjectUserMessage(UniversalBaseModel):
class AgentV1InjectUserMessage(UncheckedBaseModel):
type: typing.Literal["InjectUserMessage"] = pydantic.Field(default="InjectUserMessage")
"""
Message type identifier for injecting a user message
Expand Down
5 changes: 3 additions & 2 deletions src/deepgram/agent/v1/types/agent_v1injection_refused.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import typing

import pydantic
from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel
from ....core.pydantic_utilities import IS_PYDANTIC_V2
from ....core.unchecked_base_model import UncheckedBaseModel


class AgentV1InjectionRefused(UniversalBaseModel):
class AgentV1InjectionRefused(UncheckedBaseModel):
type: typing.Literal["InjectionRefused"] = pydantic.Field(default="InjectionRefused")
"""
Message type identifier for injection refused
Expand Down
Loading