Update AIService.set_model_name to AIService._sync_model_name_to_metrics to:

- indicate clearly that it's not meant for public use
- make it clear the `self._settings` is the single source of truth for model information
- set the stage for an upcoming change where `AIService` subclasses won't have to ever worry about explicitly calling an `AIService` method to sync model name to metrics

Across all services, switch from accessing `self._model_name` or `self.model_name` in favor of `self._settings.model`.
This commit is contained in:
Paul Kompfner
2026-02-20 11:42:24 -05:00
parent f5b86d9cdc
commit 29e2a861dc
54 changed files with 173 additions and 177 deletions

View File

@@ -42,27 +42,25 @@ class AIService(FrameProcessor):
**kwargs: Additional arguments passed to the parent FrameProcessor. **kwargs: Additional arguments passed to the parent FrameProcessor.
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
self._model_name: str = "" self._settings: ServiceSettings = ServiceSettings(model="")
self._settings: ServiceSettings = ServiceSettings()
self._session_properties: Dict[str, Any] = {} self._session_properties: Dict[str, Any] = {}
@property def _sync_model_name_to_metrics(self):
def model_name(self) -> str: """Sync the current AI model name (in `self._settings.model`) for usage in metrics.
"""Get the current model name.
Returns: We don't store model name here because there's already a single source
The name of the AI model being used. of truth for it in `self._settings.model`. This method is just for
""" syncing the model name to the metrics data.
return self._model_name
def set_model_name(self, model: str): TODO: as a next step we should make it so that service classes pass
"""Set the AI model name and update metrics. 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: Args:
model: The name of the AI model to use. model: The name of the AI model to use.
""" """
self._model_name = model self.set_core_metrics_data(MetricsData(processor=self.name, model=self._settings.model))
self.set_core_metrics_data(MetricsData(processor=self.name, model=self._model_name))
async def start(self, frame: StartFrame): async def start(self, frame: StartFrame):
"""Start the AI service. """Start the AI service.
@@ -117,7 +115,7 @@ class AIService(FrameProcessor):
changed = self._settings.apply_update(update) changed = self._settings.apply_update(update)
if "model" in changed: if "model" in changed:
self.set_model_name(self._settings.model) self._sync_model_name_to_metrics()
if changed: if changed:
logger.info(f"{self.name}: updated settings fields: {set(changed)}") logger.info(f"{self.name}: updated settings fields: {set(changed)}")

View File

