pipeline: renamed ParallelTask to SyncParallelPipeline

This commit is contained in:
Aleix Conchillo Flaqué
2024-09-18 22:56:28 -07:00
parent 62e9a33a70
commit 4f1b06e6b2
4 changed files with 51 additions and 35 deletions

View File

@@ -44,6 +44,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- `ParallelTask` has been renamed to `SyncParallelPipeline`. A
`SyncParallelPipeline` is a frame processor that contains a list of different
pipelines to be executed concurrently. The difference between a
`SyncParallelPipeline` and a `ParallelPipeline` is that, given an input frame,
the `SyncParallelPipeline` will wait for all the internal pipelines to
complete. This is achieved by ensuring all the processors in each of the
internal pipelines are synchronous.
- `StartFrame` is back a system frame so we make sure it's processed immediately - `StartFrame` is back a system frame so we make sure it's processed immediately
by all processors. `EndFrame` stays a control frame since it needs to be by all processors. `EndFrame` stays a control frame since it needs to be
ordered allowing the frames in the pipeline to be processed. ordered allowing the frames in the pipeline to be processed.

View File

@@ -14,21 +14,18 @@ from dataclasses import dataclass
from pipecat.frames.frames import ( from pipecat.frames.frames import (
AppFrame, AppFrame,
Frame, Frame,
ImageRawFrame,
LLMFullResponseStartFrame, LLMFullResponseStartFrame,
LLMMessagesFrame, LLMMessagesFrame,
TextFrame TextFrame
) )
from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.sync_parallel_pipeline import SyncParallelPipeline
from pipecat.pipeline.task import PipelineTask from pipecat.pipeline.task import PipelineTask
from pipecat.pipeline.parallel_task import ParallelTask
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from pipecat.processors.aggregators.gated import GatedAggregator
from pipecat.processors.aggregators.llm_response import LLMFullResponseAggregator
from pipecat.processors.aggregators.sentence import SentenceAggregator from pipecat.processors.aggregators.sentence import SentenceAggregator
from pipecat.services.cartesia import CartesiaHttpTTSService
from pipecat.services.openai import OpenAILLMService from pipecat.services.openai import OpenAILLMService
from pipecat.services.elevenlabs import ElevenLabsTTSService
from pipecat.services.fal import FalImageGenService from pipecat.services.fal import FalImageGenService
from pipecat.transports.services.daily import DailyParams, DailyTransport from pipecat.transports.services.daily import DailyParams, DailyTransport
@@ -88,9 +85,9 @@ async def main():
) )
) )
tts = ElevenLabsTTSService( tts = CartesiaHttpTTSService(
api_key=os.getenv("ELEVENLABS_API_KEY"), api_key=os.getenv("CARTESIA_API_KEY"),
voice_id=os.getenv("ELEVENLABS_VOICE_ID"), voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
) )
llm = OpenAILLMService( llm = OpenAILLMService(
@@ -105,24 +102,23 @@ async def main():
key=os.getenv("FAL_KEY"), key=os.getenv("FAL_KEY"),
) )
gated_aggregator = GatedAggregator(
gate_open_fn=lambda frame: isinstance(frame, ImageRawFrame),
gate_close_fn=lambda frame: isinstance(frame, LLMFullResponseStartFrame),
start_open=False
)
sentence_aggregator = SentenceAggregator() sentence_aggregator = SentenceAggregator()
month_prepender = MonthPrepender() month_prepender = MonthPrepender()
llm_full_response_aggregator = LLMFullResponseAggregator()
# With `SyncParallelPipeline` we synchronize audio and images by pushing
# them basically in order (e.g. I1 A1 A1 A1 I2 A2 A2 A2 A2 I3 A3). To do
# that, each pipeline runs concurrently and `SyncParallelPipeline` will
# wait for the input frame to be processed.
#
# Note that `SyncParallelPipeline` requires all processors in it to be
# synchronous (which is the default for most processors).
pipeline = Pipeline([ pipeline = Pipeline([
llm, # LLM llm, # LLM
sentence_aggregator, # Aggregates LLM output into full sentences sentence_aggregator, # Aggregates LLM output into full sentences
ParallelTask( # Run pipelines in parallel aggregating the result SyncParallelPipeline( # Run pipelines in parallel aggregating the result
[month_prepender, tts], # Create "Month: sentence" and output audio [month_prepender, tts], # Create "Month: sentence" and output audio
[llm_full_response_aggregator, imagegen] # Aggregate full LLM response [imagegen] # Generate image
), ),
gated_aggregator, # Queues everything until an image is available
transport.output() # Transport output transport.output() # Transport output
]) ])

View File

