diff --git a/src/pipecat/services/aws/llm.py b/src/pipecat/services/aws/llm.py index 205d02f1f..207adba36 100644 --- a/src/pipecat/services/aws/llm.py +++ b/src/pipecat/services/aws/llm.py @@ -75,10 +75,12 @@ class AWSBedrockLLMSettings(LLMSettings): """Settings for AWS Bedrock LLM services. Parameters: + stop_sequences: List of strings that stop generation. latency: Performance mode - "standard" or "optimized". additional_model_request_fields: Additional model-specific parameters. """ + stop_sequences: List[str] | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) latency: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) additional_model_request_fields: Dict[str, Any] | _NotGiven = field( default_factory=lambda: NOT_GIVEN @@ -810,6 +812,10 @@ class AWSBedrockLLMService(LLMService): deprecated parameters and *settings* are provided, *settings* values take precedence. stop_sequences: List of strings that stop generation. + + .. deprecated:: 0.0.105 + Use ``settings=AWSBedrockLLMSettings(stop_sequences=...)`` instead. + client_config: Custom boto3 client configuration. retry_timeout_secs: Request timeout in seconds for retry logic. retry_on_timeout: Whether to retry the request once if it times out. @@ -828,6 +834,7 @@ class AWSBedrockLLMService(LLMService): seed=None, filter_incomplete_user_turns=False, user_turn_completion_config=None, + stop_sequences=None, latency=None, additional_model_request_fields={}, ) @@ -836,6 +843,9 @@ class AWSBedrockLLMService(LLMService): if model is not None: _warn_deprecated_param("model", AWSBedrockLLMSettings, "model") default_settings.model = model + if stop_sequences is not None: + _warn_deprecated_param("stop_sequences", AWSBedrockLLMSettings, "stop_sequences") + default_settings.stop_sequences = stop_sequences # 3. Apply params overrides — only if settings not provided if params is not None: @@ -844,6 +854,8 @@ class AWSBedrockLLMService(LLMService): default_settings.max_tokens = params.max_tokens default_settings.temperature = params.temperature default_settings.top_p = params.top_p + if params.stop_sequences: + default_settings.stop_sequences = params.stop_sequences default_settings.latency = params.latency if isinstance(params.additional_model_request_fields, dict): default_settings.additional_model_request_fields = ( @@ -856,14 +868,6 @@ class AWSBedrockLLMService(LLMService): super().__init__(settings=default_settings, **kwargs) - # Handle stop_sequences (not a settings field) - if stop_sequences is not None: - self._stop_sequences = stop_sequences - elif params is not None: - self._stop_sequences = params.stop_sequences or [] - else: - self._stop_sequences = [] - # Initialize the AWS Bedrock client if not client_config: client_config = Config( @@ -915,6 +919,8 @@ class AWSBedrockLLMService(LLMService): inference_config["temperature"] = self._settings.temperature if self._settings.top_p is not None: inference_config["topP"] = self._settings.top_p + if self._settings.stop_sequences: + inference_config["stopSequences"] = self._settings.stop_sequences return inference_config async def run_inference( diff --git a/src/pipecat/services/aws/nova_sonic/llm.py b/src/pipecat/services/aws/nova_sonic/llm.py index 7fec1c73c..9103ebdfd 100644 --- a/src/pipecat/services/aws/nova_sonic/llm.py +++ b/src/pipecat/services/aws/nova_sonic/llm.py @@ -191,12 +191,12 @@ class AWSNovaSonicLLMSettings(LLMSettings): """Settings for AWS Nova Sonic LLM service. Parameters: - voice_id: Voice for speech synthesis. + voice: Voice identifier for speech synthesis. endpointing_sensitivity: Controls how quickly Nova Sonic decides the user has stopped speaking. Can be "LOW", "MEDIUM", or "HIGH". """ - voice_id: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + voice: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) endpointing_sensitivity: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) @@ -250,7 +250,7 @@ class AWSNovaSonicLLMService(LLMService): - Nova Sonic (the older model): see https://docs.aws.amazon.com/nova/latest/userguide/available-voices.html. .. deprecated:: - Use ``settings=AWSNovaSonicLLMSettings(voice_id=...)`` instead. + Use ``settings=AWSNovaSonicLLMSettings(voice=...)`` instead. params: Model parameters for audio configuration and inference. @@ -273,7 +273,7 @@ class AWSNovaSonicLLMService(LLMService): # 1. Initialize default_settings with hardcoded defaults default_settings = AWSNovaSonicLLMSettings( model="amazon.nova-2-sonic-v1:0", - voice_id="matthew", + voice="matthew", temperature=0.7, max_tokens=1024, top_p=0.9, @@ -291,8 +291,8 @@ class AWSNovaSonicLLMService(LLMService): _warn_deprecated_param("model", AWSNovaSonicLLMSettings, "model") default_settings.model = model if voice_id != "matthew": - _warn_deprecated_param("voice_id", AWSNovaSonicLLMSettings, "voice_id") - default_settings.voice_id = voice_id + _warn_deprecated_param("voice_id", AWSNovaSonicLLMSettings, "voice") + default_settings.voice = voice_id # 3. Apply params overrides — only if settings not provided if params is not None: @@ -811,7 +811,7 @@ class AWSNovaSonicLLMService(LLMService): "sampleRateHertz": {self._output_sample_rate}, "sampleSizeBits": {self._output_sample_size}, "channelCount": {self._output_channel_count}, - "voiceId": "{self._settings.voice_id}", + "voiceId": "{self._settings.voice}", "encoding": "base64", "audioType": "SPEECH" }}{tools_config} diff --git a/src/pipecat/services/azure/realtime/llm.py b/src/pipecat/services/azure/realtime/llm.py index fa645d8c1..0de2217d8 100644 --- a/src/pipecat/services/azure/realtime/llm.py +++ b/src/pipecat/services/azure/realtime/llm.py @@ -35,6 +35,8 @@ class AzureRealtimeLLMService(OpenAIRealtimeLLMService): real-time audio and text communication capabilities as the base OpenAI service. """ + _settings: AzureRealtimeLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/cerebras/llm.py b/src/pipecat/services/cerebras/llm.py index 40862f252..d98a2d7a4 100644 --- a/src/pipecat/services/cerebras/llm.py +++ b/src/pipecat/services/cerebras/llm.py @@ -31,6 +31,8 @@ class CerebrasLLMService(OpenAILLMService): maintaining full compatibility with OpenAI's interface and functionality. """ + _settings: CerebrasLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/deepseek/llm.py b/src/pipecat/services/deepseek/llm.py index d778639ef..684e971ca 100644 --- a/src/pipecat/services/deepseek/llm.py +++ b/src/pipecat/services/deepseek/llm.py @@ -31,6 +31,8 @@ class DeepSeekLLMService(OpenAILLMService): maintaining full compatibility with OpenAI's interface and functionality. """ + _settings: DeepSeekLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/fireworks/llm.py b/src/pipecat/services/fireworks/llm.py index 7ba3f9eac..86665cc48 100644 --- a/src/pipecat/services/fireworks/llm.py +++ b/src/pipecat/services/fireworks/llm.py @@ -31,6 +31,8 @@ class FireworksLLMService(OpenAILLMService): maintaining full compatibility with OpenAI's interface and functionality. """ + _settings: FireworksLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/google/gemini_live/llm.py b/src/pipecat/services/google/gemini_live/llm.py index 6e7a8f8a1..146a0fcc4 100644 --- a/src/pipecat/services/google/gemini_live/llm.py +++ b/src/pipecat/services/google/gemini_live/llm.py @@ -610,6 +610,7 @@ class GeminiLiveLLMSettings(LLMSettings): """Settings for Gemini Live LLM services. Parameters: + voice: TTS voice identifier (e.g. ``"Charon"``). modalities: Response modalities. language: Language for generation. media_resolution: Media resolution setting. @@ -620,6 +621,7 @@ class GeminiLiveLLMSettings(LLMSettings): proactivity: Proactivity configuration. """ + voice: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) modalities: GeminiModalities | _NotGiven = field(default_factory=lambda: NOT_GIVEN) language: Language | str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) media_resolution: GeminiMediaResolution | _NotGiven = field(default_factory=lambda: NOT_GIVEN) @@ -680,6 +682,9 @@ class GeminiLiveLLMService(LLMService): Use ``settings=GeminiLiveLLMSettings(model=...)`` instead. voice_id: TTS voice identifier. Defaults to "Charon". + + .. deprecated:: 0.0.105 + Use ``settings=GeminiLiveLLMSettings(voice=...)`` instead. start_audio_paused: Whether to start with audio input paused. Defaults to False. start_video_paused: Whether to start with video input paused. Defaults to False. system_instruction: System prompt for the model. Defaults to None. @@ -712,6 +717,7 @@ class GeminiLiveLLMService(LLMService): # 1. Initialize default_settings with hardcoded defaults default_settings = GeminiLiveLLMSettings( model="models/gemini-2.5-flash-native-audio-preview-12-2025", + voice="Charon", frequency_penalty=None, max_tokens=4096, presence_penalty=None, @@ -736,6 +742,9 @@ class GeminiLiveLLMService(LLMService): if model is not None: _warn_deprecated_param("model", GeminiLiveLLMSettings, "model") default_settings.model = model + if voice_id != "Charon": + _warn_deprecated_param("voice_id", GeminiLiveLLMSettings, "voice") + default_settings.voice = voice_id # 3. Apply params overrides — only if settings not provided if params is not None: @@ -776,7 +785,6 @@ class GeminiLiveLLMService(LLMService): self._last_sent_time = 0 self._base_url = base_url - self._voice_id = voice_id self._language_code = params.language if params is not None else Language.EN_US self._system_instruction_from_init = system_instruction @@ -1184,7 +1192,7 @@ class GeminiLiveLLMService(LLMService): response_modalities=[Modality(self._settings.modalities.value)], speech_config=SpeechConfig( voice_config=VoiceConfig( - prebuilt_voice_config={"voice_name": self._voice_id} + prebuilt_voice_config={"voice_name": self._settings.voice} ), language_code=self._settings.language, ), diff --git a/src/pipecat/services/google/gemini_live/llm_vertex.py b/src/pipecat/services/google/gemini_live/llm_vertex.py index 43d3ab207..6264ea285 100644 --- a/src/pipecat/services/google/gemini_live/llm_vertex.py +++ b/src/pipecat/services/google/gemini_live/llm_vertex.py @@ -57,6 +57,8 @@ class GeminiLiveVertexLLMService(GeminiLiveLLMService): responses, and tool usage. """ + _settings: GeminiLiveVertexLLMSettings + def __init__( self, *, @@ -90,6 +92,9 @@ class GeminiLiveVertexLLMService(GeminiLiveLLMService): Use ``settings=GeminiLiveLLMSettings(model=...)`` instead. voice_id: TTS voice identifier. Defaults to "Charon". + + .. deprecated:: 0.0.105 + Use ``settings=GeminiLiveVertexLLMSettings(voice=...)`` instead. start_audio_paused: Whether to start with audio input paused. Defaults to False. start_video_paused: Whether to start with video input paused. Defaults to False. system_instruction: System prompt for the model. Defaults to None. @@ -132,6 +137,7 @@ class GeminiLiveVertexLLMService(GeminiLiveLLMService): # 1. Initialize default_settings with hardcoded defaults default_settings = GeminiLiveVertexLLMSettings( model="google/gemini-live-2.5-flash-native-audio", + voice="Charon", frequency_penalty=None, max_tokens=4096, presence_penalty=None, @@ -156,6 +162,9 @@ class GeminiLiveVertexLLMService(GeminiLiveLLMService): if model is not None: _warn_deprecated_param("model", GeminiLiveVertexLLMSettings, "model") default_settings.model = model + if voice_id != "Charon": + _warn_deprecated_param("voice_id", GeminiLiveVertexLLMSettings, "voice") + default_settings.voice = voice_id # 3. Apply params overrides — only if settings not provided if params is not None: @@ -193,7 +202,6 @@ class GeminiLiveVertexLLMService(GeminiLiveLLMService): # api_key is required by parent class, but actually not used with # Vertex api_key="dummy", - voice_id=voice_id, start_audio_paused=start_audio_paused, start_video_paused=start_video_paused, system_instruction=system_instruction, diff --git a/src/pipecat/services/google/llm_openai.py b/src/pipecat/services/google/llm_openai.py index 1d7ae6bc4..96e208fcb 100644 --- a/src/pipecat/services/google/llm_openai.py +++ b/src/pipecat/services/google/llm_openai.py @@ -58,6 +58,8 @@ class GoogleLLMOpenAIBetaService(OpenAILLMService): https://ai.google.dev/gemini-api/docs/openai """ + _settings: GoogleOpenAILLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/google/llm_vertex.py b/src/pipecat/services/google/llm_vertex.py index e21341614..27f168042 100644 --- a/src/pipecat/services/google/llm_vertex.py +++ b/src/pipecat/services/google/llm_vertex.py @@ -59,6 +59,8 @@ class GoogleVertexLLMService(GoogleLLMService): https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference """ + _settings: GoogleVertexLLMSettings + class InputParams(GoogleLLMService.InputParams): """Input parameters specific to Vertex AI. diff --git a/src/pipecat/services/grok/llm.py b/src/pipecat/services/grok/llm.py index 7f9ac643b..59741cf9a 100644 --- a/src/pipecat/services/grok/llm.py +++ b/src/pipecat/services/grok/llm.py @@ -86,6 +86,8 @@ class GrokLLMService(OpenAILLMService): processing and reports final totals. """ + _settings: GrokLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/grok/realtime/llm.py b/src/pipecat/services/grok/realtime/llm.py index 2f2df1b45..638477b5e 100644 --- a/src/pipecat/services/grok/realtime/llm.py +++ b/src/pipecat/services/grok/realtime/llm.py @@ -13,7 +13,7 @@ https://docs.x.ai/docs/guides/voice/agent import base64 import json import time -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Any, Optional from loguru import logger @@ -56,7 +56,7 @@ from pipecat.processors.aggregators.llm_response_universal import ( from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext from pipecat.processors.frame_processor import FrameDirection from pipecat.services.llm_service import FunctionCallFromLLM, LLMService -from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven, _warn_deprecated_param +from pipecat.services.settings import LLMSettings from pipecat.utils.time import time_now_iso8601 from . import events @@ -88,15 +88,9 @@ class CurrentAudioResponse: @dataclass class GrokRealtimeLLMSettings(LLMSettings): - """Settings for Grok Realtime LLM services. + """Settings for Grok Realtime LLM services.""" - Parameters: - session_properties: Grok Realtime session configuration. - """ - - session_properties: events.SessionProperties | _NotGiven = field( - default_factory=lambda: NOT_GIVEN - ) + pass class GrokRealtimeLLMService(LLMService): @@ -137,19 +131,13 @@ class GrokRealtimeLLMService(LLMService): base_url: WebSocket base URL for the realtime API. Defaults to "wss://api.x.ai/v1/realtime". session_properties: Configuration properties for the realtime session. - - .. deprecated:: - Use ``settings=GrokRealtimeLLMSettings(session_properties=...)`` - instead. - If None, uses default SessionProperties with voice "Ara". To set a different voice, configure it in session_properties: session_properties = events.SessionProperties(voice="Rex") Available voices: Ara, Rex, Sal, Eve, Leo. - settings: Grok Realtime LLM settings. If provided together with deprecated - top-level parameters, the ``settings`` values take precedence. + settings: Runtime-updatable settings for this service. start_audio_paused: Whether to start with audio input paused. Defaults to False. **kwargs: Additional arguments passed to parent LLMService. """ @@ -165,17 +153,9 @@ class GrokRealtimeLLMService(LLMService): seed=None, filter_incomplete_user_turns=False, user_turn_completion_config=None, - session_properties=events.SessionProperties(), ) - # 2. Apply direct init arg overrides (deprecated) - if session_properties is not None: - _warn_deprecated_param( - "session_properties", GrokRealtimeLLMSettings, "session_properties" - ) - default_settings.session_properties = session_properties - - # 4. Apply settings delta (canonical API, always wins) + # 2. Apply settings delta (canonical API, always wins) if settings is not None: default_settings.apply_update(settings) @@ -187,6 +167,7 @@ class GrokRealtimeLLMService(LLMService): self.api_key = api_key self.base_url = base_url + self._session_properties = session_properties or events.SessionProperties() self._audio_input_paused = start_audio_paused self._websocket = None @@ -235,13 +216,13 @@ class GrokRealtimeLLMService(LLMService): Configured sample rate or None if not manually configured. For PCMU/PCMA formats, returns 8000 Hz (G.711 standard). """ - if not self._settings.session_properties.audio: + if not self._session_properties.audio: return None audio_config = ( - self._settings.session_properties.audio.input + self._session_properties.audio.input if direction == "input" - else self._settings.session_properties.audio.output + else self._session_properties.audio.output ) if audio_config and audio_config.format: @@ -271,8 +252,8 @@ class GrokRealtimeLLMService(LLMService): def _is_turn_detection_enabled(self) -> bool: """Check if server-side VAD is enabled.""" - if self._settings.session_properties.turn_detection: - return self._settings.session_properties.turn_detection.type == "server_vad" + if self._session_properties.turn_detection: + return self._session_properties.turn_detection.type == "server_vad" return False async def _handle_interruption(self): @@ -339,7 +320,7 @@ class GrokRealtimeLLMService(LLMService): input_sample_rate: Sample rate for audio input (Hz). output_sample_rate: Sample rate for audio output (Hz). """ - props = self._settings.session_properties + props = self._session_properties if not props.audio: props.audio = events.AudioConfiguration() if not props.audio.input: @@ -395,7 +376,12 @@ class GrokRealtimeLLMService(LLMService): # directly. The frame.delta path falls through to super, which calls # _update_settings → our override handles the rest. if isinstance(frame, LLMUpdateSettingsFrame) and frame.delta is None: - self._settings.session_properties = events.SessionProperties(**frame.settings) + # Capture current audio config before replacing session properties. + input_rate = self._get_configured_sample_rate("input") + output_rate = self._get_configured_sample_rate("output") + self._session_properties = events.SessionProperties(**frame.settings) + if input_rate and output_rate: + self._ensure_audio_config(input_rate, output_rate) await self._send_session_update() await self.push_frame(frame, direction) return @@ -498,29 +484,14 @@ class GrokRealtimeLLMService(LLMService): await self.push_error(error_msg=f"Error sending client event: {e}", exception=e) async def _update_settings(self, delta): - """Apply a settings delta, sending a session update if needed.""" - # Capture current sample rates before the update replaces them. - input_rate = self._get_configured_sample_rate("input") - output_rate = self._get_configured_sample_rate("output") - + """Apply a settings delta.""" changed = await super()._update_settings(delta) - - if "session_properties" in changed: - if input_rate and output_rate: - self._ensure_audio_config(input_rate, output_rate) - else: - logger.warning( - "Attempting to apply session properties update without configured sample rates. " - "Audio configuration may be incomplete." - ) - await self._send_session_update() - - self._warn_unhandled_updated_settings(changed.keys() - {"session_properties"}) + self._warn_unhandled_updated_settings(changed.keys()) return changed async def _send_session_update(self): """Update session settings on the server.""" - settings = self._settings.session_properties + settings = self._session_properties adapter: GrokRealtimeLLMAdapter = self.get_llm_adapter() if self._context: diff --git a/src/pipecat/services/groq/llm.py b/src/pipecat/services/groq/llm.py index 5b81a4c7d..d7fc7f939 100644 --- a/src/pipecat/services/groq/llm.py +++ b/src/pipecat/services/groq/llm.py @@ -30,6 +30,8 @@ class GroqLLMService(OpenAILLMService): maintaining full compatibility with OpenAI's interface and functionality. """ + _settings: GroqLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/mistral/llm.py b/src/pipecat/services/mistral/llm.py index c4deab4c3..801c02f9e 100644 --- a/src/pipecat/services/mistral/llm.py +++ b/src/pipecat/services/mistral/llm.py @@ -33,6 +33,8 @@ class MistralLLMService(OpenAILLMService): maintaining full compatibility with OpenAI's interface and functionality. """ + _settings: MistralLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/nvidia/llm.py b/src/pipecat/services/nvidia/llm.py index 4ebfac0d8..48ccbaaf4 100644 --- a/src/pipecat/services/nvidia/llm.py +++ b/src/pipecat/services/nvidia/llm.py @@ -36,6 +36,8 @@ class NvidiaLLMService(OpenAILLMService): in token usage reporting between NIM (incremental) and OpenAI (final summary). """ + _settings: NvidiaLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/ollama/llm.py b/src/pipecat/services/ollama/llm.py index cd0c68099..d1c0eeebd 100644 --- a/src/pipecat/services/ollama/llm.py +++ b/src/pipecat/services/ollama/llm.py @@ -30,6 +30,8 @@ class OLLamaLLMService(OpenAILLMService): providing a compatible interface for running large language models locally. """ + _settings: OllamaLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/openai/base_llm.py b/src/pipecat/services/openai/base_llm.py index 050f8dcc9..257a58501 100644 --- a/src/pipecat/services/openai/base_llm.py +++ b/src/pipecat/services/openai/base_llm.py @@ -53,11 +53,9 @@ class OpenAILLMSettings(LLMSettings): Parameters: max_completion_tokens: Maximum completion tokens to generate. - service_tier: Service tier to use (e.g., "auto", "flex", "priority"). """ max_completion_tokens: int | _NotGiven = field(default_factory=lambda: _NOT_GIVEN) - service_tier: str | _NotGiven = field(default_factory=lambda: _NOT_GIVEN) class BaseOpenAILLMService(LLMService): @@ -118,6 +116,7 @@ class BaseOpenAILLMService(LLMService): organization=None, project=None, default_headers: Optional[Mapping[str, str]] = None, + service_tier: Optional[str] = None, params: Optional[InputParams] = None, settings: Optional[OpenAILLMSettings] = None, retry_timeout_secs: Optional[float] = 5.0, @@ -138,6 +137,7 @@ class BaseOpenAILLMService(LLMService): organization: OpenAI organization ID. project: OpenAI project ID. default_headers: Additional HTTP headers to include in requests. + service_tier: Service tier to use (e.g., "auto", "flex", "priority"). params: Input parameters for model configuration and behavior. .. deprecated:: 0.0.105 @@ -161,7 +161,6 @@ class BaseOpenAILLMService(LLMService): top_k=None, max_tokens=NOT_GIVEN, max_completion_tokens=NOT_GIVEN, - service_tier=NOT_GIVEN, filter_incomplete_user_turns=False, user_turn_completion_config=None, extra={}, @@ -180,7 +179,6 @@ class BaseOpenAILLMService(LLMService): default_settings.top_p = params.top_p default_settings.max_tokens = params.max_tokens default_settings.max_completion_tokens = params.max_completion_tokens - default_settings.service_tier = params.service_tier if isinstance(params.extra, dict): default_settings.extra = params.extra @@ -192,6 +190,7 @@ class BaseOpenAILLMService(LLMService): settings=default_settings, **kwargs, ) + self._service_tier = service_tier self._retry_timeout_secs = retry_timeout_secs self._retry_on_timeout = retry_on_timeout self._system_instruction = system_instruction @@ -321,7 +320,7 @@ class BaseOpenAILLMService(LLMService): "top_p": self._settings.top_p, "max_tokens": self._settings.max_tokens, "max_completion_tokens": self._settings.max_completion_tokens, - "service_tier": self._settings.service_tier, + "service_tier": self._service_tier, } # Messages, tools, tool_choice diff --git a/src/pipecat/services/openai/llm.py b/src/pipecat/services/openai/llm.py index a81d3dc29..f2e3ad00a 100644 --- a/src/pipecat/services/openai/llm.py +++ b/src/pipecat/services/openai/llm.py @@ -76,6 +76,7 @@ class OpenAILLMService(BaseOpenAILLMService): self, *, model: Optional[str] = None, + service_tier: Optional[str] = None, params: Optional[BaseOpenAILLMService.InputParams] = None, settings: Optional[OpenAILLMSettings] = None, **kwargs, @@ -88,6 +89,7 @@ class OpenAILLMService(BaseOpenAILLMService): .. deprecated:: 0.0.105 Use ``settings=OpenAILLMSettings(model=...)`` instead. + service_tier: Service tier to use (e.g., "auto", "flex", "priority"). params: Input parameters for model configuration. .. deprecated:: 0.0.105 @@ -108,7 +110,6 @@ class OpenAILLMService(BaseOpenAILLMService): top_k=None, max_tokens=NOT_GIVEN, max_completion_tokens=NOT_GIVEN, - service_tier=NOT_GIVEN, filter_incomplete_user_turns=False, user_turn_completion_config=None, extra={}, @@ -119,6 +120,10 @@ class OpenAILLMService(BaseOpenAILLMService): _warn_deprecated_param("model", OpenAILLMSettings, "model") default_settings.model = model + # Handle service_tier from deprecated params + if params is not None and not settings and params.service_tier is not NOT_GIVEN: + service_tier = service_tier or params.service_tier + # 3. Apply params overrides — only if settings not provided if params is not None: _warn_deprecated_param("params", OpenAILLMSettings) @@ -130,7 +135,6 @@ class OpenAILLMService(BaseOpenAILLMService): default_settings.top_p = params.top_p default_settings.max_tokens = params.max_tokens default_settings.max_completion_tokens = params.max_completion_tokens - default_settings.service_tier = params.service_tier if isinstance(params.extra, dict): default_settings.extra = params.extra @@ -138,7 +142,7 @@ class OpenAILLMService(BaseOpenAILLMService): if settings is not None: default_settings.apply_update(settings) - super().__init__(settings=default_settings, **kwargs) + super().__init__(service_tier=service_tier, settings=default_settings, **kwargs) def create_context_aggregator( self, diff --git a/src/pipecat/services/openai/realtime/llm.py b/src/pipecat/services/openai/realtime/llm.py index 1230ed2db..761d7244b 100644 --- a/src/pipecat/services/openai/realtime/llm.py +++ b/src/pipecat/services/openai/realtime/llm.py @@ -10,7 +10,7 @@ import base64 import io import json import time -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Any, Optional from loguru import logger @@ -59,7 +59,7 @@ from pipecat.processors.aggregators.openai_llm_context import ( ) from pipecat.processors.frame_processor import FrameDirection from pipecat.services.llm_service import FunctionCallFromLLM, LLMService -from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven, _warn_deprecated_param +from pipecat.services.settings import LLMSettings, _warn_deprecated_param from pipecat.transcriptions.language import Language from pipecat.utils.time import time_now_iso8601 from pipecat.utils.tracing.service_decorators import traced_openai_realtime, traced_stt @@ -93,15 +93,9 @@ class CurrentAudioResponse: @dataclass class OpenAIRealtimeLLMSettings(LLMSettings): - """Settings for OpenAI Realtime LLM services. + """Settings for OpenAI Realtime LLM services.""" - Parameters: - session_properties: OpenAI Realtime session configuration. - """ - - session_properties: events.SessionProperties | _NotGiven = field( - default_factory=lambda: NOT_GIVEN - ) + pass class OpenAIRealtimeLLMService(LLMService): @@ -145,15 +139,8 @@ class OpenAIRealtimeLLMService(LLMService): base_url: WebSocket base URL for the realtime API. Defaults to "wss://api.openai.com/v1/realtime". session_properties: Configuration properties for the realtime session. - - .. deprecated:: - Use ``settings=OpenAIRealtimeLLMSettings(session_properties=...)`` - instead. - - These are session-level settings that can be updated during the session - (except for voice and model). If None, uses default SessionProperties. - settings: Realtime LLM settings. If provided together with deprecated - top-level parameters, the ``settings`` values take precedence. + If None, uses default SessionProperties. + settings: Runtime-updatable settings for this service. start_audio_paused: Whether to start with audio input paused. Defaults to False. start_video_paused: Whether to start with video input paused. Defaults to False. video_frame_detail: Detail level for video processing. Can be "auto", "low", or "high". @@ -192,20 +179,14 @@ class OpenAIRealtimeLLMService(LLMService): seed=None, filter_incomplete_user_turns=False, user_turn_completion_config=None, - session_properties=events.SessionProperties(), ) # 2. Apply direct init arg overrides (deprecated) if model is not None: _warn_deprecated_param("model", OpenAIRealtimeLLMSettings, "model") default_settings.model = model - if session_properties is not None: - _warn_deprecated_param( - "session_properties", OpenAIRealtimeLLMSettings, "session_properties" - ) - default_settings.session_properties = session_properties - # 4. Apply settings delta (canonical API, always wins) + # 3. Apply settings delta (canonical API, always wins) if settings is not None: default_settings.apply_update(settings) @@ -220,6 +201,7 @@ class OpenAIRealtimeLLMService(LLMService): self.api_key = api_key self.base_url = full_url + self._session_properties = session_properties or events.SessionProperties() self._audio_input_paused = start_audio_paused self._video_input_paused = start_video_paused self._video_frame_detail = video_frame_detail @@ -282,12 +264,12 @@ class OpenAIRealtimeLLMService(LLMService): def _is_modality_enabled(self, modality: str) -> bool: """Check if a specific modality is enabled, "text" or "audio".""" - modalities = self._settings.session_properties.output_modalities or ["audio", "text"] + modalities = self._session_properties.output_modalities or ["audio", "text"] return modality in modalities def _get_enabled_modalities(self) -> list[str]: """Get the list of enabled modalities.""" - modalities = self._settings.session_properties.output_modalities or ["audio", "text"] + modalities = self._session_properties.output_modalities or ["audio", "text"] # API only supports single modality responses: either ["text"] or ["audio"] if "audio" in modalities: return ["audio"] @@ -360,9 +342,9 @@ class OpenAIRealtimeLLMService(LLMService): # None and False are different. Check for False. None means we're using OpenAI's # built-in turn detection defaults. turn_detection_disabled = ( - self._settings.session_properties.audio - and self._settings.session_properties.audio.input - and self._settings.session_properties.audio.input.turn_detection is False + self._session_properties.audio + and self._session_properties.audio.input + and self._session_properties.audio.input.turn_detection is False ) if turn_detection_disabled: await self.send_client_event(events.InputAudioBufferClearEvent()) @@ -382,9 +364,9 @@ class OpenAIRealtimeLLMService(LLMService): # None and False are different. Check for False. None means we're using OpenAI's # built-in turn detection defaults. turn_detection_disabled = ( - self._settings.session_properties.audio - and self._settings.session_properties.audio.input - and self._settings.session_properties.audio.input.turn_detection is False + self._session_properties.audio + and self._session_properties.audio.input + and self._session_properties.audio.input.turn_detection is False ) if turn_detection_disabled: await self.send_client_event(events.InputAudioBufferCommitEvent()) @@ -457,7 +439,7 @@ class OpenAIRealtimeLLMService(LLMService): # directly. The frame.delta path falls through to super, which calls # _update_settings → our override handles the rest. if isinstance(frame, LLMUpdateSettingsFrame) and frame.delta is None: - self._settings.session_properties = events.SessionProperties(**frame.settings) + self._session_properties = events.SessionProperties(**frame.settings) await self._send_session_update() await self.push_frame(frame, direction) return @@ -576,15 +558,13 @@ class OpenAIRealtimeLLMService(LLMService): await self.push_error(error_msg=f"Error sending client event: {e}", exception=e) async def _update_settings(self, delta): - """Apply a settings delta, sending a session update if needed.""" + """Apply a settings delta.""" changed = await super()._update_settings(delta) - if "session_properties" in changed: - await self._send_session_update() - self._warn_unhandled_updated_settings(changed.keys() - {"session_properties"}) + self._warn_unhandled_updated_settings(changed.keys()) return changed async def _send_session_update(self): - settings = self._settings.session_properties + settings = self._session_properties adapter: OpenAIRealtimeLLMAdapter = self.get_llm_adapter() if self._context: diff --git a/src/pipecat/services/openai_realtime_beta/azure.py b/src/pipecat/services/openai_realtime_beta/azure.py index fb85b1e79..72a1201f7 100644 --- a/src/pipecat/services/openai_realtime_beta/azure.py +++ b/src/pipecat/services/openai_realtime_beta/azure.py @@ -42,6 +42,8 @@ class AzureRealtimeBetaLLMService(OpenAIRealtimeBetaLLMService): real-time audio and text communication capabilities as the base OpenAI service. """ + _settings: AzureRealtimeBetaLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/openai_realtime_beta/openai.py b/src/pipecat/services/openai_realtime_beta/openai.py index 11b60c3b9..67a892e6f 100644 --- a/src/pipecat/services/openai_realtime_beta/openai.py +++ b/src/pipecat/services/openai_realtime_beta/openai.py @@ -10,7 +10,7 @@ import base64 import json import time import warnings -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Optional from loguru import logger @@ -54,7 +54,7 @@ from pipecat.processors.aggregators.openai_llm_context import ( from pipecat.processors.frame_processor import FrameDirection from pipecat.services.llm_service import FunctionCallFromLLM, LLMService from pipecat.services.openai.llm import OpenAIContextAggregatorPair -from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven, _warn_deprecated_param +from pipecat.services.settings import LLMSettings, _warn_deprecated_param from pipecat.transcriptions.language import Language from pipecat.utils.time import time_now_iso8601 from pipecat.utils.tracing.service_decorators import traced_openai_realtime, traced_stt @@ -94,15 +94,9 @@ class CurrentAudioResponse: @dataclass class OpenAIRealtimeBetaLLMSettings(LLMSettings): - """Settings for OpenAI Realtime Beta LLM services. + """Settings for OpenAI Realtime Beta LLM services.""" - Parameters: - session_properties: OpenAI Realtime session configuration. - """ - - session_properties: events.SessionProperties | _NotGiven = field( - default_factory=lambda: NOT_GIVEN - ) + pass class OpenAIRealtimeBetaLLMService(LLMService): @@ -146,14 +140,8 @@ class OpenAIRealtimeBetaLLMService(LLMService): base_url: WebSocket base URL for the realtime API. Defaults to "wss://api.openai.com/v1/realtime". session_properties: Configuration properties for the realtime session. - - .. deprecated:: - Use ``settings=OpenAIRealtimeBetaLLMSettings(session_properties=...)`` - instead. - If None, uses default SessionProperties. - settings: Realtime Beta LLM settings. If provided together with deprecated - top-level parameters, the ``settings`` values take precedence. + settings: Runtime-updatable settings for this service. start_audio_paused: Whether to start with audio input paused. Defaults to False. send_transcription_frames: Whether to emit transcription frames. Defaults to True. **kwargs: Additional arguments passed to parent LLMService. @@ -179,20 +167,13 @@ class OpenAIRealtimeBetaLLMService(LLMService): seed=None, filter_incomplete_user_turns=False, user_turn_completion_config=None, - session_properties=events.SessionProperties(), ) # 2. Apply direct init arg overrides (deprecated) if model is not None: _warn_deprecated_param("model", OpenAIRealtimeBetaLLMSettings, "model") default_settings.model = model - if session_properties is not None: - _warn_deprecated_param( - "session_properties", OpenAIRealtimeBetaLLMSettings, "session_properties" - ) - default_settings.session_properties = session_properties - - # 4. Apply settings delta (canonical API, always wins) + # 3. Apply settings delta (canonical API, always wins) if settings is not None: default_settings.apply_update(settings) @@ -205,6 +186,7 @@ class OpenAIRealtimeBetaLLMService(LLMService): self.api_key = api_key self.base_url = full_url + self._session_properties = session_properties or events.SessionProperties() self._audio_input_paused = start_audio_paused self._send_transcription_frames = send_transcription_frames self._websocket = None @@ -243,12 +225,12 @@ class OpenAIRealtimeBetaLLMService(LLMService): def _is_modality_enabled(self, modality: str) -> bool: """Check if a specific modality is enabled, "text" or "audio".""" - modalities = self._settings.session_properties.modalities or ["audio", "text"] + modalities = self._session_properties.modalities or ["audio", "text"] return modality in modalities def _get_enabled_modalities(self) -> list[str]: """Get the list of enabled modalities.""" - return self._settings.session_properties.modalities or ["audio", "text"] + return self._session_properties.modalities or ["audio", "text"] async def retrieve_conversation_item(self, item_id: str): """Retrieve a conversation item by ID from the server. @@ -315,7 +297,7 @@ class OpenAIRealtimeBetaLLMService(LLMService): async def _handle_interruption(self): # None and False are different. Check for False. None means we're using OpenAI's # built-in turn detection defaults. - if self._settings.session_properties.turn_detection is False: + if self._session_properties.turn_detection is False: await self.send_client_event(events.InputAudioBufferClearEvent()) await self.send_client_event(events.ResponseCancelEvent()) await self._truncate_current_audio_response() @@ -332,7 +314,7 @@ class OpenAIRealtimeBetaLLMService(LLMService): async def _handle_user_stopped_speaking(self, frame): # None and False are different. Check for False. None means we're using OpenAI's # built-in turn detection defaults. - if self._settings.session_properties.turn_detection is False: + if self._session_properties.turn_detection is False: await self.send_client_event(events.InputAudioBufferCommitEvent()) await self.send_client_event(events.ResponseCreateEvent()) @@ -403,7 +385,7 @@ class OpenAIRealtimeBetaLLMService(LLMService): # directly. The frame.delta path falls through to super, which calls # _update_settings → our override handles the rest. if isinstance(frame, LLMUpdateSettingsFrame) and frame.delta is None: - self._settings.session_properties = events.SessionProperties(**frame.settings) + self._session_properties = events.SessionProperties(**frame.settings) await self._send_session_update() await self.push_frame(frame, direction) return @@ -520,14 +502,13 @@ class OpenAIRealtimeBetaLLMService(LLMService): await self.push_error(error_msg=f"Error sending client event: {e}", exception=e) async def _update_settings(self, delta): - """Apply a settings delta, sending a session update if needed.""" + """Apply a settings delta.""" changed = await super()._update_settings(delta) - if "session_properties" in changed: - await self._send_session_update() + self._warn_unhandled_updated_settings(changed.keys()) return changed async def _send_session_update(self): - settings = self._settings.session_properties + settings = self._session_properties # tools given in the context override the tools in the session properties if self._context and self._context.tools: settings.tools = self._context.tools diff --git a/src/pipecat/services/openpipe/llm.py b/src/pipecat/services/openpipe/llm.py index b2e953e64..f6437feef 100644 --- a/src/pipecat/services/openpipe/llm.py +++ b/src/pipecat/services/openpipe/llm.py @@ -43,6 +43,8 @@ class OpenPipeLLMService(OpenAILLMService): for model training and evaluation. """ + _settings: OpenPipeLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/openrouter/llm.py b/src/pipecat/services/openrouter/llm.py index fa2f170ee..03591de46 100644 --- a/src/pipecat/services/openrouter/llm.py +++ b/src/pipecat/services/openrouter/llm.py @@ -34,6 +34,8 @@ class OpenRouterLLMService(OpenAILLMService): maintaining full compatibility with OpenAI's interface and functionality. """ + _settings: OpenRouterLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/perplexity/llm.py b/src/pipecat/services/perplexity/llm.py index d90aea6a4..9969020c5 100644 --- a/src/pipecat/services/perplexity/llm.py +++ b/src/pipecat/services/perplexity/llm.py @@ -38,6 +38,8 @@ class PerplexityLLMService(OpenAILLMService): in token usage reporting between Perplexity (incremental) and OpenAI (final summary). """ + _settings: PerplexityLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/qwen/llm.py b/src/pipecat/services/qwen/llm.py index 6d9abeefe..e08566418 100644 --- a/src/pipecat/services/qwen/llm.py +++ b/src/pipecat/services/qwen/llm.py @@ -30,6 +30,8 @@ class QwenLLMService(OpenAILLMService): maintaining full compatibility with OpenAI's interface and functionality. """ + _settings: QwenLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/sambanova/llm.py b/src/pipecat/services/sambanova/llm.py index f87b432cc..cdd2139da 100644 --- a/src/pipecat/services/sambanova/llm.py +++ b/src/pipecat/services/sambanova/llm.py @@ -42,6 +42,8 @@ class SambaNovaLLMService(OpenAILLMService): # type: ignore maintaining full compatibility with OpenAI's interface and functionality. """ + _settings: SambaNovaLLMSettings + def __init__( self, *, diff --git a/src/pipecat/services/together/llm.py b/src/pipecat/services/together/llm.py index 23e38f752..2fa952a95 100644 --- a/src/pipecat/services/together/llm.py +++ b/src/pipecat/services/together/llm.py @@ -30,6 +30,8 @@ class TogetherLLMService(OpenAILLMService): maintaining full compatibility with OpenAI's interface and functionality. """ + _settings: TogetherLLMSettings + def __init__( self, *,