@@ -237,7 +237,6 @@ class AnthropicLLMService(LLMService):
self._client = client or AsyncAnthropic( self._client = client or AsyncAnthropic(
api_key=api_key api_key=api_key
) # if the client is provided, use it and remove it, otherwise create a new one ) # 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_timeout_secs = retry_timeout_secs
self._retry_on_timeout = retry_on_timeout self._retry_on_timeout = retry_on_timeout
self._settings = AnthropicLLMSettings( self._settings = AnthropicLLMSettings(
@@ -258,6 +257,7 @@ class AnthropicLLMService(LLMService):
thinking=params.thinking, thinking=params.thinking,
extra=params.extra if isinstance(params.extra, dict) else {}, extra=params.extra if isinstance(params.extra, dict) else {},
) )
self._sync_model_name_to_metrics()
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate usage metrics. """Check if this service can generate usage metrics.
@@ -324,7 +324,7 @@ class AnthropicLLMService(LLMService):
# Build params using the same method as streaming completions # Build params using the same method as streaming completions
params = { params = {
"model": self.model_name, "model": self._settings.model,
"max_tokens": max_tokens if max_tokens is not None else self._settings.max_tokens, "max_tokens": max_tokens if max_tokens is not None else self._settings.max_tokens,
"stream": False, "stream": False,
"temperature": self._settings.temperature, "temperature": self._settings.temperature,
@@ -438,7 +438,7 @@ class AnthropicLLMService(LLMService):
await self.start_ttfb_metrics() await self.start_ttfb_metrics()
params = { params = {
"model": self.model_name, "model": self._settings.model,
"max_tokens": self._settings.max_tokens, "max_tokens": self._settings.max_tokens,
"stream": True, "stream": True,
"temperature": self._settings.temperature, "temperature": self._settings.temperature,

View File

@@ -171,8 +171,7 @@ class AsyncAITTSService(AudioContextTTSService):
if params.language if params.language
else None, else None,
) )
self._sync_model_name_to_metrics()
self.set_model_name(model)
self._receive_task = None self._receive_task = None
self._keepalive_task = None self._keepalive_task = None
@@ -513,7 +512,7 @@ class AsyncAIHttpTTSService(TTSService):
if params.language if params.language
else None, else None,
) )
self.set_model_name(model) self._sync_model_name_to_metrics()
self._session = aiohttp_session self._session = aiohttp_session
@@ -562,7 +561,7 @@ class AsyncAIHttpTTSService(TTSService):
voice_config = {"mode": "id", "id": self._settings.voice} voice_config = {"mode": "id", "id": self._settings.voice}
await self.start_ttfb_metrics() await self.start_ttfb_metrics()
payload = { payload = {
"model_id": self._model_name, "model_id": self._settings.model,
"transcript": text, "transcript": text,
"voice": voice_config, "voice": voice_config,
"output_format": { "output_format": {

View File

@@ -820,7 +820,6 @@ class AWSBedrockLLMService(LLMService):
"config": client_config, "config": client_config,
} }
self.set_model_name(model)
self._retry_timeout_secs = retry_timeout_secs self._retry_timeout_secs = retry_timeout_secs
self._retry_on_timeout = retry_on_timeout self._retry_on_timeout = retry_on_timeout
self._settings = AWSBedrockLLMSettings( self._settings = AWSBedrockLLMSettings(
@@ -833,6 +832,7 @@ class AWSBedrockLLMService(LLMService):
if isinstance(params.additional_model_request_fields, dict) if isinstance(params.additional_model_request_fields, dict)
else {}, else {},
) )
self._sync_model_name_to_metrics()
logger.info(f"Using AWS Bedrock model: {model}") logger.info(f"Using AWS Bedrock model: {model}")
@@ -895,7 +895,7 @@ class AWSBedrockLLMService(LLMService):
inference_config["maxTokens"] = max_tokens inference_config["maxTokens"] = max_tokens
request_params = { request_params = {
"modelId": self.model_name, "modelId": self._settings.model,
"messages": messages, "messages": messages,
"additionalModelRequestFields": self._settings.additional_model_request_fields, "additionalModelRequestFields": self._settings.additional_model_request_fields,
} }
@@ -1052,7 +1052,7 @@ class AWSBedrockLLMService(LLMService):
# Prepare request parameters # Prepare request parameters
request_params = { request_params = {
"modelId": self.model_name, "modelId": self._settings.model,
"messages": messages, "messages": messages,
"additionalModelRequestFields": self._settings.additional_model_request_fields, "additionalModelRequestFields": self._settings.additional_model_request_fields,
} }

View File

@@ -269,7 +269,7 @@ class AWSNovaSonicLLMService(LLMService):
top_p=params.top_p, top_p=params.top_p,
endpointing_sensitivity=params.endpointing_sensitivity, 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) # Audio I/O config (hardware settings, not runtime-tunable)
self._input_sample_rate = params.input_sample_rate self._input_sample_rate = params.input_sample_rate

View File

@@ -54,7 +54,8 @@ class AzureImageGenServiceREST(ImageGenService):
self._api_key = api_key self._api_key = api_key
self._azure_endpoint = endpoint self._azure_endpoint = endpoint
self._api_version = api_version 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._image_size = image_size
self._aiohttp_session = aiohttp_session self._aiohttp_session = aiohttp_session

View File

@@ -236,8 +236,7 @@ class CambTTSService(TTSService):
), ),
user_instructions=params.user_instructions, user_instructions=params.user_instructions,
) )
self._sync_model_name_to_metrics()
self.set_model_name(model)
self._client = None self._client = None
@@ -272,7 +271,7 @@ class CambTTSService(TTSService):
# Use model-specific sample rate if not explicitly specified # Use model-specific sample rate if not explicitly specified
if not self._init_sample_rate: 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 @traced_tts
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]: async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
@@ -300,12 +299,12 @@ class CambTTSService(TTSService):
"text": text, "text": text,
"voice_id": self._settings.voice, "voice_id": self._settings.voice,
"language": self._settings.language, "language": self._settings.language,
"speech_model": self.model_name, "speech_model": self._settings.model,
"output_configuration": StreamTtsOutputConfiguration(format="pcm_s16le"), "output_configuration": StreamTtsOutputConfiguration(format="pcm_s16le"),
} }
# Add user instructions if using mars-instruct model # 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 tts_kwargs["user_instructions"] = self._settings.user_instructions
await self.start_tts_usage_metrics(text) await self.start_tts_usage_metrics(text)

View File

@@ -201,7 +201,7 @@ class CartesiaSTTService(WebsocketSTTService):
language=merged_options.get("language"), language=merged_options.get("language"),
encoding=merged_options.get("encoding", "pcm_s16le"), 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._api_key = api_key
self._base_url = base_url or "api.cartesia.ai" self._base_url = base_url or "api.cartesia.ai"
self._receive_task = None self._receive_task = None

View File

@@ -330,6 +330,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
self._cartesia_version = cartesia_version self._cartesia_version = cartesia_version
self._url = url self._url = url
self._settings = CartesiaTTSSettings( self._settings = CartesiaTTSSettings(
model=model,
output_container=container, output_container=container,
output_encoding=encoding, output_encoding=encoding,
output_sample_rate=0, output_sample_rate=0,
@@ -342,7 +343,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
pronunciation_dict_id=params.pronunciation_dict_id, pronunciation_dict_id=params.pronunciation_dict_id,
voice=voice_id, voice=voice_id,
) )
self.set_model_name(model) self._sync_model_name_to_metrics()
self._context_id = None self._context_id = None
self._receive_task = None self._receive_task = None
@@ -457,7 +458,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
"transcript": text, "transcript": text,
"continue": continue_transcript, "continue": continue_transcript,
"context_id": self._context_id, "context_id": self._context_id,
"model_id": self.model_name, "model_id": self._settings.model,
"voice": voice_config, "voice": voice_config,
"output_format": { "output_format": {
"container": self._settings.output_container, "container": self._settings.output_container,
@@ -465,7 +466,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
"sample_rate": self._settings.output_sample_rate, "sample_rate": self._settings.output_sample_rate,
}, },
"add_timestamps": add_timestamps, "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: if is_given(self._settings.language) and self._settings.language:
@@ -741,7 +742,7 @@ class CartesiaHttpTTSService(TTSService):
generation_config=params.generation_config, generation_config=params.generation_config,
pronunciation_dict_id=params.pronunciation_dict_id, pronunciation_dict_id=params.pronunciation_dict_id,
) )
self.set_model_name(model) self._sync_model_name_to_metrics()
self._client = AsyncCartesia( self._client = AsyncCartesia(
api_key=api_key, api_key=api_key,
@@ -829,7 +830,7 @@ class CartesiaHttpTTSService(TTSService):
} }
payload = { payload = {
"model_id": self._model_name, "model_id": self._settings.model,
"transcript": text, "transcript": text,
"voice": voice_config, "voice": voice_config,
"output_format": output_format, "output_format": output_format,

View File

@@ -66,7 +66,7 @@ class CerebrasLLMService(OpenAILLMService):
Dictionary of parameters for the chat completion request. Dictionary of parameters for the chat completion request.
""" """
params = { params = {
"model": self.model_name, "model": self._settings.model,
"stream": True, "stream": True,
"seed": self._settings.seed, "seed": self._settings.seed,
"temperature": self._settings.temperature, "temperature": self._settings.temperature,

View File

@@ -212,7 +212,7 @@ class DeepgramFluxSTTService(WebsocketSTTService):
tag=params.tag or [], tag=params.tag or [],
min_confidence=params.min_confidence, min_confidence=params.min_confidence,
) )
self.set_model_name(model) self._sync_model_name_to_metrics()
self._api_key = api_key self._api_key = api_key
self._url = url self._url = url
self._should_interrupt = should_interrupt self._should_interrupt = should_interrupt

View File

@@ -143,13 +143,13 @@ class DeepgramSTTService(STTService):
if "language" in merged_options and isinstance(merged_options["language"], Language): if "language" in merged_options and isinstance(merged_options["language"], Language):
merged_options["language"] = merged_options["language"].value merged_options["language"] = merged_options["language"].value
self.set_model_name(merged_options["model"])
merged_live_options = LiveOptions(**merged_options) merged_live_options = LiveOptions(**merged_options)
self._settings = DeepgramSTTSettings( self._settings = DeepgramSTTSettings(
model=merged_options.get("model"), model=merged_options.get("model"),
language=merged_options.get("language"), language=merged_options.get("language"),
live_options=merged_live_options, live_options=merged_live_options,
) )
self._sync_model_name_to_metrics()
self._addons = addons self._addons = addons
self._should_interrupt = should_interrupt 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: elif "live_options" in changed and self._settings.live_options.model is not None:
# Only live_options was given → pull model up. # Only live_options was given → pull model up.
self._settings.model = self._settings.live_options.model self._settings.model = self._settings.live_options.model
self.set_model_name(self._settings.model) self._sync_model_name_to_metrics()
# --- Sync language ----------------------------------------------- # --- Sync language -----------------------------------------------
if language_given: if language_given:

View File

@@ -143,13 +143,13 @@ class DeepgramSageMakerSTTService(STTService):
if "language" in merged_options and isinstance(merged_options["language"], Language): if "language" in merged_options and isinstance(merged_options["language"], Language):
merged_options["language"] = merged_options["language"].value merged_options["language"] = merged_options["language"].value
self.set_model_name(merged_options["model"])
merged_live_options = LiveOptions(**merged_options) merged_live_options = LiveOptions(**merged_options)
self._settings = DeepgramSageMakerSTTSettings( self._settings = DeepgramSageMakerSTTSettings(
model=merged_options.get("model"), model=merged_options.get("model"),
language=merged_options.get("language"), language=merged_options.get("language"),
live_options=merged_live_options, live_options=merged_live_options,
) )
self._sync_model_name_to_metrics()
self._client: Optional[SageMakerBidiClient] = None self._client: Optional[SageMakerBidiClient] = None
self._response_task: Optional[asyncio.Task] = 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: elif "live_options" in changed and self._settings.live_options.model is not None:
# Only live_options was given → pull model up. # Only live_options was given → pull model up.
self._settings.model = self._settings.live_options.model self._settings.model = self._settings.live_options.model
self.set_model_name(self._settings.model) self._sync_model_name_to_metrics()
# --- Sync language ----------------------------------------------- # --- Sync language -----------------------------------------------
if language_given: if language_given:

View File

@@ -65,7 +65,7 @@ class DeepSeekLLMService(OpenAILLMService):
Dictionary of parameters for the chat completion request. Dictionary of parameters for the chat completion request.
""" """
params = { params = {
"model": self.model_name, "model": self._settings.model,
"stream": True, "stream": True,
"stream_options": {"include_usage": True}, "stream_options": {"include_usage": True},
"frequency_penalty": self._settings.frequency_penalty, "frequency_penalty": self._settings.frequency_penalty,

View File

@@ -281,7 +281,7 @@ class ElevenLabsSTTService(SegmentedSTTService):
else "eng", else "eng",
tag_audio_events=params.tag_audio_events, tag_audio_events=params.tag_audio_events,
) )
self.set_model_name(model) self._sync_model_name_to_metrics()
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if the service can generate processing metrics. """Check if the service can generate processing metrics.
@@ -531,7 +531,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
enable_logging=params.enable_logging, enable_logging=params.enable_logging,
include_language_detection=params.include_language_detection, include_language_detection=params.include_language_detection,
) )
self.set_model_name(model) self._sync_model_name_to_metrics()
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if the service can generate processing metrics. """Check if the service can generate processing metrics.

View File

@@ -423,7 +423,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
enable_logging=params.enable_logging, enable_logging=params.enable_logging,
apply_text_normalization=params.apply_text_normalization, 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._output_format = "" # initialized in start()
self._voice_settings = self._set_voice_settings() self._voice_settings = self._set_voice_settings()
@@ -607,7 +607,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
logger.debug("Connecting to ElevenLabs") logger.debug("Connecting to ElevenLabs")
voice_id = self._settings.voice voice_id = self._settings.voice
model = self.model_name model = self._settings.model
output_format = self._output_format 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}" 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, speed=params.speed,
apply_text_normalization=params.apply_text_normalization, 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._output_format = "" # initialized in start()
self._voice_settings = self._set_voice_settings() self._voice_settings = self._set_voice_settings()
self._pronunciation_dictionary_locators = params.pronunciation_dictionary_locators 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]]]] = { payload: Dict[str, Union[str, Dict[str, Union[float, bool]]]] = {
"text": text, "text": text,
"model_id": self._model_name, "model_id": self._settings.model,
} }
# Include previous text as context if available # Include previous text as context if available
@@ -1122,7 +1122,7 @@ class ElevenLabsHttpTTSService(WordTTSService):
payload["apply_text_normalization"] = self._settings.apply_text_normalization payload["apply_text_normalization"] = self._settings.apply_text_normalization
language = self._settings.language 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 payload["language_code"] = language
logger.debug(f"Using language code: {language}") logger.debug(f"Using language code: {language}")
elif language: elif language:

View File

@@ -78,7 +78,8 @@ class FalImageGenService(ImageGenService):
**kwargs: Additional arguments passed to parent ImageGenService. **kwargs: Additional arguments passed to parent ImageGenService.
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
self.set_model_name(model) self._settings.model = model
self._sync_model_name_to_metrics()
self._params = params self._params = params
self._aiohttp_session = aiohttp_session self._aiohttp_session = aiohttp_session
if key: if key:
@@ -103,7 +104,7 @@ class FalImageGenService(ImageGenService):
logger.debug(f"Generating image from prompt: {prompt}") logger.debug(f"Generating image from prompt: {prompt}")
response = await fal_client.run_async( response = await fal_client.run_async(
self.model_name, self._settings.model,
arguments={"prompt": prompt, **self._params.model_dump(exclude_none=True)}, arguments={"prompt": prompt, **self._params.model_dump(exclude_none=True)},
) )

View File

@@ -66,7 +66,7 @@ class FireworksLLMService(OpenAILLMService):
Dictionary of parameters for the chat completion request. Dictionary of parameters for the chat completion request.
""" """
params = { params = {
"model": self.model_name, "model": self._settings.model,
"stream": True, "stream": True,
"frequency_penalty": self._settings.frequency_penalty, "frequency_penalty": self._settings.frequency_penalty,
"presence_penalty": self._settings.presence_penalty, "presence_penalty": self._settings.presence_penalty,

View File

@@ -176,6 +176,7 @@ class FishAudioTTSService(InterruptibleTTSService):
self._request_id = None self._request_id = None
self._settings = FishAudioTTSSettings( self._settings = FishAudioTTSSettings(
model=model_id,
voice=reference_id, voice=reference_id,
fish_sample_rate=0, fish_sample_rate=0,
latency=params.latency, latency=params.latency,
@@ -185,8 +186,7 @@ class FishAudioTTSService(InterruptibleTTSService):
prosody_volume=params.prosody_volume, prosody_volume=params.prosody_volume,
reference_id=reference_id, reference_id=reference_id,
) )
self._sync_model_name_to_metrics()
self.set_model_name(model_id)
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """Check if this service can generate processing metrics.
@@ -267,7 +267,7 @@ class FishAudioTTSService(InterruptibleTTSService):
logger.debug("Connecting to Fish Audio") logger.debug("Connecting to Fish Audio")
headers = {"Authorization": f"Bearer {self._api_key}"} 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) self._websocket = await websocket_connect(self._base_url, additional_headers=headers)
# Send initial start message with ormsgpack # Send initial start message with ormsgpack

