From c1492c527537c5fdca7c54e0520a6cc93b6ca04b Mon Sep 17 00:00:00 2001 From: ivaaan Date: Wed, 1 Oct 2025 17:23:55 -0700 Subject: [PATCH] fixes based on markbackman review --- ...ble-hume.py => 07ae-interruptible-hume.py} | 26 ++++++--------- pyproject.toml | 5 --- src/pipecat/services/hume/tts.py | 32 ++----------------- src/pipecat/services/tts_service.py | 1 + 4 files changed, 13 insertions(+), 51 deletions(-) rename examples/foundational/{07ad-interruptible-hume.py => 07ae-interruptible-hume.py} (81%) diff --git a/examples/foundational/07ad-interruptible-hume.py b/examples/foundational/07ae-interruptible-hume.py similarity index 81% rename from examples/foundational/07ad-interruptible-hume.py rename to examples/foundational/07ae-interruptible-hume.py index 4e51c8933..91f680ddf 100644 --- a/examples/foundational/07ad-interruptible-hume.py +++ b/examples/foundational/07ae-interruptible-hume.py @@ -7,22 +7,21 @@ import os from dotenv import load_dotenv -from loguru import logger -from pipecat.audio.vad.silero import SileroVADAnalyzer -from pipecat.frames.frames import StartFrame +from pipecat.frames.frames import LLMRunFrame from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.task import PipelineParams, PipelineTask -from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext +from pipecat.processors.aggregators.llm_context import LLMContext +from pipecat.processors.aggregators.llm_response_universal import LLMContextAggregatorPair from pipecat.runner.types import RunnerArguments from pipecat.runner.utils import create_transport from pipecat.services.deepgram.stt import DeepgramSTTService from pipecat.services.hume.tts import HUME_SAMPLE_RATE, HumeTTSService from pipecat.services.openai.llm import OpenAILLMService from pipecat.transports.base_transport import BaseTransport, TransportParams -from pipecat.transports.network.fastapi_websocket import FastAPIWebsocketParams -from pipecat.transports.services.daily import DailyParams +from pipecat.transports.daily.transport import DailyParams +from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams load_dotenv(override=True) @@ -40,12 +39,7 @@ transport_params = { audio_out_enabled=True, vad_analyzer=SileroVADAnalyzer(), ), - "webrtc": lambda: TransportParams( - audio_in_enabled=True, - audio_out_enabled=True, - vad_analyzer=SileroVADAnalyzer(), - audio_out_sample_rate=HUME_SAMPLE_RATE, - ), + "webrtc": lambda: TransportParams(), } @@ -69,8 +63,8 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): }, ] - context = OpenAILLMContext(messages) - context_aggregator = llm.create_context_aggregator(context) + context = LLMContext(messages) + context_aggregator = LLMContextAggregatorPair(context) pipeline = Pipeline( [ @@ -89,6 +83,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): params=PipelineParams( enable_metrics=True, enable_usage_metrics=True, + audio_out_sample_rate=HUME_SAMPLE_RATE, ), idle_timeout_secs=runner_args.pipeline_idle_timeout_secs, ) @@ -98,7 +93,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): logger.info(f"Client connected") # Kick off the conversation. messages.append({"role": "system", "content": "Please introduce yourself to the user."}) - await task.queue_frames([context_aggregator.user().get_context_frame()]) + await task.queue_frames([LLMRunFrame()]) @transport.event_handler("on_client_disconnected") async def on_client_disconnected(transport, client): @@ -112,7 +107,6 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): async def bot(runner_args: RunnerArguments): """Main bot entry point compatible with Pipecat Cloud.""" - runner_args.transport = "webrtc" transport = await create_transport(runner_args, transport_params) await run_bot(transport, runner_args) diff --git a/pyproject.toml b/pyproject.toml index 1d75fa858..0adbf281e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,11 +112,6 @@ webrtc = [ "aiortc>=1.13.0,<2", "opencv-python>=4.11.0.86,<5" ] websocket = [ "pipecat-ai[websockets-base]", "fastapi>=0.115.6,<0.117.0" ] websockets-base = [ "websockets>=13.1,<16.0" ] whisper = [ "faster-whisper~=1.1.1" ] -fastapi = [ - "fastapi", - "uvicorn", - "websockets", -] [dependency-groups] dev = [ diff --git a/src/pipecat/services/hume/tts.py b/src/pipecat/services/hume/tts.py index b49176d8e..7014e73fb 100644 --- a/src/pipecat/services/hume/tts.py +++ b/src/pipecat/services/hume/tts.py @@ -4,8 +4,6 @@ """Hume Text-to-Speech service implementation.""" -from __future__ import annotations - import base64 import os from typing import Any, AsyncGenerator, Optional @@ -34,7 +32,7 @@ try: except ModuleNotFoundError as e: # pragma: no cover - import-time guidance logger.error(f"Exception: {e}") logger.error("In order to use Hume, you need to `pip install pipecat-ai[hume]`.") - raise + raise Exception(f"Missing module: {e}") HUME_SAMPLE_RATE = 48_000 # Hume TTS streams at 48 kHz @@ -94,11 +92,7 @@ class HumeTTSService(TTSService): f"Hume TTS streams at {HUME_SAMPLE_RATE} Hz; configured sample_rate={sample_rate}" ) - super().__init__( - pause_frame_processing=True, - sample_rate=sample_rate, - **kwargs, - ) + super().__init__(sample_rate=sample_rate, **kwargs) self._client = AsyncHumeClient(api_key=api_key) self._params = params or HumeTTSService.InputParams() @@ -181,7 +175,6 @@ class HumeTTSService(TTSService): # Request raw PCM chunks in the streaming JSON pcm_fmt = FormatPcm(type="pcm") - measuring_ttfb = True await self.start_ttfb_metrics() await self.start_tts_usage_metrics(text) yield TTSStartedFrame() @@ -191,7 +184,6 @@ class HumeTTSService(TTSService): # Hume emits mono PCM at 48 kHz; downstream can resample if needed. # We buffer audio bytes before sending to prevent glitches. self._audio_bytes = b"" - first_audio_sent = False async for chunk in self._client.tts.synthesize_json_streaming( utterances=[utterance], format=pcm_fmt, @@ -204,17 +196,6 @@ class HumeTTSService(TTSService): pcm_bytes = base64.b64decode(audio_b64) self._audio_bytes += pcm_bytes - # Send the first audio chunk immediately to avoid client-side delays. - if not first_audio_sent: - if self._audio_bytes: - yield TTSAudioRawFrame(self._audio_bytes, self.sample_rate, 1) - if measuring_ttfb: - await self.stop_ttfb_metrics() - measuring_ttfb = False - first_audio_sent = True - # Do NOT clear _audio_bytes here. Subsequent chunks will build on this. - continue - # Buffer audio until we have enough to avoid glitches if len(self._audio_bytes) < self.chunk_size: continue @@ -226,14 +207,5 @@ class HumeTTSService(TTSService): logger.exception(f"{self} error generating TTS: {e}") yield ErrorFrame(error=str(e)) finally: - # Yield any remaining audio - if self._audio_bytes: - yield TTSAudioRawFrame(self._audio_bytes, self.sample_rate, 1) - # Ensure TTFB timer is stopped even on early failures - if measuring_ttfb: - await self.stop_ttfb_metrics() yield TTSStoppedFrame() - - -__all__ = ["HumeTTSService"] diff --git a/src/pipecat/services/tts_service.py b/src/pipecat/services/tts_service.py index d20d24f0a..02b80b609 100644 --- a/src/pipecat/services/tts_service.py +++ b/src/pipecat/services/tts_service.py @@ -142,6 +142,7 @@ class TTSService(AIService): """ return self._sample_rate + @property def chunk_size(self) -> int: """Get the recommended chunk size for audio streaming.