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
@@ -4,6 +4,13 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
+ """Pipeline runner for managing pipeline task execution.
8
+
9
+ This module provides the PipelineRunner class that handles the execution
10
+ of pipeline tasks with signal handling, garbage collection, and lifecycle
11
+ management.
12
+ """
13
+
7
14
  import asyncio
8
15
  import gc
9
16
  import signal
@@ -17,14 +24,31 @@ from pipecat.utils.base_object import BaseObject
17
24
 
18
25
 
19
26
  class PipelineRunner(BaseObject):
27
+ """Manages the execution of pipeline tasks with lifecycle and signal handling.
28
+
29
+ Provides a high-level interface for running pipeline tasks with automatic
30
+ signal handling (SIGINT/SIGTERM), optional garbage collection, and proper
31
+ cleanup of resources.
32
+ """
33
+
20
34
  def __init__(
21
35
  self,
22
36
  *,
23
37
  name: Optional[str] = None,
24
38
  handle_sigint: bool = True,
39
+ handle_sigterm: bool = False,
25
40
  force_gc: bool = False,
26
41
  loop: Optional[asyncio.AbstractEventLoop] = None,
27
42
  ):
43
+ """Initialize the pipeline runner.
44
+
45
+ Args:
46
+ name: Optional name for the runner instance.
47
+ handle_sigint: Whether to automatically handle SIGINT signals.
48
+ handle_sigterm: Whether to automatically handle SIGTERM signals.
49
+ force_gc: Whether to force garbage collection after task completion.
50
+ loop: Event loop to use. If None, uses the current running loop.
51
+ """
28
52
  super().__init__(name=name)
29
53
 
30
54
  self._tasks = {}
@@ -35,11 +59,22 @@ class PipelineRunner(BaseObject):
35
59
  if handle_sigint:
36
60
  self._setup_sigint()
37
61
 
62
+ if handle_sigterm:
63
+ self._setup_sigterm()
64
+
38
65
  async def run(self, task: PipelineTask):
66
+ """Run a pipeline task to completion.
67
+
68
+ Args:
69
+ task: The pipeline task to execute.
70
+ """
39
71
  logger.debug(f"Runner {self} started running {task}", call_id=task._conversation_id)
40
72
  self._tasks[task.name] = task
41
73
  params = PipelineTaskParams(loop=self._loop)
42
- await task.run(params)
74
+ try:
75
+ await task.run(params)
76
+ except asyncio.CancelledError:
77
+ await self._cancel()
43
78
  del self._tasks[task.name]
44
79
 
45
80
  # Cleanup base object.
@@ -56,27 +91,41 @@ class PipelineRunner(BaseObject):
56
91
  logger.debug(f"Runner {self} finished running {task}", call_id=task._conversation_id)
57
92
 
58
93
  async def stop_when_done(self):
94
+ """Schedule all running tasks to stop when their current processing is complete."""
59
95
  logger.debug(f"Runner {self} scheduled to stop when all tasks are done")
60
96
  await asyncio.gather(*[t.stop_when_done() for t in self._tasks.values()])
61
97
 
62
98
  async def cancel(self):
99
+ """Cancel all running tasks immediately."""
63
100
  logger.debug(f"Cancelling runner {self}")
101
+ await self._cancel()
102
+
103
+ async def _cancel(self):
104
+ """Cancel all running tasks immediately."""
64
105
  await asyncio.gather(*[t.cancel() for t in self._tasks.values()])
65
106
 
66
107
  def _setup_sigint(self):
108
+ """Set up signal handlers for graceful shutdown."""
67
109
  loop = asyncio.get_running_loop()
68
110
  loop.add_signal_handler(signal.SIGINT, lambda *args: self._sig_handler())
111
+
112
+ def _setup_sigterm(self):
113
+ """Set up signal handlers for graceful shutdown."""
114
+ loop = asyncio.get_running_loop()
69
115
  loop.add_signal_handler(signal.SIGTERM, lambda *args: self._sig_handler())
70
116
 
71
117
  def _sig_handler(self):
118
+ """Handle interrupt signals by cancelling all tasks."""
72
119
  if not self._sig_task:
73
120
  self._sig_task = asyncio.create_task(self._sig_cancel())
74
121
 
75
122
  async def _sig_cancel(self):
123
+ """Cancel all running tasks due to signal interruption."""
76
124
  logger.warning(f"Interruption detected. Cancelling runner {self}")
77
125
  await self.cancel()
78
126
 
79
127
  def _gc_collect(self):
128
+ """Force garbage collection and log results."""
80
129
  collected = gc.collect()
81
130
  logger.debug(f"Garbage collector: collected {collected} objects.")
82
131
  logger.debug(f"Garbage collector: uncollectable objects {gc.garbage}")
