dv-pipecat-ai 0.0.85.dev841__py3-none-any.whl → 0.0.85.dev843__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dv-pipecat-ai
3
- Version: 0.0.85.dev841
3
+ Version: 0.0.85.dev843
4
4
  Summary: An open source framework for voice (and multimodal) assistants
5
5
  License-Expression: BSD-2-Clause
6
6
  Project-URL: Source, https://github.com/pipecat-ai/pipecat
@@ -1,4 +1,4 @@
1
- dv_pipecat_ai-0.0.85.dev841.dist-info/licenses/LICENSE,sha256=DWY2QGf2eMCFhuu2ChairtT6CB7BEFffNVhXWc4Od08,1301
1
+ dv_pipecat_ai-0.0.85.dev843.dist-info/licenses/LICENSE,sha256=DWY2QGf2eMCFhuu2ChairtT6CB7BEFffNVhXWc4Od08,1301
2
2
  pipecat/__init__.py,sha256=j0Xm6adxHhd7D06dIyyPV_GlBYLlBnTAERVvD_jAARQ,861
3
3
  pipecat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  pipecat/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -210,7 +210,7 @@ pipecat/services/cartesia/tts.py,sha256=I_OZCINywkDXmYzFL35MjSN8cAuNEaJs7nj0YB_o
210
210
  pipecat/services/cerebras/__init__.py,sha256=5zBmqq9Zfcl-HC7ylekVS5qrRedbl1mAeEwUT-T-c_o,259
211
211
  pipecat/services/cerebras/llm.py,sha256=-yzSe_6YDGigwzES-LZS4vNXMPugmvsIYEpTySyr5nA,3047
212
212
  pipecat/services/deepgram/__init__.py,sha256=IjRtMI7WytRDdmYVpk2qDWClXUiNgdl7ZkvEAWg1eYE,304
213
- pipecat/services/deepgram/stt.py,sha256=IvdKvo23PxhKoWTJDxuK4Uoo0wCtkFGAE_QrMUoGdYM,13732
213
+ pipecat/services/deepgram/stt.py,sha256=fzKirTjTopwXNQEEPuUOIgk4AMvTJQcrh6H11w13q2c,16185
214
214
  pipecat/services/deepgram/tts.py,sha256=H_2WCJEx3_L4ytrHHRNkA-6GKTd1coou_vvTfiEodpQ,3745
215
215
  pipecat/services/deepgram/flux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
216
216
  pipecat/services/deepgram/flux/stt.py,sha256=yCZodrHAOShgYy_GbdviX8iAuh36dBgDL41gHMXVxEM,25887
@@ -353,7 +353,7 @@ pipecat/tests/utils.py,sha256=DEHDQV8uhCuKIqoHUPGVdUoCiKqTCG9zv5GqLXWWwvY,7870
353
353
  pipecat/transcriptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
354
354
  pipecat/transcriptions/language.py,sha256=-mWI1MiZbasuoqZTOBH69dAmoM7-UJzWq9rSCcrnmh4,8228
355
355
  pipecat/transports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
356
- pipecat/transports/base_input.py,sha256=WGtoXXlF3GIjYgjtYnAgi8nZozd5abNlGNjwRnz8FRs,20138
356
+ pipecat/transports/base_input.py,sha256=AkdE-j9UksjIrUGJc7laMOaknXgOS7L22D5sehZ-6ew,20176
357
357
  pipecat/transports/base_output.py,sha256=7WoXtAQAi-3OC9PC_zk61lCWlBTk5-NuTLUbsQUAI_U,36723
358
358
  pipecat/transports/base_transport.py,sha256=JlNiH0DysTfr6azwHauJqY_Z9HJC702O29Q0qrsLrg4,7530
359
359
  pipecat/transports/daily/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -416,7 +416,7 @@ pipecat/utils/tracing/service_decorators.py,sha256=fwzxFpi8DJl6BJbK74G0UEB4ccMJg
416
416
  pipecat/utils/tracing/setup.py,sha256=7TEgPNpq6M8lww8OQvf0P9FzYc5A30xICGklVA-fua0,2892
417
417
  pipecat/utils/tracing/turn_context_provider.py,sha256=ikon3plFOx0XbMrH6DdeHttNpb-U0gzMZIm3bWLc9eI,2485
418
418
  pipecat/utils/tracing/turn_trace_observer.py,sha256=dma16SBJpYSOE58YDWy89QzHyQFc_9gQZszKeWixuwc,9725