View File

@@ -279,9 +279,9 @@ class GladiaSTTService(WebsocketSTTService):
self._api_key = api_key self._api_key = api_key
self._region = region self._region = region
self._url = url self._url = url
self.set_model_name(model)
self._receive_task = None self._receive_task = None
self._settings = GladiaSTTSettings(model=model, input_params=params) self._settings = GladiaSTTSettings(model=model, input_params=params)
self._sync_model_name_to_metrics()
# Session management # Session management
self._session_url = None self._session_url = None
@@ -328,7 +328,7 @@ class GladiaSTTService(WebsocketSTTService):
"bit_depth": params.bit_depth or 16, "bit_depth": params.bit_depth or 16,
"sample_rate": self.sample_rate, "sample_rate": self.sample_rate,
"channels": params.channels or 1, "channels": params.channels or 1,
"model": self._model_name, "model": self._settings.model,
} }
# Add custom_metadata if provided # Add custom_metadata if provided

View File

@@ -701,7 +701,6 @@ class GeminiLiveLLMService(LLMService):
self._last_sent_time = 0 self._last_sent_time = 0
self._base_url = base_url self._base_url = base_url
self.set_model_name(model)
self._voice_id = voice_id self._voice_id = voice_id
self._language_code = params.language self._language_code = params.language
@@ -763,6 +762,7 @@ class GeminiLiveLLMService(LLMService):
proactivity=params.proactivity or {}, proactivity=params.proactivity or {},
extra=params.extra if isinstance(params.extra, dict) else {}, 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_base_url = file_api_base_url
self._file_api: Optional[GeminiFileAPI] = None 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) await self.push_error(error_msg=f"Initialization error: {e}", exception=e)
async def _connection_task_handler(self, config: LiveConnectConfig): 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") logger.info("Connected to Gemini service")
# Mark connection start time # Mark connection start time

View File

