dv-pipecat-ai 0.0.85.dev822__py3-none-any.whl → 0.0.85.dev824__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.

Potentially problematic release.


This version of dv-pipecat-ai might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dv-pipecat-ai
3
- Version: 0.0.85.dev822
3
+ Version: 0.0.85.dev824
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.dev822.dist-info/licenses/LICENSE,sha256=DWY2QGf2eMCFhuu2ChairtT6CB7BEFffNVhXWc4Od08,1301
1
+ dv_pipecat_ai-0.0.85.dev824.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
@@ -108,7 +108,7 @@ pipecat/processors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
108
108
  pipecat/processors/async_generator.py,sha256=qPOZxk5eOad_NrF_Z06vWZ6deXIxb9AKZKYO2e5pkJs,2385
109
109
  pipecat/processors/consumer_processor.py,sha256=DrWCKnfblknZJ0bLmR_unIeJ1axQw4IPUn2IB3KLGGA,3228
110
110
  pipecat/processors/dtmf_aggregator.py,sha256=mo_IXUlsnVl-_Xn8sbTGnRF4Lkts0h6E3uauGbeFyWs,10204
111
- pipecat/processors/frame_processor.py,sha256=Qf-EJCWlw2itvJTsFykKBfjcsXRQUDgSqJDF8gb60V0,33806
111
+ pipecat/processors/frame_processor.py,sha256=uBu6Waa0_diMXdQXMZ5V5a_KwaaPzcieyuv5gO9u-ME,33841
112
112
  pipecat/processors/idle_frame_processor.py,sha256=z8AuhGap61lA5K35P6XCaOpn4kkmK_9NZNppbpQxheU,3124
113
113
  pipecat/processors/logger.py,sha256=8xa4KKekXQIETlQR7zoGnwUpLNo8CeDVm7YjyXePN-w,2385
114
114
  pipecat/processors/producer_processor.py,sha256=iIIOHZd77APvUGP7JqFbznAHUnCULcq_qYiSEjwXHcc,3265
@@ -318,7 +318,7 @@ pipecat/services/riva/__init__.py,sha256=rObSsj504O_TMXhPBg_ymqKslZBhovlR-A0aaRZ
318
318
  pipecat/services/riva/stt.py,sha256=bAss4dimx8eideaSPmPHM15_rSV3tfXNf13o5n1mfv4,25146
319
319
  pipecat/services/riva/tts.py,sha256=idbqx3I2NlWCXtrIFsjEaYapxA3BLIA14ai3aMBh-2w,8158
320
320
  pipecat/services/salesforce/__init__.py,sha256=OFvYbcvCadYhcKdBAVLj3ZUXVXQ1HyVyhgxIFf6_Thg,173
321
- pipecat/services/salesforce/llm.py,sha256=_DQ5JG5QYJG5Y8_LZLsPu5NeGzDBL8oy3_fFqNhz3-E,23009
321
+ pipecat/services/salesforce/llm.py,sha256=xBy5PSftIwD0v3eXxC5QCSlb8R8HiU1KQTX4R9qEmkA,28108
322
322
  pipecat/services/sambanova/__init__.py,sha256=oTXExLic-qTcsfsiWmssf3Elclf3IIWoN41_2IpoF18,128
323
323
  pipecat/services/sambanova/llm.py,sha256=5XVfPLEk__W8ykFqLdV95ZUhlGGkAaJwmbciLdZYtTc,8976
324
324
  pipecat/services/sambanova/stt.py,sha256=ZZgEZ7WQjLFHbCko-3LNTtVajjtfUvbtVLtFcaNadVQ,2536
@@ -415,7 +415,7 @@ pipecat/utils/tracing/service_decorators.py,sha256=fwzxFpi8DJl6BJbK74G0UEB4ccMJg
415
415
  pipecat/utils/tracing/setup.py,sha256=7TEgPNpq6M8lww8OQvf0P9FzYc5A30xICGklVA-fua0,2892
416
416
  pipecat/utils/tracing/turn_context_provider.py,sha256=ikon3plFOx0XbMrH6DdeHttNpb-U0gzMZIm3bWLc9eI,2485
417
417
  pipecat/utils/tracing/turn_trace_observer.py,sha256=dma16SBJpYSOE58YDWy89QzHyQFc_9gQZszKeWixuwc,9725
