Merge pull request #474 from pipecat-ai/ruthless/improve-metrics-types-2
Ruthless/improve metrics types 2
This commit is contained in:
@@ -10,6 +10,7 @@ import os
|
||||
import sys
|
||||
|
||||
from pipecat.frames.frames import Frame, LLMMessagesFrame, MetricsFrame
|
||||
from pipecat.metrics.metrics import TTFBMetricsData, ProcessingMetricsData, LLMUsageMetricsData, TTSUsageMetricsData
|
||||
from pipecat.pipeline.pipeline import Pipeline
|
||||
from pipecat.pipeline.runner import PipelineRunner
|
||||
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||
@@ -37,8 +38,19 @@ logger.add(sys.stderr, level="DEBUG")
|
||||
class MetricsLogger(FrameProcessor):
|
||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||
if isinstance(frame, MetricsFrame):
|
||||
print(
|
||||
f"!!! MetricsFrame: {frame}, ttfb: {frame.ttfb}, processing: {frame.processing}, tokens: {frame.tokens}, characters: {frame.characters}")
|
||||
for d in frame.data:
|
||||
if isinstance(d, TTFBMetricsData):
|
||||
print(f"!!! MetricsFrame: {frame}, ttfb: {d.value}")
|
||||
elif isinstance(d, ProcessingMetricsData):
|
||||
print(f"!!! MetricsFrame: {frame}, processing: {d.value}")
|
||||
elif isinstance(d, LLMUsageMetricsData):
|
||||
tokens = d.value
|
||||
print(
|
||||
f"!!! MetricsFrame: {frame}, tokens: {
|
||||
tokens.prompt_tokens}, characters: {
|
||||
tokens.completion_tokens}")
|
||||
elif isinstance(d, TTSUsageMetricsData):
|
||||
print(f"!!! MetricsFrame: {frame}, characters: {d.value}")
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
# SPDX-License-Identifier: BSD 2-Clause License
|
||||
#
|
||||
|
||||
from typing import Any, List, Mapping, Optional, Tuple
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from pipecat.clocks.base_clock import BaseClock
|
||||
from pipecat.metrics.metrics import MetricsData
|
||||
from pipecat.transcriptions.language import Language
|
||||
from pipecat.utils.time import nanoseconds_to_str
|
||||
from pipecat.utils.utils import obj_count, obj_id
|
||||
@@ -333,10 +334,8 @@ class BotInterruptionFrame(SystemFrame):
|
||||
class MetricsFrame(SystemFrame):
|
||||
"""Emitted by processor that can compute metrics like latencies.
|
||||
"""
|
||||
ttfb: List[Mapping[str, Any]] | None = None
|
||||
processing: List[Mapping[str, Any]] | None = None
|
||||
tokens: List[Mapping[str, Any]] | None = None
|
||||
characters: List[Mapping[str, Any]] | None = None
|
||||
data: List[MetricsData]
|
||||
|
||||
|
||||
#
|
||||
# Control frames
|
||||
|
||||
0
src/pipecat/metrics/__init__.py
Normal file
0
src/pipecat/metrics/__init__.py
Normal file
31
src/pipecat/metrics/metrics.py
Normal file
31
src/pipecat/metrics/metrics.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class MetricsData(BaseModel):
|
||||
processor: str
|
||||
model: Optional[str] = None
|
||||
|
||||
|
||||
class TTFBMetricsData(MetricsData):
|
||||
value: float
|
||||
|
||||
|
||||
class ProcessingMetricsData(MetricsData):
|
||||
value: float
|
||||
|
||||
|
||||
class LLMTokenUsage(BaseModel):
|
||||
prompt_tokens: int
|
||||
completion_tokens: int
|
||||
total_tokens: int
|
||||
cache_read_input_tokens: Optional[int] = None
|
||||
cache_creation_input_tokens: Optional[int] = None
|
||||
|
||||
|
||||
class LLMUsageMetricsData(MetricsData):
|
||||
value: LLMTokenUsage
|
||||
|
||||
|
||||
class TTSUsageMetricsData(MetricsData):
|
||||
value: int
|
||||
@@ -20,6 +20,7 @@ from pipecat.frames.frames import (
|
||||
MetricsFrame,
|
||||
StartFrame,
|
||||
StopTaskFrame)
|
||||
from pipecat.metrics.metrics import TTFBMetricsData, ProcessingMetricsData
|
||||
from pipecat.pipeline.base_pipeline import BasePipeline
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
from pipecat.utils.utils import obj_count, obj_id
|
||||
@@ -118,9 +119,11 @@ class PipelineTask:
|
||||
|
||||
def _initial_metrics_frame(self) -> MetricsFrame:
|
||||
processors = self._pipeline.processors_with_metrics()
|
||||
ttfb = [{"processor": p.name, "value": 0.0} for p in processors]
|
||||
processing = [{"processor": p.name, "value": 0.0} for p in processors]
|
||||
return MetricsFrame(ttfb=ttfb, processing=processing)
|
||||
data = []
|
||||
for p in processors:
|
||||
data.append(TTFBMetricsData(processor=p.name, value=0.0))
|
||||
data.append(ProcessingMetricsData(processor=p.name, value=0.0))
|
||||
return MetricsFrame(data=data)
|
||||
|
||||
async def _process_down_queue(self):
|
||||
self._clock.start()
|
||||
|
||||
@@ -19,6 +19,13 @@ from pipecat.frames.frames import (
|
||||
StartInterruptionFrame,
|
||||
StopInterruptionFrame,
|
||||
SystemFrame)
|
||||
from pipecat.metrics.metrics import (
|
||||
LLMTokenUsage,
|
||||
LLMUsageMetricsData,
|
||||
MetricsData,
|
||||
ProcessingMetricsData,
|
||||
TTFBMetricsData,
|
||||
TTSUsageMetricsData)
|
||||
from pipecat.utils.utils import obj_count, obj_id
|
||||
|
||||
from loguru import logger
|
||||
@@ -31,11 +38,20 @@ class FrameDirection(Enum):
|
||||
|
||||
class FrameProcessorMetrics:
|
||||
def __init__(self, name: str):
|
||||
self._name = name
|
||||
self._core_metrics_data = MetricsData(processor=name)
|
||||
self._start_ttfb_time = 0
|
||||
self._start_processing_time = 0
|
||||
self._should_report_ttfb = True
|
||||
|
||||
def _processor_name(self):
|
||||
return self._core_metrics_data.processor
|
||||
|
||||
def _model_name(self):
|
||||
return self._core_metrics_data.model
|
||||
|
||||
def set_core_metrics_data(self, data: MetricsData):
|
||||
self._core_metrics_data = data
|
||||
|
||||
async def start_ttfb_metrics(self, report_only_initial_ttfb):
|
||||
if self._should_report_ttfb:
|
||||
self._start_ttfb_time = time.time()
|
||||
@@ -46,13 +62,13 @@ class FrameProcessorMetrics:
|
||||
return None
|
||||
|
||||
value = time.time() - self._start_ttfb_time
|
||||
logger.debug(f"{self._name} TTFB: {value}")
|
||||
ttfb = {
|
||||
"processor": self._name,
|
||||
"value": value
|
||||
}
|
||||
logger.debug(f"{self._processor_name()} TTFB: {value}")
|
||||
ttfb = TTFBMetricsData(
|
||||
processor=self._processor_name(),
|
||||
value=value,
|
||||
model=self._model_name())
|
||||
self._start_ttfb_time = 0
|
||||
return MetricsFrame(ttfb=[ttfb])
|
||||
return MetricsFrame(data=[ttfb])
|
||||
|
||||
async def start_processing_metrics(self):
|
||||
self._start_processing_time = time.time()
|
||||
@@ -62,26 +78,28 @@ class FrameProcessorMetrics:
|
||||
return None
|
||||
|
||||
value = time.time() - self._start_processing_time
|
||||
logger.debug(f"{self._name} processing time: {value}")
|
||||
processing = {
|
||||
"processor": self._name,
|
||||
"value": value
|
||||
}
|
||||
logger.debug(f"{self._processor_name()} processing time: {value}")
|
||||
processing = ProcessingMetricsData(
|
||||
processor=self._processor_name(), value=value, model=self._model_name())
|
||||
self._start_processing_time = 0
|
||||
return MetricsFrame(processing=[processing])
|
||||
return MetricsFrame(data=[processing])
|
||||
|
||||
async def start_llm_usage_metrics(self, tokens: dict):
|
||||
async def start_llm_usage_metrics(self, tokens: LLMTokenUsage):
|
||||
logger.debug(
|
||||
f"{self._name} prompt tokens: {tokens['prompt_tokens']}, completion tokens: {tokens['completion_tokens']}")
|
||||
return MetricsFrame(tokens=[tokens])
|
||||
f"{self._processor_name()} prompt tokens: {tokens.prompt_tokens}, completion tokens: {tokens.completion_tokens}")
|
||||
value = LLMUsageMetricsData(
|
||||
processor=self._processor_name(),
|
||||
model=self._model_name(),
|
||||
value=tokens)
|
||||
return MetricsFrame(data=[value])
|
||||
|
||||
async def start_tts_usage_metrics(self, text: str):
|
||||
characters = {
|
||||
"processor": self._name,
|
||||
"value": len(text),
|
||||
}
|
||||
logger.debug(f"{self._name} usage characters: {characters['value']}")
|
||||
return MetricsFrame(characters=[characters])
|
||||
characters = TTSUsageMetricsData(
|
||||
processor=self._processor_name(),
|
||||
model=self._model_name(),
|
||||
value=len(text))
|
||||
logger.debug(f"{self._processor_name()} usage characters: {characters.value}")
|
||||
return MetricsFrame(data=[characters])
|
||||
|
||||
|
||||
class FrameProcessor:
|
||||
@@ -140,6 +158,9 @@ class FrameProcessor:
|
||||
def can_generate_metrics(self) -> bool:
|
||||
return False
|
||||
|
||||
def set_core_metrics_data(self, data: MetricsData):
|
||||
self._metrics.set_core_metrics_data(data)
|
||||
|
||||
async def start_ttfb_metrics(self):
|
||||
if self.can_generate_metrics() and self.metrics_enabled:
|
||||
await self._metrics.start_ttfb_metrics(self._report_only_initial_ttfb)
|
||||
@@ -160,7 +181,7 @@ class FrameProcessor:
|
||||
if frame:
|
||||
await self.push_frame(frame)
|
||||
|
||||
async def start_llm_usage_metrics(self, tokens: dict):
|
||||
async def start_llm_usage_metrics(self, tokens: LLMTokenUsage):
|
||||
if self.can_generate_metrics() and self.usage_metrics_enabled:
|
||||
frame = await self._metrics.start_llm_usage_metrics(tokens)
|
||||
if frame:
|
||||
|
||||
@@ -32,6 +32,7 @@ from pipecat.frames.frames import (
|
||||
UserImageRequestFrame,
|
||||
VisionImageRawFrame
|
||||
)
|
||||
from pipecat.metrics.metrics import MetricsData
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
from pipecat.transcriptions.language import Language
|
||||
from pipecat.utils.audio import calculate_audio_volume
|
||||
@@ -46,6 +47,15 @@ from loguru import logger
|
||||
class AIService(FrameProcessor):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._model_name: str = ""
|
||||
|
||||
@property
|
||||
def model_name(self) -> str:
|
||||
return self._model_name
|
||||
|
||||
def set_model_name(self, model: str):
|
||||
self._model_name = model
|
||||
self.set_core_metrics_data(MetricsData(processor=self.name, model=self._model_name))
|
||||
|
||||
async def start(self, frame: StartFrame):
|
||||
pass
|
||||
@@ -158,7 +168,7 @@ class TTSService(AIService):
|
||||
|
||||
@abstractmethod
|
||||
async def set_model(self, model: str):
|
||||
pass
|
||||
self.set_model_name(model)
|
||||
|
||||
@abstractmethod
|
||||
async def set_voice(self, voice: str):
|
||||
@@ -367,7 +377,7 @@ class STTService(AIService):
|
||||
|
||||
@abstractmethod
|
||||
async def set_model(self, model: str):
|
||||
pass
|
||||
self.set_model_name(model)
|
||||
|
||||
@abstractmethod
|
||||
async def set_language(self, language: Language):
|
||||
|
||||
@@ -29,6 +29,7 @@ from pipecat.frames.frames import (
|
||||
FunctionCallInProgressFrame,
|
||||
StartInterruptionFrame
|
||||
)
|
||||
from pipecat.metrics.metrics import LLMTokenUsage
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.ai_services import LLMService
|
||||
from pipecat.processors.aggregators.openai_llm_context import (
|
||||
@@ -84,7 +85,7 @@ class AnthropicLLMService(LLMService):
|
||||
**kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._client = AsyncAnthropic(api_key=api_key)
|
||||
self._model = model
|
||||
self.set_model_name(model)
|
||||
self._max_tokens = max_tokens
|
||||
self._enable_prompt_caching_beta = enable_prompt_caching_beta
|
||||
|
||||
@@ -137,7 +138,7 @@ class AnthropicLLMService(LLMService):
|
||||
tools=context.tools or [],
|
||||
system=context.system,
|
||||
messages=messages,
|
||||
model=self._model,
|
||||
model=self.model_name,
|
||||
max_tokens=self._max_tokens,
|
||||
stream=True)
|
||||
|
||||
@@ -231,7 +232,7 @@ class AnthropicLLMService(LLMService):
|
||||
context = AnthropicLLMContext.from_image_frame(frame)
|
||||
elif isinstance(frame, LLMModelUpdateFrame):
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self._model = frame.model
|
||||
self.set_model_name(frame.model)
|
||||
elif isinstance(frame, LLMEnablePromptCachingFrame):
|
||||
logger.debug(f"Setting enable prompt caching to: [{frame.enable}]")
|
||||
self._enable_prompt_caching_beta = frame.enable
|
||||
@@ -251,15 +252,13 @@ class AnthropicLLMService(LLMService):
|
||||
cache_creation_input_tokens: int,
|
||||
cache_read_input_tokens: int):
|
||||
if prompt_tokens or completion_tokens or cache_creation_input_tokens or cache_read_input_tokens:
|
||||
tokens = {
|
||||
"processor": self.name,
|
||||
"model": self._model,
|
||||
"prompt_tokens": prompt_tokens,
|
||||
"completion_tokens": completion_tokens,
|
||||
"cache_creation_input_tokens": cache_creation_input_tokens,
|
||||
"cache_read_input_tokens": cache_read_input_tokens,
|
||||
"total_tokens": prompt_tokens + completion_tokens
|
||||
}
|
||||
tokens = LLMTokenUsage(
|
||||
prompt_tokens=prompt_tokens,
|
||||
completion_tokens=completion_tokens,
|
||||
cache_creation_input_tokens=cache_creation_input_tokens,
|
||||
cache_read_input_tokens=cache_read_input_tokens,
|
||||
total_tokens=prompt_tokens + completion_tokens
|
||||
)
|
||||
await self.start_llm_usage_metrics(tokens)
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ from pipecat.frames.frames import (
|
||||
TTSStoppedFrame,
|
||||
TranscriptionFrame,
|
||||
URLImageRawFrame)
|
||||
from pipecat.metrics.metrics import TTSUsageMetricsData
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.ai_services import STTService, TTSService, ImageGenService
|
||||
from pipecat.services.openai import BaseOpenAILLMService
|
||||
from pipecat.utils.time import time_now_iso8601
|
||||
@@ -190,7 +192,7 @@ class AzureImageGenServiceREST(ImageGenService):
|
||||
self._api_key = api_key
|
||||
self._azure_endpoint = endpoint
|
||||
self._api_version = api_version
|
||||
self._model = model
|
||||
self.set_model_name(model)
|
||||
self._image_size = image_size
|
||||
self._aiohttp_session = aiohttp_session
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ class CartesiaTTSService(AsyncWordTTSService):
|
||||
self._cartesia_version = cartesia_version
|
||||
self._url = url
|
||||
self._voice_id = voice_id
|
||||
self._model_id = model_id
|
||||
self.set_model_name(model_id)
|
||||
self._output_format = {
|
||||
"container": "raw",
|
||||
"encoding": encoding,
|
||||
@@ -105,8 +105,8 @@ class CartesiaTTSService(AsyncWordTTSService):
|
||||
return True
|
||||
|
||||
async def set_model(self, model: str):
|
||||
await super().set_model(model)
|
||||
logger.debug(f"Switching TTS model to: [{model}]")
|
||||
self._model_id = model
|
||||
|
||||
async def set_voice(self, voice: str):
|
||||
logger.debug(f"Switching TTS voice to: [{voice}]")
|
||||
@@ -155,6 +155,11 @@ class CartesiaTTSService(AsyncWordTTSService):
|
||||
except Exception as e:
|
||||
logger.error(f"{self} error closing websocket: {e}")
|
||||
|
||||
def _get_websocket(self):
|
||||
if self._websocket:
|
||||
return self._websocket
|
||||
raise Exception("Websocket not connected")
|
||||
|
||||
async def _handle_interruption(self, frame: StartInterruptionFrame, direction: FrameDirection):
|
||||
await super()._handle_interruption(frame, direction)
|
||||
await self.stop_all_metrics()
|
||||
@@ -169,7 +174,7 @@ class CartesiaTTSService(AsyncWordTTSService):
|
||||
"transcript": "",
|
||||
"continue": False,
|
||||
"context_id": self._context_id,
|
||||
"model_id": self._model_id,
|
||||
"model_id": self.model_name,
|
||||
"voice": {
|
||||
"mode": "id",
|
||||
"id": self._voice_id
|
||||
@@ -182,7 +187,7 @@ class CartesiaTTSService(AsyncWordTTSService):
|
||||
|
||||
async def _receive_task_handler(self):
|
||||
try:
|
||||
async for message in self._websocket:
|
||||
async for message in self._get_websocket():
|
||||
msg = json.loads(message)
|
||||
if not msg or msg["context_id"] != self._context_id:
|
||||
continue
|
||||
@@ -235,7 +240,7 @@ class CartesiaTTSService(AsyncWordTTSService):
|
||||
"transcript": text + " ",
|
||||
"continue": True,
|
||||
"context_id": self._context_id,
|
||||
"model_id": self._model_id,
|
||||
"model_id": self.model_name,
|
||||
"voice": {
|
||||
"mode": "id",
|
||||
"id": self._voice_id
|
||||
@@ -245,7 +250,7 @@ class CartesiaTTSService(AsyncWordTTSService):
|
||||
"add_timestamps": True,
|
||||
}
|
||||
try:
|
||||
await self._websocket.send(json.dumps(msg))
|
||||
await self._get_websocket().send(json.dumps(msg))
|
||||
await self.start_tts_usage_metrics(text)
|
||||
except Exception as e:
|
||||
logger.error(f"{self} error sending message: {e}")
|
||||
|
||||
@@ -135,6 +135,7 @@ class DeepgramSTTService(STTService):
|
||||
self._connection.on(LiveTranscriptionEvents.Transcript, self._on_message)
|
||||
|
||||
async def set_model(self, model: str):
|
||||
await super().set_model(model)
|
||||
logger.debug(f"Switching STT model to: [{model}]")
|
||||
self._live_options.model = model
|
||||
await self._disconnect()
|
||||
|
||||
@@ -107,7 +107,7 @@ class ElevenLabsTTSService(AsyncWordTTSService):
|
||||
|
||||
self._api_key = api_key
|
||||
self._voice_id = voice_id
|
||||
self._model = model
|
||||
self.set_model_name(model)
|
||||
self._url = url
|
||||
self._params = params
|
||||
|
||||
@@ -122,8 +122,8 @@ class ElevenLabsTTSService(AsyncWordTTSService):
|
||||
return True
|
||||
|
||||
async def set_model(self, model: str):
|
||||
await super().set_model(model)
|
||||
logger.debug(f"Switching TTS model to: [{model}]")
|
||||
self._model = model
|
||||
await self._disconnect()
|
||||
await self._connect()
|
||||
|
||||
@@ -160,7 +160,7 @@ class ElevenLabsTTSService(AsyncWordTTSService):
|
||||
async def _connect(self):
|
||||
try:
|
||||
voice_id = self._voice_id
|
||||
model = self._model
|
||||
model = self.model_name
|
||||
output_format = self._params.output_format
|
||||
url = f"{self._url}/v1/text-to-speech/{voice_id}/stream-input?model_id={model}&output_format={output_format}"
|
||||
self._websocket = await websockets.connect(url)
|
||||
|
||||
@@ -46,7 +46,7 @@ class FalImageGenService(ImageGenService):
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self._model = model
|
||||
self.set_model_name(model)
|
||||
self._params = params
|
||||
self._aiohttp_session = aiohttp_session
|
||||
if key:
|
||||
@@ -56,7 +56,7 @@ class FalImageGenService(ImageGenService):
|
||||
logger.debug(f"Generating image from prompt: {prompt}")
|
||||
|
||||
response = await fal_client.run_async(
|
||||
self._model,
|
||||
self.model_name,
|
||||
arguments={"prompt": prompt, **self._params.model_dump(exclude_none=True)}
|
||||
)
|
||||
|
||||
|
||||
@@ -22,4 +22,4 @@ class FireworksLLMService(BaseOpenAILLMService):
|
||||
*,
|
||||
model: str = "accounts/fireworks/models/firefunction-v1",
|
||||
base_url: str = "https://api.fireworks.ai/inference/v1"):
|
||||
super().__init__(model, base_url)
|
||||
super().__init__(model=model, base_url=base_url)
|
||||
|
||||
@@ -50,6 +50,7 @@ class GoogleLLMService(LLMService):
|
||||
return True
|
||||
|
||||
def _create_client(self, model: str):
|
||||
self.set_model_name(model)
|
||||
self._client = gai.GenerativeModel(model)
|
||||
|
||||
def _get_messages_from_openai_context(
|
||||
|
||||
@@ -54,6 +54,8 @@ class MoondreamService(VisionService):
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.set_model_name(model)
|
||||
|
||||
if not use_cpu:
|
||||
device, dtype = detect_device()
|
||||
else:
|
||||
@@ -73,7 +75,7 @@ class MoondreamService(VisionService):
|
||||
|
||||
async def run_vision(self, frame: VisionImageRawFrame) -> AsyncGenerator[Frame, None]:
|
||||
if not self._model:
|
||||
logger.error(f"{self} error: Moondream model not available")
|
||||
logger.error(f"{self} error: Moondream model not available ({self.model_name})")
|
||||
yield ErrorFrame("Moondream model not available")
|
||||
return
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ from pipecat.frames.frames import (
|
||||
FunctionCallInProgressFrame,
|
||||
StartInterruptionFrame
|
||||
)
|
||||
from pipecat.metrics.metrics import LLMTokenUsage
|
||||
from pipecat.processors.aggregators.llm_response import LLMUserContextAggregator, LLMAssistantContextAggregator
|
||||
|
||||
from pipecat.processors.aggregators.openai_llm_context import (
|
||||
@@ -83,7 +84,7 @@ class BaseOpenAILLMService(LLMService):
|
||||
|
||||
def __init__(self, *, model: str, api_key=None, base_url=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._model: str = model
|
||||
self.set_model_name(model)
|
||||
self._client = self.create_client(api_key=api_key, base_url=base_url, **kwargs)
|
||||
|
||||
def create_client(self, api_key=None, base_url=None, **kwargs):
|
||||
@@ -104,7 +105,7 @@ class BaseOpenAILLMService(LLMService):
|
||||
context: OpenAILLMContext,
|
||||
messages: List[ChatCompletionMessageParam]) -> AsyncStream[ChatCompletionChunk]:
|
||||
chunks = await self._client.chat.completions.create(
|
||||
model=self._model,
|
||||
model=self.model_name,
|
||||
stream=True,
|
||||
messages=messages,
|
||||
tools=context.tools,
|
||||
@@ -148,13 +149,11 @@ class BaseOpenAILLMService(LLMService):
|
||||
|
||||
async for chunk in chunk_stream:
|
||||
if chunk.usage:
|
||||
tokens = {
|
||||
"processor": self.name,
|
||||
"model": self._model,
|
||||
"prompt_tokens": chunk.usage.prompt_tokens,
|
||||
"completion_tokens": chunk.usage.completion_tokens,
|
||||
"total_tokens": chunk.usage.total_tokens
|
||||
}
|
||||
tokens = LLMTokenUsage(
|
||||
prompt_tokens=chunk.usage.prompt_tokens,
|
||||
completion_tokens=chunk.usage.completion_tokens,
|
||||
total_tokens=chunk.usage.total_tokens
|
||||
)
|
||||
await self.start_llm_usage_metrics(tokens)
|
||||
|
||||
if len(chunk.choices) == 0:
|
||||
@@ -223,7 +222,7 @@ class BaseOpenAILLMService(LLMService):
|
||||
context = OpenAILLMContext.from_image_frame(frame)
|
||||
elif isinstance(frame, LLMModelUpdateFrame):
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self._model = frame.model
|
||||
self.set_model_name(frame.model)
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
@@ -273,7 +272,7 @@ class OpenAIImageGenService(ImageGenService):
|
||||
model: str = "dall-e-3",
|
||||
):
|
||||
super().__init__()
|
||||
self._model = model
|
||||
self.set_model_name(model)
|
||||
self._image_size = image_size
|
||||
self._client = AsyncOpenAI(api_key=api_key)
|
||||
self._aiohttp_session = aiohttp_session
|
||||
@@ -283,7 +282,7 @@ class OpenAIImageGenService(ImageGenService):
|
||||
|
||||
image = await self._client.images.generate(
|
||||
prompt=prompt,
|
||||
model=self._model,
|
||||
model=self.model_name,
|
||||
n=1,
|
||||
size=self._image_size
|
||||
)
|
||||
@@ -325,7 +324,7 @@ class OpenAITTSService(TTSService):
|
||||
super().__init__(sample_rate=sample_rate, **kwargs)
|
||||
|
||||
self._voice: ValidVoice = VALID_VOICES.get(voice, "alloy")
|
||||
self._model = model
|
||||
self.set_model_name(model)
|
||||
self._sample_rate = sample_rate
|
||||
|
||||
self._client = AsyncOpenAI(api_key=api_key)
|
||||
@@ -348,7 +347,7 @@ class OpenAITTSService(TTSService):
|
||||
|
||||
async with self._client.audio.speech.with_streaming_response.create(
|
||||
input=text,
|
||||
model=self._model,
|
||||
model=self.model_name,
|
||||
voice=self._voice,
|
||||
response_format="pcm",
|
||||
) as r:
|
||||
|
||||
@@ -60,7 +60,7 @@ class OpenPipeLLMService(BaseOpenAILLMService):
|
||||
context: OpenAILLMContext,
|
||||
messages: List[ChatCompletionMessageParam]) -> AsyncStream[ChatCompletionChunk]:
|
||||
chunks = await self._client.chat.completions.create(
|
||||
model=self._model,
|
||||
model=self.model_name,
|
||||
stream=True,
|
||||
messages=messages,
|
||||
openpipe={
|
||||
|
||||
@@ -18,9 +18,7 @@ from pipecat.frames.frames import (
|
||||
Frame,
|
||||
LLMModelUpdateFrame,
|
||||
TextFrame,
|
||||
VisionImageRawFrame,
|
||||
UserImageRequestFrame,
|
||||
UserImageRawFrame,
|
||||
LLMMessagesFrame,
|
||||
LLMFullResponseStartFrame,
|
||||
LLMFullResponseEndFrame,
|
||||
@@ -28,6 +26,7 @@ from pipecat.frames.frames import (
|
||||
FunctionCallInProgressFrame,
|
||||
StartInterruptionFrame
|
||||
)
|
||||
from pipecat.metrics.metrics import LLMTokenUsage
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.ai_services import LLMService
|
||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext, OpenAILLMContextFrame
|
||||
@@ -69,7 +68,7 @@ class TogetherLLMService(LLMService):
|
||||
**kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._client = AsyncTogether(api_key=api_key)
|
||||
self._model = model
|
||||
self.set_model_name(model)
|
||||
self._max_tokens = max_tokens
|
||||
|
||||
def can_generate_metrics(self) -> bool:
|
||||
@@ -95,7 +94,7 @@ class TogetherLLMService(LLMService):
|
||||
|
||||
stream = await self._client.chat.completions.create(
|
||||
messages=context.messages,
|
||||
model=self._model,
|
||||
model=self.model_name,
|
||||
max_tokens=self._max_tokens,
|
||||
stream=True,
|
||||
)
|
||||
@@ -108,13 +107,11 @@ class TogetherLLMService(LLMService):
|
||||
async for chunk in stream:
|
||||
# logger.debug(f"Together LLM event: {chunk}")
|
||||
if chunk.usage:
|
||||
tokens = {
|
||||
"processor": self.name,
|
||||
"model": self._model,
|
||||
"prompt_tokens": chunk.usage.prompt_tokens,
|
||||
"completion_tokens": chunk.usage.completion_tokens,
|
||||
"total_tokens": chunk.usage.total_tokens
|
||||
}
|
||||
tokens = LLMTokenUsage(
|
||||
prompt_tokens=chunk.usage.prompt_tokens,
|
||||
completion_tokens=chunk.usage.completion_tokens,
|
||||
total_tokens=chunk.usage.total_tokens
|
||||
)
|
||||
await self.start_llm_usage_metrics(tokens)
|
||||
|
||||
if len(chunk.choices) == 0:
|
||||
@@ -156,7 +153,7 @@ class TogetherLLMService(LLMService):
|
||||
context = TogetherLLMContext.from_messages(frame.messages)
|
||||
elif isinstance(frame, LLMModelUpdateFrame):
|
||||
logger.debug(f"Switching LLM model to: [{frame.model}]")
|
||||
self._model = frame.model
|
||||
self.set_model_name(frame.model)
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class WhisperSTTService(SegmentedSTTService):
|
||||
super().__init__(**kwargs)
|
||||
self._device: str = device
|
||||
self._compute_type = compute_type
|
||||
self._model_name: str | Model = model
|
||||
self.set_model_name(model if isinstance(model, str) else model.value)
|
||||
self._no_speech_prob = no_speech_prob
|
||||
self._model: WhisperModel | None = None
|
||||
self._load()
|
||||
@@ -65,7 +65,7 @@ class WhisperSTTService(SegmentedSTTService):
|
||||
this model is being run, it will take time to download."""
|
||||
logger.debug("Loading Whisper model...")
|
||||
self._model = WhisperModel(
|
||||
self._model_name.value if isinstance(self._model_name, Enum) else self._model_name,
|
||||
self.model_name,
|
||||
device=self._device,
|
||||
compute_type=self._compute_type)
|
||||
logger.debug("Loaded Whisper model")
|
||||
|
||||
@@ -35,6 +35,7 @@ from pipecat.frames.frames import (
|
||||
TransportMessageFrame,
|
||||
UserImageRawFrame,
|
||||
UserImageRequestFrame)
|
||||
from pipecat.metrics.metrics import LLMUsageMetricsData, ProcessingMetricsData, TTFBMetricsData, TTSUsageMetricsData
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
from pipecat.transcriptions.language import Language
|
||||
from pipecat.transports.base_input import BaseInputTransport
|
||||
@@ -731,14 +732,23 @@ class DailyOutputTransport(BaseOutputTransport):
|
||||
|
||||
async def send_metrics(self, frame: MetricsFrame):
|
||||
metrics = {}
|
||||
if frame.ttfb:
|
||||
metrics["ttfb"] = frame.ttfb
|
||||
if frame.processing:
|
||||
metrics["processing"] = frame.processing
|
||||
if frame.tokens:
|
||||
metrics["tokens"] = frame.tokens
|
||||
if frame.characters:
|
||||
metrics["characters"] = frame.characters
|
||||
for d in frame.data:
|
||||
if isinstance(d, TTFBMetricsData):
|
||||
if "ttfb" not in metrics:
|
||||
metrics["ttfb"] = []
|
||||
metrics["ttfb"].append(d.model_dump())
|
||||
elif isinstance(d, ProcessingMetricsData):
|
||||
if "processing" not in metrics:
|
||||
metrics["processing"] = []
|
||||
metrics["processing"].append(d.model_dump())
|
||||
elif isinstance(d, LLMUsageMetricsData):
|
||||
if "tokens" not in metrics:
|
||||
metrics["tokens"] = []
|
||||
metrics["tokens"].append(d.value.model_dump(exclude_none=True))
|
||||
elif isinstance(d, TTSUsageMetricsData):
|
||||
if "characters" not in metrics:
|
||||
metrics["characters"] = []
|
||||
metrics["characters"].append(d.model_dump())
|
||||
|
||||
message = DailyTransportMessageFrame(message={
|
||||
"type": "pipecat-metrics",
|
||||
|
||||
Reference in New Issue
Block a user