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

Files changed (44) hide show
  1. {dv_pipecat_ai-0.0.85.dev699.dist-info → dv_pipecat_ai-0.0.85.dev816.dist-info}/METADATA +23 -18
  2. {dv_pipecat_ai-0.0.85.dev699.dist-info → dv_pipecat_ai-0.0.85.dev816.dist-info}/RECORD +44 -44
  3. pipecat/adapters/services/aws_nova_sonic_adapter.py +116 -6
  4. pipecat/frames/frames.py +96 -0
  5. pipecat/pipeline/runner.py +6 -2
  6. pipecat/pipeline/task.py +40 -55
  7. pipecat/processors/aggregators/llm_context.py +40 -2
  8. pipecat/processors/frameworks/rtvi.py +1 -0
  9. pipecat/runner/daily.py +59 -20
  10. pipecat/runner/run.py +149 -67
  11. pipecat/runner/types.py +5 -5
  12. pipecat/services/assemblyai/models.py +6 -0
  13. pipecat/services/assemblyai/stt.py +13 -5
  14. pipecat/services/asyncai/tts.py +3 -0
  15. pipecat/services/aws/llm.py +33 -16
  16. pipecat/services/aws/nova_sonic/context.py +69 -0
  17. pipecat/services/aws/nova_sonic/llm.py +199 -89
  18. pipecat/services/aws/stt.py +2 -0
  19. pipecat/services/aws_nova_sonic/context.py +8 -12
  20. pipecat/services/cartesia/stt.py +77 -70
  21. pipecat/services/cartesia/tts.py +3 -1
  22. pipecat/services/deepgram/flux/stt.py +4 -0
  23. pipecat/services/elevenlabs/tts.py +82 -41
  24. pipecat/services/fish/tts.py +3 -0
  25. pipecat/services/google/stt.py +4 -0
  26. pipecat/services/lmnt/tts.py +2 -0
  27. pipecat/services/neuphonic/tts.py +3 -0
  28. pipecat/services/openai/tts.py +37 -6
  29. pipecat/services/piper/tts.py +7 -9
  30. pipecat/services/playht/tts.py +3 -0
  31. pipecat/services/rime/tts.py +9 -8
  32. pipecat/services/riva/stt.py +3 -1
  33. pipecat/services/sarvam/tts.py +87 -10
  34. pipecat/services/speechmatics/stt.py +3 -1
  35. pipecat/services/stt_service.py +23 -10
  36. pipecat/services/tts_service.py +64 -13
  37. pipecat/transports/base_input.py +3 -0
  38. pipecat/transports/base_output.py +71 -77
  39. pipecat/transports/smallwebrtc/connection.py +5 -0
  40. pipecat/transports/smallwebrtc/request_handler.py +42 -0
  41. pipecat/utils/string.py +1 -0
  42. {dv_pipecat_ai-0.0.85.dev699.dist-info → dv_pipecat_ai-0.0.85.dev816.dist-info}/WHEEL +0 -0
  43. {dv_pipecat_ai-0.0.85.dev699.dist-info → dv_pipecat_ai-0.0.85.dev816.dist-info}/licenses/LICENSE +0 -0
  44. {dv_pipecat_ai-0.0.85.dev699.dist-info → dv_pipecat_ai-0.0.85.dev816.dist-info}/top_level.txt +0 -0
pipecat/runner/run.py CHANGED
@@ -70,16 +70,19 @@ import asyncio
70
70
  import mimetypes
71
71
  import os
72
72
  import sys
73
+ import uuid
73
74
  from contextlib import asynccontextmanager
75
+ from http import HTTPMethod
74
76
  from pathlib import Path
75
- from typing import Optional
77
+ from typing import Any, Dict, List, Optional, TypedDict
76
78
 
77
79
  import aiohttp
78
- from fastapi.responses import FileResponse
80
+ from fastapi.responses import FileResponse, Response
79
81
  from loguru import logger