@@ -12,17 +12,17 @@ import sys
import tkinter as tk import tkinter as tk
from pipecat.frames.frames import AudioRawFrame, Frame, URLImageRawFrame, LLMMessagesFrame, TextFrame from pipecat.frames.frames import AudioRawFrame, Frame, URLImageRawFrame, LLMMessagesFrame, TextFrame
from pipecat.pipeline.parallel_pipeline import ParallelPipeline
from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.sync_parallel_pipeline import SyncParallelPipeline
from pipecat.pipeline.task import PipelineTask from pipecat.pipeline.task import PipelineTask
from pipecat.processors.aggregators.llm_response import LLMFullResponseAggregator from pipecat.processors.aggregators.sentence import SentenceAggregator
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from pipecat.services.cartesia import CartesiaHttpTTSService
from pipecat.services.openai import OpenAILLMService from pipecat.services.openai import OpenAILLMService
from pipecat.services.elevenlabs import ElevenLabsTTSService
from pipecat.services.fal import FalImageGenService from pipecat.services.fal import FalImageGenService
from pipecat.transports.base_transport import TransportParams from pipecat.transports.base_transport import TransportParams
from pipecat.transports.local.tk import TkLocalTransport from pipecat.transports.local.tk import TkLocalTransport, TkOutputTransport
from loguru import logger from loguru import logger
@@ -60,6 +60,7 @@ async def main():
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.audio = bytearray() self.audio = bytearray()
self.frame = None
async def process_frame(self, frame: Frame, direction: FrameDirection): async def process_frame(self, frame: Frame, direction: FrameDirection):
await super().process_frame(frame, direction) await super().process_frame(frame, direction)
@@ -84,9 +85,10 @@ async def main():
api_key=os.getenv("OPENAI_API_KEY"), api_key=os.getenv("OPENAI_API_KEY"),
model="gpt-4o") model="gpt-4o")
tts = ElevenLabsTTSService( tts = CartesiaHttpTTSService(
api_key=os.getenv("ELEVENLABS_API_KEY"), api_key=os.getenv("CARTESIA_API_KEY"),
voice_id=os.getenv("ELEVENLABS_VOICE_ID")) voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
)
imagegen = FalImageGenService( imagegen = FalImageGenService(
params=FalImageGenService.InputParams( params=FalImageGenService.InputParams(
@@ -95,7 +97,7 @@ async def main():
aiohttp_session=session, aiohttp_session=session,
key=os.getenv("FAL_KEY")) key=os.getenv("FAL_KEY"))
aggregator = LLMFullResponseAggregator() sentence_aggregator = SentenceAggregator()
description = ImageDescription() description = ImageDescription()
@@ -103,12 +105,22 @@ async def main():
image_grabber = ImageGrabber() image_grabber = ImageGrabber()
# With `SyncParallelPipeline` we synchronize audio and images by
# pushing them basically in order (e.g. I1 A1 A1 A1 I2 A2 A2 A2 A2
# I3 A3). To do that, each pipeline runs concurrently and
# `SyncParallelPipeline` will wait for the input frame to be
# processed.
#
# Note that `SyncParallelPipeline` requires all processors in it to
# be synchronous (which is the default for most processors).
pipeline = Pipeline([ pipeline = Pipeline([
llm, llm, # LLM
aggregator, sentence_aggregator, # Aggregates LLM output into full sentences
description, description, # Store sentence
ParallelPipeline([tts, audio_grabber], SyncParallelPipeline(
[imagegen, image_grabber]) [tts, audio_grabber], # Generate and store audio for the given sentence
[imagegen, image_grabber] # Generate and storeimage for the given sentence
)
]) ])
task = PipelineTask(pipeline) task = PipelineTask(pipeline)

View File

@@ -49,12 +49,12 @@ class Sink(FrameProcessor):
await self._down_queue.put(frame) await self._down_queue.put(frame)
class ParallelTask(BasePipeline): class SyncParallelPipeline(BasePipeline):
def __init__(self, *args): def __init__(self, *args):
super().__init__() super().__init__()
if len(args) == 0: if len(args) == 0:
raise Exception(f"ParallelTask needs at least one argument") raise Exception(f"SyncParallelPipeline needs at least one argument")
self._sinks = [] self._sinks = []
self._sources = [] self._sources = []
@@ -66,7 +66,7 @@ class ParallelTask(BasePipeline):
logger.debug(f"Creating {self} pipelines") logger.debug(f"Creating {self} pipelines")
for processors in args: for processors in args:
if not isinstance(processors, list): if not isinstance(processors, list):
raise TypeError(f"ParallelTask argument {processors} is not a list") raise TypeError(f"SyncParallelPipeline argument {processors} is not a list")
# We add a source at the beginning of the pipeline and a sink at the end. # We add a source at the beginning of the pipeline and a sink at the end.
source = Source(self._up_queue) source = Source(self._up_queue)