@@ -4,6 +4,13 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
+ """Synchronous parallel pipeline implementation for concurrent frame processing.
8
+
9
+ This module provides a pipeline that processes frames through multiple parallel
10
+ pipelines simultaneously, synchronizing their output to maintain frame ordering
11
+ and prevent duplicate processing.
12
+ """
13
+
7
14
  import asyncio
8
15
  from dataclasses import dataclass
9
16
  from itertools import chain
@@ -15,22 +22,42 @@ from pipecat.frames.frames import ControlFrame, EndFrame, Frame, SystemFrame
15
22
  from pipecat.pipeline.base_pipeline import BasePipeline
16
23
  from pipecat.pipeline.pipeline import Pipeline
17
24
  from pipecat.processors.frame_processor import FrameDirection, FrameProcessor, FrameProcessorSetup
18
- from pipecat.utils.asyncio.watchdog_queue import WatchdogQueue
19
25
 
20
26
 
21
27
  @dataclass
22
28
  class SyncFrame(ControlFrame):
23
- """This frame is used to know when the internal pipelines have finished."""
29
+ """Control frame used to synchronize parallel pipeline processing.
30
+
31
+ This frame is sent through parallel pipelines to determine when the
32
+ internal pipelines have finished processing a batch of frames.
33
+ """
24
34
 
25
35
  pass
26
36
 
27
37
 
28
38
  class SyncParallelPipelineSource(FrameProcessor):
39
+ """Source processor for synchronous parallel pipeline processing.
40
+
41
+ Routes frames to parallel pipelines and collects upstream responses
42
+ for synchronization purposes.
43
+ """
44
+
29
45
  def __init__(self, upstream_queue: asyncio.Queue):
30
- super().__init__()
46
+ """Initialize the sync parallel pipeline source.
47
+
48
+ Args:
49
+ upstream_queue: Queue for collecting upstream frames from the pipeline.
50
+ """
51
+ super().__init__(enable_direct_mode=True)
31
52
  self._up_queue = upstream_queue
32
53
 
33
54
  async def process_frame(self, frame: Frame, direction: FrameDirection):
55
+ """Process frames and route them based on direction.
56
+
57
+ Args:
58
+ frame: The frame to process.
59
+ direction: The direction of frame flow.
60
+ """
34
61
  await super().process_frame(frame, direction)
35
62
 
36
63
  match direction:
@@ -41,11 +68,28 @@ class SyncParallelPipelineSource(FrameProcessor):
41
68
 
42
69
 
43
70
  class SyncParallelPipelineSink(FrameProcessor):
71
+ """Sink processor for synchronous parallel pipeline processing.
72
+
73
+ Collects downstream frames from parallel pipelines and routes
74
+ upstream frames back through the pipeline.
75
+ """
76
+
44
77
  def __init__(self, downstream_queue: asyncio.Queue):
45
- super().__init__()
78
+ """Initialize the sync parallel pipeline sink.
79
+
80
+ Args:
81
+ downstream_queue: Queue for collecting downstream frames from the pipeline.
82
+ """
83
+ super().__init__(enable_direct_mode=True)
46
84
  self._down_queue = downstream_queue
47
85
 
48
86
  async def process_frame(self, frame: Frame, direction: FrameDirection):
87
+ """Process frames and route them based on direction.
88
+
89
+ Args:
90
+ frame: The frame to process.
91
+ direction: The direction of frame flow.
92
+ """
49
93
  await super().process_frame(frame, direction)
50
94
 
51
95
  match direction:
@@ -56,36 +100,42 @@ class SyncParallelPipelineSink(FrameProcessor):
56
100
 
57
101
 
58
102
  class SyncParallelPipeline(BasePipeline):
103
+ """Pipeline that processes frames through multiple parallel pipelines synchronously.
104
+
105
+ Creates multiple parallel processing paths that all receive the same input frames
106
+ and produces synchronized output. Each parallel path is a separate pipeline that
107
+ processes frames independently, with synchronization points to ensure consistent
108
+ ordering and prevent duplicate frame processing.
109
+
110
+ The pipeline uses SyncFrame control frames to coordinate between parallel paths
111
+ and ensure all paths have completed processing before moving to the next frame.
112
+ """
113
+
59
114
  def __init__(self, *args):
115
+ """Initialize the synchronous parallel pipeline.
116
+
117
+ Args:
118
+ *args: Variable number of processor lists, each representing a parallel pipeline path.
119
+ Each argument should be a list of FrameProcessor instances.
120
+
121
+ Raises:
122
+ Exception: If no arguments are provided.
123
+ TypeError: If any argument is not a list of processors.
124
+ """
60
125
  super().__init__()
61
126
 
62
127
  if len(args) == 0:
63
128
  raise Exception(f"SyncParallelPipeline needs at least one argument")
64
129
 
65
- self._args = args
66
130
  self._sinks = []
67
131
  self._sources = []
68
132
  self._pipelines = []
