BaseObserver: added new on_process_frame

This commit is contained in:
Aleix Conchillo Flaqué
2025-08-18 12:29:06 -07:00
parent 13ce02c896
commit 75a6ee839b
4 changed files with 73 additions and 13 deletions

View File

@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added new `on_process_frame()` observer method which makes it possible to know
when a frame is being processed.
- Added new `FrameProcessor.entry_processor()` method. This allows you to access - Added new `FrameProcessor.entry_processor()` method. This allows you to access
the first non-compound processor in a pipeline. the first non-compound processor in a pipeline.

View File

@@ -11,7 +11,6 @@ processors without modifying the pipeline structure. Observers can be used
for logging, debugging, analytics, and monitoring pipeline behavior. for logging, debugging, analytics, and monitoring pipeline behavior.
""" """
from abc import abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from typing_extensions import TYPE_CHECKING from typing_extensions import TYPE_CHECKING
@@ -23,6 +22,28 @@ if TYPE_CHECKING:
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@dataclass
class FrameProcessed:
"""Event data for frame processing in the pipeline.
Represents an event where a frame is being processed by a processor. This
data structure is typically used by observers to track the flow of frames
through the pipeline for logging, debugging, or analytics purposes.
Parameters:
processor: The processor processing the frame.
frame: The frame being processed.
direction: The direction of the frame (e.g., downstream or upstream).
timestamp: The time when the frame was pushed, based on the pipeline clock.
"""
processor: "FrameProcessor"
frame: Frame
direction: "FrameDirection"
timestamp: int
@dataclass @dataclass
class FramePushed: class FramePushed:
"""Event data for frame transfers between processors in the pipeline. """Event data for frame transfers between processors in the pipeline.
@@ -56,7 +77,18 @@ class BaseObserver(BaseObject):
performance analysis, and analytics collection. performance analysis, and analytics collection.
""" """
@abstractmethod async def on_process_frame(self, data: FrameProcessed):
"""Handle the event when a frame is being processed by a processor.
This method should be implemented by subclasses to define specific
behavior (e.g., logging, monitoring, debugging) when a frame is
being processed by a processor.
Args:
data: The event data containing details about the frame processing.
"""
pass
async def on_push_frame(self, data: FramePushed): async def on_push_frame(self, data: FramePushed):
"""Handle the event when a frame is pushed from one processor to another. """Handle the event when a frame is pushed from one processor to another.

View File

@@ -13,11 +13,11 @@ the main pipeline execution.
import asyncio import asyncio
import inspect import inspect
from typing import Dict, List, Optional from typing import Any, Dict, List, Optional
from attr import dataclass from attr import dataclass
from pipecat.observers.base_observer import BaseObserver, FramePushed from pipecat.observers.base_observer import BaseObserver, FrameProcessed, FramePushed
from pipecat.utils.asyncio.task_manager import BaseTaskManager from pipecat.utils.asyncio.task_manager import BaseTaskManager
from pipecat.utils.asyncio.watchdog_queue import WatchdogQueue from pipecat.utils.asyncio.watchdog_queue import WatchdogQueue
@@ -120,14 +120,21 @@ class TaskObserver(BaseObserver):
for proxy in self._proxies.values(): for proxy in self._proxies.values():
await self._task_manager.cancel_task(proxy.task) await self._task_manager.cancel_task(proxy.task)
async def on_process_frame(self, data: FramePushed):
"""Queue frame data for all managed observers.
Args:
data: The frame push event data to distribute to observers.
"""
await self._send_to_proxy(data)
async def on_push_frame(self, data: FramePushed): async def on_push_frame(self, data: FramePushed):
"""Queue frame data for all managed observers. """Queue frame data for all managed observers.
Args: Args:
data: The frame push event data to distribute to observers. data: The frame push event data to distribute to observers.
""" """
for proxy in self._proxies.values(): await self._send_to_proxy(data)
await proxy.queue.put(data)
def _started(self) -> bool: def _started(self) -> bool:
"""Check if the task observer has been started.""" """Check if the task observer has been started."""
@@ -151,6 +158,10 @@ class TaskObserver(BaseObserver):
proxies[observer] = proxy proxies[observer] = proxy
return proxies return proxies
async def _send_to_proxy(self, data: Any):
for proxy in self._proxies.values():
await proxy.queue.put(data)
async def _proxy_task_handler(self, queue: asyncio.Queue, observer: BaseObserver): async def _proxy_task_handler(self, queue: asyncio.Queue, observer: BaseObserver):
"""Handle frame processing for a single observer.""" """Handle frame processing for a single observer."""
on_push_frame_deprecated = False on_push_frame_deprecated = False
@@ -169,11 +180,15 @@ class TaskObserver(BaseObserver):
while True: while True:
data = await queue.get() data = await queue.get()
if on_push_frame_deprecated:
await observer.on_push_frame( if isinstance(data, FramePushed):
data.src, data.dst, data.frame, data.direction, data.timestamp if on_push_frame_deprecated:
) await observer.on_push_frame(
else: data.src, data.dst, data.frame, data.direction, data.timestamp
await observer.on_push_frame(data) )
else:
await observer.on_push_frame(data)
elif isinstance(data, FrameProcessed):
await observer.on_process_frame(data)
queue.task_done() queue.task_done()

View File

@@ -34,7 +34,7 @@ from pipecat.frames.frames import (
SystemFrame, SystemFrame,
) )
from pipecat.metrics.metrics import LLMTokenUsage, MetricsData from pipecat.metrics.metrics import LLMTokenUsage, MetricsData
from pipecat.observers.base_observer import BaseObserver, FramePushed from pipecat.observers.base_observer import BaseObserver, FrameProcessed, FramePushed
from pipecat.processors.metrics.frame_processor_metrics import FrameProcessorMetrics from pipecat.processors.metrics.frame_processor_metrics import FrameProcessorMetrics
from pipecat.utils.asyncio.task_manager import BaseTaskManager from pipecat.utils.asyncio.task_manager import BaseTaskManager
from pipecat.utils.asyncio.watchdog_event import WatchdogEvent from pipecat.utils.asyncio.watchdog_event import WatchdogEvent
@@ -594,6 +594,16 @@ class FrameProcessor(BaseObject):
frame: The frame to process. frame: The frame to process.
direction: The direction of frame flow. direction: The direction of frame flow.
""" """
if self._observer:
timestamp = self._clock.get_time() if self._clock else 0
data = FrameProcessed(
processor=self,
frame=frame,
direction=direction,
timestamp=timestamp,
)
await self._observer.on_process_frame(data)
if isinstance(frame, StartFrame): if isinstance(frame, StartFrame):
await self.__start(frame) await self.__start(frame)
elif isinstance(frame, StartInterruptionFrame): elif isinstance(frame, StartInterruptionFrame):