diff --git a/changelog/3195.changed.md b/changelog/3195.changed.md new file mode 100644 index 000000000..29df2589e --- /dev/null +++ b/changelog/3195.changed.md @@ -0,0 +1,3 @@ +- Added tracking headers (`X-Hume-Client-Name` and `X-Hume-Client-Version`) to all requests made by `HumeTTSService` to the Hume API for better usage tracking and analytics. +- Added `stop()` and `cancel()` cleanup methods to `HumeTTSService` to properly close the HTTP client and prevent resource leaks. + diff --git a/src/pipecat/services/hume/tts.py b/src/pipecat/services/hume/tts.py index a2472c6d3..6571bff0f 100644 --- a/src/pipecat/services/hume/tts.py +++ b/src/pipecat/services/hume/tts.py @@ -8,10 +8,14 @@ import base64 import os from typing import Any, AsyncGenerator, Optional +import httpx from loguru import logger from pydantic import BaseModel +from pipecat import __version__ from pipecat.frames.frames import ( + CancelFrame, + EndFrame, ErrorFrame, Frame, InterruptionFrame, @@ -26,11 +30,7 @@ from pipecat.utils.tracing.service_decorators import traced_tts try: from hume import AsyncHumeClient - from hume.tts import ( - FormatPcm, - PostedUtterance, - PostedUtteranceVoiceWithId, - ) + from hume.tts import FormatPcm, PostedUtterance, PostedUtteranceVoiceWithId from hume.tts.types import TimestampMessage except ModuleNotFoundError as e: # pragma: no cover - import-time guidance logger.error(f"Exception: {e}") @@ -40,6 +40,12 @@ except ModuleNotFoundError as e: # pragma: no cover - import-time guidance HUME_SAMPLE_RATE = 48_000 # Hume TTS streams at 48 kHz +# Tracking headers for Hume API requests +DEFAULT_HEADERS = { + "X-Hume-Client-Name": "pipecat", + "X-Hume-Client-Version": __version__, +} + class HumeTTSService(WordTTSService): """Hume Octave Text-to-Speech service. @@ -104,7 +110,11 @@ class HumeTTSService(WordTTSService): **kwargs, ) - self._client = AsyncHumeClient(api_key=api_key) + # Create a custom httpx.AsyncClient with tracking headers + # Headers are included in all requests made by the Hume SDK + self._http_client = httpx.AsyncClient(headers=DEFAULT_HEADERS) + + self._client = AsyncHumeClient(api_key=api_key, httpx_client=self._http_client) self._params = params or HumeTTSService.InputParams() # Store voice in the base class (mirrors other services) @@ -138,6 +148,26 @@ class HumeTTSService(WordTTSService): self._cumulative_time = 0.0 self._started = False + async def stop(self, frame: EndFrame) -> None: + """Stop the service and cleanup resources. + + Args: + frame: The end frame. + """ + await super().stop(frame) + if hasattr(self, "_http_client") and self._http_client: + await self._http_client.aclose() + + async def cancel(self, frame: CancelFrame) -> None: + """Cancel the service and cleanup resources. + + Args: + frame: The cancel frame. + """ + await super().cancel(frame) + if hasattr(self, "_http_client") and self._http_client: + await self._http_client.aclose() + async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM): """Push a frame and handle state changes.