419
- dv_pipecat_ai-0.0.85.dev841.dist-info/METADATA,sha256=xq4O-F0nWpeT1pXQ6uVPqT-eYvdnGrm_ktReRPPqrYo,32955
420
- dv_pipecat_ai-0.0.85.dev841.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
421
- dv_pipecat_ai-0.0.85.dev841.dist-info/top_level.txt,sha256=kQzG20CxGf-nSsHmtXHx3hY2-8zHA3jYg8jk0TajqXc,8
422
- dv_pipecat_ai-0.0.85.dev841.dist-info/RECORD,,
419
+ dv_pipecat_ai-0.0.85.dev843.dist-info/METADATA,sha256=uUCq2aCHWQLhL2bWdrjCVsJzqAirZz-gdHC2uDeATb8,32955
420
+ dv_pipecat_ai-0.0.85.dev843.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
421
+ dv_pipecat_ai-0.0.85.dev843.dist-info/top_level.txt,sha256=kQzG20CxGf-nSsHmtXHx3hY2-8zHA3jYg8jk0TajqXc,8
422
+ dv_pipecat_ai-0.0.85.dev843.dist-info/RECORD,,
@@ -62,6 +62,8 @@ class DeepgramSTTService(STTService):
62
62
  sample_rate: Optional[int] = None,
63
63
  live_options: Optional[LiveOptions] = None,
64
64
  addons: Optional[Dict] = None,
65
+ max_connect_retries: int = 3,
66
+ connect_timeout_s: float = 2.5,
65
67
  **kwargs,
66
68
  ):
67
69
  """Initialize the Deepgram STT service.
@@ -77,6 +79,9 @@ class DeepgramSTTService(STTService):
77
79
  sample_rate: Audio sample rate. If None, uses default or live_options value.
78
80
  live_options: Deepgram LiveOptions for detailed configuration.
79
81
  addons: Additional Deepgram features to enable.
82
+ max_connect_retries: Maximum number of connection attempts before giving up.
83
+ connect_timeout_s: Maximum time in seconds to wait for a connection attempt.
84
+ Connection retries wait 100ms between attempts.
80
85
  **kwargs: Additional arguments passed to the parent STTService.
81
86
  """
82
87
  sample_rate = sample_rate or (live_options.sample_rate if live_options else None)
@@ -121,9 +126,9 @@ class DeepgramSTTService(STTService):
121
126
  self._settings = merged_options
122
127
  self._addons = addons
123
128
 
124
- # Connection retry settings
125
- self._max_connect_retries = 3
126
- self._connect_retry_delay_s = 0.1
129
+ # Connection retry settings (100ms delay between retries)
130
+ self._max_connect_retries = max_connect_retries
131
+ self._connect_timeout_s = connect_timeout_s
127
132
 
128
133
  self._client = DeepgramClient(
129
134
  api_key,
@@ -131,8 +136,8 @@ class DeepgramSTTService(STTService):
131
136
  url=base_url,
132
137
  options={
133
138
  "keepalive": "true",
134
- "open_timeout": 3, # Max wait for only 3 seconds for the connection to establish #
135
- # "termination_exception_connect": True, # Enable exception propagation
139
+ # Note: Connection timeout is enforced by asyncio.wait_for() in _connect()
140
+ # with the connect_timeout_s parameter (default 2.0s)
136
141
  },
137
142
  verbose=logging.ERROR, # Enable error level and above logging
138
143
  ),
@@ -227,6 +232,11 @@ class DeepgramSTTService(STTService):
227
232
 
228
233
  for attempt in range(self._max_connect_retries):
229
234
  try:
235
+ # Clean up any previous connection attempt in background (non-blocking)
236
+ if hasattr(self, "_connection") and self._connection is not None:
237
+ old_conn = self._connection
238
+ asyncio.create_task(self._cleanup_abandoned_connection(old_conn))
239
+
230
240
  # Create a new connection object for a clean attempt
231
241
  self._connection: AsyncListenWebSocketClient = self._client.listen.asyncwebsocket.v(
232
242
  "1"
@@ -250,10 +260,25 @@ class DeepgramSTTService(STTService):
250
260
  self._on_utterance_end,
251
261
  )
252
262
 
253
- # Attempt to start the connection (timeout handled by open_timeout config)
254
- if await self._connection.start(options=self._settings, addons=self._addons):
255
- self.logger.info("Successfully connected to Deepgram.")
256
- return # Exit the method on success
263
+ try:
264
+ start_result = await asyncio.wait_for(
265
+ self._connection.start(options=self._settings, addons=self._addons),
266
+ timeout=self._connect_timeout_s,
267
+ )
268
+ except asyncio.TimeoutError:
269
+ self.logger.warning(
270
+ f"Deepgram connection attempt {attempt + 1}/{self._max_connect_retries} timed out after {self._connect_timeout_s} second(s)."
271
+ )
272
+ start_result = False
273
+ except Exception as start_error:
274
+ self.logger.warning(
275
+ f"Deepgram connection attempt {attempt + 1}/{self._max_connect_retries} failed with an exception: {start_error}"
276
+ )
277
+ start_result = False
278
+ else:
279
+ if start_result:
280
+ self.logger.info("Successfully connected to Deepgram.")
281
+ return # Exit the method on success
257
282
 