418
- dv_pipecat_ai-0.0.85.dev822.dist-info/METADATA,sha256=32ww2Lem8OVrVN8fSto1BPjfW5dXjPi4fS9Me8Zz-YE,32924
419
- dv_pipecat_ai-0.0.85.dev822.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
420
- dv_pipecat_ai-0.0.85.dev822.dist-info/top_level.txt,sha256=kQzG20CxGf-nSsHmtXHx3hY2-8zHA3jYg8jk0TajqXc,8
421
- dv_pipecat_ai-0.0.85.dev822.dist-info/RECORD,,
418
+ dv_pipecat_ai-0.0.85.dev824.dist-info/METADATA,sha256=uKWtJ_kdcJbjQXBfvv2_jUlyrmLwFG7YVWXKZYTZFo8,32924
419
+ dv_pipecat_ai-0.0.85.dev824.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
420
+ dv_pipecat_ai-0.0.85.dev824.dist-info/top_level.txt,sha256=kQzG20CxGf-nSsHmtXHx3hY2-8zHA3jYg8jk0TajqXc,8
421
+ dv_pipecat_ai-0.0.85.dev824.dist-info/RECORD,,
@@ -591,7 +591,7 @@ class FrameProcessor(BaseObject):
591
591
 
592
592
  async def pause_processing_system_frames(self):
593
593
  """Pause processing of queued system frames."""
594
- logger.trace(f"{self}: pausing system frame processing")
594
+ self.logger.trace(f"{self}: pausing system frame processing")
595
595
  self.__should_block_system_frames = True
596
596
  if self.__input_event:
597
597
  self.__input_event.clear()
@@ -812,7 +812,7 @@ class FrameProcessor(BaseObject):
812
812
  True if the processor has been started.