80
82
 
81
83
  from pipecat.runner.types import (
82
84
  DailyRunnerArguments,
85
+ RunnerArguments,
83
86
  SmallWebRTCRunnerArguments,
84
87
  WebSocketRunnerArguments,
85
88
  )
@@ -166,6 +169,7 @@ def _create_server_app(
166
169
  host: str = "localhost",
167
170
  proxy: str,
168
171
  esp32_mode: bool = False,
172
+ whatsapp_enabled: bool = False,
169
173
  folder: Optional[str] = None,
170
174
  ):
171
175
  """Create FastAPI app with transport-specific routes."""
@@ -182,7 +186,8 @@ def _create_server_app(
182
186
  # Set up transport-specific routes
183
187
  if transport_type == "webrtc":
184
188
  _setup_webrtc_routes(app, esp32_mode=esp32_mode, host=host, folder=folder)
185
- _setup_whatsapp_routes(app)
189
+ if whatsapp_enabled:
190
+ _setup_whatsapp_routes(app)
186
191
  elif transport_type == "daily":
187
192
  _setup_daily_routes(app)
188
193
  elif transport_type in TELEPHONY_TRANSPORTS:
@@ -200,8 +205,10 @@ def _setup_webrtc_routes(
200
205
  try:
201
206
  from pipecat_ai_small_webrtc_prebuilt.frontend import SmallWebRTCPrebuiltUI
202
207
 
203
- from pipecat.transports.smallwebrtc.connection import SmallWebRTCConnection
208
+ from pipecat.transports.smallwebrtc.connection import IceServer, SmallWebRTCConnection
204
209
  from pipecat.transports.smallwebrtc.request_handler import (
210
+ IceCandidate,
211
+ SmallWebRTCPatchRequest,
205
212
  SmallWebRTCRequest,
206
213
  SmallWebRTCRequestHandler,
207
214
  )
@@ -209,6 +216,16 @@ def _setup_webrtc_routes(
209
216
  logger.error(f"WebRTC transport dependencies not installed: {e}")
210
217
  return
211
218
 
219
+ class IceConfig(TypedDict):
220
+ iceServers: List[IceServer]
221
+
222
+ class StartBotResult(TypedDict, total=False):
223
+ sessionId: str
224
+ iceConfig: Optional[IceConfig]
225
+
226
+ # In-memory store of active sessions: session_id -> session info
227
+ active_sessions: Dict[str, Dict[str, Any]] = {}
228
+
212
229
  # Mount the frontend
213
230
  app.mount("/client", SmallWebRTCPrebuiltUI)
214
231
 
@@ -217,7 +234,7 @@ def _setup_webrtc_routes(
217
234
  """Redirect root requests to client interface."""
218
235
  return RedirectResponse(url="/client/")
219
236
 
220
- @app.get("/files/{filename}")
237
+ @app.get("/files/{filename:path}")
221
238
  async def download_file(filename: str):
222
239
  """Handle file downloads."""
223
240
  if not folder:
@@ -254,6 +271,74 @@ def _setup_webrtc_routes(
254
271
  )
255
272
  return answer
256
273
 
274
+ @app.patch("/api/offer")
275
+ async def ice_candidate(request: SmallWebRTCPatchRequest):
276
+ """Handle WebRTC new ice candidate requests."""
277
+ logger.debug(f"Received patch request: {request}")
278
+ await small_webrtc_handler.handle_patch_request(request)
279
+ return {"status": "success"}
280
+
281
+ @app.post("/start")
282
+ async def rtvi_start(request: Request):
283
+ """Mimic Pipecat Cloud's /start endpoint."""
284
+ # Parse the request body
285
+ try:
286
+ request_data = await request.json()
287
+ logger.debug(f"Received request: {request_data}")
288
+ except Exception as e:
289
+ logger.error(f"Failed to parse request body: {e}")
290
+ request_data = {}
291
+
292
+ # Store session info immediately in memory, replicate the behavior expected on Pipecat Cloud
293
+ session_id = str(uuid.uuid4())
294
+ active_sessions[session_id] = request_data
295
+
296
+ result: StartBotResult = {"sessionId": session_id}
297
+ if request_data.get("enableDefaultIceServers"):
298
+ result["iceConfig"] = IceConfig(
299
+ iceServers=[IceServer(urls="stun:stun.l.google.com:19302")]
300
+ )
301
+
302
+ return result
303
+
304
+ @app.api_route(
305
+ "/sessions/{session_id}/{path:path}",
306
+ methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
307
+ )
308
+ async def proxy_request(
309
+ session_id: str, path: str, request: Request, background_tasks: BackgroundTasks
310
+ ):
311
+ """Mimic Pipecat Cloud's proxy."""
312
+ active_session = active_sessions.get(session_id)
313
+ if active_session is None:
314
+ return Response(content="Invalid or not-yet-ready session_id", status_code=404)
315
+
316
+ if path.endswith("api/offer"):
317
+ # Parse the request body and convert to SmallWebRTCRequest
318
+ try:
319
+ request_data = await request.json()
320
+ if request.method == HTTPMethod.POST.value:
321
+ webrtc_request = SmallWebRTCRequest(
322
+ sdp=request_data["sdp"],
323
+ type=request_data["type"],
324
+ pc_id=request_data.get("pc_id"),
325
+ restart_pc=request_data.get("restart_pc"),
326
+ request_data=request_data,
327
+ )
328
+ return await offer(webrtc_request, background_tasks)
329
+ elif request.method == HTTPMethod.PATCH.value:
330
+ patch_request = SmallWebRTCPatchRequest(
331
+ pc_id=request_data["pc_id"],
332
+ candidates=[IceCandidate(**c) for c in request_data.get("candidates", [])],
333
+ )
334
+ return await ice_candidate(patch_request)
335
+ except Exception as e:
336
+ logger.error(f"Failed to parse WebRTC request: {e}")
337
+ return Response(content="Invalid WebRTC request", status_code=400)
338
+
339
+ logger.info(f"Received request for path: {path}")
340
+ return Response(status_code=200)
341
+
257
342
  @asynccontextmanager
258
343
  async def smallwebrtc_lifespan(app: FastAPI):
259
344
  """Manage FastAPI application lifecycle and cleanup connections."""
@@ -289,6 +374,29 @@ def _add_lifespan_to_app(app: FastAPI, new_lifespan):
289
374
 
290
375
  def _setup_whatsapp_routes(app: FastAPI):
291
376
  """Set up WebRTC-specific routes."""
377
+ WHATSAPP_APP_SECRET = os.getenv("WHATSAPP_APP_SECRET")
378
+ WHATSAPP_PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID")
379
+ WHATSAPP_TOKEN = os.getenv("WHATSAPP_TOKEN")
380
+ WHATSAPP_WEBHOOK_VERIFICATION_TOKEN = os.getenv("WHATSAPP_WEBHOOK_VERIFICATION_TOKEN")
381
+
382
+ if not all(
383
+ [
384
+ WHATSAPP_APP_SECRET,
385
+ WHATSAPP_PHONE_NUMBER_ID,
386
+ WHATSAPP_TOKEN,
387
+ WHATSAPP_WEBHOOK_VERIFICATION_TOKEN,
388
+ ]
389
+ ):
390
+ logger.error(
391
+ """Missing required environment variables for WhatsApp transport:
392
+ WHATSAPP_APP_SECRET
393
+ WHATSAPP_PHONE_NUMBER_ID
394
+ WHATSAPP_TOKEN
395
+ WHATSAPP_WEBHOOK_VERIFICATION_TOKEN
396
+ """
397
+ )
398
+ return
399
+
292
400
  try:
293
401
  from pipecat_ai_small_webrtc_prebuilt.frontend import SmallWebRTCPrebuiltUI
294
402
 
@@ -300,24 +408,7 @@ def _setup_whatsapp_routes(app: FastAPI):
300
408
  from pipecat.transports.whatsapp.api import WhatsAppWebhookRequest
301
409
  from pipecat.transports.whatsapp.client import WhatsAppClient
302
410
  except ImportError as e:
303
- logger.error(f"WebRTC transport dependencies not installed: {e}")
304
- return
305
-
306
- WHATSAPP_TOKEN = os.getenv("WHATSAPP_TOKEN")
307
- WHATSAPP_PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID")
308
- WHATSAPP_WEBHOOK_VERIFICATION_TOKEN = os.getenv("WHATSAPP_WEBHOOK_VERIFICATION_TOKEN")
309
- WHATSAPP_APP_SECRET = os.getenv("WHATSAPP_APP_SECRET")
310
-
311
- if not all(
312
- [
313
- WHATSAPP_TOKEN,
314
- WHATSAPP_PHONE_NUMBER_ID,
315
- WHATSAPP_WEBHOOK_VERIFICATION_TOKEN,
316
- ]
317
- ):
318
- logger.debug(
319
- "Missing required environment variables for WhatsApp transport. Keeping it disabled."
320
- )
411
+ logger.error(f"WhatsApp transport dependencies not installed: {e}")
321
412
  return
322
413
 
323
414
  # Global WhatsApp client instance
@@ -439,9 +530,9 @@ def _setup_daily_routes(app: FastAPI):
439
530
  """Set up Daily-specific routes."""
440
531
 
441
532
  @app.get("/")
442
- async def start_agent():
533
+ async def create_room_and_start_agent():
443
534
  """Launch a Daily bot and redirect to room."""
444
- print("Starting bot with Daily transport")
535
+ print("Starting bot with Daily transport and redirecting to Daily room")
445
536
 
446
537
  import aiohttp
447
538
 
@@ -456,11 +547,11 @@ def _setup_daily_routes(app: FastAPI):
456
547
  asyncio.create_task(bot_module.bot(runner_args))
457
548
  return RedirectResponse(room_url)
458
549
 
459
- async def _handle_rtvi_request(request: Request):
460
- """Common handler for both /start and /connect endpoints.
550
+ @app.post("/start")
551
+ async def start_agent(request: Request):
552
+ """Handler for /start endpoints.
461
553
 
462
554
  Expects POST body like::
463
-
464
555
  {
465
556
  "createDailyRoom": true,
466
557
  "dailyRoomProperties": { "start_video_off": true },
@@ -477,47 +568,32 @@ def _setup_daily_routes(app: FastAPI):
477
568
  logger.error(f"Failed to parse request body: {e}")
478
569
  request_data = {}
479
570
 
480
- # Extract the body data that should be passed to the bot
481
- # This mimics Pipecat Cloud's behavior
482
- bot_body = request_data.get("body", {})
483
-
484
- # Log the extracted body data for debugging
485
- if bot_body:
486
- logger.info(f"Extracted body data for bot: {bot_body}")
487
- else:
488
- logger.debug("No body data provided in request")
571
+ create_daily_room = request_data.get("createDailyRoom", False)
572
+ body = request_data.get("body", {})
489
573
 
490
- import aiohttp
491
-
492
- from pipecat.runner.daily import configure
574
+ bot_module = _get_bot_module()
493
575
 
494
- async with aiohttp.ClientSession() as session:
495
- room_url, token = await configure(session)
576
+ result = None
577
+ if create_daily_room:
578
+ import aiohttp
496
579
 
497
- # Start the bot in the background with extracted body data
498
- bot_module = _get_bot_module()
499
- runner_args = DailyRunnerArguments(room_url=room_url, token=token, body=bot_body)
500
- asyncio.create_task(bot_module.bot(runner_args))
501
- # Match PCC /start endpoint response format:
502
- return {"dailyRoom": room_url, "dailyToken": token}
580
+ from pipecat.runner.daily import configure
503
581
 
504
- @app.post("/start")
505
- async def rtvi_start(request: Request):
506
- """Launch a Daily bot and return connection info for RTVI clients."""
507
- return await _handle_rtvi_request(request)
582
+ async with aiohttp.ClientSession() as session:
583
+ room_url, token = await configure(session)
584
+ runner_args = DailyRunnerArguments(room_url=room_url, token=token, body=body)
585
+ result = {
586
+ "dailyRoom": room_url,
587
+ "dailyToken": token,
588
+ "sessionId": str(uuid.uuid4()),
589
+ }
590
+ else:
591
+ runner_args = RunnerArguments(body=body)
508
592
 
509
- @app.post("/connect")
510
- async def rtvi_connect(request: Request):
511
- """Launch a Daily bot and return connection info for RTVI clients.
593
+ # Start the bot in the background
594
+ asyncio.create_task(bot_module.bot(runner_args))
512
595
 
513
- .. deprecated:: 0.0.78
514
- Use /start instead. This endpoint will be removed in a future version.
515
- """
516
- logger.warning(
517
- "DEPRECATED: /connect endpoint is deprecated. Please use /start instead. "
518
- "This endpoint will be removed in a future version."
519
- )
520
- return await _handle_rtvi_request(request)
596
+ return result
521
597
 
522
598
 
523
599
  def _setup_telephony_routes(app: FastAPI, *, transport_type: str, proxy: str):
@@ -576,8 +652,6 @@ def _setup_telephony_routes(app: FastAPI, *, transport_type: str, proxy: str):
576
652
  async def _run_daily_direct():
577
653
  """Run Daily bot with direct connection (no FastAPI server)."""
578
654
  try:
579
- import aiohttp
580
-
581
655
  from pipecat.runner.daily import configure
582
656
  except ImportError as e:
583
657
  logger.error("Daily transport dependencies not installed.")
@@ -689,6 +763,12 @@ def main():
689
763
  parser.add_argument(
690
764
  "--verbose", "-v", action="count", default=0, help="Increase logging verbosity"
691
765
  )
766
+ parser.add_argument(
767
+ "--whatsapp",
768
+ action="store_true",
769
+ default=False,
770
+ help="Ensure requried WhatsApp environment variables are present",
771
+ )
692
772
 
693
773
  args = parser.parse_args()
694
774
 
@@ -731,10 +811,11 @@ def main():
731
811
  print()
732
812
  if args.esp32:
733
813
  print(f"🚀 Bot ready! (ESP32 mode)")
734
- print(f" → Open http://{args.host}:{args.port}/client in your browser")
814
+ elif args.whatsapp:
815
+ print(f"🚀 Bot ready! (WhatsApp)")
735
816
  else:
736
817
  print(f"🚀 Bot ready!")
737
- print(f" → Open http://{args.host}:{args.port}/client in your browser")
818
+ print(f" → Open http://{args.host}:{args.port}/client in your browser")
738
819
  print()
739
820
  elif args.transport == "daily":
740
821
  print()
@@ -752,6 +833,7 @@ def main():
752
833
  host=args.host,
753
834
  proxy=args.proxy,
754
835
  esp32_mode=args.esp32,
836
+ whatsapp_enabled=args.whatsapp,
755
837
  folder=args.folder,
756
838
  )
757
839
 
pipecat/runner/types.py CHANGED
@@ -20,9 +20,11 @@ from fastapi import WebSocket
20
20
  class RunnerArguments:
21
21
  """Base class for runner session arguments."""
22
22
 
23
- handle_sigint: bool = field(init=False)
24
- handle_sigterm: bool = field(init=False)
25
- pipeline_idle_timeout_secs: int = field(init=False)
23
+ # Use kw_only so subclasses don't need to worry about ordering.
24
+ handle_sigint: bool = field(init=False, kw_only=True)
25
+ handle_sigterm: bool = field(init=False, kw_only=True)
26
+ pipeline_idle_timeout_secs: int = field(init=False, kw_only=True)
27
+ body: Optional[Any] = field(default_factory=dict, kw_only=True)
26
28
 
27
29
  def __post_init__(self):
28
30
  self.handle_sigint = False
@@ -42,7 +44,6 @@ class DailyRunnerArguments(RunnerArguments):
42
44
 
43
45
  room_url: str
44
46
  token: Optional[str] = None
45
- body: Optional[Any] = field(default_factory=dict)
46
47
 
47
48
 
48
49
  @dataclass
@@ -55,7 +56,6 @@ class WebSocketRunnerArguments(RunnerArguments):
55
56
  """
56
57
 
57
58
  websocket: WebSocket
58
- body: Optional[Any] = field(default_factory=dict)
59
59
 
60
60
 
61
61
  @dataclass
@@ -108,6 +108,8 @@ class AssemblyAIConnectionParams(BaseModel):
108
108
  end_of_turn_confidence_threshold: Confidence threshold for end-of-turn detection.
109
109
  min_end_of_turn_silence_when_confident: Minimum silence duration when confident about end-of-turn.
110
110
  max_turn_silence: Maximum silence duration before forcing end-of-turn.
111
+ keyterms_prompt: List of key terms to guide transcription. Will be JSON serialized before sending.
112
+ speech_model: Select between English and multilingual models. Defaults to "universal-streaming-english".
111
113
  """
112
114
 
113
115
  sample_rate: int = 16000
@@ -117,3 +119,7 @@ class AssemblyAIConnectionParams(BaseModel):
117
119
  end_of_turn_confidence_threshold: Optional[float] = None
118
120
  min_end_of_turn_silence_when_confident: Optional[int] = None
119
121
  max_turn_silence: Optional[int] = None
122
+ keyterms_prompt: Optional[List[str]] = None
123
+ speech_model: Literal["universal-streaming-english", "universal-streaming-multilingual"] = (
124
+ "universal-streaming-english"
125
+ )
@@ -174,11 +174,16 @@ class AssemblyAISTTService(STTService):
174
174
 
175
175
  def _build_ws_url(self) -> str:
176
176
  """Build WebSocket URL with query parameters using urllib.parse.urlencode."""
177
- params = {
178
- k: str(v).lower() if isinstance(v, bool) else v
179
- for k, v in self._connection_params.model_dump().items()
180
- if v is not None
181
- }
177
+ params = {}
178
+ for k, v in self._connection_params.model_dump().items():
179
+ if v is not None:
180
+ if k == "keyterms_prompt":
181
+ params[k] = json.dumps(v)
182
+ elif isinstance(v, bool):
183
+ params[k] = str(v).lower()
184
+ else:
185
+ params[k] = v
186
+
182
187
  if params:
183
188
  query_string = urlencode(params)
184
189
  return f"{self._api_endpoint_base_url}?{query_string}"
@@ -197,6 +202,8 @@ class AssemblyAISTTService(STTService):
197
202
  )
198
203
  self._connected = True
199
204
  self._receive_task = self.create_task(self._receive_task_handler())
205
+
206
+ await self._call_event_handler("on_connected")
200
207
  except Exception as e:
201
208
  logger.error(f"Failed to connect to AssemblyAI: {e}")
202
209
  self._connected = False
@@ -238,6 +245,7 @@ class AssemblyAISTTService(STTService):
238
245
  self._websocket = None
239
246
  self._connected = False
240
247
  self._receive_task = None
248
+ await self._call_event_handler("on_disconnected")
241
249
 
242
250
  async def _receive_task_handler(self):
243
251
  """Handle incoming WebSocket messages."""
@@ -235,6 +235,8 @@ class AsyncAITTSService(InterruptibleTTSService):
235
235
  }
236
236
 
237
237
  await self._get_websocket().send(json.dumps(init_msg))
238
+
239
+ await self._call_event_handler("on_connected")
238
240
  except Exception as e:
239
241
  logger.error(f"{self} initialization error: {e}")
240
242
  self._websocket = None
@@ -252,6 +254,7 @@ class AsyncAITTSService(InterruptibleTTSService):
252
254
  finally:
253
255
  self._websocket = None
254
256
  self._started = False
257
+ await self._call_event_handler("on_disconnected")
255
258
 
256
259
  def _get_websocket(self):
257
260
  if self._websocket:
@@ -720,11 +720,11 @@ class AWSBedrockLLMService(LLMService):
720
720
  additional_model_request_fields: Additional model-specific parameters.
721
721
  """
722
722
 
723
- max_tokens: Optional[int] = Field(default_factory=lambda: 4096, ge=1)
724
- temperature: Optional[float] = Field(default_factory=lambda: 0.7, ge=0.0, le=1.0)
725
- top_p: Optional[float] = Field(default_factory=lambda: 0.999, ge=0.0, le=1.0)
723
+ max_tokens: Optional[int] = Field(default=None, ge=1)
724
+ temperature: Optional[float] = Field(default=None, ge=0.0, le=1.0)
725
+ top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0)
726
726
  stop_sequences: Optional[List[str]] = Field(default_factory=lambda: [])
727
- latency: Optional[str] = Field(default_factory=lambda: "standard")
727
+ latency: Optional[str] = Field(default=None)
728
728
  additional_model_request_fields: Optional[Dict[str, Any]] = Field(default_factory=dict)
729
729
 
730
730
  def __init__(
@@ -801,6 +801,24 @@ class AWSBedrockLLMService(LLMService):
801
801
  """
802
802
  return True
803
803
 
804
+ def _build_inference_config(self) -> Dict[str, Any]:
805
+ """Build inference config with only the parameters that are set.
806
+
807
+ This prevents conflicts with models (e.g., Claude Sonnet 4.5) that don't
808
+ allow certain parameter combinations like temperature and top_p together.
809
+
810
+ Returns:
811
+ Dictionary containing only the inference parameters that are not None.
812
+ """
813
+ inference_config = {}
814
+ if self._settings["max_tokens"] is not None:
815
+ inference_config["maxTokens"] = self._settings["max_tokens"]
816
+ if self._settings["temperature"] is not None:
817
+ inference_config["temperature"] = self._settings["temperature"]
818
+ if self._settings["top_p"] is not None:
819
+ inference_config["topP"] = self._settings["top_p"]
820
+ return inference_config
821
+
804
822
  async def run_inference(self, context: LLMContext | OpenAILLMContext) -> Optional[str]:
805
823
  """Run a one-shot, out-of-band (i.e. out-of-pipeline) inference with the given LLM context.
806
824
 
@@ -826,16 +844,16 @@ class AWSBedrockLLMService(LLMService):
826
844
  model_id = self.model_name
827
845
 
828
846
  # Prepare request parameters
847
+ inference_config = self._build_inference_config()
848
+
829
849
  request_params = {
830
850
  "modelId": model_id,
831
851
  "messages": messages,
832
- "inferenceConfig": {
833
- "maxTokens": 8192,
834
- "temperature": 0.7,
835
- "topP": 0.9,
836
- },
837
852
  }
838
853
 
854
+ if inference_config:
855
+ request_params["inferenceConfig"] = inference_config
856
+
839
857
  if system:
840
858
  request_params["system"] = system
841
859
 
@@ -974,21 +992,20 @@ class AWSBedrockLLMService(LLMService):
974
992
  tools = params_from_context["tools"]
975
993
  tool_choice = params_from_context["tool_choice"]
976
994
 
977
- # Set up inference config
978
- inference_config = {
979
- "maxTokens": self._settings["max_tokens"],
980
- "temperature": self._settings["temperature"],
981
- "topP": self._settings["top_p"],
982
- }
995
+ # Set up inference config - only include parameters that are set
996
+ inference_config = self._build_inference_config()
983
997
 
984
998
  # Prepare request parameters
985
999
  request_params = {
986
1000
  "modelId": self.model_name,
987
1001
  "messages": messages,
988
- "inferenceConfig": inference_config,
989
1002
  "additionalModelRequestFields": self._settings["additional_model_request_fields"],
990
1003
  }
991
1004
 
1005
+ # Only add inference config if it has parameters
1006
+ if inference_config:
1007
+ request_params["inferenceConfig"] = inference_config
1008
+
992
1009
  # Add system message
993
1010
  if system:
994
1011
  request_params["system"] = system
@@ -8,8 +8,77 @@
8
8
 
9
9
  This module provides specialized context aggregators and message handling for AWS Nova Sonic,
10
10
  including conversation history management and role-specific message processing.
11
+
12
+ .. deprecated:: 0.0.91
13
+ AWS Nova Sonic no longer uses types from this module under the hood.
14
+ It now uses `LLMContext` and `LLMContextAggregatorPair`.
15
+ Using the new patterns should allow you to not need types from this module.
16
+
17
+ BEFORE:
18
+ ```
19
+ # Setup
20
+ context = OpenAILLMContext(messages, tools)
21
+ context_aggregator = llm.create_context_aggregator(context)
22
+
23
+ # Context frame type
24
+ frame: OpenAILLMContextFrame
25
+
26
+ # Context type
27
+ context: AWSNovaSonicLLMContext
28
+ # or
29
+ context: OpenAILLMContext
30
+ ```
31
+
32
+ AFTER:
33
+ ```
34
+ # Setup
35
+ context = LLMContext(messages, tools)
36
+ context_aggregator = LLMContextAggregatorPair(context)
37
+
38
+ # Context frame type
39
+ frame: LLMContextFrame
40
+
41
+ # Context type
42
+ context: LLMContext
43
+ ```
11
44
  """
12
45
 
46
+ import warnings
47
+
48
+ with warnings.catch_warnings():
49
+ warnings.simplefilter("always")
50
+ warnings.warn(
51
+ "Types in pipecat.services.aws.nova_sonic.context (or "
52
+ "pipecat.services.aws_nova_sonic.context) are deprecated. \n"
53
+ "AWS Nova Sonic no longer uses types from this module under the hood. \n"
54
+ "It now uses `LLMContext` and `LLMContextAggregatorPair`. \n"
55
+ "Using the new patterns should allow you to not need types from this module.\n\n"
56
+ "BEFORE:\n"
57
+ "```\n"
58
+ "# Setup\n"
59
+ "context = OpenAILLMContext(messages, tools)\n"
60
+ "context_aggregator = llm.create_context_aggregator(context)\n\n"
61
+ "# Context frame type\n"
62
+ "frame: OpenAILLMContextFrame\n\n"
63
+ "# Context type\n"
64
+ "context: AWSNovaSonicLLMContext\n"
65
+ "# or\n"
66
+ "context: OpenAILLMContext\n\n"
67
+ "```\n\n"
68
+ "AFTER:\n"
69
+ "```\n"
70
+ "# Setup\n"
71
+ "context = LLMContext(messages, tools)\n"
72
+ "context_aggregator = LLMContextAggregatorPair(context)\n\n"
73
+ "# Context frame type\n"
74
+ "frame: LLMContextFrame\n\n"
75
+ "# Context type\n"
76
+ "context: LLMContext\n\n"
77
+ "```",
78
+ DeprecationWarning,
79
+ stacklevel=2,
80
+ )
81
+
13
82
  import copy
14
83
  from dataclasses import dataclass, field
15
84
  from enum import Enum