@@ -79,7 +79,9 @@ class GoogleImageGenService(ImageGenService):
http_options = update_google_client_http_options(http_options) http_options = update_google_client_http_options(http_options)
self._client = genai.Client(api_key=api_key, 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: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """Check if this service can generate processing metrics.

View File

@@ -797,7 +797,6 @@ class GoogleLLMService(LLMService):
params = params or GoogleLLMService.InputParams() params = params or GoogleLLMService.InputParams()
self.set_model_name(model)
self._api_key = api_key self._api_key = api_key
self._system_instruction = system_instruction self._system_instruction = system_instruction
self._http_options = update_google_client_http_options(http_options) self._http_options = update_google_client_http_options(http_options)
@@ -811,6 +810,7 @@ class GoogleLLMService(LLMService):
thinking=params.thinking, thinking=params.thinking,
extra=params.extra if isinstance(params.extra, dict) else {}, extra=params.extra if isinstance(params.extra, dict) else {},
) )
self._sync_model_name_to_metrics()
self._tools = tools self._tools = tools
self._tool_config = tool_config self._tool_config = tool_config
@@ -870,7 +870,7 @@ class GoogleLLMService(LLMService):
# Use the new google-genai client's async method # Use the new google-genai client's async method
response = await self._client.aio.models.generate_content( response = await self._client.aio.models.generate_content(
model=self._model_name, model=self._settings.model,
contents=messages, contents=messages,
config=generation_config, config=generation_config,
) )
@@ -930,10 +930,10 @@ class GoogleLLMService(LLMService):
# There's no way to introspect on model capabilities, so # There's no way to introspect on model capabilities, so
# to check for models that we know default to thinkin on # to check for models that we know default to thinkin on
# and can be configured to turn it off. # 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 return
# If we have an image model, we don't use a budget either. # 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 return
# If thinking_config is already set, don't override it. # If thinking_config is already set, don't override it.
if "thinking_config" in generation_params: if "thinking_config" in generation_params:
@@ -974,7 +974,7 @@ class GoogleLLMService(LLMService):
await self.start_ttfb_metrics() await self.start_ttfb_metrics()
return await self._client.aio.models.generate_content_stream( return await self._client.aio.models.generate_content_stream(
model=self._model_name, model=self._settings.model,
contents=messages, contents=messages,
config=generation_config, config=generation_config,
) )

View File

@@ -62,7 +62,7 @@ class GroqSTTService(BaseWhisperSTTService):
# Build kwargs dict with only set parameters # Build kwargs dict with only set parameters
kwargs = { kwargs = {
"file": ("audio.wav", audio, "audio/wav"), "file": ("audio.wav", audio, "audio/wav"),
"model": self.model_name, "model": self._settings.model,
# Use verbose_json to get probability metrics # Use verbose_json to get probability metrics
"response_format": "verbose_json" if self._include_prob_metrics else "json", "response_format": "verbose_json" if self._include_prob_metrics else "json",
"language": self._language, "language": self._language,

View File

@@ -108,7 +108,6 @@ class GroqTTSService(TTSService):
params = params or GroqTTSService.InputParams() params = params or GroqTTSService.InputParams()
self._api_key = api_key self._api_key = api_key
self._model_name = model_name
self._output_format = output_format self._output_format = output_format
self._params = params self._params = params
@@ -120,6 +119,7 @@ class GroqTTSService(TTSService):
speed=params.speed, speed=params.speed,
groq_sample_rate=sample_rate, groq_sample_rate=sample_rate,
) )
self._sync_model_name_to_metrics()
self._client = AsyncGroq(api_key=self._api_key) self._client = AsyncGroq(api_key=self._api_key)
@@ -149,7 +149,7 @@ class GroqTTSService(TTSService):
try: try:
response = await self._client.audio.speech.create( response = await self._client.audio.speech.create(
model=self._model_name, model=self._settings.model,
voice=self._settings.voice, voice=self._settings.voice,
response_format=self._output_format, response_format=self._output_format,
input=text, input=text,

View File

@@ -105,8 +105,7 @@ class HathoraSTTService(SegmentedSTTService):
language=params.language, language=params.language,
config=params.config, config=params.config,
) )
self._sync_model_name_to_metrics()
self.set_model_name(model)
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """Check if this service can generate processing metrics.

View File

@@ -123,8 +123,7 @@ class HathoraTTSService(TTSService):
speed=params.speed, speed=params.speed,
config=params.config, config=params.config,
) )
self._sync_model_name_to_metrics()
self.set_model_name(model)
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """Check if this service can generate processing metrics.

View File

@@ -173,7 +173,7 @@ class InworldHttpTTSService(WordTTSService):
self._cumulative_time = 0.0 self._cumulative_time = 0.0
self.set_model_name(model) self._sync_model_name_to_metrics()
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """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 # Track the end time of the last word in the current generation
self._generation_end_time = 0.0 self._generation_end_time = 0.0
self.set_model_name(model) self._sync_model_name_to_metrics()
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """Check if this service can generate processing metrics.

View File

@@ -122,13 +122,13 @@ class LmntTTSService(InterruptibleTTSService):
) )
self._api_key = api_key self._api_key = api_key
self.set_model_name(model)
self._settings = LmntTTSSettings( self._settings = LmntTTSSettings(
model=model, model=model,
voice=voice_id, voice=voice_id,
language=self.language_to_service_language(language), language=self.language_to_service_language(language),
format="raw", format="raw",
) )
self._sync_model_name_to_metrics()
self._receive_task = None self._receive_task = None
self._context_id: Optional[str] = None self._context_id: Optional[str] = None
@@ -238,7 +238,7 @@ class LmntTTSService(InterruptibleTTSService):
"format": self._settings.format, "format": self._settings.format,
"sample_rate": self.sample_rate, "sample_rate": self.sample_rate,
"language": self._settings.language, "language": self._settings.language,
"model": self.model_name, "model": self._settings.model,
} }
# Connect to LMNT's websocket directly # Connect to LMNT's websocket directly

View File

@@ -235,7 +235,6 @@ class MiniMaxHttpTTSService(TTSService):
self._group_id = group_id self._group_id = group_id
self._base_url = f"{base_url}?GroupId={group_id}" self._base_url = f"{base_url}?GroupId={group_id}"
self._session = aiohttp_session self._session = aiohttp_session
self._model_name = model
# Create voice settings # Create voice settings
self._settings = MiniMaxTTSSettings( self._settings = MiniMaxTTSSettings(
@@ -249,9 +248,7 @@ class MiniMaxHttpTTSService(TTSService):
audio_format="pcm", audio_format="pcm",
audio_channel=1, audio_channel=1,
) )
self._sync_model_name_to_metrics()
# Set model
self.set_model_name(model)
# Add language boost if provided # Add language boost if provided
if params.language: if params.language:
@@ -318,14 +315,6 @@ class MiniMaxHttpTTSService(TTSService):
""" """
return language_to_minimax_language(language) 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): async def start(self, frame: StartFrame):
"""Start the MiniMax TTS service. """Start the MiniMax TTS service.
@@ -382,7 +371,7 @@ class MiniMaxHttpTTSService(TTSService):
"stream": self._settings.stream, "stream": self._settings.stream,
"voice_setting": voice_setting, "voice_setting": voice_setting,
"audio_setting": audio_setting, "audio_setting": audio_setting,
"model": self._model_name, "model": self._settings.model,
"text": text, "text": text,
} }
if is_given(self._settings.language_boost): if is_given(self._settings.language_boost):

View File

@@ -180,7 +180,7 @@ class MistralLLMService(OpenAILLMService):
fixed_messages = self._apply_mistral_fixups(params_from_context["messages"]) fixed_messages = self._apply_mistral_fixups(params_from_context["messages"])
params = { params = {
"model": self.model_name, "model": self._settings.model,
"stream": True, "stream": True,
"messages": fixed_messages, "messages": fixed_messages,
"tools": params_from_context["tools"], "tools": params_from_context["tools"],

View File

@@ -81,7 +81,8 @@ class MoondreamService(VisionService):
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
self.set_model_name(model) self._settings.model = model
self._sync_model_name_to_metrics()
if not use_cpu: if not use_cpu:
device, dtype = detect_device() device, dtype = detect_device()

View File

@@ -181,10 +181,10 @@ class NvidiaSTTService(STTService):
self._function_id = model_function_map.get("function_id") self._function_id = model_function_map.get("function_id")
self._settings = NvidiaSTTSettings( self._settings = NvidiaSTTSettings(
model=model_function_map.get("model_name"),
language=params.language, language=params.language,
) )
self._sync_model_name_to_metrics()
self.set_model_name(model_function_map.get("model_name"))
self._asr_service = None self._asr_service = None
self._queue = None self._queue = None
@@ -282,7 +282,7 @@ class NvidiaSTTService(STTService):
if not self._thread_task: if not self._thread_task:
self._thread_task = self.create_task(self._thread_task_handler()) 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): async def stop(self, frame: EndFrame):
"""Stop the NVIDIA Riva STT service and clean up resources. """Stop the NVIDIA Riva STT service and clean up resources.
@@ -467,9 +467,6 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
params = params or NvidiaSegmentedSTTService.InputParams() params = params or NvidiaSegmentedSTTService.InputParams()
# Set model name
self.set_model_name(model_function_map.get("model_name"))
# Initialize NVIDIA Riva settings # Initialize NVIDIA Riva settings
self._api_key = api_key self._api_key = api_key
self._server = server self._server = server
@@ -488,6 +485,7 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
self._config = None self._config = None
self._asr_service = None self._asr_service = None
self._settings = NvidiaSegmentedSTTSettings( self._settings = NvidiaSegmentedSTTSettings(
model=model_function_map.get("model_name"),
language=self.language_to_service_language(params.language or Language.EN_US) language=self.language_to_service_language(params.language or Language.EN_US)
or "en-US", or "en-US",
profanity_filter=params.profanity_filter, profanity_filter=params.profanity_filter,
@@ -496,6 +494,7 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
boosted_lm_words=params.boosted_lm_words, boosted_lm_words=params.boosted_lm_words,
boosted_lm_score=params.boosted_lm_score, boosted_lm_score=params.boosted_lm_score,
) )
self._sync_model_name_to_metrics()
def language_to_service_language(self, language: Language) -> Optional[str]: def language_to_service_language(self, language: Language) -> Optional[str]:
"""Convert pipecat Language enum to NVIDIA Riva's language code. """Convert pipecat Language enum to NVIDIA Riva's language code.
@@ -578,7 +577,7 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
await super().start(frame) await super().start(frame)
self._initialize_client() self._initialize_client()
self._config = self._create_recognition_config() 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]: async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
"""Apply a settings update and sync internal state. """Apply a settings update and sync internal state.

