Additional LLM and TTS metrics (#343)

* added llm and tts usage metrics

* Metrics debug logging

* cleanup
This commit is contained in:
chadbailey59
2024-08-07 08:55:51 -05:00
committed by GitHub
parent 83a037a7ce
commit 3958bb7903
11 changed files with 101 additions and 19 deletions

View File

@@ -9,14 +9,15 @@ import aiohttp
import os import os
import sys import sys
from pipecat.frames.frames import LLMMessagesFrame from pipecat.frames.frames import Frame, LLMMessagesFrame, MetricsFrame
from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineTask from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat.processors.aggregators.llm_response import ( from pipecat.processors.aggregators.llm_response import (
LLMAssistantResponseAggregator, LLMAssistantResponseAggregator,
LLMUserResponseAggregator, LLMUserResponseAggregator,
) )
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
from pipecat.processors.logger import FrameLogger from pipecat.processors.logger import FrameLogger
from pipecat.services.elevenlabs import ElevenLabsTTSService from pipecat.services.elevenlabs import ElevenLabsTTSService
from pipecat.services.openai import OpenAILLMService from pipecat.services.openai import OpenAILLMService
@@ -34,6 +35,14 @@ logger.remove(0)
logger.add(sys.stderr, level="DEBUG") 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}")
await self.push_frame(frame, direction)
async def main(): async def main():
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
(room_url, token) = await configure(session) (room_url, token) = await configure(session)
@@ -58,11 +67,10 @@ async def main():
llm = OpenAILLMService( llm = OpenAILLMService(
api_key=os.getenv("OPENAI_API_KEY"), api_key=os.getenv("OPENAI_API_KEY"),
model="gpt-4o") model="gpt-4o"
)
fl = FrameLogger("!!! after LLM", "red") ml = MetricsLogger()
fltts = FrameLogger("@@@ out of tts", "green")
flend = FrameLogger("### out of the end", "magenta")
messages = [ messages = [
{ {
@@ -77,15 +85,18 @@ async def main():
transport.input(), transport.input(),
tma_in, tma_in,
llm, llm,
fl,
tts, tts,
fltts, ml,
transport.output(), transport.output(),
tma_out, tma_out,
flend
]) ])
task = PipelineTask(pipeline) task = PipelineTask(pipeline)
task = PipelineTask(pipeline, PipelineParams(
allow_interruptions=True,
enable_metrics=True,
report_only_initial_ttfb=False,
))
@transport.event_handler("on_first_participant_joined") @transport.event_handler("on_first_participant_joined")
async def on_first_participant_joined(transport, participant): async def on_first_participant_joined(transport, participant):

View File

@@ -276,6 +276,8 @@ class MetricsFrame(SystemFrame):
""" """
ttfb: List[Mapping[str, Any]] | None = None ttfb: List[Mapping[str, Any]] | None = None
processing: 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
# #
# Control frames # Control frames

View File

