add notifiers and more frame filters
This commit is contained in:
15
CHANGELOG.md
15
CHANGELOG.md
@@ -9,6 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Added `GatedOpenAILLMContextAggregator`. This aggregator keeps the last
|
||||||
|
received OpenAI LLM context frame and it doesn't let it through until the
|
||||||
|
notifier is notified.
|
||||||
|
|
||||||
|
- Added `WakeNotifierFilter`. This processor expects a list of frame types and
|
||||||
|
will execute a given callback predicate when a frame of any of those type is
|
||||||
|
being processed. If the callback returns true the notifier will be notified.
|
||||||
|
|
||||||
|
- Added `NullFilter`. A null filter doesn't push any frames upstream or
|
||||||
|
downstream. This is usually used to disable one of the pipelines in
|
||||||
|
`ParallelPipeline`.
|
||||||
|
|
||||||
|
- Added `EventNotifier`. This can be used as a very simple synchronization
|
||||||
|
feature between processors.
|
||||||
|
|
||||||
- Added `TavusVideoService`. This is an integration for Tavus digital twins.
|
- Added `TavusVideoService`. This is an integration for Tavus digital twins.
|
||||||
(see https://www.tavus.io/)
|
(see https://www.tavus.io/)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from pipecat.frames.frames import CancelFrame, EndFrame, Frame, StartFrame
|
||||||
|
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContextFrame
|
||||||
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
from pipecat.sync.base_notifier import BaseNotifier
|
||||||
|
|
||||||
|
|
||||||
|
class GatedOpenAILLMContextAggregator(FrameProcessor):
|
||||||
|
"""This aggregator keeps the last received OpenAI LLM context frame and it
|
||||||
|
doesn't let it through until the notifier is notified.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, notifier: BaseNotifier, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._notifier = notifier
|
||||||
|
self._last_context_frame = None
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
|
if isinstance(frame, StartFrame):
|
||||||
|
await self.push_frame(frame)
|
||||||
|
await self._start()
|
||||||
|
if isinstance(frame, (EndFrame, CancelFrame)):
|
||||||
|
await self._stop()
|
||||||
|
await self.push_frame(frame)
|
||||||
|
elif isinstance(frame, OpenAILLMContextFrame):
|
||||||
|
self._last_context_frame = frame
|
||||||
|
else:
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
async def _start(self):
|
||||||
|
self._gate_task = self.get_event_loop().create_task(self._gate_task_handler())
|
||||||
|
|
||||||
|
async def _stop(self):
|
||||||
|
self._gate_task.cancel()
|
||||||
|
await self._gate_task
|
||||||
|
|
||||||
|
async def _gate_task_handler(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
await self._notifier.wait()
|
||||||
|
if self._last_context_frame:
|
||||||
|
await self.push_frame(self._last_context_frame)
|
||||||
|
self._last_context_frame = None
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
break
|
||||||
@@ -4,14 +4,14 @@
|
|||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
#
|
#
|
||||||
|
|
||||||
from typing import List
|
from typing import Tuple, Type
|
||||||
|
|
||||||
from pipecat.frames.frames import AppFrame, ControlFrame, Frame, SystemFrame
|
from pipecat.frames.frames import AppFrame, ControlFrame, Frame, SystemFrame
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
|
||||||
|
|
||||||
class FrameFilter(FrameProcessor):
|
class FrameFilter(FrameProcessor):
|
||||||
def __init__(self, types: List[type]):
|
def __init__(self, types: Tuple[Type[Frame]]):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._types = types
|
self._types = types
|
||||||
|
|
||||||
@@ -20,9 +20,8 @@ class FrameFilter(FrameProcessor):
|
|||||||
#
|
#
|
||||||
|
|
||||||
def _should_passthrough_frame(self, frame):
|
def _should_passthrough_frame(self, frame):
|
||||||
for t in self._types:
|
if isinstance(frame, self._types):
|
||||||
if isinstance(frame, t):
|
return True
|
||||||
return True
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isinstance(frame, AppFrame)
|
isinstance(frame, AppFrame)
|
||||||
|
|||||||
14
src/pipecat/processors/filters/null_filter.py
Normal file
14
src/pipecat/processors/filters/null_filter.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
from pipecat.processors.frame_processor import FrameProcessor
|
||||||
|
|
||||||
|
|
||||||
|
class NullFilter(FrameProcessor):
|
||||||
|
"""This filter doesn't allow passing any frames up or downstream."""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
40
src/pipecat/processors/filters/wake_notifier_filter.py
Normal file
40
src/pipecat/processors/filters/wake_notifier_filter.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
from typing import Awaitable, Callable, Tuple, Type
|
||||||
|
|
||||||
|
from pipecat.frames.frames import Frame
|
||||||
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
|
from pipecat.sync.base_notifier import BaseNotifier
|
||||||
|
|
||||||
|
|
||||||
|
class WakeNotifierFilter(FrameProcessor):
|
||||||
|
"""This processor expects a list of frame types and will execute a given
|
||||||
|
callback predicate when a frame of any of those type is being processed. If
|
||||||
|
the callback returns true the notifier will be notified.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
notifier: BaseNotifier,
|
||||||
|
*,
|
||||||
|
types: Tuple[Type[Frame]],
|
||||||
|
filter: Callable[[Frame], Awaitable[bool]],
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._notifier = notifier
|
||||||
|
self._types = types
|
||||||
|
self._filter = filter
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
|
if isinstance(frame, self._types) and await self._filter(frame):
|
||||||
|
await self._notifier.notify()
|
||||||
|
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
0
src/pipecat/sync/__init__.py
Normal file
0
src/pipecat/sync/__init__.py
Normal file
17
src/pipecat/sync/base_notifier.py
Normal file
17
src/pipecat/sync/base_notifier.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class BaseNotifier(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
async def notify(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def wait(self):
|
||||||
|
pass
|
||||||
21
src/pipecat/sync/event_notifier.py
Normal file
21
src/pipecat/sync/event_notifier.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from pipecat.sync.base_notifier import BaseNotifier
|
||||||
|
|
||||||
|
|
||||||
|
class EventNotifier(BaseNotifier):
|
||||||
|
def __init__(self):
|
||||||
|
self._event = asyncio.Event()
|
||||||
|
|
||||||
|
async def notify(self):
|
||||||
|
self._event.set()
|
||||||
|
|
||||||
|
async def wait(self):
|
||||||
|
await self._event.wait()
|
||||||
|
self._event.clear()
|
||||||
Reference in New Issue
Block a user