33import json
44import logging
55import typing
6- from json .decoder import JSONDecodeError
76
8- import websockets
97import websockets .sync .connection as websockets_sync_connection
108from ...core .events import EventEmitterMixin , EventType
119from ...core .unchecked_base_model import construct_type
3937 from websockets import WebSocketClientProtocol # type: ignore
4038
4139_logger = logging .getLogger (__name__ )
40+
41+
42+ def _sanitize_numeric_types (obj : typing .Any ) -> typing .Any :
43+ """
44+ Recursively convert float values that are whole numbers to int.
45+
46+ Workaround for Fern-generated models that type integer API fields
47+ (like sample_rate) as float, causing JSON serialization to produce
48+ values like 44100.0 instead of 44100. The Deepgram API rejects
49+ float representations of integer fields.
50+
51+ See: https://github.com/deepgram/internal-api-specs/issues/205
52+ """
53+ if isinstance (obj , dict ):
54+ return {k : _sanitize_numeric_types (v ) for k , v in obj .items ()}
55+ elif isinstance (obj , list ):
56+ return [_sanitize_numeric_types (item ) for item in obj ]
57+ elif isinstance (obj , float ) and obj .is_integer ():
58+ return int (obj )
59+ return obj
60+
61+
4262V1SocketClientResponse = typing .Union [
4363 AgentV1ReceiveFunctionCallResponse ,
4464 AgentV1PromptUpdated ,
@@ -70,7 +90,7 @@ async def __aiter__(self):
7090 yield message
7191 else :
7292 try :
73- yield construct_type (V1SocketClientResponse , json .loads (message )) # type: ignore
93+ yield construct_type (type_ = V1SocketClientResponse , object_ = json .loads (message )) # type: ignore
7494 except Exception :
7595 _logger .warning (
7696 "Skipping unknown WebSocket message; update your SDK version to support new message types."
@@ -95,14 +115,14 @@ async def start_listening(self):
95115 else :
96116 json_data = json .loads (raw_message )
97117 try :
98- parsed = construct_type (V1SocketClientResponse , json_data ) # type: ignore
118+ parsed = construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
99119 except Exception :
100120 _logger .warning (
101121 "Skipping unknown WebSocket message; update your SDK version to support new message types."
102122 )
103123 continue
104124 await self ._emit_async (EventType .MESSAGE , parsed )
105- except ( websockets . WebSocketException , JSONDecodeError ) as exc :
125+ except Exception as exc :
106126 await self ._emit_async (EventType .ERROR , exc )
107127 finally :
108128 await self ._emit_async (EventType .CLOSE , None )
@@ -142,12 +162,12 @@ async def send_function_call_response(self, message: AgentV1SendFunctionCallResp
142162 """
143163 await self ._send_model (message )
144164
145- async def send_keep_alive (self , message : AgentV1KeepAlive ) -> None :
165+ async def send_keep_alive (self , message : typing . Optional [ AgentV1KeepAlive ] = None ) -> None :
146166 """
147167 Send a message to the websocket connection.
148168 The message will be sent as a AgentV1KeepAlive.
149169 """
150- await self ._send_model (message )
170+ await self ._send_model (message or AgentV1KeepAlive ( type = "KeepAlive" ) )
151171
152172 async def send_update_prompt (self , message : AgentV1UpdatePrompt ) -> None :
153173 """
@@ -179,7 +199,7 @@ async def recv(self) -> V1SocketClientResponse:
179199 return data # type: ignore
180200 json_data = json .loads (data )
181201 try :
182- return construct_type (V1SocketClientResponse , json_data ) # type: ignore
202+ return construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
183203 except Exception :
184204 _logger .warning ("Skipping unknown WebSocket message; update your SDK version to support new message types." )
185205 return json_data # type: ignore
@@ -196,7 +216,7 @@ async def _send_model(self, data: typing.Any) -> None:
196216 """
197217 Send a Pydantic model to the websocket connection.
198218 """
199- await self ._send (data .dict ())
219+ await self ._send (_sanitize_numeric_types ( data .dict () ))
200220
201221
202222class V1SocketClient (EventEmitterMixin ):
@@ -210,7 +230,7 @@ def __iter__(self):
210230 yield message
211231 else :
212232 try :
213- yield construct_type (V1SocketClientResponse , json .loads (message )) # type: ignore
233+ yield construct_type (type_ = V1SocketClientResponse , object_ = json .loads (message )) # type: ignore
214234 except Exception :
215235 _logger .warning (
216236 "Skipping unknown WebSocket message; update your SDK version to support new message types."
@@ -235,14 +255,14 @@ def start_listening(self):
235255 else :
236256 json_data = json .loads (raw_message )
237257 try :
238- parsed = construct_type (V1SocketClientResponse , json_data ) # type: ignore
258+ parsed = construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
239259 except Exception :
240260 _logger .warning (
241261 "Skipping unknown WebSocket message; update your SDK version to support new message types."
242262 )
243263 continue
244264 self ._emit (EventType .MESSAGE , parsed )
245- except ( websockets . WebSocketException , JSONDecodeError ) as exc :
265+ except Exception as exc :
246266 self ._emit (EventType .ERROR , exc )
247267 finally :
248268 self ._emit (EventType .CLOSE , None )
@@ -282,12 +302,12 @@ def send_function_call_response(self, message: AgentV1SendFunctionCallResponse)
282302 """
283303 self ._send_model (message )
284304
285- def send_keep_alive (self , message : AgentV1KeepAlive ) -> None :
305+ def send_keep_alive (self , message : typing . Optional [ AgentV1KeepAlive ] = None ) -> None :
286306 """
287307 Send a message to the websocket connection.
288308 The message will be sent as a AgentV1KeepAlive.
289309 """
290- self ._send_model (message )
310+ self ._send_model (message or AgentV1KeepAlive ( type = "KeepAlive" ) )
291311
292312 def send_update_prompt (self , message : AgentV1UpdatePrompt ) -> None :
293313 """
@@ -319,7 +339,7 @@ def recv(self) -> V1SocketClientResponse:
319339 return data # type: ignore
320340 json_data = json .loads (data )
321341 try :
322- return construct_type (V1SocketClientResponse , json_data ) # type: ignore
342+ return construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
323343 except Exception :
324344 _logger .warning ("Skipping unknown WebSocket message; update your SDK version to support new message types." )
325345 return json_data # type: ignore
@@ -336,4 +356,4 @@ def _send_model(self, data: typing.Any) -> None:
336356 """
337357 Send a Pydantic model to the websocket connection.
338358 """
339- self ._send (data .dict ())
359+ self ._send (_sanitize_numeric_types ( data .dict () ))
0 commit comments