View File

@@ -112,11 +112,12 @@ class NvidiaTTSService(TTSService):
self._function_id = model_function_map.get("function_id") self._function_id = model_function_map.get("function_id")
self._use_ssl = use_ssl self._use_ssl = use_ssl
self._settings = NvidiaTTSSettings( self._settings = NvidiaTTSSettings(
model=model_function_map.get("model_name"),
voice=voice_id, voice=voice_id,
language=params.language, language=params.language,
quality=params.quality, quality=params.quality,
) )
self.set_model_name(model_function_map.get("model_name")) self._sync_model_name_to_metrics()
self._service = None self._service = None
self._config = None self._config = None
@@ -192,7 +193,7 @@ class NvidiaTTSService(TTSService):
await super().start(frame) await super().start(frame)
self._initialize_client() self._initialize_client()
self._config = self._create_synthesis_config() 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 @traced_tts
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]: async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:

View File

@@ -151,7 +151,7 @@ class BaseOpenAILLMService(LLMService):
) )
self._retry_timeout_secs = retry_timeout_secs self._retry_timeout_secs = retry_timeout_secs
self._retry_on_timeout = retry_on_timeout self._retry_on_timeout = retry_on_timeout
self.set_model_name(model) self._sync_model_name_to_metrics()
self._full_model_name: str = "" self._full_model_name: str = ""
self._client = self.create_client( self._client = self.create_client(
api_key=api_key, api_key=api_key,
@@ -265,7 +265,7 @@ class BaseOpenAILLMService(LLMService):
Dictionary of parameters for the chat completion request. Dictionary of parameters for the chat completion request.
""" """
params = { params = {
"model": self.model_name, "model": self._settings.model,
"stream": True, "stream": True,
"stream_options": {"include_usage": True}, "stream_options": {"include_usage": True},
"frequency_penalty": self._settings.frequency_penalty, "frequency_penalty": self._settings.frequency_penalty,

View File

@@ -53,7 +53,8 @@ class OpenAIImageGenService(ImageGenService):
model: DALL-E model to use for generation. Defaults to "dall-e-3". model: DALL-E model to use for generation. Defaults to "dall-e-3".
""" """
super().__init__() super().__init__()
self.set_model_name(model) self._settings.model = model
self._sync_model_name_to_metrics()
self._image_size = image_size self._image_size = image_size
self._client = AsyncOpenAI(api_key=api_key, base_url=base_url) self._client = AsyncOpenAI(api_key=api_key, base_url=base_url)
self._aiohttp_session = aiohttp_session self._aiohttp_session = aiohttp_session
@@ -70,7 +71,7 @@ class OpenAIImageGenService(ImageGenService):
logger.debug(f"Generating image from prompt: {prompt}") logger.debug(f"Generating image from prompt: {prompt}")
image = await self._client.images.generate( 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 image_url = image.data[0].url

View File

@@ -175,12 +175,12 @@ class OpenAIRealtimeLLMService(LLMService):
self.api_key = api_key self.api_key = api_key
self.base_url = full_url self.base_url = full_url
self.set_model_name(model)
self._settings = OpenAIRealtimeLLMSettings( self._settings = OpenAIRealtimeLLMSettings(
model=model, model=model,
session_properties=session_properties or events.SessionProperties(), session_properties=session_properties or events.SessionProperties(),
) )
self._sync_model_name_to_metrics()
self._audio_input_paused = start_audio_paused self._audio_input_paused = start_audio_paused
self._video_input_paused = start_video_paused self._video_input_paused = start_video_paused
self._video_frame_detail = video_frame_detail self._video_frame_detail = video_frame_detail

View File

@@ -100,24 +100,24 @@ class OpenAISTTService(BaseWhisperSTTService):
# Build kwargs dict with only set parameters # Build kwargs dict with only set parameters
kwargs = { kwargs = {
"file": ("audio.wav", audio, "audio/wav"), "file": ("audio.wav", audio, "audio/wav"),
"model": self.model_name, "model": self._settings.model,
"language": self._language, "language": self._settings.language,
} }
if self._include_prob_metrics: if self._include_prob_metrics:
# GPT-4o-transcribe models only support logprobs (not verbose_json) # 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["response_format"] = "json"
kwargs["include"] = ["logprobs"] kwargs["include"] = ["logprobs"]
else: else:
# Whisper models support verbose_json # Whisper models support verbose_json
kwargs["response_format"] = "verbose_json" kwargs["response_format"] = "verbose_json"
if self._prompt is not None: if self._settings.prompt is not None:
kwargs["prompt"] = self._prompt kwargs["prompt"] = self._settings.prompt
if self._temperature is not None: if self._settings.temperature is not None:
kwargs["temperature"] = self._temperature kwargs["temperature"] = self._settings.temperature
return await self._client.audio.transcriptions.create(**kwargs) return await self._client.audio.transcriptions.create(**kwargs)
@@ -226,7 +226,6 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
self._api_key = api_key self._api_key = api_key
self._base_url = base_url self._base_url = base_url
self.set_model_name(model)
self._prompt = prompt self._prompt = prompt
self._turn_detection = turn_detection self._turn_detection = turn_detection
@@ -238,6 +237,7 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
language=language, language=language,
prompt=prompt, prompt=prompt,
) )
self._sync_model_name_to_metrics()
self._receive_task = None self._receive_task = None
self._session_ready = False self._session_ready = False
@@ -437,7 +437,7 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
async def _send_session_update(self): async def _send_session_update(self):
"""Send ``session.update`` to configure the transcription session.""" """Send ``session.update`` to configure the transcription session."""
transcription: dict = {"model": self.model_name} transcription: dict = {"model": self._settings.model}
language_code = ( language_code = (
self._language_to_code(self._settings.language) if self._settings.language else None self._language_to_code(self._settings.language) if self._settings.language else None

View File

@@ -134,7 +134,6 @@ class OpenAITTSService(TTSService):
) )
super().__init__(sample_rate=sample_rate, **kwargs) super().__init__(sample_rate=sample_rate, **kwargs)
self.set_model_name(model)
self._client = AsyncOpenAI(api_key=api_key, base_url=base_url) self._client = AsyncOpenAI(api_key=api_key, base_url=base_url)
if instructions or speed: if instructions or speed:
@@ -154,6 +153,7 @@ class OpenAITTSService(TTSService):
instructions=params.instructions if params else instructions, instructions=params.instructions if params else instructions,
speed=params.speed if params else speed, speed=params.speed if params else speed,
) )
self._sync_model_name_to_metrics()
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """Check if this service can generate processing metrics.
@@ -194,7 +194,7 @@ class OpenAITTSService(TTSService):
# Setup API parameters # Setup API parameters
create_params = { create_params = {
"input": text, "input": text,
"model": self.model_name, "model": self._settings.model,
"voice": VALID_VOICES[self._settings.voice], "voice": VALID_VOICES[self._settings.voice],
"response_format": "pcm", "response_format": "pcm",
} }

View File

@@ -160,12 +160,12 @@ class OpenAIRealtimeBetaLLMService(LLMService):
self.api_key = api_key self.api_key = api_key
self.base_url = full_url self.base_url = full_url
self.set_model_name(model)
self._settings = OpenAIRealtimeBetaLLMSettings( self._settings = OpenAIRealtimeBetaLLMSettings(
model=model, model=model,
session_properties=session_properties or events.SessionProperties(), session_properties=session_properties or events.SessionProperties(),
) )
self._sync_model_name_to_metrics()
self._audio_input_paused = start_audio_paused self._audio_input_paused = start_audio_paused
self._send_transcription_frames = send_transcription_frames self._send_transcription_frames = send_transcription_frames
self._websocket = None self._websocket = None

View File

@@ -72,8 +72,7 @@ class OpenRouterLLMService(OpenAILLMService):
Transformed parameters ready for the API call. Transformed parameters ready for the API call.
""" """
params = super().build_chat_completion_params(params_from_context) params = super().build_chat_completion_params(params_from_context)
model = getattr(self, "model_name", getattr(self, "model", "")).lower() if "gemini" in self._settings.model.lower():
if "gemini" in model:
messages = params.get("messages", []) messages = params.get("messages", [])
if not messages: if not messages:
return params return params

View File

@@ -66,7 +66,7 @@ class PerplexityLLMService(OpenAILLMService):
Dictionary of parameters for the chat completion request. Dictionary of parameters for the chat completion request.
""" """
params = { params = {
"model": self.model_name, "model": self._settings.model,
"stream": True, "stream": True,
"messages": params_from_context["messages"], "messages": params_from_context["messages"],
} }

View File

@@ -204,7 +204,7 @@ class PlayHTTTSService(InterruptibleTTSService):
speed=params.speed, speed=params.speed,
seed=params.seed, seed=params.seed,
) )
self.set_model_name(voice_engine) self._sync_model_name_to_metrics()
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """Check if this service can generate processing metrics.
@@ -545,6 +545,7 @@ class PlayHTHttpTTSService(TTSService):
voice_engine = voice_engine.replace("-ws", "") voice_engine = voice_engine.replace("-ws", "")
self._settings = PlayHTTTSSettings( self._settings = PlayHTTTSSettings(
model=voice_engine,
voice=voice_url, voice=voice_url,
language=self.language_to_service_language(params.language) language=self.language_to_service_language(params.language)
if params.language if params.language
@@ -554,7 +555,7 @@ class PlayHTHttpTTSService(TTSService):
speed=params.speed, speed=params.speed,
seed=params.seed, seed=params.seed,
) )
self.set_model_name(voice_engine) self._sync_model_name_to_metrics()
async def start(self, frame: StartFrame): async def start(self, frame: StartFrame):
"""Start the PlayHT HTTP TTS service. """Start the PlayHT HTTP TTS service.

View File

@@ -75,7 +75,6 @@ class RimeTTSSettings(TTSSettings):
"""Settings for Rime WS JSON and HTTP TTS services. """Settings for Rime WS JSON and HTTP TTS services.
Parameters: Parameters:
modelId: Rime model identifier.
audioFormat: Audio output format. audioFormat: Audio output format.
samplingRate: Audio sample rate. samplingRate: Audio sample rate.
lang: Rime language code. lang: Rime language code.
@@ -86,7 +85,6 @@ class RimeTTSSettings(TTSSettings):
inlineSpeedAlpha: Inline speed control markup. inlineSpeedAlpha: Inline speed control markup.
""" """
modelId: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
audioFormat: 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) samplingRate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
lang: str | _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. """Settings for Rime non-JSON WS TTS service.
Parameters: Parameters:
modelId: Rime model identifier.
audioFormat: Audio output format. audioFormat: Audio output format.
samplingRate: Audio sample rate. samplingRate: Audio sample rate.
lang: Rime language code. lang: Rime language code.
@@ -114,7 +111,6 @@ class RimeNonJsonTTSSettings(TTSSettings):
top_p: Cumulative probability threshold (0.0-1.0). 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) audioFormat: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
samplingRate: int | _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) lang: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@@ -210,7 +206,7 @@ class RimeTTSService(AudioContextWordTTSService):
self._model = model self._model = model
self._settings = RimeTTSSettings( self._settings = RimeTTSSettings(
voice=voice_id, voice=voice_id,
modelId=model, model=model,
audioFormat="pcm", audioFormat="pcm",
samplingRate=0, samplingRate=0,
lang=self.language_to_service_language(params.language) if params.language else "eng", 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), pauseBetweenBrackets=json.dumps(params.pause_between_brackets),
phonemizeBetweenBrackets=json.dumps(params.phonemize_between_brackets), phonemizeBetweenBrackets=json.dumps(params.phonemize_between_brackets),
) )
self._sync_model_name_to_metrics()
# State tracking # State tracking
self._context_id = None # Tracks current turn self._context_id = None # Tracks current turn
@@ -353,7 +350,7 @@ class RimeTTSService(AudioContextWordTTSService):
f"{k}={v}" f"{k}={v}"
for k, v in { for k, v in {
"speaker": self._settings.voice, "speaker": self._settings.voice,
"modelId": self._settings.modelId, "modelId": self._settings.model,
"audioFormat": self._settings.audioFormat, "audioFormat": self._settings.audioFormat,
"samplingRate": self._settings.samplingRate, "samplingRate": self._settings.samplingRate,
"lang": self._settings.lang, "lang": self._settings.lang,
@@ -589,6 +586,7 @@ class RimeHttpTTSService(TTSService):
self._session = aiohttp_session self._session = aiohttp_session
self._base_url = "https://users.rime.ai/v1/rime-tts" self._base_url = "https://users.rime.ai/v1/rime-tts"
self._settings = RimeTTSSettings( self._settings = RimeTTSSettings(
model=model,
lang=self.language_to_service_language(params.language) if params.language else "eng", lang=self.language_to_service_language(params.language) if params.language else "eng",
speedAlpha=params.speed_alpha, speedAlpha=params.speed_alpha,
reduceLatency=params.reduce_latency, reduceLatency=params.reduce_latency,
@@ -597,7 +595,7 @@ class RimeHttpTTSService(TTSService):
inlineSpeedAlpha=params.inline_speed_alpha if params.inline_speed_alpha else NOT_GIVEN, inlineSpeedAlpha=params.inline_speed_alpha if params.inline_speed_alpha else NOT_GIVEN,
voice=voice_id, voice=voice_id,
) )
self.set_model_name(model) self._sync_model_name_to_metrics()
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """Check if this service can generate processing metrics.
@@ -648,7 +646,7 @@ class RimeHttpTTSService(TTSService):
payload["inlineSpeedAlpha"] = self._settings.inlineSpeedAlpha payload["inlineSpeedAlpha"] = self._settings.inlineSpeedAlpha
payload["text"] = text payload["text"] = text
payload["speaker"] = self._settings.voice payload["speaker"] = self._settings.voice
payload["modelId"] = self._model_name payload["modelId"] = self._settings.model
payload["samplingRate"] = self.sample_rate payload["samplingRate"] = self.sample_rate
# Arcana does not support PCM audio # Arcana does not support PCM audio
@@ -769,7 +767,7 @@ class RimeNonJsonTTSService(InterruptibleTTSService):
self._model = model self._model = model
self._settings = RimeNonJsonTTSSettings( self._settings = RimeNonJsonTTSSettings(
voice=voice_id, voice=voice_id,
modelId=model, model=model,
audioFormat=audio_format, audioFormat=audio_format,
samplingRate=sample_rate, samplingRate=sample_rate,
lang=self.language_to_service_language(params.language) 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, 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, 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 # Add any extra parameters for future compatibility
if params.extra: if params.extra:
self._settings.extra.update(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) # Build URL with query parameters (only given, non-None values)
settings_dict = { settings_dict = {
"speaker": self._settings.voice, "speaker": self._settings.voice,
"modelId": self._settings.modelId, "modelId": self._settings.model,
"audioFormat": self._settings.audioFormat, "audioFormat": self._settings.audioFormat,
"samplingRate": self._settings.samplingRate, "samplingRate": self._settings.samplingRate,
} }
@@ -981,10 +980,6 @@ class RimeNonJsonTTSService(InterruptibleTTSService):
""" """
changed = await super()._update_settings(update) changed = await super()._update_settings(update)
# Sync model to settings dict field
if "model" in changed:
self._settings.modelId = self._model_name
if changed: if changed:
logger.debug("Settings changed, reconnecting WebSocket with new parameters") logger.debug("Settings changed, reconnecting WebSocket with new parameters")
await self._disconnect() await self._disconnect()

View File

@@ -84,7 +84,7 @@ class SambaNovaLLMService(OpenAILLMService): # type: ignore
Dictionary of parameters for the chat completion request. Dictionary of parameters for the chat completion request.
""" """
params = { params = {
"model": self.model_name, "model": self._settings.model,
"stream": True, "stream": True,
"stream_options": {"include_usage": True}, "stream_options": {"include_usage": True},
"temperature": self._settings.temperature, "temperature": self._settings.temperature,

View File

@@ -72,7 +72,7 @@ class SambaNovaSTTService(BaseWhisperSTTService): # type: ignore
# Build kwargs dict with only set parameters # Build kwargs dict with only set parameters
kwargs = { kwargs = {
"file": ("audio.wav", audio, "audio/wav"), "file": ("audio.wav", audio, "audio/wav"),
"model": self.model_name, "model": self._settings.model,
"response_format": "json", "response_format": "json",
"language": self._language, "language": self._language,
} }

View File

@@ -225,7 +225,6 @@ class SarvamSTTService(STTService):
super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs) super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs)
self.set_model_name(model)
self._api_key = api_key self._api_key = api_key
# Store connection parameters # Store connection parameters
@@ -257,6 +256,7 @@ class SarvamSTTService(STTService):
vad_signals=params.vad_signals, vad_signals=params.vad_signals,
high_vad_sensitivity=params.high_vad_sensitivity, high_vad_sensitivity=params.high_vad_sensitivity,
) )
self._sync_model_name_to_metrics()
if params.vad_signals: if params.vad_signals:
self._register_event_handler("on_speech_started") 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 is_given(update.language) and update.language is not None:
if not self._config.supports_language: if not self._config.supports_language:
raise ValueError( 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)." "(auto-detects language)."
) )
@@ -330,11 +330,13 @@ class SarvamSTTService(STTService):
if is_given(update.prompt) and update.prompt is not None: if is_given(update.prompt) and update.prompt is not None:
if not self._config.supports_prompt: if not self._config.supports_prompt:
raise ValueError( 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 is_given(update.mode) and update.mode is not None:
if not self._config.supports_mode: 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) changed = await super()._update_settings(update)
@@ -374,11 +376,13 @@ class SarvamSTTService(STTService):
if not self._config.supports_prompt: if not self._config.supports_prompt:
if prompt is not None: 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) # If prompt is None and model doesn't support prompts, silently return (no-op)
return return
logger.info(f"Updating {self.model_name} prompt.") logger.info(f"Updating {self._settings.model} prompt.")
self._settings.prompt = prompt self._settings.prompt = prompt
await self._disconnect() await self._disconnect()
await self._connect() await self._connect()
@@ -460,7 +464,7 @@ class SarvamSTTService(STTService):
try: try:
# Build common connection parameters # Build common connection parameters
connect_kwargs = { connect_kwargs = {
"model": self.model_name, "model": self._settings.model,
"sample_rate": str(self.sample_rate), "sample_rate": str(self.sample_rate),
} }

