diff --git a/src/pipecat/services/ai_service.py b/src/pipecat/services/ai_service.py index ec78549c2..61ed7d456 100644 --- a/src/pipecat/services/ai_service.py +++ b/src/pipecat/services/ai_service.py @@ -42,27 +42,25 @@ class AIService(FrameProcessor): **kwargs: Additional arguments passed to the parent FrameProcessor. """ super().__init__(**kwargs) - self._model_name: str = "" - self._settings: ServiceSettings = ServiceSettings() + self._settings: ServiceSettings = ServiceSettings(model="") self._session_properties: Dict[str, Any] = {} - @property - def model_name(self) -> str: - """Get the current model name. + def _sync_model_name_to_metrics(self): + """Sync the current AI model name (in `self._settings.model`) for usage in metrics. - Returns: - The name of the AI model being used. - """ - return self._model_name + We don't store model name here because there's already a single source + of truth for it in `self._settings.model`. This method is just for + syncing the model name to the metrics data. - def set_model_name(self, model: str): - """Set the AI model name and update metrics. + TODO: as a next step we should make it so that service classes pass + model into `super().__init__` and `AIService` can be responsible for + syncing its initial value to metrics, just as it's responsible for + syncing any updates to its value to metrics via `_update_settings`. Args: model: The name of the AI model to use. """ - self._model_name = model - self.set_core_metrics_data(MetricsData(processor=self.name, model=self._model_name)) + self.set_core_metrics_data(MetricsData(processor=self.name, model=self._settings.model)) async def start(self, frame: StartFrame): """Start the AI service. @@ -117,7 +115,7 @@ class AIService(FrameProcessor): changed = self._settings.apply_update(update) if "model" in changed: - self.set_model_name(self._settings.model) + self._sync_model_name_to_metrics() if changed: logger.info(f"{self.name}: updated settings fields: {set(changed)}") diff --git a/src/pipecat/services/anthropic/llm.py b/src/pipecat/services/anthropic/llm.py index 68ebf7ab1..c7f27c0d1 100644 --- a/src/pipecat/services/anthropic/llm.py +++ b/src/pipecat/services/anthropic/llm.py @@ -237,7 +237,6 @@ class AnthropicLLMService(LLMService): self._client = client or AsyncAnthropic( api_key=api_key ) # if the client is provided, use it and remove it, otherwise create a new one - self.set_model_name(model) self._retry_timeout_secs = retry_timeout_secs self._retry_on_timeout = retry_on_timeout self._settings = AnthropicLLMSettings( @@ -258,6 +257,7 @@ class AnthropicLLMService(LLMService): thinking=params.thinking, extra=params.extra if isinstance(params.extra, dict) else {}, ) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate usage metrics. @@ -324,7 +324,7 @@ class AnthropicLLMService(LLMService): # Build params using the same method as streaming completions params = { - "model": self.model_name, + "model": self._settings.model, "max_tokens": max_tokens if max_tokens is not None else self._settings.max_tokens, "stream": False, "temperature": self._settings.temperature, @@ -438,7 +438,7 @@ class AnthropicLLMService(LLMService): await self.start_ttfb_metrics() params = { - "model": self.model_name, + "model": self._settings.model, "max_tokens": self._settings.max_tokens, "stream": True, "temperature": self._settings.temperature, diff --git a/src/pipecat/services/asyncai/tts.py b/src/pipecat/services/asyncai/tts.py index 31ce83810..36b4ab4de 100644 --- a/src/pipecat/services/asyncai/tts.py +++ b/src/pipecat/services/asyncai/tts.py @@ -171,8 +171,7 @@ class AsyncAITTSService(AudioContextTTSService): if params.language else None, ) - - self.set_model_name(model) + self._sync_model_name_to_metrics() self._receive_task = None self._keepalive_task = None @@ -513,7 +512,7 @@ class AsyncAIHttpTTSService(TTSService): if params.language else None, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() self._session = aiohttp_session @@ -562,7 +561,7 @@ class AsyncAIHttpTTSService(TTSService): voice_config = {"mode": "id", "id": self._settings.voice} await self.start_ttfb_metrics() payload = { - "model_id": self._model_name, + "model_id": self._settings.model, "transcript": text, "voice": voice_config, "output_format": { diff --git a/src/pipecat/services/aws/llm.py b/src/pipecat/services/aws/llm.py index b39d518ec..de994463b 100644 --- a/src/pipecat/services/aws/llm.py +++ b/src/pipecat/services/aws/llm.py @@ -820,7 +820,6 @@ class AWSBedrockLLMService(LLMService): "config": client_config, } - self.set_model_name(model) self._retry_timeout_secs = retry_timeout_secs self._retry_on_timeout = retry_on_timeout self._settings = AWSBedrockLLMSettings( @@ -833,6 +832,7 @@ class AWSBedrockLLMService(LLMService): if isinstance(params.additional_model_request_fields, dict) else {}, ) + self._sync_model_name_to_metrics() logger.info(f"Using AWS Bedrock model: {model}") @@ -895,7 +895,7 @@ class AWSBedrockLLMService(LLMService): inference_config["maxTokens"] = max_tokens request_params = { - "modelId": self.model_name, + "modelId": self._settings.model, "messages": messages, "additionalModelRequestFields": self._settings.additional_model_request_fields, } @@ -1052,7 +1052,7 @@ class AWSBedrockLLMService(LLMService): # Prepare request parameters request_params = { - "modelId": self.model_name, + "modelId": self._settings.model, "messages": messages, "additionalModelRequestFields": self._settings.additional_model_request_fields, } diff --git a/src/pipecat/services/aws/nova_sonic/llm.py b/src/pipecat/services/aws/nova_sonic/llm.py index 7d2b9c05e..eba5cc21b 100644 --- a/src/pipecat/services/aws/nova_sonic/llm.py +++ b/src/pipecat/services/aws/nova_sonic/llm.py @@ -269,7 +269,7 @@ class AWSNovaSonicLLMService(LLMService): top_p=params.top_p, endpointing_sensitivity=params.endpointing_sensitivity, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() # Audio I/O config (hardware settings, not runtime-tunable) self._input_sample_rate = params.input_sample_rate diff --git a/src/pipecat/services/azure/image.py b/src/pipecat/services/azure/image.py index 2bddf6c43..f5ce4a9f1 100644 --- a/src/pipecat/services/azure/image.py +++ b/src/pipecat/services/azure/image.py @@ -54,7 +54,8 @@ class AzureImageGenServiceREST(ImageGenService): self._api_key = api_key self._azure_endpoint = endpoint self._api_version = api_version - self.set_model_name(model) + self._settings.model = model + self._sync_model_name_to_metrics() self._image_size = image_size self._aiohttp_session = aiohttp_session diff --git a/src/pipecat/services/camb/tts.py b/src/pipecat/services/camb/tts.py index 411b4cabf..a2887df28 100644 --- a/src/pipecat/services/camb/tts.py +++ b/src/pipecat/services/camb/tts.py @@ -236,8 +236,7 @@ class CambTTSService(TTSService): ), user_instructions=params.user_instructions, ) - - self.set_model_name(model) + self._sync_model_name_to_metrics() self._client = None @@ -272,7 +271,7 @@ class CambTTSService(TTSService): # Use model-specific sample rate if not explicitly specified if not self._init_sample_rate: - self._sample_rate = MODEL_SAMPLE_RATES.get(self.model_name, 22050) + self._sample_rate = MODEL_SAMPLE_RATES.get(self._settings.model, 22050) @traced_tts async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]: @@ -300,12 +299,12 @@ class CambTTSService(TTSService): "text": text, "voice_id": self._settings.voice, "language": self._settings.language, - "speech_model": self.model_name, + "speech_model": self._settings.model, "output_configuration": StreamTtsOutputConfiguration(format="pcm_s16le"), } # Add user instructions if using mars-instruct model - if self._model_name == "mars-instruct" and self._settings.user_instructions: + if self._settings.model == "mars-instruct" and self._settings.user_instructions: tts_kwargs["user_instructions"] = self._settings.user_instructions await self.start_tts_usage_metrics(text) diff --git a/src/pipecat/services/cartesia/stt.py b/src/pipecat/services/cartesia/stt.py index e3270936b..6a30f9a53 100644 --- a/src/pipecat/services/cartesia/stt.py +++ b/src/pipecat/services/cartesia/stt.py @@ -201,7 +201,7 @@ class CartesiaSTTService(WebsocketSTTService): language=merged_options.get("language"), encoding=merged_options.get("encoding", "pcm_s16le"), ) - self.set_model_name(merged_options["model"]) + self._sync_model_name_to_metrics() self._api_key = api_key self._base_url = base_url or "api.cartesia.ai" self._receive_task = None diff --git a/src/pipecat/services/cartesia/tts.py b/src/pipecat/services/cartesia/tts.py index daddfa6c8..1185a2305 100644 --- a/src/pipecat/services/cartesia/tts.py +++ b/src/pipecat/services/cartesia/tts.py @@ -330,6 +330,7 @@ class CartesiaTTSService(AudioContextWordTTSService): self._cartesia_version = cartesia_version self._url = url self._settings = CartesiaTTSSettings( + model=model, output_container=container, output_encoding=encoding, output_sample_rate=0, @@ -342,7 +343,7 @@ class CartesiaTTSService(AudioContextWordTTSService): pronunciation_dict_id=params.pronunciation_dict_id, voice=voice_id, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() self._context_id = None self._receive_task = None @@ -457,7 +458,7 @@ class CartesiaTTSService(AudioContextWordTTSService): "transcript": text, "continue": continue_transcript, "context_id": self._context_id, - "model_id": self.model_name, + "model_id": self._settings.model, "voice": voice_config, "output_format": { "container": self._settings.output_container, @@ -465,7 +466,7 @@ class CartesiaTTSService(AudioContextWordTTSService): "sample_rate": self._settings.output_sample_rate, }, "add_timestamps": add_timestamps, - "use_original_timestamps": False if self.model_name == "sonic" else True, + "use_original_timestamps": False if self._settings.model == "sonic" else True, } if is_given(self._settings.language) and self._settings.language: @@ -741,7 +742,7 @@ class CartesiaHttpTTSService(TTSService): generation_config=params.generation_config, pronunciation_dict_id=params.pronunciation_dict_id, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() self._client = AsyncCartesia( api_key=api_key, @@ -829,7 +830,7 @@ class CartesiaHttpTTSService(TTSService): } payload = { - "model_id": self._model_name, + "model_id": self._settings.model, "transcript": text, "voice": voice_config, "output_format": output_format, diff --git a/src/pipecat/services/cerebras/llm.py b/src/pipecat/services/cerebras/llm.py index 01a8165f8..e1ecceef7 100644 --- a/src/pipecat/services/cerebras/llm.py +++ b/src/pipecat/services/cerebras/llm.py @@ -66,7 +66,7 @@ class CerebrasLLMService(OpenAILLMService): Dictionary of parameters for the chat completion request. """ params = { - "model": self.model_name, + "model": self._settings.model, "stream": True, "seed": self._settings.seed, "temperature": self._settings.temperature, diff --git a/src/pipecat/services/deepgram/flux/stt.py b/src/pipecat/services/deepgram/flux/stt.py index e82fc4dd8..3b6ede086 100644 --- a/src/pipecat/services/deepgram/flux/stt.py +++ b/src/pipecat/services/deepgram/flux/stt.py @@ -212,7 +212,7 @@ class DeepgramFluxSTTService(WebsocketSTTService): tag=params.tag or [], min_confidence=params.min_confidence, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() self._api_key = api_key self._url = url self._should_interrupt = should_interrupt diff --git a/src/pipecat/services/deepgram/stt.py b/src/pipecat/services/deepgram/stt.py index 8d4a72fc3..6e5955450 100644 --- a/src/pipecat/services/deepgram/stt.py +++ b/src/pipecat/services/deepgram/stt.py @@ -143,13 +143,13 @@ class DeepgramSTTService(STTService): if "language" in merged_options and isinstance(merged_options["language"], Language): merged_options["language"] = merged_options["language"].value - self.set_model_name(merged_options["model"]) merged_live_options = LiveOptions(**merged_options) self._settings = DeepgramSTTSettings( model=merged_options.get("model"), language=merged_options.get("language"), live_options=merged_live_options, ) + self._sync_model_name_to_metrics() self._addons = addons self._should_interrupt = should_interrupt @@ -225,7 +225,7 @@ class DeepgramSTTService(STTService): elif "live_options" in changed and self._settings.live_options.model is not None: # Only live_options was given → pull model up. self._settings.model = self._settings.live_options.model - self.set_model_name(self._settings.model) + self._sync_model_name_to_metrics() # --- Sync language ----------------------------------------------- if language_given: diff --git a/src/pipecat/services/deepgram/stt_sagemaker.py b/src/pipecat/services/deepgram/stt_sagemaker.py index 3184bf7f8..71fdf4417 100644 --- a/src/pipecat/services/deepgram/stt_sagemaker.py +++ b/src/pipecat/services/deepgram/stt_sagemaker.py @@ -143,13 +143,13 @@ class DeepgramSageMakerSTTService(STTService): if "language" in merged_options and isinstance(merged_options["language"], Language): merged_options["language"] = merged_options["language"].value - self.set_model_name(merged_options["model"]) merged_live_options = LiveOptions(**merged_options) self._settings = DeepgramSageMakerSTTSettings( model=merged_options.get("model"), language=merged_options.get("language"), live_options=merged_live_options, ) + self._sync_model_name_to_metrics() self._client: Optional[SageMakerBidiClient] = None self._response_task: Optional[asyncio.Task] = None @@ -193,7 +193,7 @@ class DeepgramSageMakerSTTService(STTService): elif "live_options" in changed and self._settings.live_options.model is not None: # Only live_options was given → pull model up. self._settings.model = self._settings.live_options.model - self.set_model_name(self._settings.model) + self._sync_model_name_to_metrics() # --- Sync language ----------------------------------------------- if language_given: diff --git a/src/pipecat/services/deepseek/llm.py b/src/pipecat/services/deepseek/llm.py index 806dce13d..70318c9ba 100644 --- a/src/pipecat/services/deepseek/llm.py +++ b/src/pipecat/services/deepseek/llm.py @@ -65,7 +65,7 @@ class DeepSeekLLMService(OpenAILLMService): Dictionary of parameters for the chat completion request. """ params = { - "model": self.model_name, + "model": self._settings.model, "stream": True, "stream_options": {"include_usage": True}, "frequency_penalty": self._settings.frequency_penalty, diff --git a/src/pipecat/services/elevenlabs/stt.py b/src/pipecat/services/elevenlabs/stt.py index c3e6b29e7..5ff91f597 100644 --- a/src/pipecat/services/elevenlabs/stt.py +++ b/src/pipecat/services/elevenlabs/stt.py @@ -281,7 +281,7 @@ class ElevenLabsSTTService(SegmentedSTTService): else "eng", tag_audio_events=params.tag_audio_events, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if the service can generate processing metrics. @@ -531,7 +531,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService): enable_logging=params.enable_logging, include_language_detection=params.include_language_detection, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if the service can generate processing metrics. diff --git a/src/pipecat/services/elevenlabs/tts.py b/src/pipecat/services/elevenlabs/tts.py index 7de346826..55875cb69 100644 --- a/src/pipecat/services/elevenlabs/tts.py +++ b/src/pipecat/services/elevenlabs/tts.py @@ -423,7 +423,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService): enable_logging=params.enable_logging, apply_text_normalization=params.apply_text_normalization, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() self._output_format = "" # initialized in start() self._voice_settings = self._set_voice_settings() @@ -607,7 +607,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService): logger.debug("Connecting to ElevenLabs") voice_id = self._settings.voice - model = self.model_name + model = self._settings.model output_format = self._output_format url = f"{self._url}/v1/text-to-speech/{voice_id}/multi-stream-input?model_id={model}&output_format={output_format}&auto_mode={self._settings.auto_mode}" @@ -929,7 +929,7 @@ class ElevenLabsHttpTTSService(WordTTSService): speed=params.speed, apply_text_normalization=params.apply_text_normalization, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() self._output_format = "" # initialized in start() self._voice_settings = self._set_voice_settings() self._pronunciation_dictionary_locators = params.pronunciation_dictionary_locators @@ -1100,7 +1100,7 @@ class ElevenLabsHttpTTSService(WordTTSService): payload: Dict[str, Union[str, Dict[str, Union[float, bool]]]] = { "text": text, - "model_id": self._model_name, + "model_id": self._settings.model, } # Include previous text as context if available @@ -1122,7 +1122,7 @@ class ElevenLabsHttpTTSService(WordTTSService): payload["apply_text_normalization"] = self._settings.apply_text_normalization language = self._settings.language - if self._model_name in ELEVENLABS_MULTILINGUAL_MODELS and language: + if self._settings.model in ELEVENLABS_MULTILINGUAL_MODELS and language: payload["language_code"] = language logger.debug(f"Using language code: {language}") elif language: diff --git a/src/pipecat/services/fal/image.py b/src/pipecat/services/fal/image.py index 412cedfbd..fd9d9a22d 100644 --- a/src/pipecat/services/fal/image.py +++ b/src/pipecat/services/fal/image.py @@ -78,7 +78,8 @@ class FalImageGenService(ImageGenService): **kwargs: Additional arguments passed to parent ImageGenService. """ super().__init__(**kwargs) - self.set_model_name(model) + self._settings.model = model + self._sync_model_name_to_metrics() self._params = params self._aiohttp_session = aiohttp_session if key: @@ -103,7 +104,7 @@ class FalImageGenService(ImageGenService): logger.debug(f"Generating image from prompt: {prompt}") response = await fal_client.run_async( - self.model_name, + self._settings.model, arguments={"prompt": prompt, **self._params.model_dump(exclude_none=True)}, ) diff --git a/src/pipecat/services/fireworks/llm.py b/src/pipecat/services/fireworks/llm.py index 9338d8c5a..92deb00b9 100644 --- a/src/pipecat/services/fireworks/llm.py +++ b/src/pipecat/services/fireworks/llm.py @@ -66,7 +66,7 @@ class FireworksLLMService(OpenAILLMService): Dictionary of parameters for the chat completion request. """ params = { - "model": self.model_name, + "model": self._settings.model, "stream": True, "frequency_penalty": self._settings.frequency_penalty, "presence_penalty": self._settings.presence_penalty, diff --git a/src/pipecat/services/fish/tts.py b/src/pipecat/services/fish/tts.py index 131495769..0d84ea7ab 100644 --- a/src/pipecat/services/fish/tts.py +++ b/src/pipecat/services/fish/tts.py @@ -176,6 +176,7 @@ class FishAudioTTSService(InterruptibleTTSService): self._request_id = None self._settings = FishAudioTTSSettings( + model=model_id, voice=reference_id, fish_sample_rate=0, latency=params.latency, @@ -185,8 +186,7 @@ class FishAudioTTSService(InterruptibleTTSService): prosody_volume=params.prosody_volume, reference_id=reference_id, ) - - self.set_model_name(model_id) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. @@ -267,7 +267,7 @@ class FishAudioTTSService(InterruptibleTTSService): logger.debug("Connecting to Fish Audio") headers = {"Authorization": f"Bearer {self._api_key}"} - headers["model"] = self.model_name + headers["model"] = self._settings.model self._websocket = await websocket_connect(self._base_url, additional_headers=headers) # Send initial start message with ormsgpack diff --git a/src/pipecat/services/gladia/stt.py b/src/pipecat/services/gladia/stt.py index c92d9469f..8d968e00f 100644 --- a/src/pipecat/services/gladia/stt.py +++ b/src/pipecat/services/gladia/stt.py @@ -279,9 +279,9 @@ class GladiaSTTService(WebsocketSTTService): self._api_key = api_key self._region = region self._url = url - self.set_model_name(model) self._receive_task = None self._settings = GladiaSTTSettings(model=model, input_params=params) + self._sync_model_name_to_metrics() # Session management self._session_url = None @@ -328,7 +328,7 @@ class GladiaSTTService(WebsocketSTTService): "bit_depth": params.bit_depth or 16, "sample_rate": self.sample_rate, "channels": params.channels or 1, - "model": self._model_name, + "model": self._settings.model, } # Add custom_metadata if provided diff --git a/src/pipecat/services/google/gemini_live/llm.py b/src/pipecat/services/google/gemini_live/llm.py index 00b540385..84b06a86d 100644 --- a/src/pipecat/services/google/gemini_live/llm.py +++ b/src/pipecat/services/google/gemini_live/llm.py @@ -701,7 +701,6 @@ class GeminiLiveLLMService(LLMService): self._last_sent_time = 0 self._base_url = base_url - self.set_model_name(model) self._voice_id = voice_id self._language_code = params.language @@ -763,6 +762,7 @@ class GeminiLiveLLMService(LLMService): proactivity=params.proactivity or {}, extra=params.extra if isinstance(params.extra, dict) else {}, ) + self._sync_model_name_to_metrics() self._file_api_base_url = file_api_base_url self._file_api: Optional[GeminiFileAPI] = None @@ -1230,7 +1230,9 @@ class GeminiLiveLLMService(LLMService): await self.push_error(error_msg=f"Initialization error: {e}", exception=e) async def _connection_task_handler(self, config: LiveConnectConfig): - async with self._client.aio.live.connect(model=self._model_name, config=config) as session: + async with self._client.aio.live.connect( + model=self._settings.model, config=config + ) as session: logger.info("Connected to Gemini service") # Mark connection start time diff --git a/src/pipecat/services/google/image.py b/src/pipecat/services/google/image.py index fcc8e41d0..f03b1da63 100644 --- a/src/pipecat/services/google/image.py +++ b/src/pipecat/services/google/image.py @@ -79,7 +79,9 @@ class GoogleImageGenService(ImageGenService): http_options = update_google_client_http_options(http_options) self._client = genai.Client(api_key=api_key, http_options=http_options) - self.set_model_name(self._params.model) + + self._settings.model = self._params.model + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/google/llm.py b/src/pipecat/services/google/llm.py index f5a6db78c..2004aa15a 100644 --- a/src/pipecat/services/google/llm.py +++ b/src/pipecat/services/google/llm.py @@ -797,7 +797,6 @@ class GoogleLLMService(LLMService): params = params or GoogleLLMService.InputParams() - self.set_model_name(model) self._api_key = api_key self._system_instruction = system_instruction self._http_options = update_google_client_http_options(http_options) @@ -811,6 +810,7 @@ class GoogleLLMService(LLMService): thinking=params.thinking, extra=params.extra if isinstance(params.extra, dict) else {}, ) + self._sync_model_name_to_metrics() self._tools = tools self._tool_config = tool_config @@ -870,7 +870,7 @@ class GoogleLLMService(LLMService): # Use the new google-genai client's async method response = await self._client.aio.models.generate_content( - model=self._model_name, + model=self._settings.model, contents=messages, config=generation_config, ) @@ -930,10 +930,10 @@ class GoogleLLMService(LLMService): # There's no way to introspect on model capabilities, so # to check for models that we know default to thinkin on # and can be configured to turn it off. - if not self._model_name.startswith("gemini-2.5-flash"): + if not self._settings.model.startswith("gemini-2.5-flash"): return # If we have an image model, we don't use a budget either. - if "image" in self._model_name: + if "image" in self._settings.model: return # If thinking_config is already set, don't override it. if "thinking_config" in generation_params: @@ -974,7 +974,7 @@ class GoogleLLMService(LLMService): await self.start_ttfb_metrics() return await self._client.aio.models.generate_content_stream( - model=self._model_name, + model=self._settings.model, contents=messages, config=generation_config, ) diff --git a/src/pipecat/services/groq/stt.py b/src/pipecat/services/groq/stt.py index 52cb0a7cc..d51e93c68 100644 --- a/src/pipecat/services/groq/stt.py +++ b/src/pipecat/services/groq/stt.py @@ -62,7 +62,7 @@ class GroqSTTService(BaseWhisperSTTService): # Build kwargs dict with only set parameters kwargs = { "file": ("audio.wav", audio, "audio/wav"), - "model": self.model_name, + "model": self._settings.model, # Use verbose_json to get probability metrics "response_format": "verbose_json" if self._include_prob_metrics else "json", "language": self._language, diff --git a/src/pipecat/services/groq/tts.py b/src/pipecat/services/groq/tts.py index 5c3eab636..cc073f8c7 100644 --- a/src/pipecat/services/groq/tts.py +++ b/src/pipecat/services/groq/tts.py @@ -108,7 +108,6 @@ class GroqTTSService(TTSService): params = params or GroqTTSService.InputParams() self._api_key = api_key - self._model_name = model_name self._output_format = output_format self._params = params @@ -120,6 +119,7 @@ class GroqTTSService(TTSService): speed=params.speed, groq_sample_rate=sample_rate, ) + self._sync_model_name_to_metrics() self._client = AsyncGroq(api_key=self._api_key) @@ -149,7 +149,7 @@ class GroqTTSService(TTSService): try: response = await self._client.audio.speech.create( - model=self._model_name, + model=self._settings.model, voice=self._settings.voice, response_format=self._output_format, input=text, diff --git a/src/pipecat/services/hathora/stt.py b/src/pipecat/services/hathora/stt.py index e77e382c0..a08a80aa2 100644 --- a/src/pipecat/services/hathora/stt.py +++ b/src/pipecat/services/hathora/stt.py @@ -105,8 +105,7 @@ class HathoraSTTService(SegmentedSTTService): language=params.language, config=params.config, ) - - self.set_model_name(model) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/hathora/tts.py b/src/pipecat/services/hathora/tts.py index 9778a2f14..1e7662aab 100644 --- a/src/pipecat/services/hathora/tts.py +++ b/src/pipecat/services/hathora/tts.py @@ -123,8 +123,7 @@ class HathoraTTSService(TTSService): speed=params.speed, config=params.config, ) - - self.set_model_name(model) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/inworld/tts.py b/src/pipecat/services/inworld/tts.py index 1b6dee46f..1d8aa7e4b 100644 --- a/src/pipecat/services/inworld/tts.py +++ b/src/pipecat/services/inworld/tts.py @@ -173,7 +173,7 @@ class InworldHttpTTSService(WordTTSService): self._cumulative_time = 0.0 - self.set_model_name(model) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. @@ -562,7 +562,7 @@ class InworldTTSService(AudioContextWordTTSService): # Track the end time of the last word in the current generation self._generation_end_time = 0.0 - self.set_model_name(model) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/lmnt/tts.py b/src/pipecat/services/lmnt/tts.py index 71ffb4f5f..f70bfa402 100644 --- a/src/pipecat/services/lmnt/tts.py +++ b/src/pipecat/services/lmnt/tts.py @@ -122,13 +122,13 @@ class LmntTTSService(InterruptibleTTSService): ) self._api_key = api_key - self.set_model_name(model) self._settings = LmntTTSSettings( model=model, voice=voice_id, language=self.language_to_service_language(language), format="raw", ) + self._sync_model_name_to_metrics() self._receive_task = None self._context_id: Optional[str] = None @@ -238,7 +238,7 @@ class LmntTTSService(InterruptibleTTSService): "format": self._settings.format, "sample_rate": self.sample_rate, "language": self._settings.language, - "model": self.model_name, + "model": self._settings.model, } # Connect to LMNT's websocket directly diff --git a/src/pipecat/services/minimax/tts.py b/src/pipecat/services/minimax/tts.py index 507febf2d..54925f7e4 100644 --- a/src/pipecat/services/minimax/tts.py +++ b/src/pipecat/services/minimax/tts.py @@ -235,7 +235,6 @@ class MiniMaxHttpTTSService(TTSService): self._group_id = group_id self._base_url = f"{base_url}?GroupId={group_id}" self._session = aiohttp_session - self._model_name = model # Create voice settings self._settings = MiniMaxTTSSettings( @@ -249,9 +248,7 @@ class MiniMaxHttpTTSService(TTSService): audio_format="pcm", audio_channel=1, ) - - # Set model - self.set_model_name(model) + self._sync_model_name_to_metrics() # Add language boost if provided if params.language: @@ -318,14 +315,6 @@ class MiniMaxHttpTTSService(TTSService): """ return language_to_minimax_language(language) - def set_model_name(self, model: str): - """Set the TTS model to use. - - Args: - model: The model name to use for synthesis. - """ - self._model_name = model - async def start(self, frame: StartFrame): """Start the MiniMax TTS service. @@ -382,7 +371,7 @@ class MiniMaxHttpTTSService(TTSService): "stream": self._settings.stream, "voice_setting": voice_setting, "audio_setting": audio_setting, - "model": self._model_name, + "model": self._settings.model, "text": text, } if is_given(self._settings.language_boost): diff --git a/src/pipecat/services/mistral/llm.py b/src/pipecat/services/mistral/llm.py index 7a8f5b71a..984ffb7dd 100644 --- a/src/pipecat/services/mistral/llm.py +++ b/src/pipecat/services/mistral/llm.py @@ -180,7 +180,7 @@ class MistralLLMService(OpenAILLMService): fixed_messages = self._apply_mistral_fixups(params_from_context["messages"]) params = { - "model": self.model_name, + "model": self._settings.model, "stream": True, "messages": fixed_messages, "tools": params_from_context["tools"], diff --git a/src/pipecat/services/moondream/vision.py b/src/pipecat/services/moondream/vision.py index 6a180b4cb..16be15ac5 100644 --- a/src/pipecat/services/moondream/vision.py +++ b/src/pipecat/services/moondream/vision.py @@ -81,7 +81,8 @@ class MoondreamService(VisionService): """ super().__init__(**kwargs) - self.set_model_name(model) + self._settings.model = model + self._sync_model_name_to_metrics() if not use_cpu: device, dtype = detect_device() diff --git a/src/pipecat/services/nvidia/stt.py b/src/pipecat/services/nvidia/stt.py index a79119c34..fd924204e 100644 --- a/src/pipecat/services/nvidia/stt.py +++ b/src/pipecat/services/nvidia/stt.py @@ -181,10 +181,10 @@ class NvidiaSTTService(STTService): self._function_id = model_function_map.get("function_id") self._settings = NvidiaSTTSettings( + model=model_function_map.get("model_name"), language=params.language, ) - - self.set_model_name(model_function_map.get("model_name")) + self._sync_model_name_to_metrics() self._asr_service = None self._queue = None @@ -282,7 +282,7 @@ class NvidiaSTTService(STTService): if not self._thread_task: self._thread_task = self.create_task(self._thread_task_handler()) - logger.debug(f"Initialized NvidiaSTTService with model: {self.model_name}") + logger.debug(f"Initialized NvidiaSTTService with model: {self._settings.model}") async def stop(self, frame: EndFrame): """Stop the NVIDIA Riva STT service and clean up resources. @@ -467,9 +467,6 @@ class NvidiaSegmentedSTTService(SegmentedSTTService): params = params or NvidiaSegmentedSTTService.InputParams() - # Set model name - self.set_model_name(model_function_map.get("model_name")) - # Initialize NVIDIA Riva settings self._api_key = api_key self._server = server @@ -488,6 +485,7 @@ class NvidiaSegmentedSTTService(SegmentedSTTService): self._config = None self._asr_service = None self._settings = NvidiaSegmentedSTTSettings( + model=model_function_map.get("model_name"), language=self.language_to_service_language(params.language or Language.EN_US) or "en-US", profanity_filter=params.profanity_filter, @@ -496,6 +494,7 @@ class NvidiaSegmentedSTTService(SegmentedSTTService): boosted_lm_words=params.boosted_lm_words, boosted_lm_score=params.boosted_lm_score, ) + self._sync_model_name_to_metrics() def language_to_service_language(self, language: Language) -> Optional[str]: """Convert pipecat Language enum to NVIDIA Riva's language code. @@ -578,7 +577,7 @@ class NvidiaSegmentedSTTService(SegmentedSTTService): await super().start(frame) self._initialize_client() self._config = self._create_recognition_config() - logger.debug(f"Initialized NvidiaSegmentedSTTService with model: {self.model_name}") + logger.debug(f"Initialized NvidiaSegmentedSTTService with model: {self._settings.model}") async def _update_settings(self, update: STTSettings) -> dict[str, Any]: """Apply a settings update and sync internal state. diff --git a/src/pipecat/services/nvidia/tts.py b/src/pipecat/services/nvidia/tts.py index 22cffc6c1..ade5da63d 100644 --- a/src/pipecat/services/nvidia/tts.py +++ b/src/pipecat/services/nvidia/tts.py @@ -112,11 +112,12 @@ class NvidiaTTSService(TTSService): self._function_id = model_function_map.get("function_id") self._use_ssl = use_ssl self._settings = NvidiaTTSSettings( + model=model_function_map.get("model_name"), voice=voice_id, language=params.language, quality=params.quality, ) - self.set_model_name(model_function_map.get("model_name")) + self._sync_model_name_to_metrics() self._service = None self._config = None @@ -192,7 +193,7 @@ class NvidiaTTSService(TTSService): await super().start(frame) self._initialize_client() self._config = self._create_synthesis_config() - logger.debug(f"Initialized NvidiaTTSService with model: {self.model_name}") + logger.debug(f"Initialized NvidiaTTSService with model: {self._settings.model}") @traced_tts async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]: diff --git a/src/pipecat/services/openai/base_llm.py b/src/pipecat/services/openai/base_llm.py index 5b624010f..aad88472a 100644 --- a/src/pipecat/services/openai/base_llm.py +++ b/src/pipecat/services/openai/base_llm.py @@ -151,7 +151,7 @@ class BaseOpenAILLMService(LLMService): ) self._retry_timeout_secs = retry_timeout_secs self._retry_on_timeout = retry_on_timeout - self.set_model_name(model) + self._sync_model_name_to_metrics() self._full_model_name: str = "" self._client = self.create_client( api_key=api_key, @@ -265,7 +265,7 @@ class BaseOpenAILLMService(LLMService): Dictionary of parameters for the chat completion request. """ params = { - "model": self.model_name, + "model": self._settings.model, "stream": True, "stream_options": {"include_usage": True}, "frequency_penalty": self._settings.frequency_penalty, diff --git a/src/pipecat/services/openai/image.py b/src/pipecat/services/openai/image.py index d6ca51ae7..36efc5987 100644 --- a/src/pipecat/services/openai/image.py +++ b/src/pipecat/services/openai/image.py @@ -53,7 +53,8 @@ class OpenAIImageGenService(ImageGenService): model: DALL-E model to use for generation. Defaults to "dall-e-3". """ super().__init__() - self.set_model_name(model) + self._settings.model = model + self._sync_model_name_to_metrics() self._image_size = image_size self._client = AsyncOpenAI(api_key=api_key, base_url=base_url) self._aiohttp_session = aiohttp_session @@ -70,7 +71,7 @@ class OpenAIImageGenService(ImageGenService): logger.debug(f"Generating image from prompt: {prompt}") image = await self._client.images.generate( - prompt=prompt, model=self.model_name, n=1, size=self._image_size + prompt=prompt, model=self._settings.model, n=1, size=self._image_size ) image_url = image.data[0].url diff --git a/src/pipecat/services/openai/realtime/llm.py b/src/pipecat/services/openai/realtime/llm.py index 3560f0c27..d765fea75 100644 --- a/src/pipecat/services/openai/realtime/llm.py +++ b/src/pipecat/services/openai/realtime/llm.py @@ -175,12 +175,12 @@ class OpenAIRealtimeLLMService(LLMService): self.api_key = api_key self.base_url = full_url - self.set_model_name(model) self._settings = OpenAIRealtimeLLMSettings( model=model, session_properties=session_properties or events.SessionProperties(), ) + self._sync_model_name_to_metrics() self._audio_input_paused = start_audio_paused self._video_input_paused = start_video_paused self._video_frame_detail = video_frame_detail diff --git a/src/pipecat/services/openai/stt.py b/src/pipecat/services/openai/stt.py index 82ad8c0f0..13a37a2b1 100644 --- a/src/pipecat/services/openai/stt.py +++ b/src/pipecat/services/openai/stt.py @@ -100,24 +100,24 @@ class OpenAISTTService(BaseWhisperSTTService): # Build kwargs dict with only set parameters kwargs = { "file": ("audio.wav", audio, "audio/wav"), - "model": self.model_name, - "language": self._language, + "model": self._settings.model, + "language": self._settings.language, } if self._include_prob_metrics: # GPT-4o-transcribe models only support logprobs (not verbose_json) - if self.model_name in ("gpt-4o-transcribe", "gpt-4o-mini-transcribe"): + if self._settings.model in ("gpt-4o-transcribe", "gpt-4o-mini-transcribe"): kwargs["response_format"] = "json" kwargs["include"] = ["logprobs"] else: # Whisper models support verbose_json kwargs["response_format"] = "verbose_json" - if self._prompt is not None: - kwargs["prompt"] = self._prompt + if self._settings.prompt is not None: + kwargs["prompt"] = self._settings.prompt - if self._temperature is not None: - kwargs["temperature"] = self._temperature + if self._settings.temperature is not None: + kwargs["temperature"] = self._settings.temperature return await self._client.audio.transcriptions.create(**kwargs) @@ -226,7 +226,6 @@ class OpenAIRealtimeSTTService(WebsocketSTTService): self._api_key = api_key self._base_url = base_url - self.set_model_name(model) self._prompt = prompt self._turn_detection = turn_detection @@ -238,6 +237,7 @@ class OpenAIRealtimeSTTService(WebsocketSTTService): language=language, prompt=prompt, ) + self._sync_model_name_to_metrics() self._receive_task = None self._session_ready = False @@ -437,7 +437,7 @@ class OpenAIRealtimeSTTService(WebsocketSTTService): async def _send_session_update(self): """Send ``session.update`` to configure the transcription session.""" - transcription: dict = {"model": self.model_name} + transcription: dict = {"model": self._settings.model} language_code = ( self._language_to_code(self._settings.language) if self._settings.language else None diff --git a/src/pipecat/services/openai/tts.py b/src/pipecat/services/openai/tts.py index 853647f7f..2693bcc27 100644 --- a/src/pipecat/services/openai/tts.py +++ b/src/pipecat/services/openai/tts.py @@ -134,7 +134,6 @@ class OpenAITTSService(TTSService): ) super().__init__(sample_rate=sample_rate, **kwargs) - self.set_model_name(model) self._client = AsyncOpenAI(api_key=api_key, base_url=base_url) if instructions or speed: @@ -154,6 +153,7 @@ class OpenAITTSService(TTSService): instructions=params.instructions if params else instructions, speed=params.speed if params else speed, ) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. @@ -194,7 +194,7 @@ class OpenAITTSService(TTSService): # Setup API parameters create_params = { "input": text, - "model": self.model_name, + "model": self._settings.model, "voice": VALID_VOICES[self._settings.voice], "response_format": "pcm", } diff --git a/src/pipecat/services/openai_realtime_beta/openai.py b/src/pipecat/services/openai_realtime_beta/openai.py index ef40dcb15..efac34223 100644 --- a/src/pipecat/services/openai_realtime_beta/openai.py +++ b/src/pipecat/services/openai_realtime_beta/openai.py @@ -160,12 +160,12 @@ class OpenAIRealtimeBetaLLMService(LLMService): self.api_key = api_key self.base_url = full_url - self.set_model_name(model) self._settings = OpenAIRealtimeBetaLLMSettings( model=model, session_properties=session_properties or events.SessionProperties(), ) + self._sync_model_name_to_metrics() self._audio_input_paused = start_audio_paused self._send_transcription_frames = send_transcription_frames self._websocket = None diff --git a/src/pipecat/services/openrouter/llm.py b/src/pipecat/services/openrouter/llm.py index a86b18573..c33fda2fc 100644 --- a/src/pipecat/services/openrouter/llm.py +++ b/src/pipecat/services/openrouter/llm.py @@ -72,8 +72,7 @@ class OpenRouterLLMService(OpenAILLMService): Transformed parameters ready for the API call. """ params = super().build_chat_completion_params(params_from_context) - model = getattr(self, "model_name", getattr(self, "model", "")).lower() - if "gemini" in model: + if "gemini" in self._settings.model.lower(): messages = params.get("messages", []) if not messages: return params diff --git a/src/pipecat/services/perplexity/llm.py b/src/pipecat/services/perplexity/llm.py index d2dd40a57..04f25621d 100644 --- a/src/pipecat/services/perplexity/llm.py +++ b/src/pipecat/services/perplexity/llm.py @@ -66,7 +66,7 @@ class PerplexityLLMService(OpenAILLMService): Dictionary of parameters for the chat completion request. """ params = { - "model": self.model_name, + "model": self._settings.model, "stream": True, "messages": params_from_context["messages"], } diff --git a/src/pipecat/services/playht/tts.py b/src/pipecat/services/playht/tts.py index 630351164..26f1e2dad 100644 --- a/src/pipecat/services/playht/tts.py +++ b/src/pipecat/services/playht/tts.py @@ -204,7 +204,7 @@ class PlayHTTTSService(InterruptibleTTSService): speed=params.speed, seed=params.seed, ) - self.set_model_name(voice_engine) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. @@ -545,6 +545,7 @@ class PlayHTHttpTTSService(TTSService): voice_engine = voice_engine.replace("-ws", "") self._settings = PlayHTTTSSettings( + model=voice_engine, voice=voice_url, language=self.language_to_service_language(params.language) if params.language @@ -554,7 +555,7 @@ class PlayHTHttpTTSService(TTSService): speed=params.speed, seed=params.seed, ) - self.set_model_name(voice_engine) + self._sync_model_name_to_metrics() async def start(self, frame: StartFrame): """Start the PlayHT HTTP TTS service. diff --git a/src/pipecat/services/rime/tts.py b/src/pipecat/services/rime/tts.py index 2f006efc7..d4cb045df 100644 --- a/src/pipecat/services/rime/tts.py +++ b/src/pipecat/services/rime/tts.py @@ -75,7 +75,6 @@ class RimeTTSSettings(TTSSettings): """Settings for Rime WS JSON and HTTP TTS services. Parameters: - modelId: Rime model identifier. audioFormat: Audio output format. samplingRate: Audio sample rate. lang: Rime language code. @@ -86,7 +85,6 @@ class RimeTTSSettings(TTSSettings): inlineSpeedAlpha: Inline speed control markup. """ - modelId: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) audioFormat: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) samplingRate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN) lang: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) @@ -104,7 +102,6 @@ class RimeNonJsonTTSSettings(TTSSettings): """Settings for Rime non-JSON WS TTS service. Parameters: - modelId: Rime model identifier. audioFormat: Audio output format. samplingRate: Audio sample rate. lang: Rime language code. @@ -114,7 +111,6 @@ class RimeNonJsonTTSSettings(TTSSettings): top_p: Cumulative probability threshold (0.0-1.0). """ - modelId: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) audioFormat: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) samplingRate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN) lang: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) @@ -210,7 +206,7 @@ class RimeTTSService(AudioContextWordTTSService): self._model = model self._settings = RimeTTSSettings( voice=voice_id, - modelId=model, + model=model, audioFormat="pcm", samplingRate=0, lang=self.language_to_service_language(params.language) if params.language else "eng", @@ -219,6 +215,7 @@ class RimeTTSService(AudioContextWordTTSService): pauseBetweenBrackets=json.dumps(params.pause_between_brackets), phonemizeBetweenBrackets=json.dumps(params.phonemize_between_brackets), ) + self._sync_model_name_to_metrics() # State tracking self._context_id = None # Tracks current turn @@ -353,7 +350,7 @@ class RimeTTSService(AudioContextWordTTSService): f"{k}={v}" for k, v in { "speaker": self._settings.voice, - "modelId": self._settings.modelId, + "modelId": self._settings.model, "audioFormat": self._settings.audioFormat, "samplingRate": self._settings.samplingRate, "lang": self._settings.lang, @@ -589,6 +586,7 @@ class RimeHttpTTSService(TTSService): self._session = aiohttp_session self._base_url = "https://users.rime.ai/v1/rime-tts" self._settings = RimeTTSSettings( + model=model, lang=self.language_to_service_language(params.language) if params.language else "eng", speedAlpha=params.speed_alpha, reduceLatency=params.reduce_latency, @@ -597,7 +595,7 @@ class RimeHttpTTSService(TTSService): inlineSpeedAlpha=params.inline_speed_alpha if params.inline_speed_alpha else NOT_GIVEN, voice=voice_id, ) - self.set_model_name(model) + self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. @@ -648,7 +646,7 @@ class RimeHttpTTSService(TTSService): payload["inlineSpeedAlpha"] = self._settings.inlineSpeedAlpha payload["text"] = text payload["speaker"] = self._settings.voice - payload["modelId"] = self._model_name + payload["modelId"] = self._settings.model payload["samplingRate"] = self.sample_rate # Arcana does not support PCM audio @@ -769,7 +767,7 @@ class RimeNonJsonTTSService(InterruptibleTTSService): self._model = model self._settings = RimeNonJsonTTSSettings( voice=voice_id, - modelId=model, + model=model, audioFormat=audio_format, samplingRate=sample_rate, lang=self.language_to_service_language(params.language) @@ -782,6 +780,7 @@ class RimeNonJsonTTSService(InterruptibleTTSService): temperature=params.temperature if params.temperature is not None else NOT_GIVEN, top_p=params.top_p if params.top_p is not None else NOT_GIVEN, ) + self._sync_model_name_to_metrics() # Add any extra parameters for future compatibility if params.extra: self._settings.extra.update(params.extra) @@ -863,7 +862,7 @@ class RimeNonJsonTTSService(InterruptibleTTSService): # Build URL with query parameters (only given, non-None values) settings_dict = { "speaker": self._settings.voice, - "modelId": self._settings.modelId, + "modelId": self._settings.model, "audioFormat": self._settings.audioFormat, "samplingRate": self._settings.samplingRate, } @@ -981,10 +980,6 @@ class RimeNonJsonTTSService(InterruptibleTTSService): """ changed = await super()._update_settings(update) - # Sync model to settings dict field - if "model" in changed: - self._settings.modelId = self._model_name - if changed: logger.debug("Settings changed, reconnecting WebSocket with new parameters") await self._disconnect() diff --git a/src/pipecat/services/sambanova/llm.py b/src/pipecat/services/sambanova/llm.py index 99c7bca2c..016e1740d 100644 --- a/src/pipecat/services/sambanova/llm.py +++ b/src/pipecat/services/sambanova/llm.py @@ -84,7 +84,7 @@ class SambaNovaLLMService(OpenAILLMService): # type: ignore Dictionary of parameters for the chat completion request. """ params = { - "model": self.model_name, + "model": self._settings.model, "stream": True, "stream_options": {"include_usage": True}, "temperature": self._settings.temperature, diff --git a/src/pipecat/services/sambanova/stt.py b/src/pipecat/services/sambanova/stt.py index a1cbe8a22..f313f0d7b 100644 --- a/src/pipecat/services/sambanova/stt.py +++ b/src/pipecat/services/sambanova/stt.py @@ -72,7 +72,7 @@ class SambaNovaSTTService(BaseWhisperSTTService): # type: ignore # Build kwargs dict with only set parameters kwargs = { "file": ("audio.wav", audio, "audio/wav"), - "model": self.model_name, + "model": self._settings.model, "response_format": "json", "language": self._language, } diff --git a/src/pipecat/services/sarvam/stt.py b/src/pipecat/services/sarvam/stt.py index aa6baef14..b2c9560e4 100644 --- a/src/pipecat/services/sarvam/stt.py +++ b/src/pipecat/services/sarvam/stt.py @@ -225,7 +225,6 @@ class SarvamSTTService(STTService): super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs) - self.set_model_name(model) self._api_key = api_key # Store connection parameters @@ -257,6 +256,7 @@ class SarvamSTTService(STTService): vad_signals=params.vad_signals, high_vad_sensitivity=params.high_vad_sensitivity, ) + self._sync_model_name_to_metrics() if params.vad_signals: self._register_event_handler("on_speech_started") @@ -322,7 +322,7 @@ class SarvamSTTService(STTService): if is_given(update.language) and update.language is not None: if not self._config.supports_language: raise ValueError( - f"Model '{self.model_name}' does not support language parameter " + f"Model '{self._settings.model}' does not support language parameter " "(auto-detects language)." ) @@ -330,11 +330,13 @@ class SarvamSTTService(STTService): if is_given(update.prompt) and update.prompt is not None: if not self._config.supports_prompt: raise ValueError( - f"Model '{self.model_name}' does not support prompt parameter." + f"Model '{self._settings.model}' does not support prompt parameter." ) if is_given(update.mode) and update.mode is not None: if not self._config.supports_mode: - raise ValueError(f"Model '{self.model_name}' does not support mode parameter.") + raise ValueError( + f"Model '{self._settings.model}' does not support mode parameter." + ) changed = await super()._update_settings(update) @@ -374,11 +376,13 @@ class SarvamSTTService(STTService): if not self._config.supports_prompt: if prompt is not None: - raise ValueError(f"Model '{self.model_name}' does not support prompt parameter.") + raise ValueError( + f"Model '{self._settings.model}' does not support prompt parameter." + ) # If prompt is None and model doesn't support prompts, silently return (no-op) return - logger.info(f"Updating {self.model_name} prompt.") + logger.info(f"Updating {self._settings.model} prompt.") self._settings.prompt = prompt await self._disconnect() await self._connect() @@ -460,7 +464,7 @@ class SarvamSTTService(STTService): try: # Build common connection parameters connect_kwargs = { - "model": self.model_name, + "model": self._settings.model, "sample_rate": str(self.sample_rate), } diff --git a/src/pipecat/services/sarvam/tts.py b/src/pipecat/services/sarvam/tts.py index cbccd0130..191689f5a 100644 --- a/src/pipecat/services/sarvam/tts.py +++ b/src/pipecat/services/sarvam/tts.py @@ -489,6 +489,7 @@ class SarvamHttpTTSService(TTSService): model=model, voice=voice_id, ) + self._sync_model_name_to_metrics() # Add parameters based on model support if self._config.supports_pitch: @@ -506,8 +507,6 @@ class SarvamHttpTTSService(TTSService): elif params.temperature != 0.6: logger.warning(f"temperature parameter is ignored for {model}") - self.set_model_name(model) - def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. @@ -559,7 +558,7 @@ class SarvamHttpTTSService(TTSService): "speaker": self._settings.voice, "sample_rate": self.sample_rate, "enable_preprocessing": self._settings.enable_preprocessing, - "model": self._model_name, + "model": self._settings.model, "pace": self._settings.pace if is_given(self._settings.pace) else 1.0, } @@ -828,7 +827,6 @@ class SarvamTTSService(InterruptibleTTSService): # WebSocket endpoint URL with model query parameter self._websocket_url = f"{url}?model={model}" self._api_key = api_key - self.set_model_name(model) # Validate and clamp pace to model's valid range pace = params.pace @@ -854,6 +852,7 @@ class SarvamTTSService(InterruptibleTTSService): model=model, voice=voice_id, ) + self._sync_model_name_to_metrics() # Add parameters based on model support if self._config.supports_pitch: diff --git a/src/pipecat/services/soniox/stt.py b/src/pipecat/services/soniox/stt.py index 1f34c061c..1e4b49705 100644 --- a/src/pipecat/services/soniox/stt.py +++ b/src/pipecat/services/soniox/stt.py @@ -195,13 +195,13 @@ class SonioxSTTService(WebsocketSTTService): self._api_key = api_key self._url = url - self.set_model_name(params.model) self._vad_force_turn_endpoint = vad_force_turn_endpoint self._settings = SonioxSTTSettings( model=params.model, input_params=params, ) + self._sync_model_name_to_metrics() self._final_transcription_buffer = [] self._last_tokens_received: Optional[float] = None @@ -247,7 +247,7 @@ class SonioxSTTService(WebsocketSTTService): elif "input_params" in changed and self._settings.input_params.model is not None: # Only input_params was given → pull model up. self._settings.model = self._settings.input_params.model - self.set_model_name(self._settings.model) + self._sync_model_name_to_metrics() # TODO: someday we could reconnect here to apply updated settings. # Code might look something like the below: @@ -380,7 +380,7 @@ class SonioxSTTService(WebsocketSTTService): # Send the initial configuration message. config = { "api_key": self._api_key, - "model": self._model_name, + "model": self._settings.model, "audio_format": params.audio_format, "num_channels": params.num_channels or 1, "enable_endpoint_detection": enable_endpoint_detection, diff --git a/src/pipecat/services/speechmatics/stt.py b/src/pipecat/services/speechmatics/stt.py index 2e23765b2..0ec5609b2 100644 --- a/src/pipecat/services/speechmatics/stt.py +++ b/src/pipecat/services/speechmatics/stt.py @@ -459,7 +459,7 @@ class SpeechmaticsSTTService(STTService): # Model + metrics (operating_point comes from the SDK config/preset) self._settings.model = self._config.operating_point.value - self.set_model_name(self._config.operating_point.value) + self._sync_model_name_to_metrics() # Message queue self._stt_msg_queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue() diff --git a/src/pipecat/services/stt_service.py b/src/pipecat/services/stt_service.py index 47b83ab56..de2539932 100644 --- a/src/pipecat/services/stt_service.py +++ b/src/pipecat/services/stt_service.py @@ -492,7 +492,7 @@ class STTService(AIService): if self.metrics_enabled: ttfb_data = TTFBMetricsData( processor=self.name, - model=self.model_name, + model=self._settings.model, value=ttfb, ) await super().push_frame(MetricsFrame(data=[ttfb_data])) diff --git a/src/pipecat/services/whisper/base_stt.py b/src/pipecat/services/whisper/base_stt.py index 74ca2d102..9def3c2f1 100644 --- a/src/pipecat/services/whisper/base_stt.py +++ b/src/pipecat/services/whisper/base_stt.py @@ -156,7 +156,6 @@ class BaseWhisperSTTService(SegmentedSTTService): **kwargs: Additional arguments passed to SegmentedSTTService. """ super().__init__(ttfs_p99_latency=ttfs_p99_latency, **kwargs) - self.set_model_name(model) self._client = self._create_client(api_key, base_url) self._language = self.language_to_service_language(language or Language.EN) self._prompt = prompt @@ -170,6 +169,7 @@ class BaseWhisperSTTService(SegmentedSTTService): prompt=self._prompt, temperature=self._temperature, ) + self._sync_model_name_to_metrics() def _create_client(self, api_key: Optional[str], base_url: Optional[str]): return AsyncOpenAI(api_key=api_key, base_url=base_url) diff --git a/src/pipecat/services/whisper/stt.py b/src/pipecat/services/whisper/stt.py index 033d815d9..205838314 100644 --- a/src/pipecat/services/whisper/stt.py +++ b/src/pipecat/services/whisper/stt.py @@ -236,7 +236,6 @@ class WhisperSTTService(SegmentedSTTService): super().__init__(**kwargs) self._device: str = device self._compute_type = compute_type - self.set_model_name(model if isinstance(model, str) else model.value) self._no_speech_prob = no_speech_prob self._model: Optional[WhisperModel] = None @@ -247,6 +246,7 @@ class WhisperSTTService(SegmentedSTTService): compute_type=self._compute_type, no_speech_prob=self._no_speech_prob, ) + self._sync_model_name_to_metrics() self._load() @@ -281,7 +281,7 @@ class WhisperSTTService(SegmentedSTTService): logger.debug("Loading Whisper model...") self._model = WhisperModel( - self.model_name, device=self._device, compute_type=self._compute_type + self._settings.model, device=self._device, compute_type=self._compute_type ) logger.debug("Loaded Whisper model") except ModuleNotFoundError as e: @@ -370,7 +370,6 @@ class WhisperSTTServiceMLX(WhisperSTTService): # Skip WhisperSTTService.__init__ and call its parent directly SegmentedSTTService.__init__(self, **kwargs) - self.set_model_name(model if isinstance(model, str) else model.value) self._no_speech_prob = no_speech_prob self._temperature = temperature @@ -381,6 +380,7 @@ class WhisperSTTServiceMLX(WhisperSTTService): temperature=self._temperature, engine="mlx", ) + self._sync_model_name_to_metrics() # No need to call _load() as MLX Whisper loads models on demand @@ -421,7 +421,7 @@ class WhisperSTTServiceMLX(WhisperSTTService): chunk = await asyncio.to_thread( mlx_whisper.transcribe, audio_float, - path_or_hf_repo=self.model_name, + path_or_hf_repo=self._settings.model, temperature=self._temperature, language=self._settings.language, ) diff --git a/src/pipecat/utils/tracing/service_decorators.py b/src/pipecat/utils/tracing/service_decorators.py index 3b23f337a..296ff9522 100644 --- a/src/pipecat/utils/tracing/service_decorators.py +++ b/src/pipecat/utils/tracing/service_decorators.py @@ -44,6 +44,23 @@ T = TypeVar("T") R = TypeVar("R") +def _get_model_name(service) -> str: + """Get the model name from a service instance. + + This is a bit of a mess — there were multiple places a model name could live. + Soon, self._settings should be the only source of truth about model name. + In fact...it might already be the case, but juuuuust to be safe, we'll + check all the places we used to store it. + """ + return ( + getattr(getattr(service, "_settings", None), "model", None) + or getattr(service, "_full_model_name", None) + or getattr(service, "model_name", None) + or getattr(service, "_model_name", None) + or "unknown" + ) + + def _noop_decorator(func): """No-op fallback decorator when tracing is unavailable. @@ -194,7 +211,7 @@ def traced_tts(func: Optional[Callable] = None, *, name: Optional[str] = None) - add_tts_span_attributes( span=span, service_name=service_class_name, - model=getattr(self, "model_name") or "unknown", + model=_get_model_name(self), voice_id=getattr(settings, "voice", "unknown"), text=text, settings=settings, @@ -311,7 +328,7 @@ def traced_stt(func: Optional[Callable] = None, *, name: Optional[str] = None) - add_stt_span_attributes( span=current_span, service_name=service_class_name, - model=getattr(self, "model_name") or settings.get("model", "unknown"), + model=_get_model_name(self), transcript=transcript, is_final=is_final, language=str(language) if language else None, @@ -491,10 +508,7 @@ def traced_llm(func: Optional[Callable] = None, *, name: Optional[str] = None) - # Add all available attributes to the span attribute_kwargs = { "service_name": service_class_name, - "model": getattr(self, "_full_model_name", None) - or getattr(self, "model_name", None) - or params.get("model") - or "unknown", + "model": _get_model_name(self), "stream": True, # Most LLM services use streaming "parameters": params, } @@ -593,11 +607,7 @@ def traced_gemini_live(operation: str) -> Callable: ) as current_span: try: # Base service attributes - model_name = ( - getattr(self, "model_name", None) - or getattr(self, "_model_name", None) - or "unknown" - ) + model_name = _get_model_name(self) voice_id = getattr(self, "_voice_id", None) language_code = getattr(self, "_language_code", None) settings = getattr(self, "_settings", {}) @@ -900,11 +910,7 @@ def traced_openai_realtime(operation: str) -> Callable: ) as current_span: try: # Base service attributes - model_name = ( - getattr(self, "model_name", None) - or getattr(self, "_model_name", None) - or "unknown" - ) + model_name = _get_model_name(self) # Operation-specific attribute collection operation_attrs = {}