69
133
 
70
- #
71
- # BasePipeline
72
- #
73
-
74
- def processors_with_metrics(self) -> List[FrameProcessor]:
75
- return list(chain.from_iterable(p.processors_with_metrics() for p in self._pipelines))
76
-
77
- #
78
- # Frame processor
79
- #
80
-
81
- async def setup(self, setup: FrameProcessorSetup):
82
- await super().setup(setup)
83
-
84
- self._up_queue = WatchdogQueue(setup.task_manager)
85
- self._down_queue = WatchdogQueue(setup.task_manager)
134
+ self._up_queue = asyncio.Queue()
135
+ self._down_queue = asyncio.Queue()
86
136
 
87
137
  logger.debug(f"Creating {self} pipelines")
88
- for processors in self._args:
138
+ for processors in args:
89
139
  if not isinstance(processors, list):
90
140
  raise TypeError(f"SyncParallelPipeline argument {processors} is not a list")
91
141
 
@@ -95,30 +145,80 @@ class SyncParallelPipeline(BasePipeline):
95
145
  source = SyncParallelPipelineSource(up_queue)
96
146
  sink = SyncParallelPipelineSink(down_queue)
97
147
 
98
- # Create pipeline
99
- pipeline = Pipeline(processors)
100
- source.link(pipeline)
101
- pipeline.link(sink)
102
- self._pipelines.append(pipeline)
103
-
104
148
  # Keep track of sources and sinks. We also keep the output queue of
105
149
  # the source and the sinks so we can use it later.
106
150
  self._sources.append({"processor": source, "queue": down_queue})
107
151
  self._sinks.append({"processor": sink, "queue": up_queue})
108
152
 
153
+ # Create pipeline
154
+ pipeline = Pipeline(processors, source=source, sink=sink)
155
+ self._pipelines.append(pipeline)
156
+
109
157
  logger.debug(f"Finished creating {self} pipelines")
110
158
 
111
- await asyncio.gather(*[s["processor"].setup(setup) for s in self._sources])
159
+ #
160
+ # Frame processor
161
+ #
162
+
163
+ @property
164
+ def processors(self):
165
+ """Return the list of sub-processors contained within this processor.
166
+
167
+ Only compound processors (e.g. pipelines and parallel pipelines) have
168
+ sub-processors. Non-compound processors will return an empty list.
169
+
170
+ Returns:
171
+ The list of sub-processors if this is a compound processor.
172
+ """
173
+ return self._pipelines
174
+
175
+ @property
176
+ def entry_processors(self) -> List["FrameProcessor"]:
177
+ """Return the list of entry processors for this processor.
178
+
179
+ Entry processors are the first processors in a compound processor
180
+ (e.g. pipelines, parallel pipelines). Note that pipelines can also be an
181
+ entry processor as pipelines are processors themselves. Non-compound
182
+ processors will simply return an empty list.
183
+
184
+ Returns:
185
+ The list of entry processors.
186
+ """
187
+ return self._sources
188
+
189
+ def processors_with_metrics(self) -> List[FrameProcessor]:
190
+ """Collect processors that can generate metrics from all parallel pipelines.
191
+
192
+ Returns:
193
+ List of frame processors that support metrics collection from all parallel paths.
194
+ """
195
+ return list(chain.from_iterable(p.processors_with_metrics() for p in self._pipelines))
196
+
197
+ async def setup(self, setup: FrameProcessorSetup):
198
+ """Set up the parallel pipeline and all contained processors.
199
+
200
+ Args:
201
+ setup: Configuration for frame processor setup.
202
+ """
203
+ await super().setup(setup)
112
204
  await asyncio.gather(*[p.setup(setup) for p in self._pipelines])
113
- await asyncio.gather(*[s["processor"].setup(setup) for s in self._sinks])
114
205
 
115
206
  async def cleanup(self):
207
+ """Clean up the parallel pipeline and all contained processors."""
116
208
  await super().cleanup()
117
- await asyncio.gather(*[s["processor"].cleanup() for s in self._sources])
118
209
  await asyncio.gather(*[p.cleanup() for p in self._pipelines])
119
- await asyncio.gather(*[s["processor"].cleanup() for s in self._sinks])
120
210
 
121
211
  async def process_frame(self, frame: Frame, direction: FrameDirection):
212
+ """Process frames through all parallel pipelines with synchronization.
213
+
214
+ Distributes frames to all parallel pipelines and synchronizes their output
215
+ to maintain proper ordering and prevent duplicate processing. Uses SyncFrame
216
+ control frames to coordinate between parallel paths.
217
+
218
+ Args:
219
+ frame: The frame to process.
220
+ direction: The direction of frame flow.
221
+ """
122
222
  await super().process_frame(frame, direction)
123
223
 
124
224
  # The last processor of each pipeline needs to be synchronous otherwise