dv-pipecat-ai 0.0.74.dev770__py3-none-any.whl → 0.0.82.dev776__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 (244) hide show
  1. {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/METADATA +137 -93
  2. dv_pipecat_ai-0.0.82.dev776.dist-info/RECORD +340 -0
  3. pipecat/__init__.py +17 -0
  4. pipecat/adapters/base_llm_adapter.py +36 -1
  5. pipecat/adapters/schemas/direct_function.py +296 -0
  6. pipecat/adapters/schemas/function_schema.py +15 -6
  7. pipecat/adapters/schemas/tools_schema.py +55 -7
  8. pipecat/adapters/services/anthropic_adapter.py +22 -3
  9. pipecat/adapters/services/aws_nova_sonic_adapter.py +23 -3
  10. pipecat/adapters/services/bedrock_adapter.py +22 -3
  11. pipecat/adapters/services/gemini_adapter.py +16 -3
  12. pipecat/adapters/services/open_ai_adapter.py +17 -2
  13. pipecat/adapters/services/open_ai_realtime_adapter.py +23 -3
  14. pipecat/audio/filters/base_audio_filter.py +30 -6
  15. pipecat/audio/filters/koala_filter.py +37 -2
  16. pipecat/audio/filters/krisp_filter.py +59 -6
  17. pipecat/audio/filters/noisereduce_filter.py +37 -0
  18. pipecat/audio/interruptions/base_interruption_strategy.py +25 -5
  19. pipecat/audio/interruptions/min_words_interruption_strategy.py +21 -4
  20. pipecat/audio/mixers/base_audio_mixer.py +30 -7
  21. pipecat/audio/mixers/soundfile_mixer.py +53 -6
  22. pipecat/audio/resamplers/base_audio_resampler.py +17 -9
  23. pipecat/audio/resamplers/resampy_resampler.py +26 -1
  24. pipecat/audio/resamplers/soxr_resampler.py +32 -1
  25. pipecat/audio/resamplers/soxr_stream_resampler.py +101 -0
  26. pipecat/audio/utils.py +194 -1
  27. pipecat/audio/vad/silero.py +60 -3
  28. pipecat/audio/vad/vad_analyzer.py +114 -30
  29. pipecat/clocks/base_clock.py +19 -0
  30. pipecat/clocks/system_clock.py +25 -0
  31. pipecat/extensions/voicemail/__init__.py +0 -0
  32. pipecat/extensions/voicemail/voicemail_detector.py +707 -0
  33. pipecat/frames/frames.py +590 -156
  34. pipecat/metrics/metrics.py +64 -1
  35. pipecat/observers/base_observer.py +58 -19
  36. pipecat/observers/loggers/debug_log_observer.py +56 -64
  37. pipecat/observers/loggers/llm_log_observer.py +8 -1
  38. pipecat/observers/loggers/transcription_log_observer.py +19 -7
  39. pipecat/observers/loggers/user_bot_latency_log_observer.py +32 -5
  40. pipecat/observers/turn_tracking_observer.py +26 -1
  41. pipecat/pipeline/base_pipeline.py +5 -7
  42. pipecat/pipeline/base_task.py +52 -9
  43. pipecat/pipeline/parallel_pipeline.py +121 -177
  44. pipecat/pipeline/pipeline.py +129 -20
  45. pipecat/pipeline/runner.py +50 -1
  46. pipecat/pipeline/sync_parallel_pipeline.py +132 -32
  47. pipecat/pipeline/task.py +263 -280
  48. pipecat/pipeline/task_observer.py +85 -34
  49. pipecat/pipeline/to_be_updated/merge_pipeline.py +32 -2
  50. pipecat/processors/aggregators/dtmf_aggregator.py +29 -22
  51. pipecat/processors/aggregators/gated.py +25 -24
  52. pipecat/processors/aggregators/gated_openai_llm_context.py +22 -2
  53. pipecat/processors/aggregators/llm_response.py +398 -89
  54. pipecat/processors/aggregators/openai_llm_context.py +161 -13
  55. pipecat/processors/aggregators/sentence.py +25 -14
  56. pipecat/processors/aggregators/user_response.py +28 -3
  57. pipecat/processors/aggregators/vision_image_frame.py +24 -14
  58. pipecat/processors/async_generator.py +28 -0
  59. pipecat/processors/audio/audio_buffer_processor.py +78 -37
  60. pipecat/processors/consumer_processor.py +25 -6
  61. pipecat/processors/filters/frame_filter.py +23 -0
  62. pipecat/processors/filters/function_filter.py +30 -0
  63. pipecat/processors/filters/identity_filter.py +17 -2
  64. pipecat/processors/filters/null_filter.py +24 -1
  65. pipecat/processors/filters/stt_mute_filter.py +56 -21
  66. pipecat/processors/filters/wake_check_filter.py +46 -3
  67. pipecat/processors/filters/wake_notifier_filter.py +21 -3
  68. pipecat/processors/frame_processor.py +488 -131
  69. pipecat/processors/frameworks/langchain.py +38 -3
  70. pipecat/processors/frameworks/rtvi.py +719 -34
  71. pipecat/processors/gstreamer/pipeline_source.py +41 -0
  72. pipecat/processors/idle_frame_processor.py +26 -3
  73. pipecat/processors/logger.py +23 -0
  74. pipecat/processors/metrics/frame_processor_metrics.py +77 -4
  75. pipecat/processors/metrics/sentry.py +42 -4
  76. pipecat/processors/producer_processor.py +34 -14
  77. pipecat/processors/text_transformer.py +22 -10
  78. pipecat/processors/transcript_processor.py +48 -29
  79. pipecat/processors/user_idle_processor.py +31 -21
  80. pipecat/runner/__init__.py +1 -0
  81. pipecat/runner/daily.py +132 -0
  82. pipecat/runner/livekit.py +148 -0
  83. pipecat/runner/run.py +543 -0
  84. pipecat/runner/types.py +67 -0
  85. pipecat/runner/utils.py +515 -0
  86. pipecat/serializers/base_serializer.py +42 -0
  87. pipecat/serializers/exotel.py +17 -6
  88. pipecat/serializers/genesys.py +95 -0
  89. pipecat/serializers/livekit.py +33 -0
  90. pipecat/serializers/plivo.py +16 -15
  91. pipecat/serializers/protobuf.py +37 -1
  92. pipecat/serializers/telnyx.py +18 -17
  93. pipecat/serializers/twilio.py +32 -16
  94. pipecat/services/ai_service.py +5 -3
  95. pipecat/services/anthropic/llm.py +113 -43
  96. pipecat/services/assemblyai/models.py +63 -5
  97. pipecat/services/assemblyai/stt.py +64 -11
  98. pipecat/services/asyncai/__init__.py +0 -0
  99. pipecat/services/asyncai/tts.py +501 -0
  100. pipecat/services/aws/llm.py +185 -111
  101. pipecat/services/aws/stt.py +217 -23
  102. pipecat/services/aws/tts.py +118 -52
  103. pipecat/services/aws/utils.py +101 -5
  104. pipecat/services/aws_nova_sonic/aws.py +82 -64
  105. pipecat/services/aws_nova_sonic/context.py +15 -6
  106. pipecat/services/azure/common.py +10 -2
  107. pipecat/services/azure/image.py +32 -0
  108. pipecat/services/azure/llm.py +9 -7
  109. pipecat/services/azure/stt.py +65 -2
  110. pipecat/services/azure/tts.py +154 -23
  111. pipecat/services/cartesia/stt.py +125 -8
  112. pipecat/services/cartesia/tts.py +102 -38
  113. pipecat/services/cerebras/llm.py +15 -23
  114. pipecat/services/deepgram/stt.py +19 -11
  115. pipecat/services/deepgram/tts.py +36 -0
  116. pipecat/services/deepseek/llm.py +14 -23
  117. pipecat/services/elevenlabs/tts.py +330 -64
  118. pipecat/services/fal/image.py +43 -0
  119. pipecat/services/fal/stt.py +48 -10
  120. pipecat/services/fireworks/llm.py +14 -21
  121. pipecat/services/fish/tts.py +109 -9
  122. pipecat/services/gemini_multimodal_live/__init__.py +1 -0
  123. pipecat/services/gemini_multimodal_live/events.py +83 -2
  124. pipecat/services/gemini_multimodal_live/file_api.py +189 -0
  125. pipecat/services/gemini_multimodal_live/gemini.py +218 -21
  126. pipecat/services/gladia/config.py +17 -10
  127. pipecat/services/gladia/stt.py +82 -36
  128. pipecat/services/google/frames.py +40 -0
  129. pipecat/services/google/google.py +2 -0
  130. pipecat/services/google/image.py +39 -2
  131. pipecat/services/google/llm.py +176 -58
  132. pipecat/services/google/llm_openai.py +26 -4
  133. pipecat/services/google/llm_vertex.py +37 -15
  134. pipecat/services/google/rtvi.py +41 -0
  135. pipecat/services/google/stt.py +65 -17
  136. pipecat/services/google/test-google-chirp.py +45 -0
  137. pipecat/services/google/tts.py +390 -19
  138. pipecat/services/grok/llm.py +8 -6
  139. pipecat/services/groq/llm.py +8 -6
  140. pipecat/services/groq/stt.py +13 -9
  141. pipecat/services/groq/tts.py +40 -0
  142. pipecat/services/hamsa/__init__.py +9 -0
  143. pipecat/services/hamsa/stt.py +241 -0
  144. pipecat/services/heygen/__init__.py +5 -0
  145. pipecat/services/heygen/api.py +281 -0
  146. pipecat/services/heygen/client.py +620 -0
  147. pipecat/services/heygen/video.py +338 -0
  148. pipecat/services/image_service.py +5 -3
  149. pipecat/services/inworld/__init__.py +1 -0
  150. pipecat/services/inworld/tts.py +592 -0
  151. pipecat/services/llm_service.py +127 -45
  152. pipecat/services/lmnt/tts.py +80 -7
  153. pipecat/services/mcp_service.py +85 -44
  154. pipecat/services/mem0/memory.py +42 -13
  155. pipecat/services/minimax/tts.py +74 -15
  156. pipecat/services/mistral/__init__.py +0 -0
  157. pipecat/services/mistral/llm.py +185 -0
  158. pipecat/services/moondream/vision.py +55 -10
  159. pipecat/services/neuphonic/tts.py +275 -48
  160. pipecat/services/nim/llm.py +8 -6
  161. pipecat/services/ollama/llm.py +27 -7
  162. pipecat/services/openai/base_llm.py +54 -16
  163. pipecat/services/openai/image.py +30 -0
  164. pipecat/services/openai/llm.py +7 -5
  165. pipecat/services/openai/stt.py +13 -9
  166. pipecat/services/openai/tts.py +42 -10
  167. pipecat/services/openai_realtime_beta/azure.py +11 -9
  168. pipecat/services/openai_realtime_beta/context.py +7 -5
  169. pipecat/services/openai_realtime_beta/events.py +10 -7
  170. pipecat/services/openai_realtime_beta/openai.py +37 -18
  171. pipecat/services/openpipe/llm.py +30 -24
  172. pipecat/services/openrouter/llm.py +9 -7
  173. pipecat/services/perplexity/llm.py +15 -19
  174. pipecat/services/piper/tts.py +26 -12
  175. pipecat/services/playht/tts.py +227 -65
  176. pipecat/services/qwen/llm.py +8 -6
  177. pipecat/services/rime/tts.py +128 -17
  178. pipecat/services/riva/stt.py +160 -22
  179. pipecat/services/riva/tts.py +67 -2
  180. pipecat/services/sambanova/llm.py +19 -17
  181. pipecat/services/sambanova/stt.py +14 -8
  182. pipecat/services/sarvam/tts.py +60 -13
  183. pipecat/services/simli/video.py +82 -21
  184. pipecat/services/soniox/__init__.py +0 -0
  185. pipecat/services/soniox/stt.py +398 -0
  186. pipecat/services/speechmatics/stt.py +29 -17
  187. pipecat/services/stt_service.py +47 -11
  188. pipecat/services/tavus/video.py +94 -25
  189. pipecat/services/together/llm.py +8 -6
  190. pipecat/services/tts_service.py +77 -53
  191. pipecat/services/ultravox/stt.py +46 -43
  192. pipecat/services/vision_service.py +5 -3
  193. pipecat/services/websocket_service.py +12 -11
  194. pipecat/services/whisper/base_stt.py +58 -12
  195. pipecat/services/whisper/stt.py +69 -58
  196. pipecat/services/xtts/tts.py +59 -2
  197. pipecat/sync/base_notifier.py +19 -0
  198. pipecat/sync/event_notifier.py +24 -0
  199. pipecat/tests/utils.py +73 -5
  200. pipecat/transcriptions/language.py +24 -0
  201. pipecat/transports/base_input.py +112 -8
  202. pipecat/transports/base_output.py +235 -13
  203. pipecat/transports/base_transport.py +119 -0
  204. pipecat/transports/local/audio.py +76 -0
  205. pipecat/transports/local/tk.py +84 -0
  206. pipecat/transports/network/fastapi_websocket.py +174 -15
  207. pipecat/transports/network/small_webrtc.py +383 -39
  208. pipecat/transports/network/webrtc_connection.py +214 -8
  209. pipecat/transports/network/websocket_client.py +171 -1
  210. pipecat/transports/network/websocket_server.py +147 -9
  211. pipecat/transports/services/daily.py +792 -70
  212. pipecat/transports/services/helpers/daily_rest.py +122 -129
  213. pipecat/transports/services/livekit.py +339 -4
  214. pipecat/transports/services/tavus.py +273 -38
  215. pipecat/utils/asyncio/task_manager.py +92 -186
  216. pipecat/utils/base_object.py +83 -1
  217. pipecat/utils/network.py +2 -0
  218. pipecat/utils/string.py +114 -58
  219. pipecat/utils/text/base_text_aggregator.py +44 -13
  220. pipecat/utils/text/base_text_filter.py +46 -0
  221. pipecat/utils/text/markdown_text_filter.py +70 -14
  222. pipecat/utils/text/pattern_pair_aggregator.py +18 -14
  223. pipecat/utils/text/simple_text_aggregator.py +43 -2
  224. pipecat/utils/text/skip_tags_aggregator.py +21 -13
  225. pipecat/utils/time.py +36 -0
  226. pipecat/utils/tracing/class_decorators.py +32 -7
  227. pipecat/utils/tracing/conversation_context_provider.py +12 -2
  228. pipecat/utils/tracing/service_attributes.py +80 -64
  229. pipecat/utils/tracing/service_decorators.py +48 -21
  230. pipecat/utils/tracing/setup.py +13 -7
  231. pipecat/utils/tracing/turn_context_provider.py +12 -2
  232. pipecat/utils/tracing/turn_trace_observer.py +27 -0
  233. pipecat/utils/utils.py +14 -14
  234. dv_pipecat_ai-0.0.74.dev770.dist-info/RECORD +0 -319
  235. pipecat/examples/daily_runner.py +0 -64
  236. pipecat/examples/run.py +0 -265
  237. pipecat/utils/asyncio/watchdog_async_iterator.py +0 -72
  238. pipecat/utils/asyncio/watchdog_event.py +0 -42
  239. pipecat/utils/asyncio/watchdog_priority_queue.py +0 -48
  240. pipecat/utils/asyncio/watchdog_queue.py +0 -48
  241. {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/WHEEL +0 -0
  242. {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/licenses/LICENSE +0 -0
  243. {dv_pipecat_ai-0.0.74.dev770.dist-info → dv_pipecat_ai-0.0.82.dev776.dist-info}/top_level.txt +0 -0
  244. /pipecat/{examples → extensions}/__init__.py +0 -0
@@ -0,0 +1,95 @@
1
+ import base64
2
+ import json
3
+ from typing import Optional
4
+
5
+ from pydantic import BaseModel
6
+
7
+ from pipecat.audio.utils import create_default_resampler, pcm_to_ulaw, ulaw_to_pcm
8
+ from pipecat.frames.frames import (
9
+ AudioRawFrame,
10
+ Frame,
11
+ InputAudioRawFrame,
12
+ InputDTMFFrame,
13
+ KeypadEntry,
14
+ StartFrame,
15
+ StartInterruptionFrame,
16
+ TransportMessageFrame,
17
+ TransportMessageUrgentFrame,
18
+ )
19
+ from pipecat.serializers.base_serializer import FrameSerializer, FrameSerializerType
20
+
21
+
22
+ class GenesysFrameSerializer(FrameSerializer):
23
+ class InputParams(BaseModel):
24
+ genesys_sample_rate: int = 8000 # Default Genesys rate (8kHz)
25
+ sample_rate: Optional[int] = None # Pipeline input rate
26
+
27
+ def __init__(self, session_id: str, params: InputParams = InputParams()):
28
+ self._session_id = session_id
29
+ self._params = params
30
+ self._genesys_sample_rate = self._params.genesys_sample_rate
31
+ self._sample_rate = 0 # Pipeline input rate
32
+ self._resampler = create_default_resampler()
33
+ self._seq = 1 # Sequence number for outgoing messages
34
+
35
+ @property
36
+ def type(self) -> FrameSerializerType:
37
+ return FrameSerializerType.TEXT
38
+
39
+ async def setup(self, frame: StartFrame):
40
+ self._sample_rate = self._params.sample_rate or frame.audio_in_sample_rate
41
+
42
+ async def serialize(self, frame: Frame) -> str | bytes | None:
43
+ if isinstance(frame, StartInterruptionFrame):
44
+ answer = {
45
+ "version": "2",
46
+ "type": "clearAudio", # Or appropriate event for interruption
47
+ "seq": self._seq,
48
+ "id": self._session_id,
49
+ }
50
+ self._seq += 1
51
+ return json.dumps(answer)
52
+ elif isinstance(frame, AudioRawFrame):
53
+ data = frame.audio
54
+ # Convert PCM to 8kHz μ-law for Genesys
55
+ serialized_data = await pcm_to_ulaw(
56
+ data, frame.sample_rate, self._genesys_sample_rate, self._resampler
57
+ )
58
+ payload = base64.b64encode(serialized_data).decode("utf-8")
59
+ answer = {
60
+ "version": "2",
61
+ "type": "audio",
62
+ "seq": self._seq,
63
+ "id": self._session_id,
64
+ "media": {
65
+ "payload": payload,
66
+ "format": "PCMU",
67
+ "rate": self._genesys_sample_rate,
68
+ },
69
+ }
70
+ self._seq += 1
71
+ return json.dumps(answer)
72
+ elif isinstance(frame, (TransportMessageFrame, TransportMessageUrgentFrame)):
73
+ return json.dumps(frame.message)
74
+
75
+ async def deserialize(self, data: str | bytes) -> Frame | None:
76
+ message = json.loads(data)
77
+ if message.get("type") == "audio":
78
+ payload_base64 = message["media"]["payload"]
79
+ payload = base64.b64decode(payload_base64)
80
+ # Convert Genesys 8kHz μ-law to PCM at pipeline input rate
81
+ deserialized_data = await ulaw_to_pcm(
82
+ payload, self._genesys_sample_rate, self._sample_rate, self._resampler
83
+ )
84
+ audio_frame = InputAudioRawFrame(
85
+ audio=deserialized_data, num_channels=1, sample_rate=self._sample_rate
86
+ )
87
+ return audio_frame
88
+ elif message.get("type") == "dtmf":
89
+ digit = message.get("dtmf", {}).get("digit")
90
+ try:
91
+ return InputDTMFFrame(KeypadEntry(digit))
92
+ except ValueError:
93
+ return None
94
+ else:
95
+ return None
@@ -4,6 +4,8 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
+ """LiveKit frame serializer for Pipecat."""
8
+
7
9
  import ctypes
8
10
  import pickle
9
11
 
@@ -21,11 +23,33 @@ except ModuleNotFoundError as e:
21
23
 
22
24
 
23
25
  class LivekitFrameSerializer(FrameSerializer):
26
+ """Serializer for converting between Pipecat frames and LiveKit audio frames.
27
+
28
+ This serializer handles the conversion of Pipecat's OutputAudioRawFrame objects
29
+ to LiveKit AudioFrame objects for transmission, and the reverse conversion
30
+ for received audio data.
31
+ """
32
+
24
33
  @property
25
34
  def type(self) -> FrameSerializerType:
35
+ """Get the serializer type.
36
+
37
+ Returns:
38
+ The serializer type indicating binary serialization.
39
+ """
26
40
  return FrameSerializerType.BINARY
27
41
 
28
42
  async def serialize(self, frame: Frame) -> str | bytes | None:
43
+ """Serialize a Pipecat frame to LiveKit AudioFrame format.
44
+
45
+ Args:
46
+ frame: The Pipecat frame to serialize. Only OutputAudioRawFrame
47
+ instances are supported.
48
+
49
+ Returns:
50
+ Pickled LiveKit AudioFrame bytes if frame is OutputAudioRawFrame,
51
+ None otherwise.
52
+ """
29
53
  if not isinstance(frame, OutputAudioRawFrame):
30
54
  return None
31
55
  audio_frame = AudioFrame(
@@ -37,6 +61,15 @@ class LivekitFrameSerializer(FrameSerializer):
37
61
  return pickle.dumps(audio_frame)
38
62
 
39
63
  async def deserialize(self, data: str | bytes) -> Frame | None:
64
+ """Deserialize LiveKit AudioFrame data to a Pipecat frame.
65
+
66
+ Args:
67
+ data: Pickled data containing a LiveKit AudioFrame.
68
+
69
+ Returns:
70
+ InputAudioRawFrame containing the deserialized audio data,
71
+ or None if deserialization fails.
72
+ """
40
73
  audio_frame: AudioFrame = pickle.loads(data)["frame"]
41
74
  return InputAudioRawFrame(
42
75
  audio=bytes(audio_frame.data),
@@ -4,6 +4,8 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
+ """Plivo WebSocket frame serializer for audio streaming."""
8
+
7
9
  import base64
8
10
  import json
9
11
  from typing import Optional
@@ -11,7 +13,7 @@ from typing import Optional
11
13
  from loguru import logger
12
14
  from pydantic import BaseModel
13
15
 
14
- from pipecat.audio.utils import create_default_resampler, pcm_to_ulaw, ulaw_to_pcm
16
+ from pipecat.audio.utils import create_stream_resampler, pcm_to_ulaw, ulaw_to_pcm
15
17
  from pipecat.frames.frames import (
16
18
  AudioRawFrame,
17
19
  CancelFrame,
@@ -38,22 +40,12 @@ class PlivoFrameSerializer(FrameSerializer):
38
40
  When auto_hang_up is enabled (default), the serializer will automatically terminate
39
41
  the Plivo call when an EndFrame or CancelFrame is processed, but requires Plivo
40
42
  credentials to be provided.
41
-
42
- Attributes:
43
- _stream_id: The Plivo Stream ID.
44
- _call_id: The associated Plivo Call ID.
45
- _auth_id: Plivo auth ID for API access.
46
- _auth_token: Plivo authentication token for API access.
47
- _params: Configuration parameters.
48
- _plivo_sample_rate: Sample rate used by Plivo (typically 8kHz).
49
- _sample_rate: Input sample rate for the pipeline.
50
- _resampler: Audio resampler for format conversion.
51
43
  """
52
44
 
53
45
  class InputParams(BaseModel):
54
46
  """Configuration parameters for PlivoFrameSerializer.
55
47
 
56
- Attributes:
48
+ Parameters:
57
49
  plivo_sample_rate: Sample rate used by Plivo, defaults to 8000 Hz.
58
50
  sample_rate: Optional override for pipeline input sample rate.
59
51
  auto_hang_up: Whether to automatically terminate call on EndFrame.
@@ -89,7 +81,8 @@ class PlivoFrameSerializer(FrameSerializer):
89
81
  self._plivo_sample_rate = self._params.plivo_sample_rate
90
82
  self._sample_rate = 0 # Pipeline input rate
91
83
 
92
- self._resampler = create_default_resampler()
84
+ self._input_resampler = create_stream_resampler()
85
+ self._output_resampler = create_stream_resampler()
93
86
  self._hangup_attempted = False
94
87
 
95
88
  @property
@@ -137,8 +130,12 @@ class PlivoFrameSerializer(FrameSerializer):
137
130
 
138
131
  # Output: Convert PCM at frame's rate to 8kHz μ-law for Plivo
139
132
  serialized_data = await pcm_to_ulaw(
140
- data, frame.sample_rate, self._plivo_sample_rate, self._resampler
133
+ data, frame.sample_rate, self._plivo_sample_rate, self._output_resampler
141
134
  )
135
+ if serialized_data is None or len(serialized_data) == 0:
136
+ # Ignoring in case we don't have audio
137
+ return None
138
+
142
139
  payload = base64.b64encode(serialized_data).decode("utf-8")
143
140
  answer = {
144
141
  "event": "playAudio",
@@ -232,8 +229,12 @@ class PlivoFrameSerializer(FrameSerializer):
232
229
 
233
230
  # Input: Convert Plivo's 8kHz μ-law to PCM at pipeline input rate
234
231
  deserialized_data = await ulaw_to_pcm(
235
- payload, self._plivo_sample_rate, self._sample_rate, self._resampler
232
+ payload, self._plivo_sample_rate, self._sample_rate, self._input_resampler
236
233
  )
234
+ if deserialized_data is None or len(deserialized_data) == 0:
235
+ # Ignoring in case we don't have audio
236
+ return None
237
+
237
238
  audio_frame = InputAudioRawFrame(
238
239
  audio=deserialized_data, num_channels=1, sample_rate=self._sample_rate
239
240
  )
@@ -4,6 +4,8 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
+ """Protobuf frame serialization for Pipecat."""
8
+
7
9
  import dataclasses
8
10
  import json
9
11
 
@@ -22,13 +24,25 @@ from pipecat.frames.frames import (
22
24
  from pipecat.serializers.base_serializer import FrameSerializer, FrameSerializerType
23
25
 
24
26
 
25
- # Data class for converting transport messages into Protobuf format.
26
27
  @dataclasses.dataclass
27
28
  class MessageFrame:
29
+ """Data class for converting transport messages into Protobuf format.
30
+
31
+ Parameters:
32
+ data: JSON-encoded message data for transport.
33
+ """
34
+
28
35
  data: str
29
36
 
30
37
 
31
38
  class ProtobufFrameSerializer(FrameSerializer):
39
+ """Serializer for converting Pipecat frames to/from Protocol Buffer format.
40
+
41
+ Provides efficient binary serialization for frame transport over network
42
+ connections. Supports text, audio, transcription, and message frames with
43
+ automatic conversion between transport message types.
44
+ """
45
+
32
46
  SERIALIZABLE_TYPES = {
33
47
  TextFrame: "text",
34
48
  OutputAudioRawFrame: "audio",
@@ -46,13 +60,27 @@ class ProtobufFrameSerializer(FrameSerializer):
46
60
  DESERIALIZABLE_FIELDS = {v: k for k, v in DESERIALIZABLE_TYPES.items()}
47
61
 
48
62
  def __init__(self):
63
+ """Initialize the Protobuf frame serializer."""
49
64
  pass
50
65
 
51
66
  @property
52
67
  def type(self) -> FrameSerializerType:
68
+ """Get the serializer type.
69
+
70
+ Returns:
71
+ FrameSerializerType.BINARY indicating binary serialization format.
72
+ """
53
73
  return FrameSerializerType.BINARY
54
74
 
55
75
  async def serialize(self, frame: Frame) -> str | bytes | None:
76
+ """Serialize a frame to Protocol Buffer binary format.
77
+
78
+ Args:
79
+ frame: The frame to serialize.
80
+
81
+ Returns:
82
+ Serialized frame as bytes, or None if frame type is not serializable.
83
+ """
56
84
  # Wrapping this messages as a JSONFrame to send
57
85
  if isinstance(frame, (TransportMessageFrame, TransportMessageUrgentFrame)):
58
86
  frame = MessageFrame(
@@ -75,6 +103,14 @@ class ProtobufFrameSerializer(FrameSerializer):
75
103
  return proto_frame.SerializeToString()
76
104
 
77
105
  async def deserialize(self, data: str | bytes) -> Frame | None:
106
+ """Deserialize Protocol Buffer binary data to a frame.
107
+
108
+ Args:
109
+ data: Binary protobuf data to deserialize.
110
+
111
+ Returns:
112
+ Deserialized frame instance, or None if deserialization fails.
113
+ """
78
114
  proto = frame_protos.Frame.FromString(data)
79
115
  which = proto.WhichOneof("frame")
80
116
  if which not in self.DESERIALIZABLE_FIELDS:
@@ -4,6 +4,8 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
+ """Telnyx WebSocket frame serializer for Pipecat."""
8
+
7
9
  import base64
8
10
  import json
9
11
  from typing import Optional
@@ -14,7 +16,7 @@ from pydantic import BaseModel
14
16
 
15
17
  from pipecat.audio.utils import (
16
18
  alaw_to_pcm,
17
- create_default_resampler,
19
+ create_stream_resampler,
18
20
  pcm_to_alaw,
19
21
  pcm_to_ulaw,
20
22
  ulaw_to_pcm,
@@ -43,22 +45,12 @@ class TelnyxFrameSerializer(FrameSerializer):
43
45
  When auto_hang_up is enabled (default), the serializer will automatically terminate
44
46
  the Telnyx call when an EndFrame or CancelFrame is processed, but requires Telnyx
45
47
  credentials to be provided.
46
-
47
- Attributes:
48
- _stream_id: The Telnyx Stream ID.
49
- _call_control_id: The associated Telnyx Call Control ID.
50
- _api_key: Telnyx API key for API access.
51
- _params: Configuration parameters.
52
- _telnyx_sample_rate: Sample rate used by Telnyx (typically 8kHz).
53
- _sample_rate: Input sample rate for the pipeline.
54
- _resampler: Audio resampler for format conversion.
55
- _hangup_attempted: Flag to track if hang-up has been attempted.
56
48
  """
57
49
 
58
50
  class InputParams(BaseModel):
59
51
  """Configuration parameters for TelnyxFrameSerializer.
60
52
 
61
- Attributes:
53
+ Parameters:
62
54
  telnyx_sample_rate: Sample rate used by Telnyx, defaults to 8000 Hz.
63
55
  sample_rate: Optional override for pipeline input sample rate.
64
56
  inbound_encoding: Audio encoding for data sent to Telnyx (e.g., "PCMU").
@@ -101,7 +93,8 @@ class TelnyxFrameSerializer(FrameSerializer):
101
93
  self._telnyx_sample_rate = self._params.telnyx_sample_rate
102
94
  self._sample_rate = 0 # Pipeline input rate
103
95
 
104
- self._resampler = create_default_resampler()
96
+ self._input_resampler = create_stream_resampler()
97
+ self._output_resampler = create_stream_resampler()
105
98
  self._hangup_attempted = False
106
99
 
107
100
  @property
@@ -153,15 +146,19 @@ class TelnyxFrameSerializer(FrameSerializer):
153
146
  # Output: Convert PCM at frame's rate to 8kHz encoded for Telnyx
154
147
  if self._params.inbound_encoding == "PCMU":
155
148
  serialized_data = await pcm_to_ulaw(
156
- data, frame.sample_rate, self._telnyx_sample_rate, self._resampler
149
+ data, frame.sample_rate, self._telnyx_sample_rate, self._output_resampler
157
150
  )
158
151
  elif self._params.inbound_encoding == "PCMA":
159
152
  serialized_data = await pcm_to_alaw(
160
- data, frame.sample_rate, self._telnyx_sample_rate, self._resampler
153
+ data, frame.sample_rate, self._telnyx_sample_rate, self._output_resampler
161
154
  )
162
155
  else:
163
156
  raise ValueError(f"Unsupported encoding: {self._params.inbound_encoding}")
164
157
 
158
+ if serialized_data is None or len(serialized_data) == 0:
159
+ # Ignoring in case we don't have audio
160
+ return None
161
+
165
162
  payload = base64.b64encode(serialized_data).decode("utf-8")
166
163
  answer = {
167
164
  "event": "media",
@@ -257,18 +254,22 @@ class TelnyxFrameSerializer(FrameSerializer):
257
254
  payload,
258
255
  self._telnyx_sample_rate,
259
256
  self._sample_rate,
260
- self._resampler,
257
+ self._input_resampler,
261
258
  )
262
259
  elif self._params.outbound_encoding == "PCMA":
263
260
  deserialized_data = await alaw_to_pcm(
264
261
  payload,
265
262
  self._telnyx_sample_rate,
266
263
  self._sample_rate,
267
- self._resampler,
264
+ self._input_resampler,
268
265
  )
269
266
  else:
270
267
  raise ValueError(f"Unsupported encoding: {self._params.outbound_encoding}")
271
268
 
269
+ if deserialized_data is None or len(deserialized_data) == 0:
270
+ # Ignoring in case we don't have audio
271
+ return None
272
+
272
273
  audio_frame = InputAudioRawFrame(
273
274
  audio=deserialized_data, num_channels=1, sample_rate=self._sample_rate
274
275
  )
@@ -4,6 +4,8 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
+ """Twilio Media Streams WebSocket protocol serializer for Pipecat."""
8
+
7
9
  import base64
8
10
  import json
9
11
  from typing import Optional
@@ -11,7 +13,7 @@ from typing import Optional
11
13
  from loguru import logger
12
14
  from pydantic import BaseModel
13
15
 
14
- from pipecat.audio.utils import create_default_resampler, pcm_to_ulaw, ulaw_to_pcm
16
+ from pipecat.audio.utils import create_stream_resampler, pcm_to_ulaw, ulaw_to_pcm
15
17
  from pipecat.frames.frames import (
16
18
  AudioRawFrame,
17
19
  CancelFrame,
@@ -38,22 +40,12 @@ class TwilioFrameSerializer(FrameSerializer):
38
40
  When auto_hang_up is enabled (default), the serializer will automatically terminate
39
41
  the Twilio call when an EndFrame or CancelFrame is processed, but requires Twilio
40
42
  credentials to be provided.
41
-
42
- Attributes:
43
- _stream_sid: The Twilio Media Stream SID.
44
- _call_sid: The associated Twilio Call SID.
45
- _account_sid: Twilio account SID for API access.
46
- _auth_token: Twilio authentication token for API access.
47
- _params: Configuration parameters.
48
- _twilio_sample_rate: Sample rate used by Twilio (typically 8kHz).
49
- _sample_rate: Input sample rate for the pipeline.
50
- _resampler: Audio resampler for format conversion.
51
43
  """
52
44
 
53
45
  class InputParams(BaseModel):
54
46
  """Configuration parameters for TwilioFrameSerializer.
55
47
 
56
- Attributes:
48
+ Parameters:
57
49
  twilio_sample_rate: Sample rate used by Twilio, defaults to 8000 Hz.
58
50
  sample_rate: Optional override for pipeline input sample rate.
59
51
  auto_hang_up: Whether to automatically terminate call on EndFrame.
@@ -89,7 +81,8 @@ class TwilioFrameSerializer(FrameSerializer):
89
81
  self._twilio_sample_rate = self._params.twilio_sample_rate
90
82
  self._sample_rate = 0 # Pipeline input rate
91
83
 
92
- self._resampler = create_default_resampler()
84
+ self._input_resampler = create_stream_resampler()
85
+ self._output_resampler = create_stream_resampler()
93
86
  self._hangup_attempted = False
94
87
 
95
88
  @property
@@ -137,11 +130,12 @@ class TwilioFrameSerializer(FrameSerializer):
137
130
 
138
131
  # Output: Convert PCM at frame's rate to 8kHz μ-law for Twilio
139
132
  serialized_data = await pcm_to_ulaw(
140
- data, frame.sample_rate, self._twilio_sample_rate, self._resampler
133
+ data, frame.sample_rate, self._twilio_sample_rate, self._output_resampler
141
134
  )
142
135
  if serialized_data is None or len(serialized_data) == 0:
143
136
  # Ignoring in case we don't have audio
144
137
  return None
138
+
145
139
  payload = base64.b64encode(serialized_data).decode("utf-8")
146
140
  answer = {
147
141
  "event": "media",
@@ -195,8 +189,26 @@ class TwilioFrameSerializer(FrameSerializer):
195
189
  async with session.post(endpoint, auth=auth, data=params) as response:
196
190
  if response.status == 200:
197
191
  logger.info(f"Successfully terminated Twilio call {call_sid}")
192
+ elif response.status == 404:
193
+ # Handle the case where the call has already ended
194
+ # Error code 20404: "The requested resource was not found"
195
+ # Source: https://www.twilio.com/docs/errors/20404
196
+ try:
197
+ error_data = await response.json()
198
+ if error_data.get("code") == 20404:
199
+ logger.debug(f"Twilio call {call_sid} was already terminated")
200
+ return
201
+ except:
202
+ pass # Fall through to log the raw error
203
+
204
+ # Log other 404 errors
205
+ error_text = await response.text()
206
+ logger.error(
207
+ f"Failed to terminate Twilio call {call_sid}: "
208
+ f"Status {response.status}, Response: {error_text}"
209
+ )
198
210
  else:
199
- # Get the error details for better debugging
211
+ # Log other errors
200
212
  error_text = await response.text()
201
213
  logger.error(
202
214
  f"Failed to terminate Twilio call {call_sid}: "
@@ -225,8 +237,12 @@ class TwilioFrameSerializer(FrameSerializer):
225
237
 
226
238
  # Input: Convert Twilio's 8kHz μ-law to PCM at pipeline input rate
227
239
  deserialized_data = await ulaw_to_pcm(
228
- payload, self._twilio_sample_rate, self._sample_rate, self._resampler
240
+ payload, self._twilio_sample_rate, self._sample_rate, self._input_resampler
229
241
  )
242
+ if deserialized_data is None or len(deserialized_data) == 0:
243
+ # Ignoring in case we don't have audio
244
+ return None
245
+
230
246
  audio_frame = InputAudioRawFrame(
231
247
  audio=deserialized_data, num_channels=1, sample_rate=self._sample_rate
232
248
  )
@@ -32,12 +32,14 @@ class AIService(FrameProcessor):
32
32
  settings handling, session properties, and frame processing lifecycle.
33
33
  Subclasses should implement specific AI functionality while leveraging
34
34
  this base infrastructure.
35
-
36
- Args:
37
- **kwargs: Additional arguments passed to the parent FrameProcessor.
38
35
  """
39
36
 
40
37
  def __init__(self, **kwargs):
38
+ """Initialize the AI service.
39
+
40
+ Args:
41
+ **kwargs: Additional arguments passed to the parent FrameProcessor.
42
+ """
41
43
  super().__init__(**kwargs)
42
44
  self._model_name: str = ""
43
45
  self._settings: Dict[str, Any] = {}