@@ -537,7 +537,11 @@ class RTVIProcessor(FrameProcessor):
processors.extend(pipeline.processors_with_metrics()) processors.extend(pipeline.processors_with_metrics())
ttfb = [{"processor": p.name, "value": 0.0} for p in processors] ttfb = [{"processor": p.name, "value": 0.0} for p in processors]
processing = [{"processor": p.name, "value": 0.0} for p in processors] processing = [{"processor": p.name, "value": 0.0} for p in processors]
await self.push_frame(MetricsFrame(ttfb=ttfb, processing=processing)) tokens = [{"processor": p.name, "value": {"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0}} for p in processors]
characters = [{"processor": p.name, "value": 0} for p in processors]
await self.push_frame(MetricsFrame(ttfb=ttfb, processing=processing, tokens=tokens, characters=characters))
self._pipeline = pipeline self._pipeline = pipeline

View File

@@ -18,6 +18,7 @@ from pipecat.frames.frames import (
EndFrame, EndFrame,
ErrorFrame, ErrorFrame,
Frame, Frame,
MetricsFrame,
StartFrame, StartFrame,
SystemFrame, SystemFrame,
TranscriptionFrame, TranscriptionFrame,
@@ -87,7 +88,13 @@ class AzureTTSService(TTSService):
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}]")
if self.can_generate_metrics() and self.metrics_enabled:
characters = {
"processor": self.name,
"value": len(text),
}
logger.debug(f"{self.name} Characters: {characters['value']}")
await self.push_frame(MetricsFrame(characters=[characters]))
await self.start_ttfb_metrics() await self.start_ttfb_metrics()
ssml = ( ssml = (

View File

@@ -21,6 +21,7 @@ from pipecat.frames.frames import (
StartFrame, StartFrame,
EndFrame, EndFrame,
TextFrame, TextFrame,
MetricsFrame,
LLMFullResponseEndFrame LLMFullResponseEndFrame
) )
from pipecat.services.ai_services import TTSService from pipecat.services.ai_services import TTSService
@@ -200,6 +201,13 @@ class CartesiaTTSService(TTSService):
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}]")
if self.can_generate_metrics() and self.metrics_enabled:
characters = {
"processor": self.name,
"value": len(text),
}
logger.debug(f"{self.name} Characters: {characters['value']}")
await self.push_frame(MetricsFrame(characters=[characters]))
try: try:
if not self._websocket: if not self._websocket:

View File

@@ -15,6 +15,7 @@ from pipecat.frames.frames import (
ErrorFrame, ErrorFrame,
Frame, Frame,
InterimTranscriptionFrame, InterimTranscriptionFrame,
MetricsFrame,
StartFrame, StartFrame,
SystemFrame, SystemFrame,
TranscriptionFrame) TranscriptionFrame)
@@ -70,7 +71,13 @@ class DeepgramTTSService(TTSService):
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}]")
if self.can_generate_metrics() and self.metrics_enabled:
characters = {
"processor": self.name,
"value": len(text),
}
logger.debug(f"{self.name} Characters: {characters['value']}")
await self.push_frame(MetricsFrame(characters=[characters]))
base_url = self._base_url base_url = self._base_url
request_url = f"{base_url}?model={self._voice}&encoding={self._encoding}&container=none&sample_rate={self._sample_rate}" request_url = f"{base_url}?model={self._voice}&encoding={self._encoding}&container=none&sample_rate={self._sample_rate}"
headers = {"authorization": f"token {self._api_key}"} headers = {"authorization": f"token {self._api_key}"}

View File

@@ -8,7 +8,7 @@ import aiohttp
from typing import AsyncGenerator from typing import AsyncGenerator
from pipecat.frames.frames import AudioRawFrame, ErrorFrame, Frame from pipecat.frames.frames import AudioRawFrame, ErrorFrame, Frame, MetricsFrame
from pipecat.services.ai_services import TTSService from pipecat.services.ai_services import TTSService
from loguru import logger from loguru import logger
@@ -40,7 +40,13 @@ class ElevenLabsTTSService(TTSService):
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}]")
if self.can_generate_metrics() and self.metrics_enabled:
characters = {
"processor": self.name,
"value": len(text),
}
logger.debug(f"{self.name} Characters: {characters['value']}")
await self.push_frame(MetricsFrame(characters=[characters]))
url = f"https://api.elevenlabs.io/v1/text-to-speech/{self._voice_id}/stream" url = f"https://api.elevenlabs.io/v1/text-to-speech/{self._voice_id}/stream"
payload = {"text": text, "model_id": self._model} payload = {"text": text, "model_id": self._model}

View File

