processors: remove block_on_frames and add pause_processing_frames() instead

This commit is contained in:
Aleix Conchillo Flaqué
2024-11-06 14:20:25 -08:00
parent 7071482583
commit 865768039b
5 changed files with 39 additions and 23 deletions

View File

@@ -14,12 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`FrameProcessor.queue_frame()` on the next processor (upstream or downstream) `FrameProcessor.queue_frame()` on the next processor (upstream or downstream)
and the frame will be internally queued (except system frames). Then, the and the frame will be internally queued (except system frames). Then, the
queued frames will get processed. With this input queue it is also possible queued frames will get processed. With this input queue it is also possible
for FrameProcessors to block processing more frames via a list of blocking for FrameProcessors to block processing more frames by calling
frames. For example, some TTS services now block processing more frames if `FrameProcessor.pause_processing_frames()`. The way to resume processing
they see a `TTSSpeakFrame` or a `LLMFullResponseEndFrame` until the bot has frames is by calling `FrameProcessor.resume_processing_frames()`.
stopped speaking. This makes sure we don't mix audio from different
sentences. The way to resume processing frames is by calling
`FrameProcessor.resume_processing_frames()`.
- Added audio filter `NoisereduceFilter`. - Added audio filter `NoisereduceFilter`.

View File

@@ -37,7 +37,6 @@ class FrameProcessor:
self, self,
*, *,
name: str | None = None, name: str | None = None,
block_on_frames: tuple = (),
metrics: FrameProcessorMetrics | None = None, metrics: FrameProcessorMetrics | None = None,
loop: asyncio.AbstractEventLoop | None = None, loop: asyncio.AbstractEventLoop | None = None,
**kwargs, **kwargs,
@@ -65,10 +64,10 @@ class FrameProcessor:
self._metrics.set_processor_name(self.name) self._metrics.set_processor_name(self.name)
# Processors have an input queue. The input queue will be processed # Processors have an input queue. The input queue will be processed
# immediately (default) or it will block if one of the `block_on_frames` # immediately (default) or it will block if `pause_processing_frames()`
# is found. To resume processing frames we need to call # is called. To resume processing frames we need to call
# `resume_processing_frames()`. # `resume_processing_frames()`.
self._block_on_frames = block_on_frames self.__should_block_frames = False
self.__create_input_task() self.__create_input_task()
# Every processor in Pipecat should only output frames from a single # Every processor in Pipecat should only output frames from a single
@@ -170,8 +169,12 @@ class FrameProcessor:
# We queue everything else. # We queue everything else.
await self.__input_queue.put((frame, direction, callback)) await self.__input_queue.put((frame, direction, callback))
async def pause_processing_frames(self):
self.__should_block_frames = True
async def resume_processing_frames(self): async def resume_processing_frames(self):
self.__input_event.set() self.__input_event.set()
self.__should_block_frames = False
async def process_frame(self, frame: Frame, direction: FrameDirection): async def process_frame(self, frame: Frame, direction: FrameDirection):
if isinstance(frame, StartFrame): if isinstance(frame, StartFrame):
@@ -257,13 +260,11 @@ class FrameProcessor:
async def __input_frame_task_handler(self): async def __input_frame_task_handler(self):
running = True running = True
should_block_frames = False
while running: while running:
try: try:
if should_block_frames: if self.__should_block_frames:
await self.__input_event.wait() await self.__input_event.wait()
self.__input_event.clear() self.__input_event.clear()
should_block_frames = False
(frame, direction, callback) = await self.__input_queue.get() (frame, direction, callback) = await self.__input_queue.get()
@@ -274,11 +275,6 @@ class FrameProcessor:
if callback: if callback:
await callback(self, frame, direction) await callback(self, frame, direction)
# Check if we should block incoming frames from now on (until we
# resume processing).
if isinstance(frame, self._block_on_frames):
should_block_frames = True
running = not isinstance(frame, EndFrame) running = not isinstance(frame, EndFrame)
self.__input_queue.task_done() self.__input_queue.task_done()

View File

