dv-pipecat-ai 0.0.82.dev815__py3-none-any.whl → 0.0.82.dev857__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 (106) hide show
  1. {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/METADATA +8 -3
  2. {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/RECORD +106 -79
  3. pipecat/adapters/base_llm_adapter.py +44 -6
  4. pipecat/adapters/services/anthropic_adapter.py +302 -2
  5. pipecat/adapters/services/aws_nova_sonic_adapter.py +40 -2
  6. pipecat/adapters/services/bedrock_adapter.py +40 -2
  7. pipecat/adapters/services/gemini_adapter.py +276 -6
  8. pipecat/adapters/services/open_ai_adapter.py +88 -7
  9. pipecat/adapters/services/open_ai_realtime_adapter.py +39 -1
  10. pipecat/audio/dtmf/__init__.py +0 -0
  11. pipecat/audio/dtmf/types.py +47 -0
  12. pipecat/audio/dtmf/utils.py +70 -0
  13. pipecat/audio/filters/aic_filter.py +199 -0
  14. pipecat/audio/utils.py +9 -7
  15. pipecat/extensions/ivr/__init__.py +0 -0
  16. pipecat/extensions/ivr/ivr_navigator.py +452 -0
  17. pipecat/frames/frames.py +156 -43
  18. pipecat/pipeline/llm_switcher.py +76 -0
  19. pipecat/pipeline/parallel_pipeline.py +3 -3
  20. pipecat/pipeline/service_switcher.py +144 -0
  21. pipecat/pipeline/task.py +68 -28
  22. pipecat/pipeline/task_observer.py +10 -0
  23. pipecat/processors/aggregators/dtmf_aggregator.py +2 -2
  24. pipecat/processors/aggregators/llm_context.py +277 -0
  25. pipecat/processors/aggregators/llm_response.py +48 -15
  26. pipecat/processors/aggregators/llm_response_universal.py +840 -0
  27. pipecat/processors/aggregators/openai_llm_context.py +3 -3
  28. pipecat/processors/dtmf_aggregator.py +0 -2
  29. pipecat/processors/filters/stt_mute_filter.py +0 -2
  30. pipecat/processors/frame_processor.py +18 -11
  31. pipecat/processors/frameworks/rtvi.py +17 -10
  32. pipecat/processors/metrics/sentry.py +2 -0
  33. pipecat/runner/daily.py +137 -36
  34. pipecat/runner/run.py +1 -1
  35. pipecat/runner/utils.py +7 -7
  36. pipecat/serializers/asterisk.py +20 -4
  37. pipecat/serializers/exotel.py +1 -1
  38. pipecat/serializers/plivo.py +1 -1
  39. pipecat/serializers/telnyx.py +1 -1
  40. pipecat/serializers/twilio.py +1 -1
  41. pipecat/services/__init__.py +2 -2
  42. pipecat/services/anthropic/llm.py +113 -28
  43. pipecat/services/asyncai/tts.py +4 -0
  44. pipecat/services/aws/llm.py +82 -8
  45. pipecat/services/aws/tts.py +0 -10
  46. pipecat/services/aws_nova_sonic/aws.py +5 -0
  47. pipecat/services/cartesia/tts.py +28 -16
  48. pipecat/services/cerebras/llm.py +15 -10
  49. pipecat/services/deepgram/stt.py +8 -0
  50. pipecat/services/deepseek/llm.py +13 -8
  51. pipecat/services/fireworks/llm.py +13 -8
  52. pipecat/services/fish/tts.py +8 -6
  53. pipecat/services/gemini_multimodal_live/gemini.py +5 -0
  54. pipecat/services/gladia/config.py +7 -1
  55. pipecat/services/gladia/stt.py +23 -15
  56. pipecat/services/google/llm.py +159 -59
  57. pipecat/services/google/llm_openai.py +18 -3
  58. pipecat/services/grok/llm.py +2 -1
  59. pipecat/services/llm_service.py +38 -3
  60. pipecat/services/mem0/memory.py +2 -1
  61. pipecat/services/mistral/llm.py +5 -6
  62. pipecat/services/nim/llm.py +2 -1
  63. pipecat/services/openai/base_llm.py +88 -26
  64. pipecat/services/openai/image.py +6 -1
  65. pipecat/services/openai_realtime_beta/openai.py +5 -2
  66. pipecat/services/openpipe/llm.py +6 -8
  67. pipecat/services/perplexity/llm.py +13 -8
  68. pipecat/services/playht/tts.py +9 -6
  69. pipecat/services/rime/tts.py +1 -1
  70. pipecat/services/sambanova/llm.py +18 -13
  71. pipecat/services/sarvam/tts.py +415 -10
  72. pipecat/services/speechmatics/stt.py +2 -2
  73. pipecat/services/tavus/video.py +1 -1
  74. pipecat/services/tts_service.py +15 -5
  75. pipecat/services/vistaar/llm.py +2 -5
  76. pipecat/transports/base_input.py +32 -19
  77. pipecat/transports/base_output.py +39 -5
  78. pipecat/transports/daily/__init__.py +0 -0
  79. pipecat/transports/daily/transport.py +2371 -0
  80. pipecat/transports/daily/utils.py +410 -0
  81. pipecat/transports/livekit/__init__.py +0 -0
  82. pipecat/transports/livekit/transport.py +1042 -0
  83. pipecat/transports/network/fastapi_websocket.py +12 -546
  84. pipecat/transports/network/small_webrtc.py +12 -922
  85. pipecat/transports/network/webrtc_connection.py +9 -595
  86. pipecat/transports/network/websocket_client.py +12 -481
  87. pipecat/transports/network/websocket_server.py +12 -487
  88. pipecat/transports/services/daily.py +9 -2334
  89. pipecat/transports/services/helpers/daily_rest.py +12 -396
  90. pipecat/transports/services/livekit.py +12 -975
  91. pipecat/transports/services/tavus.py +12 -757
  92. pipecat/transports/smallwebrtc/__init__.py +0 -0
  93. pipecat/transports/smallwebrtc/connection.py +612 -0
  94. pipecat/transports/smallwebrtc/transport.py +936 -0
  95. pipecat/transports/tavus/__init__.py +0 -0
  96. pipecat/transports/tavus/transport.py +770 -0
  97. pipecat/transports/websocket/__init__.py +0 -0
  98. pipecat/transports/websocket/client.py +494 -0
  99. pipecat/transports/websocket/fastapi.py +559 -0
  100. pipecat/transports/websocket/server.py +500 -0
  101. pipecat/transports/whatsapp/__init__.py +0 -0
  102. pipecat/transports/whatsapp/api.py +345 -0
  103. pipecat/transports/whatsapp/client.py +364 -0
  104. {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/WHEEL +0 -0
  105. {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/licenses/LICENSE +0 -0
  106. {dv_pipecat_ai-0.0.82.dev815.dist-info → dv_pipecat_ai-0.0.82.dev857.dist-info}/top_level.txt +0 -0
@@ -36,10 +36,15 @@ from pipecat.frames.frames import (
36
36
  FunctionCallResultFrame,
37
37
  FunctionCallResultProperties,
38
38
  FunctionCallsStartedFrame,
39
+ LLMConfigureOutputFrame,
40
+ LLMFullResponseEndFrame,
41
+ LLMFullResponseStartFrame,
42
+ LLMTextFrame,
39
43
  StartFrame,
40
44
  StartInterruptionFrame,
41
45
  UserImageRequestFrame,
42
46
  )
47
+ from pipecat.processors.aggregators.llm_context import LLMContext
43
48
  from pipecat.processors.aggregators.llm_response import (
44
49
  LLMAssistantAggregatorParams,
45
50
  LLMUserAggregatorParams,
@@ -88,7 +93,7 @@ class FunctionCallParams:
88
93
  tool_call_id: str
89
94
  arguments: Mapping[str, Any]
90
95
  llm: "LLMService"
91
- context: OpenAILLMContext
96
+ context: OpenAILLMContext | LLMContext
92
97
  result_callback: FunctionCallResultCallback
93
98
 
94
99
 
@@ -129,7 +134,7 @@ class FunctionCallRunnerItem:
129
134
  function_name: str
130
135
  tool_call_id: str
131
136
  arguments: Mapping[str, Any]
132
- context: OpenAILLMContext
137
+ context: OpenAILLMContext | LLMContext
133
138
  run_llm: Optional[bool] = None
134
139
 
135
140
 
@@ -177,6 +182,7 @@ class LLMService(AIService):
177
182
  self._function_call_tasks: Dict[asyncio.Task, FunctionCallRunnerItem] = {}
178
183
  self._sequential_runner_task: Optional[asyncio.Task] = None
179
184
  self._tracing_enabled: bool = False
185
+ self._skip_tts: bool = False
180
186
 
181
187
  self._register_event_handler("on_function_calls_started")
182
188
  self._register_event_handler("on_completion_timeout")
@@ -189,6 +195,19 @@ class LLMService(AIService):
189
195
  """
190
196
  return self._adapter
191
197
 
198
+ async def run_inference(self, context: LLMContext | OpenAILLMContext) -> Optional[str]:
199
+ """Run a one-shot, out-of-band (i.e. out-of-pipeline) inference with the given LLM context.
200
+
201
+ Must be implemented by subclasses.
202
+
203
+ Args:
204
+ context: The LLM context containing conversation history.
205
+
206
+ Returns:
207
+ The LLM's response as a string, or None if no response is generated.
208
+ """
209
+ raise NotImplementedError(f"run_inference() not supported by {self.__class__.__name__}")
210
+
192
211
  def create_context_aggregator(
193
212
  self,
194
213
  context: OpenAILLMContext,
@@ -252,6 +271,20 @@ class LLMService(AIService):
252
271
 
253
272
  if isinstance(frame, StartInterruptionFrame):
254
273
  await self._handle_interruptions(frame)
274
+ elif isinstance(frame, LLMConfigureOutputFrame):
275
+ self._skip_tts = frame.skip_tts
276
+
277
+ async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
278
+ """Pushes a frame.
279
+
280
+ Args:
281
+ frame: The frame to push.
282
+ direction: The direction of frame pushing.
283
+ """
284
+ if isinstance(frame, (LLMTextFrame, LLMFullResponseStartFrame, LLMFullResponseEndFrame)):
285
+ frame.skip_tts = self._skip_tts
286
+
287
+ await super().push_frame(frame, direction)
255
288
 
256
289
  async def _handle_interruptions(self, _: StartInterruptionFrame):
257
290
  # logger.info("In LLM Handling interruptions")
@@ -434,7 +467,9 @@ class LLMService(AIService):
434
467
  else:
435
468
  await self._sequential_runner_queue.put(runner_item)
436
469
 
437
- async def _call_start_function(self, context: OpenAILLMContext, function_name: str):
470
+ async def _call_start_function(
471
+ self, context: OpenAILLMContext | LLMContext, function_name: str
472
+ ):
438
473
  if function_name in self._start_callbacks.keys():
439
474
  await self._start_callbacks[function_name](function_name, self, context)
440
475
  elif None in self._start_callbacks.keys():
@@ -120,6 +120,7 @@ class Mem0MemoryService(FrameProcessor):
120
120
  try:
121
121
  logger.debug(f"Storing {len(messages)} messages in Mem0")
122
122
  params = {
123
+ "async_mode": True,
123
124
  "messages": messages,
124
125
  "metadata": {"platform": "pipecat"},
125
126
  "output_format": "v1.1",
@@ -163,7 +164,7 @@ class Mem0MemoryService(FrameProcessor):
163
164
  ("run_id", self.run_id),
164
165
  ]
165
166
  clauses = [{name: value} for name, value in id_pairs if value is not None]
166
- filters = {"AND": clauses} if clauses else {}
167
+ filters = {"OR": clauses} if clauses else {}
167
168
  results = self.memory_client.search(
168
169
  query=query,
169
170
  filters=filters,
@@ -12,6 +12,7 @@ from loguru import logger
12
12
  from openai import AsyncStream
13
13
  from openai.types.chat import ChatCompletionChunk, ChatCompletionMessageParam
14
14
 
15
+ from pipecat.adapters.services.open_ai_adapter import OpenAILLMInvocationParams
15
16
  from pipecat.frames.frames import FunctionCallFromLLM
16
17
  from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
17
18
  from pipecat.services.openai.llm import OpenAILLMService
@@ -148,9 +149,7 @@ class MistralLLMService(OpenAILLMService):
148
149
  if calls_to_execute:
149
150
  await super().run_function_calls(calls_to_execute)
150
151
 
151
- def build_chat_completion_params(
152
- self, context: OpenAILLMContext, messages: List[ChatCompletionMessageParam]
153
- ) -> dict:
152
+ def build_chat_completion_params(self, params_from_context: OpenAILLMInvocationParams) -> dict:
154
153
  """Build parameters for Mistral chat completion request.
155
154
 
156
155
  Handles Mistral-specific requirements including:
@@ -159,14 +158,14 @@ class MistralLLMService(OpenAILLMService):
159
158
  - Core completion settings
160
159
  """
161
160
  # Apply Mistral's assistant prefix requirement for API compatibility
162
- fixed_messages = self._apply_mistral_assistant_prefix(messages)
161
+ fixed_messages = self._apply_mistral_assistant_prefix(params_from_context["messages"])
163
162
 
164
163
  params = {
165
164
  "model": self.model_name,
166
165
  "stream": True,
167
166
  "messages": fixed_messages,
168
- "tools": context.tools,
169
- "tool_choice": context.tool_choice,
167
+ "tools": params_from_context["tools"],
168
+ "tool_choice": params_from_context["tool_choice"],
170
169
  "frequency_penalty": self._settings["frequency_penalty"],
171
170
  "presence_penalty": self._settings["presence_penalty"],
172
171
  "temperature": self._settings["temperature"],
@@ -11,6 +11,7 @@ Microservice) API while maintaining compatibility with the OpenAI-style interfac
11
11
  """
12
12
 
13
13
  from pipecat.metrics.metrics import LLMTokenUsage
14
+ from pipecat.processors.aggregators.llm_context import LLMContext
14
15
  from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
15
16
  from pipecat.services.openai.llm import OpenAILLMService
16
17
 
@@ -47,7 +48,7 @@ class NimLLMService(OpenAILLMService):
47
48
  self._has_reported_prompt_tokens = False
48
49
  self._is_processing = False
49
50
 
50
- async def _process_context(self, context: OpenAILLMContext):
51
+ async def _process_context(self, context: OpenAILLMContext | LLMContext):
51
52
  """Process a context through the LLM and accumulate token usage metrics.
52
53
 
53
54
  This method overrides the parent class implementation to handle NVIDIA's
@@ -4,7 +4,7 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
- """Base OpenAI LLM service implementation."""
7
+ """Base LLM service implementation for services that use the AsyncOpenAI client."""
8
8
 
9
9
  import asyncio
10
10
  import base64
@@ -23,8 +23,10 @@ from openai import (
23
23
  from openai.types.chat import ChatCompletionChunk, ChatCompletionMessageParam
24
24
  from pydantic import BaseModel, Field
25
25
 
26
+ from pipecat.adapters.services.open_ai_adapter import OpenAILLMInvocationParams
26
27
  from pipecat.frames.frames import (
27
28
  Frame,
29
+ LLMContextFrame,
28
30
  LLMFullResponseEndFrame,
29
31
  LLMFullResponseStartFrame,
30
32
  LLMMessagesFrame,
@@ -33,6 +35,7 @@ from pipecat.frames.frames import (
33
35
  VisionImageRawFrame,
34
36
  )
35
37
  from pipecat.metrics.metrics import LLMTokenUsage
38
+ from pipecat.processors.aggregators.llm_context import LLMContext
36
39
  from pipecat.processors.aggregators.openai_llm_context import (
37
40
  OpenAILLMContext,
38
41
  OpenAILLMContextFrame,
@@ -45,10 +48,11 @@ from pipecat.utils.tracing.service_decorators import traced_llm
45
48
  class BaseOpenAILLMService(LLMService):
46
49
  """Base class for all services that use the AsyncOpenAI client.
47
50
 
48
- This service consumes OpenAILLMContextFrame frames, which contain a reference
49
- to an OpenAILLMContext object. The context defines what is sent to the LLM for
50
- completion, including user, assistant, and system messages, as well as tool
51
- choices and function call configurations.
51
+ This service consumes OpenAILLMContextFrame or LLMContextFrame frames,
52
+ which contain a reference to an OpenAILLMContext or LLMContext object. The
53
+ context defines what is sent to the LLM for completion, including user,
54
+ assistant, and system messages, as well as tool choices and function call
55
+ configurations.
52
56
  """
53
57
 
54
58
  class InputParams(BaseModel):
@@ -180,18 +184,19 @@ class BaseOpenAILLMService(LLMService):
180
184
  return True
181
185
 
182
186
  async def get_chat_completions(
183
- self, context: OpenAILLMContext, messages: List[ChatCompletionMessageParam]
187
+ self, params_from_context: OpenAILLMInvocationParams
184
188
  ) -> AsyncStream[ChatCompletionChunk]:
185
189
  """Get streaming chat completions from OpenAI API with optional timeout and retry.
186
190
 
187
191
  Args:
188
- context: The LLM context containing tools and configuration.
189
- messages: List of chat completion messages to send.
192
+ params_from_context: Parameters, derived from the LLM context, to
193
+ use for the chat completion. Contains messages, tools, and tool
194
+ choice.
190
195
 
191
196
  Returns:
192
197
  Async stream of chat completion chunks.
193
198
  """
194
- params = self.build_chat_completion_params(context, messages)
199
+ params = self.build_chat_completion_params(params_from_context)
195
200
 
196
201
  if self._retry_on_timeout:
197
202
  try:
@@ -208,16 +213,15 @@ class BaseOpenAILLMService(LLMService):
208
213
  chunks = await self._client.chat.completions.create(**params)
209
214
  return chunks
210
215
 
211
- def build_chat_completion_params(
212
- self, context: OpenAILLMContext, messages: List[ChatCompletionMessageParam]
213
- ) -> dict:
216
+ def build_chat_completion_params(self, params_from_context: OpenAILLMInvocationParams) -> dict:
214
217
  """Build parameters for chat completion request.
215
218
 
216
219
  Subclasses can override this to customize parameters for different providers.
217
220
 
218
221
  Args:
219
- context: The LLM context containing tools and configuration.
220
- messages: List of chat completion messages to send.
222
+ params_from_context: Parameters, derived from the LLM context, to
223
+ use for the chat completion. Contains messages, tools, and tool
224
+ choice.
221
225
 
222
226
  Returns:
223
227
  Dictionary of parameters for the chat completion request.
@@ -225,9 +229,6 @@ class BaseOpenAILLMService(LLMService):
225
229
  params = {
226
230
  "model": self.model_name,
227
231
  "stream": True,
228
- "messages": messages,
229
- "tools": context.tools,
230
- "tool_choice": context.tool_choice,
231
232
  "stream_options": {"include_usage": True},
232
233
  "frequency_penalty": self._settings["frequency_penalty"],
233
234
  "presence_penalty": self._settings["presence_penalty"],
@@ -238,13 +239,43 @@ class BaseOpenAILLMService(LLMService):
238
239
  "max_completion_tokens": self._settings["max_completion_tokens"],
239
240
  }
240
241
 
242
+ # Messages, tools, tool_choice
243
+ params.update(params_from_context)
244
+
241
245
  params.update(self._settings["extra"])
242
246
  return params
243
247
 
244
- async def _stream_chat_completions(
248
+ async def run_inference(self, context: LLMContext | OpenAILLMContext) -> Optional[str]:
249
+ """Run a one-shot, out-of-band (i.e. out-of-pipeline) inference with the given LLM context.
250
+
251
+ Args:
252
+ context: The LLM context containing conversation history.
253
+
254
+ Returns:
255
+ The LLM's response as a string, or None if no response is generated.
256
+ """
257
+ if isinstance(context, LLMContext):
258
+ adapter = self.get_llm_adapter()
259
+ params: OpenAILLMInvocationParams = adapter.get_llm_invocation_params(context)
260
+ messages = params["messages"]
261
+ else:
262
+ messages = context.messages
263
+
264
+ # LLM completion
265
+ response = await self._client.chat.completions.create(
266
+ model=self.model_name,
267
+ messages=messages,
268
+ stream=False,
269
+ )
270
+
271
+ return response.choices[0].message.content
272
+
273
+ async def _stream_chat_completions_specific_context(
245
274
  self, context: OpenAILLMContext
246
275
  ) -> AsyncStream[ChatCompletionChunk]:
247
- self.logger.debug(f"{self}: Generating chat [{context.get_messages_for_logging()}]")
276
+ self.logger.debug(
277
+ f"{self}: Generating chat from LLM-specific context {context.get_messages_for_logging()}"
278
+ )
248
279
 
249
280
  messages: List[ChatCompletionMessageParam] = context.get_messages()
250
281
 
@@ -263,12 +294,28 @@ class BaseOpenAILLMService(LLMService):
263
294
  del message["data"]
264
295
  del message["mime_type"]
265
296
 
266
- chunks = await self.get_chat_completions(context, messages)
297
+ params = OpenAILLMInvocationParams(
298
+ messages=messages, tools=context.tools, tool_choice=context.tool_choice
299
+ )
300
+ chunks = await self.get_chat_completions(params)
301
+
302
+ return chunks
303
+
304
+ async def _stream_chat_completions_universal_context(
305
+ self, context: LLMContext
306
+ ) -> AsyncStream[ChatCompletionChunk]:
307
+ adapter = self.get_llm_adapter()
308
+ logger.debug(
309
+ f"{self}: Generating chat from universal context {adapter.get_messages_for_logging(context)}"
310
+ )
311
+
312
+ params: OpenAILLMInvocationParams = adapter.get_llm_invocation_params(context)
313
+ chunks = await self.get_chat_completions(params)
267
314
 
268
315
  return chunks
269
316
 
270
317
  @traced_llm
271
- async def _process_context(self, context: OpenAILLMContext):
318
+ async def _process_context(self, context: OpenAILLMContext | LLMContext):
272
319
  functions_list = []
273
320
  arguments_list = []
274
321
  tool_id_list = []
@@ -279,8 +326,11 @@ class BaseOpenAILLMService(LLMService):
279
326
 
280
327
  await self.start_ttfb_metrics()
281
328
 
282
- chunk_stream: AsyncStream[ChatCompletionChunk] = await self._stream_chat_completions(
283
- context
329
+ # Generate chat completions using either OpenAILLMContext or universal LLMContext
330
+ chunk_stream = await (
331
+ self._stream_chat_completions_specific_context(context)
332
+ if isinstance(context, OpenAILLMContext)
333
+ else self._stream_chat_completions_universal_context(context)
284
334
  )
285
335
 
286
336
  async for chunk in chunk_stream:
@@ -367,8 +417,9 @@ class BaseOpenAILLMService(LLMService):
367
417
  async def process_frame(self, frame: Frame, direction: FrameDirection):
368
418
  """Process frames for LLM completion requests.
369
419
 
370
- Handles OpenAILLMContextFrame, LLMMessagesFrame, VisionImageRawFrame,
371
- and LLMUpdateSettingsFrame to trigger LLM completions and manage settings.
420
+ Handles OpenAILLMContextFrame, LLMContextFrame, LLMMessagesFrame,
421
+ VisionImageRawFrame, and LLMUpdateSettingsFrame to trigger LLM
422
+ completions and manage settings.
372
423
 
373
424
  Args:
374
425
  frame: The frame to process.
@@ -378,10 +429,21 @@ class BaseOpenAILLMService(LLMService):
378
429
 
379
430
  context = None
380
431
  if isinstance(frame, OpenAILLMContextFrame):
381
- context: OpenAILLMContext = frame.context
432
+ # Handle OpenAI-specific context frames
433
+ context = frame.context
434
+ elif isinstance(frame, LLMContextFrame):
435
+ # Handle universal (LLM-agnostic) LLM context frames
436
+ context = frame.context
382
437
  elif isinstance(frame, LLMMessagesFrame):
438
+ # NOTE: LLMMessagesFrame is deprecated, so we don't support the newer universal
439
+ # LLMContext with it
383
440
  context = OpenAILLMContext.from_messages(frame.messages)
384
441
  elif isinstance(frame, VisionImageRawFrame):
442
+ # This is only useful in very simple pipelines because it creates
443
+ # a new context. Generally we want a context manager to catch
444
+ # UserImageRawFrames coming through the pipeline and add them
445
+ # to the context.
446
+ # TODO: support the newer universal LLMContext with a VisionImageRawFrame equivalent?
385
447
  context = OpenAILLMContext()
386
448
  context.add_image_frame_message(
387
449
  format=frame.format, size=frame.size, image=frame.image, text=frame.text
@@ -84,5 +84,10 @@ class OpenAIImageGenService(ImageGenService):
84
84
  async with self._aiohttp_session.get(image_url) as response:
85
85
  image_stream = io.BytesIO(await response.content.read())
86
86
  image = Image.open(image_stream)
87
- frame = URLImageRawFrame(image_url, image.tobytes(), image.size, image.format)
87
+ frame = URLImageRawFrame(
88
+ image=image.tobytes(),
89
+ size=image.size,
90
+ format=image.format,
91
+ url=image_url,
92
+ )
88
93
  yield frame
@@ -23,6 +23,7 @@ from pipecat.frames.frames import (
23
23
  Frame,
24
24
  InputAudioRawFrame,
25
25
  InterimTranscriptionFrame,
26
+ LLMContextFrame,
26
27
  LLMFullResponseEndFrame,
27
28
  LLMFullResponseStartFrame,
28
29
  LLMMessagesAppendFrame,
@@ -31,7 +32,6 @@ from pipecat.frames.frames import (
31
32
  LLMUpdateSettingsFrame,
32
33
  StartFrame,
33
34
  StartInterruptionFrame,
34
- StopInterruptionFrame,
35
35
  TranscriptionFrame,
36
36
  TTSAudioRawFrame,
37
37
  TTSStartedFrame,
@@ -343,6 +343,10 @@ class OpenAIRealtimeBetaLLMService(LLMService):
343
343
  await self.reset_conversation()
344
344
  # Run the LLM at next opportunity
345
345
  await self._create_response()
346
+ elif isinstance(frame, LLMContextFrame):
347
+ raise NotImplementedError(
348
+ "Universal LLMContext is not yet supported for OpenAI Realtime."
349
+ )
346
350
  elif isinstance(frame, InputAudioRawFrame):
347
351
  if not self._audio_input_paused:
348
352
  await self._send_user_audio(frame)
@@ -648,7 +652,6 @@ class OpenAIRealtimeBetaLLMService(LLMService):
648
652
  await self.start_ttfb_metrics()
649
653
  await self.start_processing_metrics()
650
654
  await self._stop_interruption()
651
- await self.push_frame(StopInterruptionFrame())
652
655
  await self.push_frame(UserStoppedSpeakingFrame())
653
656
 
654
657
  async def _maybe_handle_evt_retrieve_conversation_item_error(self, evt: events.ErrorEvent):
@@ -13,9 +13,8 @@ enabling integration with OpenPipe's fine-tuning and monitoring capabilities.
13
13
  from typing import Dict, List, Optional
14
14
 
15
15
  from loguru import logger
16
- from openai.types.chat import ChatCompletionMessageParam
17
16
 
18
- from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
17
+ from pipecat.adapters.services.open_ai_adapter import OpenAILLMInvocationParams
19
18
  from pipecat.services.openai.llm import OpenAILLMService
20
19
 
21
20
  try:
@@ -86,22 +85,21 @@ class OpenPipeLLMService(OpenAILLMService):
86
85
  )
87
86
  return client
88
87
 
89
- def build_chat_completion_params(
90
- self, context: OpenAILLMContext, messages: List[ChatCompletionMessageParam]
91
- ) -> dict:
88
+ def build_chat_completion_params(self, params_from_context: OpenAILLMInvocationParams) -> dict:
92
89
  """Build parameters for OpenPipe chat completion request.
93
90
 
94
91
  Adds OpenPipe-specific logging and tagging parameters.
95
92
 
96
93
  Args:
97
- context: The LLM context containing tools and configuration.
98
- messages: List of chat completion messages to send.
94
+ params_from_context: Parameters, derived from the LLM context, to
95
+ use for the chat completion. Contains messages, tools, and tool
96
+ choice.
99
97
 
100
98
  Returns:
101
99
  Dictionary of parameters for the chat completion request.
102
100
  """
103
101
  # Start with base parameters
104
- params = super().build_chat_completion_params(context, messages)
102
+ params = super().build_chat_completion_params(params_from_context)
105
103
 
106
104
  # Add OpenPipe-specific parameters
107
105
  params["openpipe"] = {
@@ -11,12 +11,11 @@ an OpenAI-compatible interface. It handles Perplexity's unique token usage
11
11
  reporting patterns while maintaining compatibility with the Pipecat framework.
12
12
  """
13
13
 
14
- from typing import List
15
-
16
14
  from openai import NOT_GIVEN
17
- from openai.types.chat import ChatCompletionMessageParam
18
15
 
16
+ from pipecat.adapters.services.open_ai_adapter import OpenAILLMInvocationParams
19
17
  from pipecat.metrics.metrics import LLMTokenUsage
18
+ from pipecat.processors.aggregators.llm_context import LLMContext
20
19
  from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
21
20
  from pipecat.services.openai.llm import OpenAILLMService
22
21
 
@@ -53,17 +52,23 @@ class PerplexityLLMService(OpenAILLMService):
53
52
  self._has_reported_prompt_tokens = False
54
53
  self._is_processing = False
55
54
 
56
- def build_chat_completion_params(
57
- self, context: OpenAILLMContext, messages: List[ChatCompletionMessageParam]
58
- ) -> dict:
55
+ def build_chat_completion_params(self, params_from_context: OpenAILLMInvocationParams) -> dict:
59
56
  """Build parameters for Perplexity chat completion request.
60
57
 
61
58
  Perplexity uses a subset of OpenAI parameters and doesn't support tools.
59
+
60
+ Args:
61
+ params_from_context: Parameters, derived from the LLM context, to
62
+ use for the chat completion. Contains messages, tools, and tool
63
+ choice.
64
+
65
+ Returns:
66
+ Dictionary of parameters for the chat completion request.
62
67
  """
63
68
  params = {
64
69
  "model": self.model_name,
65
70
  "stream": True,
66
- "messages": messages,
71
+ "messages": params_from_context["messages"],
67
72
  }
68
73
 
69
74
  # Add OpenAI-compatible parameters if they're set
@@ -80,7 +85,7 @@ class PerplexityLLMService(OpenAILLMService):
80
85
 
81
86
  return params
82
87
 
83
- async def _process_context(self, context: OpenAILLMContext):
88
+ async def _process_context(self, context: OpenAILLMContext | LLMContext):
84
89
  """Process a context through the LLM and accumulate token usage metrics.
85
90
 
86
91
  This method overrides the parent class implementation to handle
@@ -14,7 +14,6 @@ import io
14
14
  import json
15
15
  import struct
16
16
  import uuid
17
- import warnings
18
17
  from typing import AsyncGenerator, Optional
19
18
 
20
19
  import aiohttp
@@ -455,11 +454,15 @@ class PlayHTHttpTTSService(TTSService):
455
454
 
456
455
  # Warn about deprecated protocol parameter if explicitly provided
457
456
  if protocol:
458
- warnings.warn(
459
- "The 'protocol' parameter is deprecated and will be removed in a future version.",
460
- DeprecationWarning,
461
- stacklevel=2,
462
- )
457
+ import warnings
458
+
459
+ with warnings.catch_warnings():
460
+ warnings.simplefilter("always")
461
+ warnings.warn(
462
+ "The 'protocol' parameter is deprecated and will be removed in a future version.",
463
+ DeprecationWarning,
464
+ stacklevel=2,
465
+ )
463
466
 
464
467
  params = params or PlayHTHttpTTSService.InputParams()
465
468
 
@@ -323,7 +323,7 @@ class RimeTTSService(AudioContextWordTTSService):
323
323
  return
324
324
 
325
325
  logger.trace(f"{self}: flushing audio")
326
- await self._get_websocket().send(json.dumps({"text": " "}))
326
+ await self._get_websocket().send(json.dumps({"operation": "flush"}))
327
327
  self._context_id = None
328
328
 
329
329
  async def _receive_messages(self):
@@ -7,16 +7,18 @@
7
7
  """SambaNova LLM service implementation using OpenAI-compatible interface."""
8
8
 
9
9
  import json
10
- from typing import Any, Dict, List, Optional
10
+ from typing import Any, Dict, Optional
11
11
 
12
12
  from loguru import logger
13
13
  from openai import AsyncStream
14
- from openai.types.chat import ChatCompletionChunk, ChatCompletionMessageParam
14
+ from openai.types.chat import ChatCompletionChunk
15
15
 
16
+ from pipecat.adapters.services.open_ai_adapter import OpenAILLMInvocationParams
16
17
  from pipecat.frames.frames import (
17
18
  LLMTextFrame,
18
19
  )
19
20
  from pipecat.metrics.metrics import LLMTokenUsage
21
+ from pipecat.processors.aggregators.llm_context import LLMContext
20
22
  from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
21
23
  from pipecat.services.llm_service import FunctionCallFromLLM
22
24
  from pipecat.services.openai.llm import OpenAILLMService
@@ -67,17 +69,16 @@ class SambaNovaLLMService(OpenAILLMService): # type: ignore
67
69
  logger.debug(f"Creating SambaNova client with API {base_url}")
68
70
  return super().create_client(api_key, base_url, **kwargs)
69
71
 
70
- def build_chat_completion_params(
71
- self, context: OpenAILLMContext, messages: List[ChatCompletionMessageParam]
72
- ) -> dict:
72
+ def build_chat_completion_params(self, params_from_context: OpenAILLMInvocationParams) -> dict:
73
73
  """Build parameters for SambaNova chat completion request.
74
74
 
75
75
  SambaNova doesn't support some OpenAI parameters like frequency_penalty,
76
76
  presence_penalty, and seed.
77
77
 
78
78
  Args:
79
- context: The LLM context containing tools and configuration.
80
- messages: List of chat completion messages to send.
79
+ params_from_context: Parameters, derived from the LLM context, to
80
+ use for the chat completion. Contains messages, tools, and tool
81
+ choice.
81
82
 
82
83
  Returns:
83
84
  Dictionary of parameters for the chat completion request.
@@ -85,9 +86,6 @@ class SambaNovaLLMService(OpenAILLMService): # type: ignore
85
86
  params = {
86
87
  "model": self.model_name,
87
88
  "stream": True,
88
- "messages": messages,
89
- "tools": context.tools,
90
- "tool_choice": context.tool_choice,
91
89
  "stream_options": {"include_usage": True},
92
90
  "temperature": self._settings["temperature"],
93
91
  "top_p": self._settings["top_p"],
@@ -95,11 +93,16 @@ class SambaNovaLLMService(OpenAILLMService): # type: ignore
95
93
  "max_completion_tokens": self._settings["max_completion_tokens"],
96
94
  }
97
95
 
96
+ # Messages, tools, tool_choice
97
+ params.update(params_from_context)
98
+
98
99
  params.update(self._settings["extra"])
99
100
  return params
100
101
 
101
102
  @traced_llm # type: ignore
102
- async def _process_context(self, context: OpenAILLMContext) -> AsyncStream[ChatCompletionChunk]:
103
+ async def _process_context(
104
+ self, context: OpenAILLMContext | LLMContext
105
+ ) -> AsyncStream[ChatCompletionChunk]:
103
106
  """Process OpenAI LLM context and stream chat completion chunks.
104
107
 
105
108
  This method handles the streaming response from SambaNova API, including
@@ -122,8 +125,10 @@ class SambaNovaLLMService(OpenAILLMService): # type: ignore
122
125
 
123
126
  await self.start_ttfb_metrics()
124
127
 
125
- chunk_stream: AsyncStream[ChatCompletionChunk] = await self._stream_chat_completions(
126
- context
128
+ chunk_stream = await (
129
+ self._stream_chat_completions_specific_context(context)
130
+ if isinstance(context, OpenAILLMContext)
131
+ else self._stream_chat_completions_universal_context(context)
127
132
  )
128
133
 
129
134
  async for chunk in chunk_stream: