diff --git a/src/dailyai/pipeline/aggregators.py b/src/dailyai/pipeline/aggregators.py index e1873e981..17c7775ff 100644 --- a/src/dailyai/pipeline/aggregators.py +++ b/src/dailyai/pipeline/aggregators.py @@ -170,8 +170,18 @@ class ParallelPipeline(FrameProcessor): await asyncio.gather(*[pipeline.run_pipeline() for pipeline in self.pipelines]) + seen_ids = set() while not self.sink.empty(): frame = await self.sink.get() + + # de-dup frames. Because the convention is to yield a frame that isn't processed, + # each pipeline will likely yield the same frame, so we will end up with _n_ copies + # of unprocessed frames where _n_ is the number of parallel pipes that don't + # process that frame. + if id(frame) in seen_ids: + continue + seen_ids.add(id(frame)) + # Skip passing along EndParallelPipeQueueFrame, because we use them for our own flow control. if not isinstance(frame, EndParallelPipeQueueFrame): yield frame diff --git a/src/dailyai/pipeline/pipeline.py b/src/dailyai/pipeline/pipeline.py index 4cb2fb3f2..f6618a85d 100644 --- a/src/dailyai/pipeline/pipeline.py +++ b/src/dailyai/pipeline/pipeline.py @@ -39,12 +39,12 @@ class Pipeline: for frame_generator in frame_generators: async for frame in frame_generator: await self.sink.put(frame) - if isinstance( - frame, EndStreamQueueFrame - ) or isinstance( - frame, EndParallelPipeQueueFrame - ): - return + if isinstance( + frame, EndStreamQueueFrame + ) or isinstance( + frame, EndParallelPipeQueueFrame + ): + return except asyncio.CancelledError: # this means there's been an interruption, do any cleanup necessary here. for processor in self.processors: diff --git a/src/examples/foundational/06-listen-and-respond.py b/src/examples/foundational/06-listen-and-respond.py index 8bf9b4d85..044105f33 100644 --- a/src/examples/foundational/06-listen-and-respond.py +++ b/src/examples/foundational/06-listen-and-respond.py @@ -1,5 +1,6 @@ import asyncio import os +from dailyai.pipeline.pipeline import Pipeline from dailyai.services.daily_transport_service import DailyTransportService from dailyai.services.azure_ai_services import AzureLLMService, AzureTTSService @@ -44,21 +45,19 @@ async def main(room_url: str, token): tma_in = LLMUserContextAggregator(messages, transport._my_participant_id) tma_out = LLMAssistantContextAggregator(messages, transport._my_participant_id) - await tts.run_to_queue( - transport.send_queue, - tma_out.run( - fl2.run( - llm.run( - tma_in.run( - fl.run( - transport.get_receive_frames() - ) - ) - ) - ) - - ) - ) + pipeline = Pipeline( + source=transport.receive_queue, + sink=transport.send_queue, + processors=[ + fl, + tma_in, + llm, + fl2, + tma_out, + tts + ], + ) + await pipeline.run_pipeline() transport.transcription_settings["extra"]["endpointing"] = True transport.transcription_settings["extra"]["punctuate"] = True