View File

@@ -489,6 +489,7 @@ class SarvamHttpTTSService(TTSService):
model=model, model=model,
voice=voice_id, voice=voice_id,
) )
self._sync_model_name_to_metrics()
# Add parameters based on model support # Add parameters based on model support
if self._config.supports_pitch: if self._config.supports_pitch:
@@ -506,8 +507,6 @@ class SarvamHttpTTSService(TTSService):
elif params.temperature != 0.6: elif params.temperature != 0.6:
logger.warning(f"temperature parameter is ignored for {model}") logger.warning(f"temperature parameter is ignored for {model}")
self.set_model_name(model)
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
"""Check if this service can generate processing metrics. """Check if this service can generate processing metrics.
@@ -559,7 +558,7 @@ class SarvamHttpTTSService(TTSService):
"speaker": self._settings.voice, "speaker": self._settings.voice,
"sample_rate": self.sample_rate, "sample_rate": self.sample_rate,
"enable_preprocessing": self._settings.enable_preprocessing, "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, "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 # WebSocket endpoint URL with model query parameter
self._websocket_url = f"{url}?model={model}" self._websocket_url = f"{url}?model={model}"
self._api_key = api_key self._api_key = api_key
self.set_model_name(model)
# Validate and clamp pace to model's valid range # Validate and clamp pace to model's valid range
pace = params.pace pace = params.pace
@@ -854,6 +852,7 @@ class SarvamTTSService(InterruptibleTTSService):
model=model, model=model,
voice=voice_id, voice=voice_id,
) )
self._sync_model_name_to_metrics()
# Add parameters based on model support # Add parameters based on model support
if self._config.supports_pitch: if self._config.supports_pitch:

