dv-pipecat-ai 0.0.85.dev823__py3-none-any.whl → 0.0.85.dev825__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.
- {dv_pipecat_ai-0.0.85.dev823.dist-info → dv_pipecat_ai-0.0.85.dev825.dist-info}/METADATA +1 -1
- {dv_pipecat_ai-0.0.85.dev823.dist-info → dv_pipecat_ai-0.0.85.dev825.dist-info}/RECORD +7 -7
- pipecat/services/salesforce/llm.py +136 -28
- pipecat/transports/base_input.py +11 -10
- {dv_pipecat_ai-0.0.85.dev823.dist-info → dv_pipecat_ai-0.0.85.dev825.dist-info}/WHEEL +0 -0
- {dv_pipecat_ai-0.0.85.dev823.dist-info → dv_pipecat_ai-0.0.85.dev825.dist-info}/licenses/LICENSE +0 -0
- {dv_pipecat_ai-0.0.85.dev823.dist-info → dv_pipecat_ai-0.0.85.dev825.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
dv_pipecat_ai-0.0.85.
|
|
1
|
+
dv_pipecat_ai-0.0.85.dev825.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
|
|
@@ -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=
|
|
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
|
|
@@ -352,7 +352,7 @@ pipecat/tests/utils.py,sha256=DEHDQV8uhCuKIqoHUPGVdUoCiKqTCG9zv5GqLXWWwvY,7870
|
|
|
352
352
|
pipecat/transcriptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
353
353
|
pipecat/transcriptions/language.py,sha256=-mWI1MiZbasuoqZTOBH69dAmoM7-UJzWq9rSCcrnmh4,8228
|
|
354
354
|
pipecat/transports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
355
|
-
pipecat/transports/base_input.py,sha256=
|
|
355
|
+
pipecat/transports/base_input.py,sha256=WGtoXXlF3GIjYgjtYnAgi8nZozd5abNlGNjwRnz8FRs,20138
|
|
356
356
|
pipecat/transports/base_output.py,sha256=mNlIOo7tETlbYPbDyOtA2H-TkBGFKmjuCMDzQUtiwmk,35423
|
|
357
357
|
pipecat/transports/base_transport.py,sha256=JlNiH0DysTfr6azwHauJqY_Z9HJC702O29Q0qrsLrg4,7530
|
|
358
358
|
pipecat/transports/daily/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -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.
|
|
419
|
-
dv_pipecat_ai-0.0.85.
|
|
420
|
-
dv_pipecat_ai-0.0.85.
|
|
421
|
-
dv_pipecat_ai-0.0.85.
|
|
418
|
+
dv_pipecat_ai-0.0.85.dev825.dist-info/METADATA,sha256=9do57Wuqp0n9sMyV0YkeHfibkx3w57ANLCiGI7Pbzsk,32924
|
|
419
|
+
dv_pipecat_ai-0.0.85.dev825.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
420
|
+
dv_pipecat_ai-0.0.85.dev825.dist-info/top_level.txt,sha256=kQzG20CxGf-nSsHmtXHx3hY2-8zHA3jYg8jk0TajqXc,8
|
|
421
|
+
dv_pipecat_ai-0.0.85.dev825.dist-info/RECORD,,
|
|
@@ -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
|
|
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
|
|
252
|
-
"""
|
|
253
|
-
|
|
254
|
-
if
|
|
255
|
-
return
|
|
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.
|
|
299
|
-
|
|
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
|
-
|
|
357
|
-
"
|
|
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
|
pipecat/transports/base_input.py
CHANGED
|
@@ -297,6 +297,17 @@ class BaseInputTransport(FrameProcessor):
|
|
|
297
297
|
elif isinstance(frame, EmulateUserStoppedSpeakingFrame):
|
|
298
298
|
self.logger.debug("Emulating user stopped speaking")
|
|
299
299
|
await self._handle_user_interruption(VADState.QUIET, emulated=True)
|
|
300
|
+
elif isinstance(frame, VADParamsUpdateFrame):
|
|
301
|
+
if self.vad_analyzer:
|
|
302
|
+
self.vad_analyzer.set_params(frame.params)
|
|
303
|
+
speech_frame = SpeechControlParamsFrame(
|
|
304
|
+
vad_params=frame.params,
|
|
305
|
+
turn_params=self._params.turn_analyzer.params
|
|
306
|
+
if self._params.turn_analyzer
|
|
307
|
+
else None,
|
|
308
|
+
)
|
|
309
|
+
await self.push_frame(speech_frame)
|
|
310
|
+
await self.push_frame(frame, direction)
|
|
300
311
|
# All other system frames
|
|
301
312
|
elif isinstance(frame, SystemFrame):
|
|
302
313
|
await self.push_frame(frame, direction)
|
|
@@ -309,16 +320,6 @@ class BaseInputTransport(FrameProcessor):
|
|
|
309
320
|
elif isinstance(frame, StopFrame):
|
|
310
321
|
await self.push_frame(frame, direction)
|
|
311
322
|
await self.pause(frame)
|
|
312
|
-
elif isinstance(frame, VADParamsUpdateFrame):
|
|
313
|
-
if self.vad_analyzer:
|
|
314
|
-
self.vad_analyzer.set_params(frame.params)
|
|
315
|
-
speech_frame = SpeechControlParamsFrame(
|
|
316
|
-
vad_params=frame.params,
|
|
317
|
-
turn_params=self._params.turn_analyzer.params
|
|
318
|
-
if self._params.turn_analyzer
|
|
319
|
-
else None,
|
|
320
|
-
)
|
|
321
|
-
await self.push_frame(speech_frame)
|
|
322
323
|
elif isinstance(frame, FilterUpdateSettingsFrame) and self._params.audio_in_filter:
|
|
323
324
|
await self._params.audio_in_filter.process_frame(frame)
|
|
324
325
|
# Other frames
|
|
File without changes
|
{dv_pipecat_ai-0.0.85.dev823.dist-info → dv_pipecat_ai-0.0.85.dev825.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{dv_pipecat_ai-0.0.85.dev823.dist-info → dv_pipecat_ai-0.0.85.dev825.dist-info}/top_level.txt
RENAMED
|
File without changes
|