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
4260V1SocketClientResponse = typing .Union [
4361 AgentV1ReceiveFunctionCallResponse ,
4462 AgentV1PromptUpdated ,
@@ -70,7 +88,7 @@ async def __aiter__(self):
7088 yield message
7189 else :
7290 try :
73- yield construct_type (V1SocketClientResponse , json .loads (message )) # type: ignore
91+ yield construct_type (type_ = V1SocketClientResponse , object_ = json .loads (message )) # type: ignore
7492 except Exception :
7593 _logger .warning (
7694 "Skipping unknown WebSocket message; update your SDK version to support new message types."
@@ -95,14 +113,14 @@ async def start_listening(self):
95113 else :
96114 json_data = json .loads (raw_message )
97115 try :
98- parsed = construct_type (V1SocketClientResponse , json_data ) # type: ignore
116+ parsed = construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
99117 except Exception :
100118 _logger .warning (
101119 "Skipping unknown WebSocket message; update your SDK version to support new message types."
102120 )
103121 continue
104122 await self ._emit_async (EventType .MESSAGE , parsed )
105- except ( websockets . WebSocketException , JSONDecodeError ) as exc :
123+ except Exception as exc :
106124 await self ._emit_async (EventType .ERROR , exc )
107125 finally :
108126 await self ._emit_async (EventType .CLOSE , None )
@@ -142,12 +160,12 @@ async def send_function_call_response(self, message: AgentV1SendFunctionCallResp
142160 """
143161 await self ._send_model (message )
144162
145- async def send_keep_alive (self , message : AgentV1KeepAlive ) -> None :
163+ async def send_keep_alive (self , message : typing . Optional [ AgentV1KeepAlive ] = None ) -> None :
146164 """
147165 Send a message to the websocket connection.
148166 The message will be sent as a AgentV1KeepAlive.
149167 """
150- await self ._send_model (message )
168+ await self ._send_model (message or AgentV1KeepAlive ( type = "KeepAlive" ) )
151169
152170 async def send_update_prompt (self , message : AgentV1UpdatePrompt ) -> None :
153171 """
@@ -179,7 +197,7 @@ async def recv(self) -> V1SocketClientResponse:
179197 return data # type: ignore
180198 json_data = json .loads (data )
181199 try :
182- return construct_type (V1SocketClientResponse , json_data ) # type: ignore
200+ return construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
183201 except Exception :
184202 _logger .warning ("Skipping unknown WebSocket message; update your SDK version to support new message types." )
185203 return json_data # type: ignore
@@ -196,7 +214,7 @@ async def _send_model(self, data: typing.Any) -> None:
196214 """
197215 Send a Pydantic model to the websocket connection.
198216 """
199- await self ._send (data .dict ())
217+ await self ._send (_sanitize_numeric_types ( data .dict () ))
200218
201219
202220class V1SocketClient (EventEmitterMixin ):
@@ -210,7 +228,7 @@ def __iter__(self):
210228 yield message
211229 else :
212230 try :
213- yield construct_type (V1SocketClientResponse , json .loads (message )) # type: ignore
231+ yield construct_type (type_ = V1SocketClientResponse , object_ = json .loads (message )) # type: ignore
214232 except Exception :
215233 _logger .warning (
216234 "Skipping unknown WebSocket message; update your SDK version to support new message types."
@@ -235,14 +253,14 @@ def start_listening(self):
235253 else :
236254 json_data = json .loads (raw_message )
237255 try :
238- parsed = construct_type (V1SocketClientResponse , json_data ) # type: ignore
256+ parsed = construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
239257 except Exception :
240258 _logger .warning (
241259 "Skipping unknown WebSocket message; update your SDK version to support new message types."
242260 )
243261 continue
244262 self ._emit (EventType .MESSAGE , parsed )
245- except ( websockets . WebSocketException , JSONDecodeError ) as exc :
263+ except Exception as exc :
246264 self ._emit (EventType .ERROR , exc )
247265 finally :
248266 self ._emit (EventType .CLOSE , None )
@@ -282,12 +300,12 @@ def send_function_call_response(self, message: AgentV1SendFunctionCallResponse)
282300 """
283301 self ._send_model (message )
284302
285- def send_keep_alive (self , message : AgentV1KeepAlive ) -> None :
303+ def send_keep_alive (self , message : typing . Optional [ AgentV1KeepAlive ] = None ) -> None :
286304 """
287305 Send a message to the websocket connection.
288306 The message will be sent as a AgentV1KeepAlive.
289307 """
290- self ._send_model (message )
308+ self ._send_model (message or AgentV1KeepAlive ( type = "KeepAlive" ) )
291309
292310 def send_update_prompt (self , message : AgentV1UpdatePrompt ) -> None :
293311 """
@@ -319,7 +337,7 @@ def recv(self) -> V1SocketClientResponse:
319337 return data # type: ignore
320338 json_data = json .loads (data )
321339 try :
322- return construct_type (V1SocketClientResponse , json_data ) # type: ignore
340+ return construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
323341 except Exception :
324342 _logger .warning ("Skipping unknown WebSocket message; update your SDK version to support new message types." )
325343 return json_data # type: ignore
@@ -336,4 +354,4 @@ def _send_model(self, data: typing.Any) -> None:
336354 """
337355 Send a Pydantic model to the websocket connection.
338356 """
339- self ._send (data .dict ())
357+ self ._send (_sanitize_numeric_types ( data .dict () ))
0 commit comments