View File

@@ -195,13 +195,13 @@ class SonioxSTTService(WebsocketSTTService):
self._api_key = api_key self._api_key = api_key
self._url = url self._url = url
self.set_model_name(params.model)
self._vad_force_turn_endpoint = vad_force_turn_endpoint self._vad_force_turn_endpoint = vad_force_turn_endpoint
self._settings = SonioxSTTSettings( self._settings = SonioxSTTSettings(
model=params.model, model=params.model,
input_params=params, input_params=params,
) )
self._sync_model_name_to_metrics()
self._final_transcription_buffer = [] self._final_transcription_buffer = []
self._last_tokens_received: Optional[float] = None 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: elif "input_params" in changed and self._settings.input_params.model is not None:
# Only input_params was given → pull model up. # Only input_params was given → pull model up.
self._settings.model = self._settings.input_params.model 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. # TODO: someday we could reconnect here to apply updated settings.
# Code might look something like the below: # Code might look something like the below:
@@ -380,7 +380,7 @@ class SonioxSTTService(WebsocketSTTService):
# Send the initial configuration message. # Send the initial configuration message.
config = { config = {
"api_key": self._api_key, "api_key": self._api_key,
"model": self._model_name, "model": self._settings.model,
"audio_format": params.audio_format, "audio_format": params.audio_format,
"num_channels": params.num_channels or 1, "num_channels": params.num_channels or 1,
"enable_endpoint_detection": enable_endpoint_detection, "enable_endpoint_detection": enable_endpoint_detection,

View File

@@ -459,7 +459,7 @@ class SpeechmaticsSTTService(STTService):
# Model + metrics (operating_point comes from the SDK config/preset) # Model + metrics (operating_point comes from the SDK config/preset)
self._settings.model = self._config.operating_point.value 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 # Message queue
self._stt_msg_queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue() self._stt_msg_queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue()

View File

@@ -492,7 +492,7 @@ class STTService(AIService):
if self.metrics_enabled: if self.metrics_enabled:
ttfb_data = TTFBMetricsData( ttfb_data = TTFBMetricsData(
processor=self.name, processor=self.name,
model=self.model_name, model=self._settings.model,
value=ttfb, value=ttfb,
) )
await super().push_frame(MetricsFrame(data=[ttfb_data])) await super().push_frame(MetricsFrame(data=[ttfb_data]))

View File

@@ -156,7 +156,6 @@ class BaseWhisperSTTService(SegmentedSTTService):
**kwargs: Additional arguments passed to SegmentedSTTService. **kwargs: Additional arguments passed to SegmentedSTTService.
""" """
super().__init__(ttfs_p99_latency=ttfs_p99_latency, **kwargs) super().__init__(ttfs_p99_latency=ttfs_p99_latency, **kwargs)
self.set_model_name(model)
self._client = self._create_client(api_key, base_url) self._client = self._create_client(api_key, base_url)
self._language = self.language_to_service_language(language or Language.EN) self._language = self.language_to_service_language(language or Language.EN)
self._prompt = prompt self._prompt = prompt
@@ -170,6 +169,7 @@ class BaseWhisperSTTService(SegmentedSTTService):
prompt=self._prompt, prompt=self._prompt,
temperature=self._temperature, temperature=self._temperature,
) )
self._sync_model_name_to_metrics()
def _create_client(self, api_key: Optional[str], base_url: Optional[str]): def _create_client(self, api_key: Optional[str], base_url: Optional[str]):
return AsyncOpenAI(api_key=api_key, base_url=base_url) return AsyncOpenAI(api_key=api_key, base_url=base_url)