813
813
  """
814
814
  if not self.__started:
815
- logger.error(f"{self} Trying to process {frame} but StartFrame not received yet")
815
+ self.logger.error(f"{self} Trying to process {frame} but StartFrame not received yet")
816
816
  return self.__started
817
817
 
818
818
  def __create_input_task(self):
@@ -876,7 +876,7 @@ class FrameProcessor(BaseObject):
876
876
 
877
877
  await self._call_event_handler("on_after_process_frame", frame)
878
878
  except Exception as e:
879
- logger.exception(f"{self}: error processing frame: {e}")
879
+ self.logger.exception(f"{self}: error processing frame: {e}")
880
880
  await self.push_error(ErrorFrame(str(e)))
881
881
 
882
882
  async def __input_frame_task_handler(self):
@@ -890,11 +890,11 @@ class FrameProcessor(BaseObject):
890
890
  (frame, direction, callback) = await self.__input_queue.get()
891
891
 
892
892
  if self.__should_block_system_frames and self.__input_event:
893
- logger.trace(f"{self}: system frame processing paused")
893
+ self.logger.trace(f"{self}: system frame processing paused")
894
894
  await self.__input_event.wait()
895
895
  self.__input_event.clear()
896
896
  self.__should_block_system_frames = False
897
- logger.trace(f"{self}: system frame processing resumed")
897
+ self.logger.trace(f"{self}: system frame processing resumed")
898
898
 
899
899
  if isinstance(frame, SystemFrame):
900
900
  await self.__process_frame(frame, direction, callback)
@@ -913,11 +913,11 @@ class FrameProcessor(BaseObject):
913
913
  (frame, direction, callback) = await self.__process_queue.get()
914
914
 
915
915
  if self.__should_block_frames and self.__process_event:
916
- logger.trace(f"{self}: frame processing paused")
916
+ self.logger.trace(f"{self}: frame processing paused")
917
917
  await self.__process_event.wait()
918
918
  self.__process_event.clear()
919
919
  self.__should_block_frames = False
920
- logger.trace(f"{self}: frame processing resumed")
920
+ self.logger.trace(f"{self}: frame processing resumed")
921
921
 
922
922
  await self.__process_frame(frame, direction, callback)
923
923
 
@@ -236,23 +236,49 @@ class SalesforceAgentLLMService(LLMService):
236
236
  if not redis_client:
237
237
  return
238
238
 
239
- ttl_seconds = 3600
239
+ ttl_seconds = 3600 # Default fallback
240
+
241
+ # Try to get expiration from expires_in parameter first
240
242
  if expires_in is not None:
241
243
  try:
242
244
  ttl_seconds = max(int(expires_in) - self._token_cache_leeway_secs, 30)
245
+ logger.debug(f"Using expires_in parameter: {expires_in}s, TTL: {ttl_seconds}s")
243
246
  except (TypeError, ValueError):
244
- logger.debug("Unable to parse Salesforce token expiry; falling back to default TTL")
247
+ logger.debug("Unable to parse expires_in parameter")
248
+ expires_in = None
249
+
250
+ # If no expires_in available, use default TTL
251
+ if expires_in is None:
252
+ logger.debug("No expiration info found, using default TTL")
245
253
 
246
254
  try:
247
255
  await redis_client.set(self._token_cache_key, token, ex=ttl_seconds)
256
+ logger.debug(f"Cached Salesforce token with TTL: {ttl_seconds}s")
248
257
  except Exception as exc: # pragma: no cover - cache failures shouldn't break flow
249
258
  logger.warning(f"Failed to store Salesforce token in Redis: {exc}")
250
259
 
251
- async def _get_access_token(self) -> str:
252
- """Get OAuth access token using client credentials."""
253
- cached_token = await self._get_cached_access_token()
254
- if cached_token:
255
- return cached_token
260
+ async def _clear_cached_access_token(self):
261
+ """Clear cached access token from Redis."""
262
+ redis_client = self._get_redis_client()
263
+ if not redis_client:
264
+ return
265
+
266
+ try:
267
+ await redis_client.delete(self._token_cache_key)
268
+ logger.debug("Cleared cached Salesforce access token")
269
+ except Exception as exc: # pragma: no cover - cache failures shouldn't break flow
270
+ logger.warning(f"Failed to clear Salesforce token from Redis: {exc}")
271
+
272
+ async def _get_access_token(self, *, force_refresh: bool = False) -> str:
273
+ """Get OAuth access token using client credentials.
274
+
275
+ Args:
276
+ force_refresh: If True, skip cache and fetch fresh token from Salesforce.
277
+ """
278
+ if not force_refresh:
279
+ cached_token = await self._get_cached_access_token()
280
+ if cached_token:
281
+ return cached_token
256
282
 
257
283
  token_url = f"{self._org_domain}/services/oauth2/token"
258
284
  data = {
@@ -267,21 +293,58 @@ class SalesforceAgentLLMService(LLMService):
267
293
  token_data = response.json()
268
294
  access_token = token_data["access_token"]
269
295
  await self._set_cached_access_token(access_token, token_data.get("expires_in"))
296
+ logger.debug("Retrieved fresh Salesforce access token")
270
297
  return access_token
271
298
  except Exception as e:
272
299
  logger.error(f"Failed to get access token: {e}")
273
300
  raise
274
301
 
302
+ async def _make_authenticated_request(self, method: str, url: str, **kwargs):
303
+ """Make an authenticated HTTP request with automatic token refresh on auth errors.
304
+
305
+ Args:
306
+ method: HTTP method (GET, POST, DELETE, etc.)
307
+ url: Request URL
308
+ **kwargs: Additional arguments passed to httpx request
309
+
310
+ Returns:
311
+ httpx.Response: The HTTP response
312
+
313
+ Raises:
314
+ Exception: If request fails after token refresh attempt
315
+ """
316
+ # First attempt with current token
317
+ access_token = await self._get_access_token()
318
+ headers = kwargs.get("headers", {})
319
+ headers["Authorization"] = f"Bearer {access_token}"
320
+ kwargs["headers"] = headers
321
+
322
+ try:
323
+ response = await self._http_client.request(method, url, **kwargs)
324
+ response.raise_for_status()
325
+ return response
326
+ except httpx.HTTPStatusError as e:
327
+ # If authentication error, clear cache and retry with fresh token
328
+ if e.response.status_code in (401, 403):
329
+ logger.warning(f"Salesforce authentication error ({e.response.status_code}), refreshing token")
330
+ await self._clear_cached_access_token()
331
+
332
+ # Retry with fresh token
333
+ fresh_token = await self._get_access_token(force_refresh=True)
334
+ headers["Authorization"] = f"Bearer {fresh_token}"
335
+ kwargs["headers"] = headers
336
+
337
+ response = await self._http_client.request(method, url, **kwargs)
338
+ response.raise_for_status()
339
+ return response
340
+ else:
341
+ # Re-raise non-auth errors
342
+ raise
343
+
275
344
  async def _create_session(self) -> str:
276
345
  """Create a new Salesforce Agent session."""
277
- access_token = await self._get_access_token()
278
346
  session_url = f"{self._api_host}/einstein/ai-agent/v1/agents/{self._agent_id}/sessions"
279
347
 
280
- headers = {
281
- "Authorization": f"Bearer {access_token}",
282
- "Content-Type": "application/json",
283
- }
284
-
285
348
  external_session_key = f"pipecat-{int(time.time())}-{id(self)}"
286
349
 
287
350
  payload = {
@@ -295,8 +358,11 @@ class SalesforceAgentLLMService(LLMService):
295
358
  }
296
359
 
297
360
  try:
298
- response = await self._http_client.post(session_url, headers=headers, json=payload)
299
- response.raise_for_status()
361
+ response = await self._make_authenticated_request(
362
+ "POST", session_url,
363
+ headers={"Content-Type": "application/json"},
364
+ json=payload
365
+ )
300
366
  session_data = response.json()
301
367
  session_id = session_data["sessionId"]
302
368
 
@@ -351,13 +417,11 @@ class SalesforceAgentLLMService(LLMService):
351
417
  for session_id in expired_sessions:
352
418
  try:
353
419
  # End the session via API
354
- access_token = await self._get_access_token()
355
420
  url = f"{self._api_host}/einstein/ai-agent/v1/sessions/{session_id}"
356
- headers = {
357
- "Authorization": f"Bearer {access_token}",
358
- "x-session-end-reason": "UserRequest",
359
- }
360
- await self._http_client.delete(url, headers=headers)
421
+ await self._make_authenticated_request(
422
+ "DELETE", url,
423
+ headers={"x-session-end-reason": "UserRequest"}
424
+ )
361
425
  except Exception as e:
362
426
  logger.warning(f"Failed to end session {session_id}: {e}")
363
427
  finally:
@@ -402,14 +466,7 @@ class SalesforceAgentLLMService(LLMService):
402
466
 
403
467
  async def _stream_salesforce_response(self, session_id: str, user_message: str) -> AsyncGenerator[str, None]:
404
468
  """Stream response from Salesforce Agent API."""
405
- access_token = await self._get_access_token()
406
469
  url = f"{self._api_host}/einstein/ai-agent/v1/sessions/{session_id}/messages/stream"
407
-
408
- headers = {
409
- "Authorization": f"Bearer {access_token}",
410
- "Content-Type": "application/json",
411
- "Accept": "text/event-stream",
412
- }
413
470
 
414
471
  message_data = {
415
472
  "message": {
@@ -426,6 +483,14 @@ class SalesforceAgentLLMService(LLMService):
426
483
  ]
427
484
  }
428
485
 
486
+ # First attempt with current token
487
+ access_token = await self._get_access_token()
488
+ headers = {
489
+ "Authorization": f"Bearer {access_token}",
490
+ "Content-Type": "application/json",
491
+ "Accept": "text/event-stream",
492
+ }
493
+
429
494
  try:
430
495
  logger.info(f"🌐 Salesforce API request: {user_message[:50]}...")
431
496
  async with self._http_client.stream("POST", url, headers=headers, json=message_data) as response:
@@ -457,6 +522,49 @@ class SalesforceAgentLLMService(LLMService):
457
522
  logger.warning(f"JSON decode error: {e}, line: {line}")
458
523
  continue
459
524
 
525
+ except httpx.HTTPStatusError as e:
526
+ # If authentication error, retry with fresh token
527
+ if e.response.status_code in (401, 403):
528
+ logger.warning(f"Salesforce streaming authentication error ({e.response.status_code}), refreshing token")
529
+ await self._clear_cached_access_token()
530
+
531
+ # Retry with fresh token
532
+ fresh_token = await self._get_access_token(force_refresh=True)
533
+ headers["Authorization"] = f"Bearer {fresh_token}"
534
+
535
+ logger.info(f"🔄 Retrying Salesforce stream with fresh token: {user_message[:50]}...")
536
+ async with self._http_client.stream("POST", url, headers=headers, json=message_data) as response:
537
+ response.raise_for_status()
538
+
539
+ async for line in response.aiter_lines():
540
+ if not line:
541
+ continue
542
+
543
+ # Parse SSE format
544
+ if line.startswith("data: "):
545
+ try:
546
+ data = json.loads(line[6:])
547
+ message = data.get("message", {})
548
+ message_type = message.get("type")
549
+
550
+ if message_type == "TextChunk":
551
+ content = message.get("text", "") or message.get("message", "")
552
+ if content:
553
+ yield content
554
+ elif message_type == "EndOfTurn":
555
+ logger.info("🏁 Salesforce response complete")
556
+ break
557
+ elif message_type == "Inform":
558
+ # Skip INFORM events to avoid duplication
559
+ continue
560
+
561
+ except json.JSONDecodeError as e:
562
+ logger.warning(f"JSON decode error: {e}, line: {line}")
563
+ continue
564
+ else:
565
+ # Re-raise non-auth errors
566
+ logger.error(f"Failed to stream from Salesforce Agent API: {e}")
567
+ raise
460
568
  except Exception as e:
461
569
  logger.error(f"Failed to stream from Salesforce Agent API: {e}")
462
570
  raise