Merge pull request #599 from pipecat-ai/mb/remove-metrics-from-transport
Move metrics from transport to rtvi
This commit is contained in:
@@ -5,6 +5,12 @@ All notable changes to **Pipecat** will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Metrics messages have moved out from the transport's base output into RTVI.
|
||||||
|
|
||||||
## [0.0.44] - 2024-10-15
|
## [0.0.44] - 2024-10-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -6,7 +6,17 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Awaitable, Callable, Dict, List, Literal, Optional, Union
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Awaitable,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Literal,
|
||||||
|
Mapping,
|
||||||
|
Optional,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel, Field, PrivateAttr, ValidationError
|
from pydantic import BaseModel, Field, PrivateAttr, ValidationError
|
||||||
@@ -24,6 +34,7 @@ from pipecat.frames.frames import (
|
|||||||
InterimTranscriptionFrame,
|
InterimTranscriptionFrame,
|
||||||
LLMFullResponseEndFrame,
|
LLMFullResponseEndFrame,
|
||||||
LLMFullResponseStartFrame,
|
LLMFullResponseStartFrame,
|
||||||
|
MetricsFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
SystemFrame,
|
SystemFrame,
|
||||||
TextFrame,
|
TextFrame,
|
||||||
@@ -35,6 +46,12 @@ from pipecat.frames.frames import (
|
|||||||
UserStartedSpeakingFrame,
|
UserStartedSpeakingFrame,
|
||||||
UserStoppedSpeakingFrame,
|
UserStoppedSpeakingFrame,
|
||||||
)
|
)
|
||||||
|
from pipecat.metrics.metrics import (
|
||||||
|
LLMUsageMetricsData,
|
||||||
|
ProcessingMetricsData,
|
||||||
|
TTFBMetricsData,
|
||||||
|
TTSUsageMetricsData,
|
||||||
|
)
|
||||||
from pipecat.processors.aggregators.openai_llm_context import (
|
from pipecat.processors.aggregators.openai_llm_context import (
|
||||||
OpenAILLMContext,
|
OpenAILLMContext,
|
||||||
OpenAILLMContextFrame,
|
OpenAILLMContextFrame,
|
||||||
@@ -343,6 +360,12 @@ class RTVIBotStoppedSpeakingMessage(BaseModel):
|
|||||||
type: Literal["bot-stopped-speaking"] = "bot-stopped-speaking"
|
type: Literal["bot-stopped-speaking"] = "bot-stopped-speaking"
|
||||||
|
|
||||||
|
|
||||||
|
class RTVIMetricsMessage(BaseModel):
|
||||||
|
label: Literal["rtvi-ai"] = "rtvi-ai"
|
||||||
|
type: Literal["metrics"] = "metrics"
|
||||||
|
data: Mapping[str, Any]
|
||||||
|
|
||||||
|
|
||||||
class RTVIProcessorParams(BaseModel):
|
class RTVIProcessorParams(BaseModel):
|
||||||
send_bot_ready: bool = True
|
send_bot_ready: bool = True
|
||||||
|
|
||||||
@@ -509,6 +532,42 @@ class RTVIBotTTSProcessor(RTVIFrameProcessor):
|
|||||||
await self._push_transport_message_urgent(message)
|
await self._push_transport_message_urgent(message)
|
||||||
|
|
||||||
|
|
||||||
|
class RTVIMetricsProcessor(RTVIFrameProcessor):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
if isinstance(frame, MetricsFrame):
|
||||||
|
await self._handle_metrics(frame)
|
||||||
|
|
||||||
|
async def _handle_metrics(self, frame: MetricsFrame):
|
||||||
|
metrics = {}
|
||||||
|
for d in frame.data:
|
||||||
|
if isinstance(d, TTFBMetricsData):
|
||||||
|
if "ttfb" not in metrics:
|
||||||
|
metrics["ttfb"] = []
|
||||||
|
metrics["ttfb"].append(d.model_dump(exclude_none=True))
|
||||||
|
elif isinstance(d, ProcessingMetricsData):
|
||||||
|
if "processing" not in metrics:
|
||||||
|
metrics["processing"] = []
|
||||||
|
metrics["processing"].append(d.model_dump(exclude_none=True))
|
||||||
|
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(exclude_none=True))
|
||||||
|
|
||||||
|
message = RTVIMetricsMessage(data=metrics)
|
||||||
|
await self._push_transport_message_urgent(message)
|
||||||
|
|
||||||
|
|
||||||
class RTVIProcessor(FrameProcessor):
|
class RTVIProcessor(FrameProcessor):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -6,37 +6,34 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import itertools
|
import itertools
|
||||||
import time
|
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
from PIL import Image
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from loguru import logger
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
BotSpeakingFrame,
|
BotSpeakingFrame,
|
||||||
BotStartedSpeakingFrame,
|
BotStartedSpeakingFrame,
|
||||||
BotStoppedSpeakingFrame,
|
BotStoppedSpeakingFrame,
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
MetricsFrame,
|
EndFrame,
|
||||||
|
Frame,
|
||||||
OutputAudioRawFrame,
|
OutputAudioRawFrame,
|
||||||
OutputImageRawFrame,
|
OutputImageRawFrame,
|
||||||
SpriteFrame,
|
SpriteFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
EndFrame,
|
|
||||||
Frame,
|
|
||||||
StartInterruptionFrame,
|
StartInterruptionFrame,
|
||||||
StopInterruptionFrame,
|
StopInterruptionFrame,
|
||||||
SystemFrame,
|
SystemFrame,
|
||||||
TTSStartedFrame,
|
|
||||||
TTSStoppedFrame,
|
|
||||||
TransportMessageFrame,
|
TransportMessageFrame,
|
||||||
TransportMessageUrgentFrame,
|
TransportMessageUrgentFrame,
|
||||||
|
TTSStartedFrame,
|
||||||
|
TTSStoppedFrame,
|
||||||
)
|
)
|
||||||
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
from pipecat.transports.base_transport import TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from pipecat.utils.time import nanoseconds_to_seconds
|
from pipecat.utils.time import nanoseconds_to_seconds
|
||||||
|
|
||||||
|
|
||||||
@@ -141,9 +138,6 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
async def send_message(self, frame: TransportMessageFrame | TransportMessageUrgentFrame):
|
async def send_message(self, frame: TransportMessageFrame | TransportMessageUrgentFrame):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def send_metrics(self, frame: MetricsFrame):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def write_frame_to_camera(self, frame: OutputImageRawFrame):
|
async def write_frame_to_camera(self, frame: OutputImageRawFrame):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -173,9 +167,6 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
elif isinstance(frame, (StartInterruptionFrame, StopInterruptionFrame)):
|
elif isinstance(frame, (StartInterruptionFrame, StopInterruptionFrame)):
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
await self._handle_interruptions(frame)
|
await self._handle_interruptions(frame)
|
||||||
elif isinstance(frame, MetricsFrame):
|
|
||||||
await self.push_frame(frame, direction)
|
|
||||||
await self.send_metrics(frame)
|
|
||||||
elif isinstance(frame, TransportMessageUrgentFrame):
|
elif isinstance(frame, TransportMessageUrgentFrame):
|
||||||
await self.send_message(frame)
|
await self.send_message(frame)
|
||||||
elif isinstance(frame, SystemFrame):
|
elif isinstance(frame, SystemFrame):
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ from pipecat.frames.frames import (
|
|||||||
Frame,
|
Frame,
|
||||||
InputAudioRawFrame,
|
InputAudioRawFrame,
|
||||||
InterimTranscriptionFrame,
|
InterimTranscriptionFrame,
|
||||||
MetricsFrame,
|
|
||||||
OutputAudioRawFrame,
|
OutputAudioRawFrame,
|
||||||
OutputImageRawFrame,
|
OutputImageRawFrame,
|
||||||
SpriteFrame,
|
SpriteFrame,
|
||||||
@@ -39,12 +38,6 @@ from pipecat.frames.frames import (
|
|||||||
UserImageRawFrame,
|
UserImageRawFrame,
|
||||||
UserImageRequestFrame,
|
UserImageRequestFrame,
|
||||||
)
|
)
|
||||||
from pipecat.metrics.metrics import (
|
|
||||||
LLMUsageMetricsData,
|
|
||||||
ProcessingMetricsData,
|
|
||||||
TTFBMetricsData,
|
|
||||||
TTSUsageMetricsData,
|
|
||||||
)
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.transcriptions.language import Language
|
from pipecat.transcriptions.language import Language
|
||||||
from pipecat.transports.base_input import BaseInputTransport
|
from pipecat.transports.base_input import BaseInputTransport
|
||||||
@@ -759,31 +752,6 @@ class DailyOutputTransport(BaseOutputTransport):
|
|||||||
async def send_message(self, frame: TransportMessageFrame | TransportMessageUrgentFrame):
|
async def send_message(self, frame: TransportMessageFrame | TransportMessageUrgentFrame):
|
||||||
await self._messages_queue.put(frame)
|
await self._messages_queue.put(frame)
|
||||||
|
|
||||||
async def send_metrics(self, frame: MetricsFrame):
|
|
||||||
metrics = {}
|
|
||||||
for d in frame.data:
|
|
||||||
if isinstance(d, TTFBMetricsData):
|
|
||||||
if "ttfb" not in metrics:
|
|
||||||
metrics["ttfb"] = []
|
|
||||||
metrics["ttfb"].append(d.model_dump(exclude_none=True))
|
|
||||||
elif isinstance(d, ProcessingMetricsData):
|
|
||||||
if "processing" not in metrics:
|
|
||||||
metrics["processing"] = []
|
|
||||||
metrics["processing"].append(d.model_dump(exclude_none=True))
|
|
||||||
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(exclude_none=True))
|
|
||||||
|
|
||||||
message = DailyTransportMessageFrame(
|
|
||||||
message={"label": "rtvi-ai", "type": "metrics", "data": metrics}
|
|
||||||
)
|
|
||||||
await self._messages_queue.put(message)
|
|
||||||
|
|
||||||
async def write_raw_audio_frames(self, frames: bytes):
|
async def write_raw_audio_frames(self, frames: bytes):
|
||||||
await self._client.write_raw_audio_frames(frames)
|
await self._client.write_raw_audio_frames(frames)
|
||||||
|
|
||||||
|
|||||||
@@ -10,31 +10,25 @@ from typing import Any, Awaitable, Callable, List
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from scipy import signal
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
AudioRawFrame,
|
AudioRawFrame,
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
EndFrame,
|
EndFrame,
|
||||||
Frame,
|
Frame,
|
||||||
InputAudioRawFrame,
|
InputAudioRawFrame,
|
||||||
MetricsFrame,
|
|
||||||
OutputAudioRawFrame,
|
OutputAudioRawFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
TransportMessageFrame,
|
TransportMessageFrame,
|
||||||
TransportMessageUrgentFrame,
|
TransportMessageUrgentFrame,
|
||||||
)
|
)
|
||||||
from pipecat.metrics.metrics import (
|
|
||||||
LLMUsageMetricsData,
|
|
||||||
ProcessingMetricsData,
|
|
||||||
TTFBMetricsData,
|
|
||||||
TTSUsageMetricsData,
|
|
||||||
)
|
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.transports.base_input import BaseInputTransport
|
from pipecat.transports.base_input import BaseInputTransport
|
||||||
from pipecat.transports.base_output import BaseOutputTransport
|
from pipecat.transports.base_output import BaseOutputTransport
|
||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||||
from pipecat.vad.vad_analyzer import VADAnalyzer
|
from pipecat.vad.vad_analyzer import VADAnalyzer
|
||||||
from pydantic import BaseModel
|
|
||||||
from scipy import signal
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from livekit import rtc
|
from livekit import rtc
|
||||||
@@ -450,31 +444,6 @@ class LiveKitOutputTransport(BaseOutputTransport):
|
|||||||
else:
|
else:
|
||||||
await self._client.send_data(frame.message.encode())
|
await self._client.send_data(frame.message.encode())
|
||||||
|
|
||||||
async def send_metrics(self, frame: MetricsFrame):
|
|
||||||
metrics = {}
|
|
||||||
for d in frame.data:
|
|
||||||
if isinstance(d, TTFBMetricsData):
|
|
||||||
if "ttfb" not in metrics:
|
|
||||||
metrics["ttfb"] = []
|
|
||||||
metrics["ttfb"].append(d.model_dump(exclude_none=True))
|
|
||||||
elif isinstance(d, ProcessingMetricsData):
|
|
||||||
if "processing" not in metrics:
|
|
||||||
metrics["processing"] = []
|
|
||||||
metrics["processing"].append(d.model_dump(exclude_none=True))
|
|
||||||
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(exclude_none=True))
|
|
||||||
|
|
||||||
message = LiveKitTransportMessageFrame(
|
|
||||||
message={"type": "pipecat-metrics", "metrics": metrics}
|
|
||||||
)
|
|
||||||
await self._client.send_data(str(message.message).encode())
|
|
||||||
|
|
||||||
async def write_raw_audio_frames(self, frames: bytes):
|
async def write_raw_audio_frames(self, frames: bytes):
|
||||||
livekit_audio = self._convert_pipecat_audio_to_livekit(frames)
|
livekit_audio = self._convert_pipecat_audio_to_livekit(frames)
|
||||||
await self._client.publish_audio(livekit_audio)
|
await self._client.publish_audio(livekit_audio)
|
||||||
|
|||||||
Reference in New Issue
Block a user