@@ -23,6 +23,7 @@ from pipecat.frames.frames import (
LLMFullResponseStartFrame, LLMFullResponseStartFrame,
LLMMessagesFrame, LLMMessagesFrame,
LLMModelUpdateFrame, LLMModelUpdateFrame,
MetricsFrame,
TextFrame, TextFrame,
URLImageRawFrame, URLImageRawFrame,
VisionImageRawFrame VisionImageRawFrame
@@ -95,6 +96,7 @@ class BaseOpenAILLMService(LLMService):
messages=messages, messages=messages,
tools=context.tools, tools=context.tools,
tool_choice=context.tool_choice, tool_choice=context.tool_choice,
stream_options={"include_usage": True}
) )
return chunks return chunks
@@ -132,6 +134,19 @@ class BaseOpenAILLMService(LLMService):
) )
async for chunk in chunk_stream: async for chunk in chunk_stream:
if chunk.usage:
if self.can_generate_metrics() and self.metrics_enabled:
tokens = {
"processor": self.name,
"prompt_tokens": chunk.usage.prompt_tokens,
"completion_tokens": chunk.usage.completion_tokens,
"total_tokens": chunk.usage.total_tokens
}
logger.debug(
f"{self.name} prompt tokens: {tokens['prompt_tokens']}, completion tokens: {tokens['completion_tokens']}")
await self.push_frame(MetricsFrame(tokens=[tokens]))
if len(chunk.choices) == 0: if len(chunk.choices) == 0:
continue continue
@@ -323,7 +338,13 @@ class OpenAITTSService(TTSService):
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}]")
if self.can_generate_metrics() and self.metrics_enabled:
characters = {
"processor": self.name,
"value": len(text),
}
logger.debug(f"{self.name} Characters: {characters['value']}")
await self.push_frame(MetricsFrame(characters=[characters]))
try: try:
await self.start_ttfb_metrics() await self.start_ttfb_metrics()

View File

@@ -9,7 +9,7 @@ import struct
from typing import AsyncGenerator from typing import AsyncGenerator
from pipecat.frames.frames import AudioRawFrame, Frame from pipecat.frames.frames import AudioRawFrame, Frame, MetricsFrame
from pipecat.services.ai_services import TTSService from pipecat.services.ai_services import TTSService
from loguru import logger from loguru import logger
@@ -48,7 +48,13 @@ class PlayHTTTSService(TTSService):
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}]")
if self.can_generate_metrics() and self.metrics_enabled:
characters = {
"processor": self.name,
"value": len(text),
}
logger.debug(f"{self.name} Characters: {characters['value']}")
await self.push_frame(MetricsFrame(characters=[characters]))
try: try:
b = bytearray() b = bytearray()
in_header = True in_header = True

View File

@@ -8,7 +8,7 @@ import aiohttp
from typing import Any, AsyncGenerator, Dict from typing import Any, AsyncGenerator, Dict
from pipecat.frames.frames import AudioRawFrame, ErrorFrame, Frame, StartFrame from pipecat.frames.frames import AudioRawFrame, ErrorFrame, Frame, MetricsFrame, StartFrame
from pipecat.services.ai_services import TTSService from pipecat.services.ai_services import TTSService
from loguru import logger from loguru import logger
@@ -70,7 +70,13 @@ class XTTSService(TTSService):
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}]")
if self.can_generate_metrics() and self.metrics_enabled:
characters = {
"processor": self.name,
"value": len(text),
}
logger.debug(f"{self.name} Characters: {characters['value']}")
await self.push_frame(MetricsFrame(characters=[characters]))
if not self._studio_speakers: if not self._studio_speakers:
logger.error(f"{self} no studio speakers available") logger.error(f"{self} no studio speakers available")
return return

View File

@@ -699,6 +699,10 @@ class DailyOutputTransport(BaseOutputTransport):
metrics["ttfb"] = frame.ttfb metrics["ttfb"] = frame.ttfb
if frame.processing: if frame.processing:
metrics["processing"] = frame.processing metrics["processing"] = frame.processing
if frame.tokens:
metrics["tokens"] = frame.tokens
if frame.characters:
metrics["characters"] = frame.characters
message = DailyTransportMessageFrame(message={ message = DailyTransportMessageFrame(message={
"type": "pipecat-metrics", "type": "pipecat-metrics",