@@ -15,7 +15,6 @@ from loguru import logger
from pipecat.audio.utils import calculate_audio_volume, exp_smoothing from pipecat.audio.utils import calculate_audio_volume, exp_smoothing
from pipecat.frames.frames import ( from pipecat.frames.frames import (
AudioRawFrame, AudioRawFrame,
BotStoppedSpeakingFrame,
CancelFrame, CancelFrame,
EndFrame, EndFrame,
ErrorFrame, ErrorFrame,
@@ -294,8 +293,6 @@ class TTSService(AIService):
await self._process_text_frame(frame) await self._process_text_frame(frame)
elif isinstance(frame, StartInterruptionFrame): elif isinstance(frame, StartInterruptionFrame):
await self._handle_interruption(frame, direction) await self._handle_interruption(frame, direction)
elif isinstance(frame, BotStoppedSpeakingFrame):
await self.resume_processing_frames()
elif isinstance(frame, (LLMFullResponseEndFrame, EndFrame)): elif isinstance(frame, (LLMFullResponseEndFrame, EndFrame)):
sentence = self._current_sentence sentence = self._current_sentence
self._current_sentence = "" self._current_sentence = ""

View File

@@ -14,6 +14,7 @@ from loguru import logger
from pydantic.main import BaseModel from pydantic.main import BaseModel
from pipecat.frames.frames import ( from pipecat.frames.frames import (
BotStoppedSpeakingFrame,
CancelFrame, CancelFrame,
EndFrame, EndFrame,
ErrorFrame, ErrorFrame,
@@ -102,7 +103,6 @@ class CartesiaTTSService(WordTTSService):
aggregate_sentences=True, aggregate_sentences=True,
push_text_frames=False, push_text_frames=False,
sample_rate=sample_rate, sample_rate=sample_rate,
block_on_frames=(TTSSpeakFrame, LLMFullResponseEndFrame),
**kwargs, **kwargs,
) )
@@ -261,6 +261,19 @@ class CartesiaTTSService(WordTTSService):
except Exception as e: except Exception as e:
logger.error(f"{self} exception: {e}") logger.error(f"{self} exception: {e}")
async def process_frame(self, frame: Frame, direction: FrameDirection):
await super().process_frame(frame, direction)
# If we received a TTSSpeakFrame and the LLM response included text (it
# might be that it's only a function calling response) we pause
# processing more frames until we receive a BotStoppedSpeakingFrame.
if isinstance(frame, TTSSpeakFrame):
await self.pause_processing_frames()
elif isinstance(frame, LLMFullResponseEndFrame) and self._context_id:
await self.pause_processing_frames()
elif isinstance(frame, BotStoppedSpeakingFrame):
await self.resume_processing_frames()
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]: async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
logger.debug(f"Generating TTS: [{text}]") logger.debug(f"Generating TTS: [{text}]")

View File

@@ -17,6 +17,7 @@ from loguru import logger
from pydantic.main import BaseModel from pydantic.main import BaseModel
from pipecat.frames.frames import ( from pipecat.frames.frames import (
BotStoppedSpeakingFrame,
CancelFrame, CancelFrame,
EndFrame, EndFrame,
ErrorFrame, ErrorFrame,
@@ -125,7 +126,6 @@ class PlayHTTTSService(TTSService):
): ):
super().__init__( super().__init__(
sample_rate=sample_rate, sample_rate=sample_rate,
block_on_frames=(TTSSpeakFrame, LLMFullResponseEndFrame),
**kwargs, **kwargs,
) )
@@ -266,6 +266,19 @@ class PlayHTTTSService(TTSService):
except Exception as e: except Exception as e:
logger.error(f"{self} exception in receive task: {e}") logger.error(f"{self} exception in receive task: {e}")
async def process_frame(self, frame: Frame, direction: FrameDirection):
await super().process_frame(frame, direction)
# If we received a TTSSpeakFrame and the LLM response included text (it
# might be that it's only a function calling response) we pause
# processing more frames until we receive a BotStoppedSpeakingFrame.
if isinstance(frame, TTSSpeakFrame):
await self.pause_processing_frames()
elif isinstance(frame, LLMFullResponseEndFrame) and self._request_id:
await self.pause_processing_frames()
elif isinstance(frame, BotStoppedSpeakingFrame):
await self.resume_processing_frames()
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]: async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
logger.debug(f"Generating TTS: [{text}]") logger.debug(f"Generating TTS: [{text}]")