processors: add processors_with_metrics() and can_generate_metrics()

This commit is contained in:
Aleix Conchillo Flaqué
2024-06-07 13:38:21 -07:00
parent 3e019fb512
commit 1afe6901d9
15 changed files with 51 additions and 16 deletions

View File

@@ -12,9 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a new `FunctionFilter`. This filter will let you filter frames based on - Added a new `FunctionFilter`. This filter will let you filter frames based on
a given function, except system messages which should never be filtered. a given function, except system messages which should never be filtered.
- Added `FrameProcessor.can_generate_metrics()` method to indicate if a
processor can generate metrics. In the future this might get an extra argument
to ask for a specific type of metric.
- Added `BasePipeline`. All pipeline classes should be based on this class. All - Added `BasePipeline`. All pipeline classes should be based on this class. All
subclasses should implement a `services()` method that returns a list of subclasses should implement a `processors_with_metrics()` method that returns
all `AIServices` in the pipeline. a list of all `FrameProcessor`s in the pipeline that can generate metrics.
- Added `enable_metrics` to `PipelineParams`. - Added `enable_metrics` to `PipelineParams`.

View File

@@ -17,5 +17,5 @@ class BasePipeline(FrameProcessor):
super().__init__() super().__init__()
@abstractmethod @abstractmethod
def services(self) -> List[FrameProcessor]: def processors_with_metrics(self) -> List[FrameProcessor]:
pass pass

View File

@@ -89,8 +89,8 @@ class ParallelPipeline(BasePipeline):
# BasePipeline # BasePipeline
# #
def services(self) -> List[FrameProcessor]: def processors_with_metrics(self) -> List[FrameProcessor]:
return list(chain.from_iterable(p.services() for p in self._pipelines)) return list(chain.from_iterable(p.processors_with_metrics() for p in self._pipelines))
# #
# Frame processor # Frame processor

View File

@@ -85,8 +85,8 @@ class ParallelTask(BasePipeline):
# BasePipeline # BasePipeline
# #
def services(self) -> List[FrameProcessor]: def processors_with_metrics(self) -> List[FrameProcessor]:
return list(chain.from_iterable(p.services() for p in self._pipelines)) return list(chain.from_iterable(p.processors_with_metrics() for p in self._pipelines))
# #
# Frame processor # Frame processor

View File

@@ -4,12 +4,13 @@
# SPDX-License-Identifier: BSD 2-Clause License # SPDX-License-Identifier: BSD 2-Clause License
# #
from itertools import chain
from typing import Callable, Coroutine, List from typing import Callable, Coroutine, List
from pipecat.frames.frames import Frame, MetricsFrame, StartFrame from pipecat.frames.frames import Frame, MetricsFrame, StartFrame
from pipecat.pipeline.base_pipeline import BasePipeline from pipecat.pipeline.base_pipeline import BasePipeline
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from pipecat.services.ai_services import AIService
class PipelineSource(FrameProcessor): class PipelineSource(FrameProcessor):
@@ -61,13 +62,13 @@ class Pipeline(BasePipeline):
# BasePipeline # BasePipeline
# #
def services(self): def processors_with_metrics(self):
services = [] services = []
for p in self._processors: for p in self._processors:
if isinstance(p, AIService): if isinstance(p, BasePipeline):
services += p.processors_with_metrics()
elif p.can_generate_metrics():
services.append(p) services.append(p)
elif isinstance(p, Pipeline):
services += p.services()
return services return services
# #
@@ -99,7 +100,7 @@ class Pipeline(BasePipeline):
prev = curr prev = curr
async def _send_initial_metrics(self): async def _send_initial_metrics(self):
services = self.services() processors = self.processors_with_metrics()
ttfb = dict(zip([s.name for s in services], [0] * len(services))) ttfb = dict(zip([p.name for p in processors], [0] * len(processors)))
frame = MetricsFrame(ttfb=ttfb) frame = MetricsFrame(ttfb=ttfb)
await self._source.process_frame(frame, FrameDirection.DOWNSTREAM) await self._source.process_frame(frame, FrameDirection.DOWNSTREAM)