258
283
  self.logger.warning(
259
284
  f"Deepgram connection attempt {attempt + 1}/{self._max_connect_retries} failed."
@@ -264,17 +289,21 @@ class DeepgramSTTService(STTService):
264
289
  f"Deepgram connection attempt {attempt + 1}/{self._max_connect_retries} failed with an exception: {e}"
265
290
  )
266
291
 
267
- # If this is not the last attempt, wait briefly before retrying
292
+ # If this is not the last attempt, wait 100ms before retrying
268
293
  if attempt < self._max_connect_retries - 1:
269
- self.logger.info(f"Retrying in {self._connect_retry_delay_s} second(s)...")
270
- await asyncio.sleep(self._connect_retry_delay_s)
294
+ self.logger.info("Retrying in 0.1 second(s)...")
295
+ await asyncio.sleep(0.1)
271
296
 
272
- self.logger.error(
297
+ error_msg = (
273
298
  f"{self}: unable to connect to Deepgram after {self._max_connect_retries} attempts."
274
299
  )
300
+ self.logger.error(error_msg)
301
+ await self.push_error(ErrorFrame(error_msg, fatal=True))
275
302
 
276
303
  async def _disconnect(self):
277
- if self._connection.is_connected:
304
+ # Guard against missing connection instance and ensure proper async check
305
+ connection: AsyncListenWebSocketClient = getattr(self, "_connection", None)
306
+ if connection and await connection.is_connected():
278
307
  self.logger.debug("Disconnecting from Deepgram")
279
308
  # Deepgram swallows asyncio.CancelledError internally which prevents
280
309
  # proper cancellation propagation. This issue was found with
@@ -284,7 +313,25 @@ class DeepgramSTTService(STTService):
284
313
  # Deepgram disconnection was still finishing and therefore
285
314
  # preventing the task cancellation that occurs during `cleanup()`.
286
315
  # GH issue: https://github.com/deepgram/deepgram-python-sdk/issues/570
287
- await self._connection.finish()
316
+ await connection.finish()
317
+
318
+ async def _cleanup_abandoned_connection(self, conn: AsyncListenWebSocketClient):
319
+ """Clean up abandoned connection attempt in background (non-blocking).
320
+
321
+ This prevents zombie connections from triggering spurious error events
322
+ when they eventually timeout and call _on_error().
323
+
324
+ Args:
325
+ conn: The abandoned connection object to clean up.
326
+ """
327
+ try:
328
+ # Try to finish with short timeout
329
+ await asyncio.wait_for(conn.finish(), timeout=5)
330
+ self.logger.debug("Successfully cleaned up abandoned connection")
331
+ except Exception as e:
332
+ # Ignore all cleanup errors - connection might not be fully started
333
+ # This is expected and fine - we just want best-effort cleanup
334
+ self.logger.debug(f"Abandoned connection cleanup failed: {e}")
288
335
 
289
336
  async def start_metrics(self):
290
337
  """Start TTFB and processing metrics collection."""
@@ -299,7 +299,7 @@ class BaseInputTransport(FrameProcessor):
299
299
  await self._handle_user_interruption(VADState.QUIET, emulated=True)
300
300
  elif isinstance(frame, VADParamsUpdateFrame):
301
301
  if self.vad_analyzer:
302
- self.vad_analyzer.set_params(frame.params)
302
+ self.vad_analyzer.set_params(frame.params, self.logger)
303
303
  speech_frame = SpeechControlParamsFrame(
304
304
  vad_params=frame.params,
305
305
  turn_params=self._params.turn_analyzer.params
@@ -445,7 +445,10 @@ class BaseInputTransport(FrameProcessor):
445
445
  await self._handle_user_interruption(VADState.QUIET)
446
446
 
447
447
  async def _run_turn_analyzer(
448
- self, frame: InputAudioRawFrame, vad_state: VADState, previous_vad_state: VADState
448
+ self,
449
+ frame: InputAudioRawFrame,
450
+ vad_state: VADState,
451
+ previous_vad_state: VADState,
449
452
  ):
450
453
  """Run turn analysis on audio frame and handle results."""
451
454
  is_speech = vad_state == VADState.SPEAKING or vad_state == VADState.STARTING