View File

@@ -236,7 +236,6 @@ class WhisperSTTService(SegmentedSTTService):
super().__init__(**kwargs) super().__init__(**kwargs)
self._device: str = device self._device: str = device
self._compute_type = compute_type 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._no_speech_prob = no_speech_prob
self._model: Optional[WhisperModel] = None self._model: Optional[WhisperModel] = None
@@ -247,6 +246,7 @@ class WhisperSTTService(SegmentedSTTService):
compute_type=self._compute_type, compute_type=self._compute_type,
no_speech_prob=self._no_speech_prob, no_speech_prob=self._no_speech_prob,
) )
self._sync_model_name_to_metrics()
self._load() self._load()
@@ -281,7 +281,7 @@ class WhisperSTTService(SegmentedSTTService):
logger.debug("Loading Whisper model...") logger.debug("Loading Whisper model...")
self._model = WhisperModel( 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") logger.debug("Loaded Whisper model")
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
@@ -370,7 +370,6 @@ class WhisperSTTServiceMLX(WhisperSTTService):
# Skip WhisperSTTService.__init__ and call its parent directly # Skip WhisperSTTService.__init__ and call its parent directly
SegmentedSTTService.__init__(self, **kwargs) SegmentedSTTService.__init__(self, **kwargs)
self.set_model_name(model if isinstance(model, str) else model.value)
self._no_speech_prob = no_speech_prob self._no_speech_prob = no_speech_prob
self._temperature = temperature self._temperature = temperature
@@ -381,6 +380,7 @@ class WhisperSTTServiceMLX(WhisperSTTService):
temperature=self._temperature, temperature=self._temperature,
engine="mlx", engine="mlx",
) )
self._sync_model_name_to_metrics()
# No need to call _load() as MLX Whisper loads models on demand # No need to call _load() as MLX Whisper loads models on demand
@@ -421,7 +421,7 @@ class WhisperSTTServiceMLX(WhisperSTTService):
chunk = await asyncio.to_thread( chunk = await asyncio.to_thread(
mlx_whisper.transcribe, mlx_whisper.transcribe,
audio_float, audio_float,
path_or_hf_repo=self.model_name, path_or_hf_repo=self._settings.model,
temperature=self._temperature, temperature=self._temperature,
language=self._settings.language, language=self._settings.language,
) )

View File

@@ -44,6 +44,23 @@ T = TypeVar("T")
R = TypeVar("R") 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): def _noop_decorator(func):
"""No-op fallback decorator when tracing is unavailable. """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( add_tts_span_attributes(
span=span, span=span,
service_name=service_class_name, service_name=service_class_name,
model=getattr(self, "model_name") or "unknown", model=_get_model_name(self),
voice_id=getattr(settings, "voice", "unknown"), voice_id=getattr(settings, "voice", "unknown"),
text=text, text=text,
settings=settings, settings=settings,
@@ -311,7 +328,7 @@ def traced_stt(func: Optional[Callable] = None, *, name: Optional[str] = None) -
add_stt_span_attributes( add_stt_span_attributes(
span=current_span, span=current_span,
service_name=service_class_name, service_name=service_class_name,
model=getattr(self, "model_name") or settings.get("model", "unknown"), model=_get_model_name(self),
transcript=transcript, transcript=transcript,
is_final=is_final, is_final=is_final,
language=str(language) if language else None, 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 # Add all available attributes to the span
attribute_kwargs = { attribute_kwargs = {
"service_name": service_class_name, "service_name": service_class_name,
"model": getattr(self, "_full_model_name", None) "model": _get_model_name(self),
or getattr(self, "model_name", None)
or params.get("model")
or "unknown",
"stream": True, # Most LLM services use streaming "stream": True, # Most LLM services use streaming
"parameters": params, "parameters": params,
} }
@@ -593,11 +607,7 @@ def traced_gemini_live(operation: str) -> Callable:
) as current_span: ) as current_span:
try: try:
# Base service attributes # Base service attributes
model_name = ( model_name = _get_model_name(self)
getattr(self, "model_name", None)
or getattr(self, "_model_name", None)
or "unknown"
)
voice_id = getattr(self, "_voice_id", None) voice_id = getattr(self, "_voice_id", None)
language_code = getattr(self, "_language_code", None) language_code = getattr(self, "_language_code", None)
settings = getattr(self, "_settings", {}) settings = getattr(self, "_settings", {})
@@ -900,11 +910,7 @@ def traced_openai_realtime(operation: str) -> Callable:
) as current_span: ) as current_span:
try: try:
# Base service attributes # Base service attributes
model_name = ( model_name = _get_model_name(self)
getattr(self, "model_name", None)
or getattr(self, "_model_name", None)
or "unknown"
)
# Operation-specific attribute collection # Operation-specific attribute collection
operation_attrs = {} operation_attrs = {}