View File

@@ -44,6 +44,9 @@ class FrameProcessor:
def metrics_enabled(self): def metrics_enabled(self):
return self._enable_metrics return self._enable_metrics
def can_generate_metrics(self) -> bool:
return False
async def start_ttfb_metrics(self): async def start_ttfb_metrics(self):
if self.metrics_enabled: if self.metrics_enabled:
self._start_ttfb_time = time.time() self._start_ttfb_time = time.time()

View File

@@ -49,6 +49,9 @@ class AnthropicLLMService(LLMService):
self._model = model self._model = model
self._max_tokens = max_tokens self._max_tokens = max_tokens
def can_generate_metrics(self) -> bool:
return True
def _get_messages_from_openai_context( def _get_messages_from_openai_context(
self, context: OpenAILLMContext): self, context: OpenAILLMContext):
openai_messages = context.get_messages() openai_messages = context.get_messages()

View File

@@ -44,6 +44,9 @@ class AzureTTSService(TTSService):
) )
self._voice = voice self._voice = voice
def can_generate_metrics(self) -> bool:
return True
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

@@ -39,6 +39,9 @@ class CartesiaTTSService(TTSService):
except Exception as e: except Exception as e:
logger.error(f"Cartesia initialization error: {e}") logger.error(f"Cartesia initialization error: {e}")
def can_generate_metrics(self) -> bool:
return True
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

@@ -29,6 +29,9 @@ class DeepgramTTSService(TTSService):
self._api_key = api_key self._api_key = api_key
self._aiohttp_session = aiohttp_session self._aiohttp_session = aiohttp_session
def can_generate_metrics(self) -> bool:
return True
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

@@ -31,6 +31,9 @@ class ElevenLabsTTSService(TTSService):
self._aiohttp_session = aiohttp_session self._aiohttp_session = aiohttp_session
self._model = model self._model = model
def can_generate_metrics(self) -> bool:
return True
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

@@ -47,6 +47,9 @@ class GoogleLLMService(LLMService):
gai.configure(api_key=api_key) gai.configure(api_key=api_key)
self._client = gai.GenerativeModel(model) self._client = gai.GenerativeModel(model)
def can_generate_metrics(self) -> bool:
return True
def _get_messages_from_openai_context( def _get_messages_from_openai_context(
self, context: OpenAILLMContext) -> List[glm.Content]: self, context: OpenAILLMContext) -> List[glm.Content]:
openai_messages = context.get_messages() openai_messages = context.get_messages()

View File

@@ -76,6 +76,9 @@ class BaseOpenAILLMService(LLMService):
def create_client(self, api_key=None, base_url=None): def create_client(self, api_key=None, base_url=None):
return AsyncOpenAI(api_key=api_key, base_url=base_url) return AsyncOpenAI(api_key=api_key, base_url=base_url)
def can_generate_metrics(self) -> bool:
return True
async def _stream_chat_completions( async def _stream_chat_completions(
self, context: OpenAILLMContext self, context: OpenAILLMContext
) -> AsyncStream[ChatCompletionChunk]: ) -> AsyncStream[ChatCompletionChunk]:
@@ -298,6 +301,9 @@ class OpenAITTSService(TTSService):
self._client = AsyncOpenAI(api_key=api_key) self._client = AsyncOpenAI(api_key=api_key)
def can_generate_metrics(self) -> bool:
return True
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

@@ -43,8 +43,8 @@ class PlayHTTTSService(TTSService):
quality="higher", quality="higher",
format=Format.FORMAT_WAV) format=Format.FORMAT_WAV)
def __del__(self): def can_generate_metrics(self) -> bool:
self._client.close() return True
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

@@ -56,6 +56,9 @@ class WhisperSTTService(STTService):
self._model: WhisperModel | None = None self._model: WhisperModel | None = None
self._load() self._load()
def can_generate_metrics(self) -> bool:
return True
def _load(self): def _load(self):
"""Loads the Whisper model. Note that if this is the first time """Loads the Whisper model. Note that if this is the first time
this model is being run, it will take time to download.""" this model is being run, it will take time to download."""