fix(types): assert_given at store-mode settings read sites

Apply assert_given across service modules to narrow reads from
store-mode settings fields (self._settings.X, default_settings.X),
where _NotGiven is declared in the field type but should never appear
at runtime (enforced by validate_complete()).

Two idioms used:

- Inline wrap for single uses:
    func(assert_given(self._settings.enable_prompt_caching), ...)

- Extract-and-reuse when the same value is used multiple times:
    thinking = assert_given(self._settings.thinking)
    if thinking:
        params["thinking"] = thinking.model_dump(...)

43 service files touched. Cleared ~172 pyright errors; remaining
_NotGiven-related errors are in adjacent categories (flavor mismatch
between openai/anthropic NotGiven and pipecat _NotGiven, settings
field types that should allow None but don't) that need different
fixes.
This commit is contained in:
Paul Kompfner
2026-04-23 17:39:17 -04:00
parent 70f3d32734
commit 6a337f1bc6
43 changed files with 344 additions and 231 deletions

View File

@@ -39,7 +39,7 @@ from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.llm_service import FunctionCallFromLLM, LLMService from pipecat.services.llm_service import FunctionCallFromLLM, LLMService
from pipecat.services.settings import NOT_GIVEN as _NOT_GIVEN from pipecat.services.settings import NOT_GIVEN as _NOT_GIVEN
from pipecat.services.settings import LLMSettings, _NotGiven, is_given from pipecat.services.settings import LLMSettings, _NotGiven, assert_given, is_given
from pipecat.utils.tracing.service_decorators import traced_llm from pipecat.utils.tracing.service_decorators import traced_llm
try: try:
@@ -281,11 +281,13 @@ class AnthropicLLMService(LLMService):
messages = [] messages = []
system = NOT_GIVEN system = NOT_GIVEN
tools = [] tools = []
effective_instruction = system_instruction or self._settings.system_instruction effective_instruction = system_instruction or assert_given(
self._settings.system_instruction
)
adapter: AnthropicLLMAdapter = self.get_llm_adapter() adapter: AnthropicLLMAdapter = self.get_llm_adapter()
invocation_params = adapter.get_llm_invocation_params( invocation_params = adapter.get_llm_invocation_params(
context, context,
enable_prompt_caching=self._settings.enable_prompt_caching, enable_prompt_caching=assert_given(self._settings.enable_prompt_caching),
system_instruction=effective_instruction, system_instruction=effective_instruction,
) )
messages = invocation_params["messages"] messages = invocation_params["messages"]
@@ -305,8 +307,9 @@ class AnthropicLLMService(LLMService):
"tools": tools, "tools": tools,
"betas": ["interleaved-thinking-2025-05-14"], "betas": ["interleaved-thinking-2025-05-14"],
} }
if self._settings.thinking: thinking = assert_given(self._settings.thinking)
params["thinking"] = self._settings.thinking.model_dump(exclude_unset=True) if thinking:
params["thinking"] = thinking.model_dump(exclude_unset=True)
params.update(self._settings.extra) params.update(self._settings.extra)
@@ -319,8 +322,8 @@ class AnthropicLLMService(LLMService):
adapter: AnthropicLLMAdapter = self.get_llm_adapter() adapter: AnthropicLLMAdapter = self.get_llm_adapter()
params: AnthropicLLMInvocationParams = adapter.get_llm_invocation_params( params: AnthropicLLMInvocationParams = adapter.get_llm_invocation_params(
context, context,
enable_prompt_caching=self._settings.enable_prompt_caching, enable_prompt_caching=assert_given(self._settings.enable_prompt_caching),
system_instruction=self._settings.system_instruction, system_instruction=assert_given(self._settings.system_instruction),
) )
return params return params
@@ -359,8 +362,9 @@ class AnthropicLLMService(LLMService):
} }
# Add thinking parameter if set # Add thinking parameter if set
if self._settings.thinking: thinking = assert_given(self._settings.thinking)
params["thinking"] = self._settings.thinking.model_dump(exclude_unset=True) if thinking:
params["thinking"] = thinking.model_dump(exclude_unset=True)
# Messages, system, tools # Messages, system, tools
params.update(params_from_context) params.update(params_from_context)

View File

@@ -37,7 +37,7 @@ from pipecat.metrics.metrics import LLMTokenUsage
from pipecat.processors.aggregators.llm_context import LLMContext from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.llm_service import LLMService from pipecat.services.llm_service import LLMService
from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven, assert_given
from pipecat.utils.tracing.service_decorators import traced_llm from pipecat.utils.tracing.service_decorators import traced_llm
try: try:
@@ -279,7 +279,9 @@ class AWSBedrockLLMService(LLMService):
""" """
messages = [] messages = []
system = [] system = []
effective_instruction = system_instruction or self._settings.system_instruction effective_instruction = system_instruction or assert_given(
self._settings.system_instruction
)
adapter: AWSBedrockLLMAdapter = self.get_llm_adapter() adapter: AWSBedrockLLMAdapter = self.get_llm_adapter()
params: AWSBedrockLLMInvocationParams = adapter.get_llm_invocation_params( params: AWSBedrockLLMInvocationParams = adapter.get_llm_invocation_params(
context, system_instruction=effective_instruction context, system_instruction=effective_instruction
@@ -371,7 +373,7 @@ class AWSBedrockLLMService(LLMService):
def _get_llm_invocation_params(self, context: LLMContext) -> AWSBedrockLLMInvocationParams: def _get_llm_invocation_params(self, context: LLMContext) -> AWSBedrockLLMInvocationParams:
adapter: AWSBedrockLLMAdapter = self.get_llm_adapter() adapter: AWSBedrockLLMAdapter = self.get_llm_adapter()
params: AWSBedrockLLMInvocationParams = adapter.get_llm_invocation_params( params: AWSBedrockLLMInvocationParams = adapter.get_llm_invocation_params(
context, system_instruction=self._settings.system_instruction context, system_instruction=assert_given(self._settings.system_instruction)
) )
return params return params

View File

@@ -51,7 +51,7 @@ from pipecat.frames.frames import (
from pipecat.processors.aggregators.llm_context import LLMContext from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.llm_service import LLMService from pipecat.services.llm_service import LLMService
from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven, assert_given
from pipecat.utils.time import time_now_iso8601 from pipecat.utils.time import time_now_iso8601
try: try:
@@ -559,7 +559,9 @@ class AWSNovaSonicLLMService(LLMService):
# Start the bidirectional stream # Start the bidirectional stream
self._stream = await self._client.invoke_model_with_bidirectional_stream( self._stream = await self._client.invoke_model_with_bidirectional_stream(
InvokeModelWithBidirectionalStreamOperationInput(model_id=self._settings.model) InvokeModelWithBidirectionalStreamOperationInput(
model_id=assert_given(self._settings.model)
)
) )
# Send session start event # Send session start event
@@ -598,7 +600,7 @@ class AWSNovaSonicLLMService(LLMService):
# Read context # Read context
adapter: AWSNovaSonicLLMAdapter = self.get_llm_adapter() adapter: AWSNovaSonicLLMAdapter = self.get_llm_adapter()
llm_connection_params = adapter.get_llm_invocation_params( llm_connection_params = adapter.get_llm_invocation_params(
self._context, system_instruction=self._settings.system_instruction self._context, system_instruction=assert_given(self._settings.system_instruction)
) )
# Send prompt start event, specifying tools. # Send prompt start event, specifying tools.

View File

@@ -30,7 +30,7 @@ from pipecat.frames.frames import (
TranscriptionFrame, TranscriptionFrame,
) )
from pipecat.services.aws.utils import build_event_message, decode_event, get_presigned_url from pipecat.services.aws.utils import build_event_message, decode_event, get_presigned_url
from pipecat.services.settings import STTSettings from pipecat.services.settings import STTSettings, assert_given
from pipecat.services.stt_latency import AWS_TRANSCRIBE_TTFS_P99 from pipecat.services.stt_latency import AWS_TRANSCRIBE_TTFS_P99
from pipecat.services.stt_service import WebsocketSTTService from pipecat.services.stt_service import WebsocketSTTService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
@@ -260,7 +260,7 @@ class AWSTranscribeSTTService(WebsocketSTTService):
logger.debug("Connecting to AWS Transcribe WebSocket") logger.debug("Connecting to AWS Transcribe WebSocket")
language_code = self._settings.language language_code = assert_given(self._settings.language)
if not language_code: if not language_code:
raise ValueError(f"Unsupported language: {language_code}") raise ValueError(f"Unsupported language: {language_code}")
@@ -534,20 +534,21 @@ class AWSTranscribeSTTService(WebsocketSTTService):
is_final = not result.get("IsPartial", True) is_final = not result.get("IsPartial", True)
if transcript: if transcript:
language = assert_given(self._settings.language)
if is_final: if is_final:
await self.push_frame( await self.push_frame(
TranscriptionFrame( TranscriptionFrame(
transcript, transcript,
self._user_id, self._user_id,
time_now_iso8601(), time_now_iso8601(),
self._settings.language, language,
result=result, result=result,
) )
) )
await self._handle_transcription( await self._handle_transcription(
transcript, transcript,
is_final, is_final,
self._settings.language, language,
) )
await self.stop_processing_metrics() await self.stop_processing_metrics()
else: else:
@@ -556,7 +557,7 @@ class AWSTranscribeSTTService(WebsocketSTTService):
transcript, transcript,
self._user_id, self._user_id,
time_now_iso8601(), time_now_iso8601(),
self._settings.language, language,
result=result, result=result,
) )
) )

View File

@@ -27,7 +27,7 @@ from pipecat.frames.frames import (
TranscriptionFrame, TranscriptionFrame,
) )
from pipecat.services.azure.common import language_to_azure_language from pipecat.services.azure.common import language_to_azure_language
from pipecat.services.settings import STTSettings from pipecat.services.settings import STTSettings, assert_given
from pipecat.services.stt_latency import AZURE_TTFS_P99 from pipecat.services.stt_latency import AZURE_TTFS_P99
from pipecat.services.stt_service import STTService from pipecat.services.stt_service import STTService
from pipecat.transcriptions.language import Language from pipecat.transcriptions.language import Language
@@ -128,9 +128,9 @@ class AzureSTTService(STTService):
**kwargs, **kwargs,
) )
recognition_language = default_settings.language or language_to_azure_language( recognition_language = assert_given(
Language.EN_US default_settings.language
) ) or language_to_azure_language(Language.EN_US)
if not region and not private_endpoint: if not region and not private_endpoint:
raise ValueError("Either 'region' or 'private_endpoint' must be provided.") raise ValueError("Either 'region' or 'private_endpoint' must be provided.")
@@ -280,7 +280,9 @@ class AzureSTTService(STTService):
def _on_handle_recognized(self, event): def _on_handle_recognized(self, event):
if event.result.reason == ResultReason.RecognizedSpeech and len(event.result.text) > 0: if event.result.reason == ResultReason.RecognizedSpeech and len(event.result.text) > 0:
language = getattr(event.result, "language", None) or self._settings.language language = getattr(event.result, "language", None) or assert_given(
self._settings.language
)
frame = TranscriptionFrame( frame = TranscriptionFrame(
event.result.text, event.result.text,
self._user_id, self._user_id,
@@ -295,7 +297,9 @@ class AzureSTTService(STTService):
def _on_handle_recognizing(self, event): def _on_handle_recognizing(self, event):
if event.result.reason == ResultReason.RecognizingSpeech and len(event.result.text) > 0: if event.result.reason == ResultReason.RecognizingSpeech and len(event.result.text) > 0:
language = getattr(event.result, "language", None) or self._settings.language language = getattr(event.result, "language", None) or assert_given(
self._settings.language
)
frame = InterimTranscriptionFrame( frame = InterimTranscriptionFrame(
event.result.text, event.result.text,
self._user_id, self._user_id,

View File

@@ -25,7 +25,7 @@ from pipecat.frames.frames import (
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.azure.common import language_to_azure_language from pipecat.services.azure.common import language_to_azure_language
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given
from pipecat.services.tts_service import TextAggregationMode, TTSService from pipecat.services.tts_service import TextAggregationMode, TTSService
from pipecat.transcriptions.language import Language from pipecat.transcriptions.language import Language
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -428,7 +428,7 @@ class AzureTTSService(TTSService, AzureBaseTTSService):
Returns: Returns:
True if the language is CJK, False otherwise. True if the language is CJK, False otherwise.
""" """
language = (self._settings.language if self._settings.language else "").lower() language = (assert_given(self._settings.language) or "").lower()
# Check if language starts with CJK language codes # Check if language starts with CJK language codes
return language.startswith(("zh", "ja", "ko", "cmn", "yue", "wuu")) return language.startswith(("zh", "ja", "ko", "cmn", "yue", "wuu"))

View File

@@ -31,7 +31,7 @@ from pipecat.frames.frames import (
StartFrame, StartFrame,
TTSAudioRawFrame, TTSAudioRawFrame,
) )
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -270,8 +270,8 @@ class CambTTSService(TTSService):
default_settings.apply_update(settings) default_settings.apply_update(settings)
# Warn if sample rate doesn't match model's supported rate # Warn if sample rate doesn't match model's supported rate
_model = default_settings.model _model = assert_given(default_settings.model)
if sample_rate and sample_rate != MODEL_SAMPLE_RATES.get(_model): if sample_rate and _model is not None and sample_rate != MODEL_SAMPLE_RATES.get(_model):
logger.warning( logger.warning(
f"Camb.ai's {_model} model only supports {MODEL_SAMPLE_RATES.get(_model)}Hz " f"Camb.ai's {_model} model only supports {MODEL_SAMPLE_RATES.get(_model)}Hz "
f"sample rate. Current rate of {sample_rate}Hz may cause issues." f"sample rate. Current rate of {sample_rate}Hz may cause issues."
@@ -321,7 +321,8 @@ 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._settings.model, 22050) model = assert_given(self._settings.model)
self._sample_rate = MODEL_SAMPLE_RATES.get(model, 22050) if model is not None else 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]:

View File

@@ -26,7 +26,7 @@ from pipecat.frames.frames import (
TTSAudioRawFrame, TTSAudioRawFrame,
TTSStoppedFrame, TTSStoppedFrame,
) )
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given
from pipecat.services.tts_service import TextAggregationMode, TTSService, WebsocketTTSService from pipecat.services.tts_service import TextAggregationMode, TTSService, WebsocketTTSService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.text.skip_tags_aggregator import SkipTagsAggregator from pipecat.utils.text.skip_tags_aggregator import SkipTagsAggregator
@@ -427,7 +427,7 @@ class CartesiaTTSService(WebsocketTTSService):
Returns: Returns:
List of (word, start_time) tuples processed for the language. List of (word, start_time) tuples processed for the language.
""" """
current_language = self._settings.language current_language = assert_given(self._settings.language)
# Check if this is a CJK language (if language is None, treat as non-CJK) # Check if this is a CJK language (if language is None, treat as non-CJK)
if current_language and self._is_cjk_language(current_language): if current_language and self._is_cjk_language(current_language):
@@ -472,10 +472,9 @@ class CartesiaTTSService(WebsocketTTSService):
if self._settings.language: if self._settings.language:
msg["language"] = self._settings.language msg["language"] = self._settings.language
if self._settings.generation_config: generation_config = assert_given(self._settings.generation_config)
msg["generation_config"] = self._settings.generation_config.model_dump( if generation_config:
exclude_none=True msg["generation_config"] = generation_config.model_dump(exclude_none=True)
)
if self._settings.pronunciation_dict_id: if self._settings.pronunciation_dict_id:
msg["pronunciation_dict_id"] = self._settings.pronunciation_dict_id msg["pronunciation_dict_id"] = self._settings.pronunciation_dict_id
@@ -904,10 +903,9 @@ class CartesiaHttpTTSService(TTSService):
if self._settings.language: if self._settings.language:
payload["language"] = self._settings.language payload["language"] = self._settings.language
if self._settings.generation_config: generation_config = assert_given(self._settings.generation_config)
payload["generation_config"] = self._settings.generation_config.model_dump( if generation_config:
exclude_none=True payload["generation_config"] = generation_config.model_dump(exclude_none=True)
)
if self._settings.pronunciation_dict_id: if self._settings.pronunciation_dict_id:
payload["pronunciation_dict_id"] = self._settings.pronunciation_dict_id payload["pronunciation_dict_id"] = self._settings.pronunciation_dict_id

View File

@@ -25,7 +25,7 @@ from pipecat.frames.frames import (
UserStartedSpeakingFrame, UserStartedSpeakingFrame,
UserStoppedSpeakingFrame, UserStoppedSpeakingFrame,
) )
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, assert_given
from pipecat.services.stt_service import STTService from pipecat.services.stt_service import STTService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.time import time_now_iso8601 from pipecat.utils.time import time_now_iso8601
@@ -647,7 +647,8 @@ class DeepgramFluxSTTBase(STTService):
average_confidence = self._calculate_average_confidence(data) average_confidence = self._calculate_average_confidence(data)
detected_language = self._primary_detected_language(data) detected_language = self._primary_detected_language(data)
if not self._settings.min_confidence or average_confidence > self._settings.min_confidence: min_confidence = assert_given(self._settings.min_confidence)
if not min_confidence or average_confidence > min_confidence:
# EndOfTurn means Flux has determined the turn is complete, # EndOfTurn means Flux has determined the turn is complete,
# so this TranscriptionFrame is always finalized # so this TranscriptionFrame is always finalized
await self.push_frame( await self.push_frame(

View File

@@ -39,7 +39,7 @@ from pipecat.frames.frames import (
TTSStoppedFrame, TTSStoppedFrame,
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given
from pipecat.services.tts_service import ( from pipecat.services.tts_service import (
TextAggregationMode, TextAggregationMode,
TTSService, TTSService,
@@ -1259,9 +1259,10 @@ class ElevenLabsHttpTTSService(TTSService):
# Use the with-timestamps endpoint # Use the with-timestamps endpoint
url = f"{self._base_url}/v1/text-to-speech/{self._settings.voice}/stream/with-timestamps" url = f"{self._base_url}/v1/text-to-speech/{self._settings.voice}/stream/with-timestamps"
model_id = assert_given(self._settings.model)
payload: dict[str, str | dict[str, float | bool]] = { payload: dict[str, str | dict[str, float | bool]] = {
"text": text, "text": text,
"model_id": self._settings.model, "model_id": model_id,
} }
# Include previous text as context if available # Include previous text as context if available
@@ -1276,11 +1277,12 @@ class ElevenLabsHttpTTSService(TTSService):
locator.model_dump() for locator in self._pronunciation_dictionary_locators locator.model_dump() for locator in self._pronunciation_dictionary_locators
] ]
if self._settings.apply_text_normalization is not None: apply_text_normalization = assert_given(self._settings.apply_text_normalization)
payload["apply_text_normalization"] = self._settings.apply_text_normalization if apply_text_normalization is not None:
payload["apply_text_normalization"] = apply_text_normalization
language = self._settings.language language = assert_given(self._settings.language)
if self._settings.model in ELEVENLABS_MULTILINGUAL_MODELS and language: if model_id 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:
@@ -1297,8 +1299,9 @@ class ElevenLabsHttpTTSService(TTSService):
params = { params = {
"output_format": self._output_format, "output_format": self._output_format,
} }
if self._settings.optimize_streaming_latency is not None: optimize_streaming_latency = assert_given(self._settings.optimize_streaming_latency)
params["optimize_streaming_latency"] = self._settings.optimize_streaming_latency if optimize_streaming_latency is not None:
params["optimize_streaming_latency"] = str(optimize_streaming_latency)
if self._enable_logging is not None: if self._enable_logging is not None:
params["enable_logging"] = str(self._enable_logging).lower() params["enable_logging"] = str(self._enable_logging).lower()

View File

@@ -26,7 +26,7 @@ from pipecat.frames.frames import (
TTSAudioRawFrame, TTSAudioRawFrame,
TTSStoppedFrame, TTSStoppedFrame,
) )
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given
from pipecat.services.tts_service import InterruptibleTTSService from pipecat.services.tts_service import InterruptibleTTSService
from pipecat.transcriptions.language import Language from pipecat.transcriptions.language import Language
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -278,7 +278,9 @@ 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._settings.model model = assert_given(self._settings.model)
if model is not None:
headers["model"] = 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

@@ -39,7 +39,7 @@ from pipecat.services.gladia.config import (
PreProcessingConfig, PreProcessingConfig,
RealtimeProcessingConfig, RealtimeProcessingConfig,
) )
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, assert_given
from pipecat.services.stt_latency import GLADIA_TTFS_P99 from pipecat.services.stt_latency import GLADIA_TTFS_P99
from pipecat.services.stt_service import WebsocketSTTService from pipecat.services.stt_service import WebsocketSTTService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
@@ -377,7 +377,7 @@ class GladiaSTTService(WebsocketSTTService):
} }
# Add custom_metadata if provided # Add custom_metadata if provided
settings["custom_metadata"] = dict(s.custom_metadata or {}) settings["custom_metadata"] = dict(assert_given(s.custom_metadata) or {})
settings["custom_metadata"]["pipecat"] = pipecat_version() settings["custom_metadata"]["pipecat"] = pipecat_version()
# Add endpointing parameters if provided # Add endpointing parameters if provided
@@ -389,20 +389,24 @@ class GladiaSTTService(WebsocketSTTService):
) )
# Add language configuration # Add language configuration
if s.language_config: language_config = assert_given(s.language_config)
settings["language_config"] = s.language_config.model_dump(exclude_none=True) if language_config:
settings["language_config"] = language_config.model_dump(exclude_none=True)
# Add pre_processing configuration if provided # Add pre_processing configuration if provided
if s.pre_processing: pre_processing = assert_given(s.pre_processing)
settings["pre_processing"] = s.pre_processing.model_dump(exclude_none=True) if pre_processing:
settings["pre_processing"] = pre_processing.model_dump(exclude_none=True)
# Add realtime_processing configuration if provided # Add realtime_processing configuration if provided
if s.realtime_processing: realtime_processing = assert_given(s.realtime_processing)
settings["realtime_processing"] = s.realtime_processing.model_dump(exclude_none=True) if realtime_processing:
settings["realtime_processing"] = realtime_processing.model_dump(exclude_none=True)
# Add messages_config if provided # Add messages_config if provided
if s.messages_config: messages_config = assert_given(s.messages_config)
settings["messages_config"] = s.messages_config.model_dump(exclude_none=True) if messages_config:
settings["messages_config"] = messages_config.model_dump(exclude_none=True)
return settings return settings

View File

@@ -61,7 +61,7 @@ from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.google.frames import LLMSearchOrigin, LLMSearchResponseFrame, LLMSearchResult from pipecat.services.google.frames import LLMSearchOrigin, LLMSearchResponseFrame, LLMSearchResult
from pipecat.services.google.utils import update_google_client_http_options from pipecat.services.google.utils import update_google_client_http_options
from pipecat.services.llm_service import FunctionCallFromLLM, LLMService from pipecat.services.llm_service import FunctionCallFromLLM, LLMService
from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven, assert_given
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.string import match_endofsentence from pipecat.utils.string import match_endofsentence
from pipecat.utils.time import time_now_iso8601 from pipecat.utils.time import time_now_iso8601
@@ -368,7 +368,7 @@ class GeminiLiveLLMService(LLMService):
@property @property
def _is_gemini_3(self) -> bool: def _is_gemini_3(self) -> bool:
"""Check if the current model is a Gemini 3.x model.""" """Check if the current model is a Gemini 3.x model."""
return "gemini-3" in (self._settings.model or "") return "gemini-3" in (assert_given(self._settings.model) or "")
def __init__( def __init__(
self, self,
@@ -482,9 +482,10 @@ class GeminiLiveLLMService(LLMService):
default_settings.apply_update(settings) default_settings.apply_update(settings)
# Warn if user requested TEXT modality # Warn if user requested TEXT modality
if default_settings.modalities == GeminiModalities.TEXT: default_modalities = assert_given(default_settings.modalities)
if default_modalities == GeminiModalities.TEXT:
logger.warning( logger.warning(
f"Modality {default_settings.modalities.value!r} may not be supported by recent " f"Modality {default_modalities.value!r} may not be supported by recent "
"Gemini Live models." "Gemini Live models."
) )
@@ -524,13 +525,12 @@ class GeminiLiveLLMService(LLMService):
self._sample_rate = 24000 self._sample_rate = 24000
self._language = self._settings.language self._language = assert_given(self._settings.language)
self._language_code = ( self._language_code = (
language_to_gemini_language(self._settings.language) language_to_gemini_language(self._language) if self._language else "en-US"
if self._settings.language
else "en-US"
) )
self._vad_disabled = bool(self._settings.vad and self._settings.vad.disabled) vad_settings = assert_given(self._settings.vad)
self._vad_disabled = bool(vad_settings and vad_settings.disabled)
# Reconnection tracking # Reconnection tracking
self._consecutive_failures = 0 self._consecutive_failures = 0
@@ -780,7 +780,7 @@ class GeminiLiveLLMService(LLMService):
# chooses the init-provided value if there is one. # chooses the init-provided value if there is one.
adapter: GeminiLLMAdapter = self.get_llm_adapter() adapter: GeminiLLMAdapter = self.get_llm_adapter()
params = adapter.get_llm_invocation_params( params = adapter.get_llm_invocation_params(
self._context, system_instruction=self._system_instruction_from_init self._context, system_instruction=assert_given(self._system_instruction_from_init)
) )
system_instruction = params["system_instruction"] system_instruction = params["system_instruction"]
tools = params["tools"] tools = params["tools"]
@@ -812,7 +812,10 @@ class GeminiLiveLLMService(LLMService):
"No messages found in initial context; seeding with system instruction to trigger bot response." "No messages found in initial context; seeding with system instruction to trigger bot response."
) )
self._context.add_message( self._context.add_message(
{"role": "system", "content": self._system_instruction_from_init} {
"role": "system",
"content": assert_given(self._system_instruction_from_init),
}
) )
else: else:
logger.warning( logger.warning(
@@ -931,22 +934,25 @@ class GeminiLiveLLMService(LLMService):
logger.info("Connecting to Gemini service") logger.info("Connecting to Gemini service")
try: try:
# Assemble basic configuration # Assemble basic configuration
modalities = assert_given(self._settings.modalities)
media_resolution = assert_given(self._settings.media_resolution)
language = assert_given(self._settings.language)
config = LiveConnectConfig( config = LiveConnectConfig(
generation_config=GenerationConfig( generation_config=GenerationConfig(
frequency_penalty=self._settings.frequency_penalty, frequency_penalty=assert_given(self._settings.frequency_penalty),
max_output_tokens=self._settings.max_tokens, max_output_tokens=assert_given(self._settings.max_tokens),
presence_penalty=self._settings.presence_penalty, presence_penalty=assert_given(self._settings.presence_penalty),
temperature=self._settings.temperature, temperature=assert_given(self._settings.temperature),
top_k=self._settings.top_k, top_k=assert_given(self._settings.top_k),
top_p=self._settings.top_p, top_p=assert_given(self._settings.top_p),
response_modalities=[Modality(self._settings.modalities.value)], response_modalities=[Modality(modalities.value)],
speech_config=SpeechConfig( speech_config=SpeechConfig(
voice_config=VoiceConfig( voice_config=VoiceConfig(
prebuilt_voice_config={"voice_name": self._settings.voice} prebuilt_voice_config={"voice_name": assert_given(self._settings.voice)}
), ),
language_code=self._settings.language, language_code=language,
), ),
media_resolution=MediaResolution(self._settings.media_resolution.value), media_resolution=MediaResolution(media_resolution.value),
), ),
input_audio_transcription=AudioTranscriptionConfig(), input_audio_transcription=AudioTranscriptionConfig(),
output_audio_transcription=AudioTranscriptionConfig(), output_audio_transcription=AudioTranscriptionConfig(),
@@ -959,7 +965,7 @@ class GeminiLiveLLMService(LLMService):
config.history_config = history_config config.history_config = history_config
# Add context window compression to configuration, if enabled # Add context window compression to configuration, if enabled
cwc = self._settings.context_window_compression or {} cwc = assert_given(self._settings.context_window_compression) or {}
if cwc.get("enabled", False): if cwc.get("enabled", False):
compression_config = ContextWindowCompressionConfig() compression_config = ContextWindowCompressionConfig()
@@ -986,9 +992,9 @@ class GeminiLiveLLMService(LLMService):
config.proactivity = self._settings.proactivity config.proactivity = self._settings.proactivity
# Add VAD configuration to configuration, if provided # Add VAD configuration to configuration, if provided
if self._settings.vad: vad_params = assert_given(self._settings.vad)
if vad_params:
vad_config = AutomaticActivityDetection() vad_config = AutomaticActivityDetection()
vad_params = self._settings.vad
has_vad_settings = False has_vad_settings = False
# Only add parameters that are explicitly set # Only add parameters that are explicitly set
@@ -1026,7 +1032,8 @@ class GeminiLiveLLMService(LLMService):
tools = None tools = None
if self._context: if self._context:
params = adapter.get_llm_invocation_params( params = adapter.get_llm_invocation_params(
self._context, system_instruction=self._system_instruction_from_init self._context,
system_instruction=assert_given(self._system_instruction_from_init),
) )
system_instruction = params["system_instruction"] system_instruction = params["system_instruction"]
tools = params["tools"] tools = params["tools"]
@@ -1048,9 +1055,10 @@ 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 = assert_given(self._settings.model)
model=self._settings.model, config=config if model is None:
) as session: raise ValueError("Gemini Live model must be specified")
async with self._client.aio.live.connect(model=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

@@ -27,7 +27,7 @@ from pydantic import BaseModel, Field
from pipecat.frames.frames import ErrorFrame, Frame, URLImageRawFrame from pipecat.frames.frames import ErrorFrame, Frame, URLImageRawFrame
from pipecat.services.google.utils import update_google_client_http_options from pipecat.services.google.utils import update_google_client_http_options
from pipecat.services.image_service import ImageGenService from pipecat.services.image_service import ImageGenService
from pipecat.services.settings import NOT_GIVEN, ImageGenSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, ImageGenSettings, _NotGiven, assert_given
try: try:
from google import genai from google import genai
@@ -157,8 +157,8 @@ class GoogleImageGenService(ImageGenService):
model=self._settings.model, model=self._settings.model,
prompt=prompt, prompt=prompt,
config=types.GenerateImagesConfig( config=types.GenerateImagesConfig(
number_of_images=self._settings.number_of_images, number_of_images=assert_given(self._settings.number_of_images),
negative_prompt=self._settings.negative_prompt, negative_prompt=assert_given(self._settings.negative_prompt),
), ),
) )
await self.stop_ttfb_metrics() await self.stop_ttfb_metrics()

View File

@@ -43,6 +43,7 @@ from pipecat.services.settings import (
NOT_GIVEN, NOT_GIVEN,
LLMSettings, LLMSettings,
_NotGiven, _NotGiven,
assert_given,
is_given, is_given,
) )
from pipecat.utils.tracing.service_decorators import traced_llm from pipecat.utils.tracing.service_decorators import traced_llm
@@ -356,10 +357,9 @@ class GoogleLLMService(LLMService):
} }
# Add thinking parameters if configured # Add thinking parameters if configured
if self._settings.thinking: thinking = assert_given(self._settings.thinking)
generation_params["thinking_config"] = self._settings.thinking.model_dump( if thinking:
exclude_unset=True generation_params["thinking_config"] = thinking.model_dump(exclude_unset=True)
)
if self._settings.extra: if self._settings.extra:
generation_params.update(self._settings.extra) generation_params.update(self._settings.extra)
@@ -368,8 +368,9 @@ class GoogleLLMService(LLMService):
def _maybe_unset_thinking_budget(self, generation_params: dict[str, Any]): def _maybe_unset_thinking_budget(self, generation_params: dict[str, Any]):
try: try:
model = assert_given(self._settings.model)
# If we have an image model, we don't apply a thinking default. # If we have an image model, we don't apply a thinking default.
if "image" in self._settings.model: if model is None or "image" in 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:
@@ -377,7 +378,6 @@ class GoogleLLMService(LLMService):
# Apply model-aware low-latency thinking defaults. # Apply model-aware low-latency thinking defaults.
# Gemini 2.5 Flash: disable thinking via thinking_budget. # Gemini 2.5 Flash: disable thinking via thinking_budget.
# Gemini 3+ Flash: use minimal thinking via thinking_level. # Gemini 3+ Flash: use minimal thinking via thinking_level.
model = self._settings.model
if model.startswith("gemini-2.5-flash"): if model.startswith("gemini-2.5-flash"):
generation_params["thinking_config"] = {"thinking_budget": 0} generation_params["thinking_config"] = {"thinking_budget": 0}
elif model.startswith("gemini-3") and "flash" in model: elif model.startswith("gemini-3") and "flash" in model:
@@ -388,7 +388,7 @@ class GoogleLLMService(LLMService):
async def _stream_content(self, context: LLMContext) -> AsyncIterator[GenerateContentResponse]: async def _stream_content(self, context: LLMContext) -> AsyncIterator[GenerateContentResponse]:
adapter = self.get_llm_adapter() adapter = self.get_llm_adapter()
params: GeminiLLMInvocationParams = adapter.get_llm_invocation_params( params: GeminiLLMInvocationParams = adapter.get_llm_invocation_params(
context, system_instruction=self._settings.system_instruction context, system_instruction=assert_given(self._settings.system_instruction)
) )
logger.debug( logger.debug(

View File

@@ -37,7 +37,7 @@ from pipecat.frames.frames import (
StartFrame, StartFrame,
TranscriptionFrame, TranscriptionFrame,
) )
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, assert_given
from pipecat.services.stt_latency import GOOGLE_TTFS_P99 from pipecat.services.stt_latency import GOOGLE_TTFS_P99
from pipecat.services.stt_service import STTService from pipecat.services.stt_service import STTService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
@@ -641,8 +641,9 @@ class GoogleSTTService(STTService):
""" """
if self._settings.languages: if self._settings.languages:
return [self.language_to_service_language(lang) for lang in self._settings.languages] return [self.language_to_service_language(lang) for lang in self._settings.languages]
if self._settings.language_codes: language_codes = assert_given(self._settings.language_codes)
return list(self._settings.language_codes) if language_codes:
return list(language_codes)
return ["en-US"] return ["en-US"]
async def _reconnect_if_needed(self): async def _reconnect_if_needed(self):

View File

@@ -39,6 +39,7 @@ from pipecat.services.settings import (
NOT_GIVEN, NOT_GIVEN,
TTSSettings, TTSSettings,
_NotGiven, _NotGiven,
assert_given,
is_given, is_given,
) )
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
@@ -815,8 +816,9 @@ class GoogleHttpTTSService(TTSService):
try: try:
# Check if the voice is a Chirp voice (including Chirp 3) or Journey voice # Check if the voice is a Chirp voice (including Chirp 3) or Journey voice
is_chirp_voice = "chirp" in self._settings.voice.lower() voice_name = assert_given(self._settings.voice)
is_journey_voice = "journey" in self._settings.voice.lower() is_chirp_voice = "chirp" in (voice_name or "").lower()
is_journey_voice = "journey" in (voice_name or "").lower()
# Create synthesis input based on voice_id # Create synthesis input based on voice_id
if is_chirp_voice or is_journey_voice: if is_chirp_voice or is_journey_voice:
@@ -1447,7 +1449,7 @@ class GeminiTTSService(GoogleBaseTTSService):
# Use base class streaming logic with prompt support # Use base class streaming logic with prompt support
async for frame in self._stream_tts( async for frame in self._stream_tts(
streaming_config, text, context_id, self._settings.prompt streaming_config, text, context_id, assert_given(self._settings.prompt)
): ):
yield frame yield frame

View File

@@ -31,7 +31,7 @@ from pipecat.frames.frames import (
VADUserStoppedSpeakingFrame, VADUserStoppedSpeakingFrame,
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, assert_given
from pipecat.services.stt_latency import GRADIUM_TTFS_P99 from pipecat.services.stt_latency import GRADIUM_TTFS_P99
from pipecat.services.stt_service import WebsocketSTTService from pipecat.services.stt_service import WebsocketSTTService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
@@ -397,8 +397,9 @@ class GradiumSTTService(WebsocketSTTService):
json_config = {} json_config = {}
if self._json_config: if self._json_config:
json_config = json.loads(self._json_config) json_config = json.loads(self._json_config)
if self._settings.language: language = assert_given(self._settings.language)
gradium_language = language_to_gradium_language(self._settings.language) if language:
gradium_language = language_to_gradium_language(language)
if gradium_language: if gradium_language:
json_config["language"] = gradium_language json_config["language"] = gradium_language
if self._settings.delay_in_frames: if self._settings.delay_in_frames:
@@ -482,7 +483,7 @@ class GradiumSTTService(WebsocketSTTService):
text=accumulated, text=accumulated,
user_id=self._user_id, user_id=self._user_id,
timestamp=time_now_iso8601(), timestamp=time_now_iso8601(),
language=self._settings.language, language=assert_given(self._settings.language),
) )
) )
await self.stop_processing_metrics() await self.stop_processing_metrics()
@@ -514,12 +515,13 @@ class GradiumSTTService(WebsocketSTTService):
text = " ".join(self._accumulated_text) text = " ".join(self._accumulated_text)
self._accumulated_text.clear() self._accumulated_text.clear()
logger.debug(f"Final transcription: [{text}]") logger.debug(f"Final transcription: [{text}]")
language = assert_given(self._settings.language)
await self.push_frame( await self.push_frame(
TranscriptionFrame( TranscriptionFrame(
text, text,
self._user_id, self._user_id,
time_now_iso8601(), time_now_iso8601(),
self._settings.language, language,
) )
) )
await self._trace_transcription(text, is_final=True, language=self._settings.language) await self._trace_transcription(text, is_final=True, language=language)

View File

@@ -19,7 +19,7 @@ from pipecat.frames.frames import (
Frame, Frame,
TTSAudioRawFrame, TTSAudioRawFrame,
) )
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
from pipecat.transcriptions.language import Language from pipecat.transcriptions.language import Language
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -173,13 +173,20 @@ class GroqTTSService(TTSService):
logger.debug(f"{self}: Generating TTS [{text}]") logger.debug(f"{self}: Generating TTS [{text}]")
measuring_ttfb = True measuring_ttfb = True
try: try:
model = assert_given(self._settings.model)
voice = assert_given(self._settings.voice)
speed = assert_given(self._settings.speed)
if model is None:
raise ValueError("Groq TTS model must be specified")
if speed is None:
raise ValueError("Groq TTS speed must be specified")
response = await self._client.audio.speech.create( response = await self._client.audio.speech.create(
model=self._settings.model, model=model,
voice=self._settings.voice, voice=voice,
response_format=self._output_format, response_format=self._output_format,
# Note: as of 2026-02-25, only a speed of 1.0 is supported, but # Note: as of 2026-02-25, only a speed of 1.0 is supported, but
# here we pass it for completeness and future-proofing # here we pass it for completeness and future-proofing
speed=self._settings.speed, speed=speed,
input=text, input=text,
) )

View File

@@ -26,7 +26,7 @@ from pipecat.frames.frames import (
TTSStoppedFrame, TTSStoppedFrame,
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -292,7 +292,7 @@ class HumeTTSService(TTSService):
# Build the request payload # Build the request payload
utterance_kwargs: dict[str, Any] = { utterance_kwargs: dict[str, Any] = {
"text": text, "text": text,
"voice": PostedUtteranceVoiceWithId(id=self._settings.voice), "voice": PostedUtteranceVoiceWithId(id=assert_given(self._settings.voice)),
} }
if self._settings.description is not None: if self._settings.description is not None:
utterance_kwargs["description"] = self._settings.description utterance_kwargs["description"] = self._settings.description

View File

@@ -55,6 +55,7 @@ from pipecat.services.settings import (
NOT_GIVEN, NOT_GIVEN,
LLMSettings, LLMSettings,
_NotGiven, _NotGiven,
assert_given,
is_given, is_given,
) )
from pipecat.utils.time import time_now_iso8601 from pipecat.utils.time import time_now_iso8601
@@ -385,13 +386,14 @@ class InworldRealtimeLLMService(LLMService):
Returns: Returns:
Configured sample rate or None if not manually configured. Configured sample rate or None if not manually configured.
""" """
if not self._settings.session_properties.audio: session_properties = assert_given(self._settings.session_properties)
if not session_properties.audio:
return None return None
audio_config = ( audio_config = (
self._settings.session_properties.audio.input session_properties.audio.input
if direction == "input" if direction == "input"
else self._settings.session_properties.audio.output else session_properties.audio.output
) )
if audio_config and audio_config.format: if audio_config and audio_config.format:
@@ -463,7 +465,7 @@ class InworldRealtimeLLMService(LLMService):
""" """
self._input_sample_rate = input_sample_rate self._input_sample_rate = input_sample_rate
self._output_sample_rate = output_sample_rate self._output_sample_rate = output_sample_rate
props = self._settings.session_properties props = assert_given(self._settings.session_properties)
if not props.audio: if not props.audio:
props.audio = events.AudioConfiguration() props.audio = events.AudioConfiguration()
if not props.audio.input: if not props.audio.input:
@@ -661,12 +663,13 @@ class InworldRealtimeLLMService(LLMService):
async def _send_session_update(self): async def _send_session_update(self):
"""Update session settings on the server.""" """Update session settings on the server."""
settings = self._settings.session_properties settings = assert_given(self._settings.session_properties)
adapter: InworldRealtimeLLMAdapter = self.get_llm_adapter() adapter: InworldRealtimeLLMAdapter = self.get_llm_adapter()
if self._context: if self._context:
llm_invocation_params = adapter.get_llm_invocation_params( llm_invocation_params = adapter.get_llm_invocation_params(
self._context, system_instruction=self._settings.system_instruction self._context,
system_instruction=assert_given(self._settings.system_instruction),
) )
if llm_invocation_params["tools"]: if llm_invocation_params["tools"]:
@@ -969,7 +972,8 @@ class InworldRealtimeLLMService(LLMService):
) )
llm_invocation_params = adapter.get_llm_invocation_params( llm_invocation_params = adapter.get_llm_invocation_params(
self._context, system_instruction=self._settings.system_instruction self._context,
system_instruction=assert_given(self._settings.system_instruction),
) )
messages = llm_invocation_params["messages"] messages = llm_invocation_params["messages"]
@@ -986,7 +990,10 @@ class InworldRealtimeLLMService(LLMService):
await self.start_processing_metrics() await self.start_processing_metrics()
await self.start_ttfb_metrics() await self.start_ttfb_metrics()
modalities = self._settings.session_properties.output_modalities or ["text", "audio"] modalities = assert_given(self._settings.session_properties).output_modalities or [
"text",
"audio",
]
await self.send_client_event( await self.send_client_event(
events.ResponseCreateEvent(response=events.ResponseProperties(modalities=modalities)) events.ResponseCreateEvent(response=events.ResponseProperties(modalities=modalities))
) )

View File

@@ -21,7 +21,7 @@ from pipecat.frames.frames import (
Frame, Frame,
TTSAudioRawFrame, TTSAudioRawFrame,
) )
from pipecat.services.settings import TTSSettings from pipecat.services.settings import TTSSettings, assert_given
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -215,9 +215,11 @@ class KokoroTTSService(TTSService):
try: try:
await self.start_tts_usage_metrics(text) await self.start_tts_usage_metrics(text)
stream = self._kokoro.create_stream( voice = assert_given(self._settings.voice)
text, voice=self._settings.voice, lang=self._settings.language, speed=1.0 lang = assert_given(self._settings.language)
) if lang is None:
raise ValueError("Kokoro TTS language must be specified")
stream = self._kokoro.create_stream(text, voice=voice, lang=lang, speed=1.0)
async for samples, sample_rate in stream: async for samples, sample_rate in stream:
await self.stop_ttfb_metrics() await self.stop_ttfb_metrics()

View File

@@ -53,7 +53,7 @@ from pipecat.processors.aggregators.llm_context import (
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.ai_service import AIService from pipecat.services.ai_service import AIService
from pipecat.services.settings import LLMSettings from pipecat.services.settings import LLMSettings, assert_given
from pipecat.services.websocket_service import WebsocketService from pipecat.services.websocket_service import WebsocketService
from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionLLMServiceMixin from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionLLMServiceMixin
from pipecat.utils.async_tool_cancellation import ( from pipecat.utils.async_tool_cancellation import (
@@ -378,7 +378,9 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService):
self._base_system_instruction = None self._base_system_instruction = None
if "user_turn_completion_config" in changed and self._filter_incomplete_user_turns: if "user_turn_completion_config" in changed and self._filter_incomplete_user_turns:
self.set_user_turn_completion_config(self._settings.user_turn_completion_config) self.set_user_turn_completion_config(
assert_given(self._settings.user_turn_completion_config)
)
self._compose_system_instruction() self._compose_system_instruction()
if ( if (

View File

@@ -27,7 +27,7 @@ from pipecat.frames.frames import (
VADUserStoppedSpeakingFrame, VADUserStoppedSpeakingFrame,
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import STTSettings from pipecat.services.settings import STTSettings, assert_given
from pipecat.services.stt_latency import MISTRAL_TTFS_P99 from pipecat.services.stt_latency import MISTRAL_TTFS_P99
from pipecat.services.stt_service import STTService from pipecat.services.stt_service import STTService
from pipecat.utils.time import time_now_iso8601 from pipecat.utils.time import time_now_iso8601
@@ -214,8 +214,11 @@ class MistralSTTService(STTService):
sample_rate=self.sample_rate, sample_rate=self.sample_rate,
) )
model = assert_given(self._settings.model)
if model is None:
raise ValueError("Mistral STT model must be specified")
self._connection = await self._client.audio.realtime.connect( self._connection = await self._client.audio.realtime.connect(
model=self._settings.model, model=model,
audio_format=audio_format, audio_format=audio_format,
target_streaming_delay_ms=self._target_streaming_delay_ms, target_streaming_delay_ms=self._target_streaming_delay_ms,
) )

View File

@@ -22,7 +22,7 @@ from pipecat.frames.frames import (
Frame, Frame,
TTSAudioRawFrame, TTSAudioRawFrame,
) )
from pipecat.services.settings import TTSSettings from pipecat.services.settings import TTSSettings, assert_given
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -137,8 +137,8 @@ class MistralTTSService(TTSService):
async with await self._client.audio.speech.complete_async( async with await self._client.audio.speech.complete_async(
input=text, input=text,
model=self._settings.model, model=assert_given(self._settings.model),
voice_id=self._settings.voice, voice_id=assert_given(self._settings.voice),
response_format="pcm", response_format="pcm",
stream=True, stream=True,
) as event_stream: ) as event_stream:

View File

@@ -25,7 +25,7 @@ from pipecat.frames.frames import (
VisionFullResponseStartFrame, VisionFullResponseStartFrame,
VisionTextFrame, VisionTextFrame,
) )
from pipecat.services.settings import VisionSettings from pipecat.services.settings import VisionSettings, assert_given
from pipecat.services.vision_service import VisionService from pipecat.services.vision_service import VisionService
try: try:
@@ -127,8 +127,11 @@ class MoondreamService(VisionService):
logger.debug("Loading Moondream model...") logger.debug("Loading Moondream model...")
model_path = assert_given(self._settings.model)
if model_path is None:
raise ValueError("Moondream model must be specified")
self._model = AutoModelForCausalLM.from_pretrained( self._model = AutoModelForCausalLM.from_pretrained(
self._settings.model, model_path,
trust_remote_code=True, trust_remote_code=True,
revision=revision, revision=revision,
device_map={"": device}, device_map={"": device},

View File

@@ -30,7 +30,7 @@ from pipecat.frames.frames import (
StartFrame, StartFrame,
TranscriptionFrame, TranscriptionFrame,
) )
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, assert_given
from pipecat.services.stt_latency import NVIDIA_TTFS_P99 from pipecat.services.stt_latency import NVIDIA_TTFS_P99
from pipecat.services.stt_service import SegmentedSTTService, STTService from pipecat.services.stt_service import SegmentedSTTService, STTService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
@@ -307,8 +307,11 @@ class NvidiaSTTService(STTService):
interim_results=s.interim_results, interim_results=s.interim_results,
) )
if s.boosted_lm_words: boosted_lm_words = assert_given(s.boosted_lm_words)
riva.client.add_word_boosting_to_config(config, s.boosted_lm_words, s.boosted_lm_score) if boosted_lm_words:
riva.client.add_word_boosting_to_config(
config, boosted_lm_words, assert_given(s.boosted_lm_score)
)
riva.client.add_endpoint_parameters_to_config( riva.client.add_endpoint_parameters_to_config(
config, config,
@@ -323,9 +326,10 @@ class NvidiaSTTService(STTService):
if self._custom_configuration: if self._custom_configuration:
riva.client.add_custom_configuration_to_config(config, self._custom_configuration) riva.client.add_custom_configuration_to_config(config, self._custom_configuration)
if s.speaker_diarization: speaker_diarization = assert_given(s.speaker_diarization)
if speaker_diarization:
riva.client.add_speaker_diarization_to_config( riva.client.add_speaker_diarization_to_config(
config, s.speaker_diarization, s.diarization_max_speakers config, speaker_diarization, assert_given(s.diarization_max_speakers)
) )
return config return config
@@ -468,6 +472,7 @@ class NvidiaSTTService(STTService):
transcript = result.alternatives[0].transcript transcript = result.alternatives[0].transcript
if transcript and len(transcript) > 0: if transcript and len(transcript) > 0:
language = assert_given(self._settings.language)
if result.is_final: if result.is_final:
await self.stop_processing_metrics() await self.stop_processing_metrics()
logger.debug(f"Transcription: [{transcript}]") logger.debug(f"Transcription: [{transcript}]")
@@ -476,7 +481,7 @@ class NvidiaSTTService(STTService):
transcript, transcript,
self._user_id, self._user_id,
time_now_iso8601(), time_now_iso8601(),
self._settings.language, language,
result=result, result=result,
finalized=True, finalized=True,
) )
@@ -484,7 +489,7 @@ class NvidiaSTTService(STTService):
await self._handle_transcription( await self._handle_transcription(
transcript=transcript, transcript=transcript,
is_final=result.is_final, is_final=result.is_final,
language=self._settings.language, language=language,
) )
else: else:
await self.push_frame( await self.push_frame(
@@ -492,7 +497,7 @@ class NvidiaSTTService(STTService):
transcript, transcript,
self._user_id, self._user_id,
time_now_iso8601(), time_now_iso8601(),
self._settings.language, language,
result=result, result=result,
) )
) )
@@ -689,7 +694,7 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
def _get_language_code(self) -> str: def _get_language_code(self) -> str:
"""Get the current NVIDIA Nemotron Speech language code string.""" """Get the current NVIDIA Nemotron Speech language code string."""
return self._settings.language or "en-US" return assert_given(self._settings.language) or "en-US"
def _create_recognition_config(self): def _create_recognition_config(self):
"""Create the NVIDIA Nemotron Speech ASR recognition configuration.""" """Create the NVIDIA Nemotron Speech ASR recognition configuration."""
@@ -705,8 +710,11 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
) )
# Add word boosting if specified # Add word boosting if specified
if s.boosted_lm_words: boosted_lm_words = assert_given(s.boosted_lm_words)
riva.client.add_word_boosting_to_config(config, s.boosted_lm_words, s.boosted_lm_score) if boosted_lm_words:
riva.client.add_word_boosting_to_config(
config, boosted_lm_words, assert_given(s.boosted_lm_score)
)
# Add any custom configuration # Add any custom configuration
if self._custom_configuration: if self._custom_configuration:
@@ -796,15 +804,16 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
text = alternatives[0].transcript.strip() text = alternatives[0].transcript.strip()
if text: if text:
logger.debug(f"Transcription: [{text}]") logger.debug(f"Transcription: [{text}]")
language = assert_given(self._settings.language)
yield TranscriptionFrame( yield TranscriptionFrame(
text, text,
self._user_id, self._user_id,
time_now_iso8601(), time_now_iso8601(),
self._settings.language, language,
) )
transcription_found = True transcription_found = True
await self._handle_transcription(text, True, self._settings.language) await self._handle_transcription(text, True, language)
if not transcription_found: if not transcription_found:
logger.debug( logger.debug(

View File

@@ -26,7 +26,7 @@ from pipecat.frames.frames import (
URLImageRawFrame, URLImageRawFrame,
) )
from pipecat.services.image_service import ImageGenService from pipecat.services.image_service import ImageGenService
from pipecat.services.settings import NOT_GIVEN, ImageGenSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, ImageGenSettings, _NotGiven, assert_given
@dataclass @dataclass
@@ -117,7 +117,10 @@ 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._settings.model, n=1, size=self._settings.image_size prompt=prompt,
model=assert_given(self._settings.model),
n=1,
size=assert_given(self._settings.image_size),
) )
image_url = image.data[0].url image_url = image.data[0].url

View File

@@ -55,6 +55,7 @@ from pipecat.services.settings import (
NOT_GIVEN, NOT_GIVEN,
LLMSettings, LLMSettings,
_NotGiven, _NotGiven,
assert_given,
is_given, is_given,
) )
from pipecat.transcriptions.language import Language from pipecat.transcriptions.language import Language
@@ -359,12 +360,18 @@ class OpenAIRealtimeLLMService(LLMService):
def _is_modality_enabled(self, modality: str) -> bool: def _is_modality_enabled(self, modality: str) -> bool:
"""Check if a specific modality is enabled, "text" or "audio".""" """Check if a specific modality is enabled, "text" or "audio"."""
modalities = self._settings.session_properties.output_modalities or ["audio", "text"] modalities = assert_given(self._settings.session_properties).output_modalities or [
"audio",
"text",
]
return modality in modalities return modality in modalities
def _get_enabled_modalities(self) -> list[str]: def _get_enabled_modalities(self) -> list[str]:
"""Get the list of enabled modalities.""" """Get the list of enabled modalities."""
modalities = self._settings.session_properties.output_modalities or ["audio", "text"] modalities = assert_given(self._settings.session_properties).output_modalities or [
"audio",
"text",
]
# API only supports single modality responses: either ["text"] or ["audio"] # API only supports single modality responses: either ["text"] or ["audio"]
if "audio" in modalities: if "audio" in modalities:
return ["audio"] return ["audio"]
@@ -436,10 +443,11 @@ class OpenAIRealtimeLLMService(LLMService):
async def _handle_interruption(self): async def _handle_interruption(self):
# None and False are different. Check for False. None means we're using OpenAI's # None and False are different. Check for False. None means we're using OpenAI's
# built-in turn detection defaults. # built-in turn detection defaults.
session_properties = assert_given(self._settings.session_properties)
turn_detection_disabled = ( turn_detection_disabled = (
self._settings.session_properties.audio session_properties.audio
and self._settings.session_properties.audio.input and session_properties.audio.input
and self._settings.session_properties.audio.input.turn_detection is False and session_properties.audio.input.turn_detection is False
) )
if turn_detection_disabled: if turn_detection_disabled:
await self.send_client_event(events.InputAudioBufferClearEvent()) await self.send_client_event(events.InputAudioBufferClearEvent())
@@ -458,10 +466,11 @@ class OpenAIRealtimeLLMService(LLMService):
async def _handle_user_stopped_speaking(self, frame): async def _handle_user_stopped_speaking(self, frame):
# None and False are different. Check for False. None means we're using OpenAI's # None and False are different. Check for False. None means we're using OpenAI's
# built-in turn detection defaults. # built-in turn detection defaults.
session_properties = assert_given(self._settings.session_properties)
turn_detection_disabled = ( turn_detection_disabled = (
self._settings.session_properties.audio session_properties.audio
and self._settings.session_properties.audio.input and session_properties.audio.input
and self._settings.session_properties.audio.input.turn_detection is False and session_properties.audio.input.turn_detection is False
) )
if turn_detection_disabled: if turn_detection_disabled:
await self.send_client_event(events.InputAudioBufferCommitEvent()) await self.send_client_event(events.InputAudioBufferCommitEvent())
@@ -647,12 +656,13 @@ class OpenAIRealtimeLLMService(LLMService):
return changed return changed
async def _send_session_update(self): async def _send_session_update(self):
settings = self._settings.session_properties settings = assert_given(self._settings.session_properties)
adapter: OpenAIRealtimeLLMAdapter = self.get_llm_adapter() adapter: OpenAIRealtimeLLMAdapter = self.get_llm_adapter()
if self._context: if self._context:
llm_invocation_params = adapter.get_llm_invocation_params( llm_invocation_params = adapter.get_llm_invocation_params(
self._context, system_instruction=self._settings.system_instruction self._context,
system_instruction=assert_given(self._settings.system_instruction),
) )
# tools given in the context override the tools in the session properties # tools given in the context override the tools in the session properties

View File

@@ -49,7 +49,7 @@ from pipecat.services.llm_service import (
WebsocketReconnectedError, WebsocketReconnectedError,
) )
from pipecat.services.settings import NOT_GIVEN as _NOT_GIVEN from pipecat.services.settings import NOT_GIVEN as _NOT_GIVEN
from pipecat.services.settings import LLMSettings, _NotGiven from pipecat.services.settings import LLMSettings, _NotGiven, assert_given
from pipecat.utils.tracing.service_decorators import traced_llm from pipecat.utils.tracing.service_decorators import traced_llm
try: try:
@@ -285,7 +285,9 @@ class _BaseOpenAIResponsesLLMService(LLMService):
The LLM's response as a string, or None if no response is generated. The LLM's response as a string, or None if no response is generated.
""" """
adapter: OpenAIResponsesLLMAdapter = self.get_llm_adapter() adapter: OpenAIResponsesLLMAdapter = self.get_llm_adapter()
effective_instruction = system_instruction or self._settings.system_instruction effective_instruction = system_instruction or assert_given(
self._settings.system_instruction
)
invocation_params = adapter.get_llm_invocation_params( invocation_params = adapter.get_llm_invocation_params(
context, system_instruction=effective_instruction context, system_instruction=effective_instruction
) )
@@ -742,7 +744,7 @@ class OpenAIResponsesLLMService(_BaseOpenAIResponsesLLMService, WebsocketLLMServ
) )
invocation_params = adapter.get_llm_invocation_params( invocation_params = adapter.get_llm_invocation_params(
context, system_instruction=self._settings.system_instruction context, system_instruction=assert_given(self._settings.system_instruction)
) )
full_input = invocation_params["input"] full_input = invocation_params["input"]
@@ -982,7 +984,7 @@ class OpenAIResponsesHttpLLMService(_BaseOpenAIResponsesLLMService):
) )
invocation_params = adapter.get_llm_invocation_params( invocation_params = adapter.get_llm_invocation_params(
context, system_instruction=self._settings.system_instruction context, system_instruction=assert_given(self._settings.system_instruction)
) )
params = self._build_response_params(invocation_params) params = self._build_response_params(invocation_params)

View File

@@ -36,7 +36,7 @@ from pipecat.frames.frames import (
VADUserStoppedSpeakingFrame, VADUserStoppedSpeakingFrame,
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, assert_given
from pipecat.services.stt_latency import OPENAI_REALTIME_TTFS_P99, OPENAI_TTFS_P99 from pipecat.services.stt_latency import OPENAI_REALTIME_TTFS_P99, OPENAI_TTFS_P99
from pipecat.services.stt_service import WebsocketSTTService from pipecat.services.stt_service import WebsocketSTTService
from pipecat.services.whisper.base_stt import ( from pipecat.services.whisper.base_stt import (
@@ -534,9 +534,8 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
"""Send ``session.update`` to configure the transcription session.""" """Send ``session.update`` to configure the transcription session."""
transcription: dict = {"model": self._settings.model} transcription: dict = {"model": self._settings.model}
language_code = ( language = assert_given(self._settings.language)
self._language_to_code(self._settings.language) if self._settings.language else None language_code = self._language_to_code(language) if language else None
)
if language_code: if language_code:
transcription["language"] = language_code transcription["language"] = language_code

View File

@@ -24,7 +24,7 @@ from pipecat.frames.frames import (
StartFrame, StartFrame,
TTSAudioRawFrame, TTSAudioRawFrame,
) )
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -240,7 +240,7 @@ class OpenAITTSService(TTSService):
create_params = { create_params = {
"input": text, "input": text,
"model": self._settings.model, "model": self._settings.model,
"voice": VALID_VOICES[self._settings.voice], "voice": VALID_VOICES[assert_given(self._settings.voice)],
"response_format": "pcm", "response_format": "pcm",
} }

View File

@@ -17,6 +17,7 @@ from loguru import logger
from pipecat.services.openai.base_llm import BaseOpenAILLMService from pipecat.services.openai.base_llm import BaseOpenAILLMService
from pipecat.services.openai.llm import OpenAILLMService from pipecat.services.openai.llm import OpenAILLMService
from pipecat.services.settings import assert_given
@dataclass @dataclass
@@ -105,7 +106,8 @@ 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)
if "gemini" in self._settings.model.lower(): model = assert_given(self._settings.model)
if model is not None and "gemini" in model.lower():
messages = params.get("messages", []) messages = params.get("messages", [])
if not messages: if not messages:
return params return params

View File

@@ -20,7 +20,7 @@ from pipecat.frames.frames import (
Frame, Frame,
TTSStoppedFrame, TTSStoppedFrame,
) )
from pipecat.services.settings import TTSSettings from pipecat.services.settings import TTSSettings, assert_given
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -100,7 +100,7 @@ class PiperTTSService(TTSService):
download_dir = download_dir or Path.cwd() download_dir = download_dir or Path.cwd()
_voice = self._settings.voice _voice = assert_given(self._settings.voice)
model_file = f"{_voice}.onnx" model_file = f"{_voice}.onnx"
model_path_resolved = Path(download_dir) / model_file model_path_resolved = Path(download_dir) / model_file

View File

@@ -37,6 +37,7 @@ from pipecat.services.settings import (
NOT_GIVEN, NOT_GIVEN,
STTSettings, STTSettings,
_NotGiven, _NotGiven,
assert_given,
is_given, is_given,
) )
from pipecat.services.stt_latency import SARVAM_TTFS_P99 from pipecat.services.stt_latency import SARVAM_TTFS_P99
@@ -319,8 +320,8 @@ class SarvamSTTService(STTService):
default_settings.apply_update(settings) default_settings.apply_update(settings)
# Resolve model config and validate (after all overrides) # Resolve model config and validate (after all overrides)
resolved_model = default_settings.model resolved_model = assert_given(default_settings.model)
if resolved_model not in MODEL_CONFIGS: if resolved_model is None or resolved_model not in MODEL_CONFIGS:
allowed = ", ".join(sorted(MODEL_CONFIGS.keys())) allowed = ", ".join(sorted(MODEL_CONFIGS.keys()))
raise ValueError(f"Unsupported model '{resolved_model}'. Allowed values: {allowed}.") raise ValueError(f"Unsupported model '{resolved_model}'. Allowed values: {allowed}.")
@@ -407,8 +408,9 @@ class SarvamSTTService(STTService):
def _get_language_string(self) -> str | None: def _get_language_string(self) -> str | None:
"""Resolve the current language setting to a Sarvam language code string.""" """Resolve the current language setting to a Sarvam language code string."""
if self._settings.language: language = assert_given(self._settings.language)
return language_to_sarvam_language(self._settings.language) if language:
return language_to_sarvam_language(language)
return self._config.default_language return self._config.default_language
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:

View File

@@ -59,7 +59,7 @@ from pipecat.frames.frames import (
TTSStoppedFrame, TTSStoppedFrame,
) )
from pipecat.services.sarvam._sdk import sdk_headers from pipecat.services.sarvam._sdk import sdk_headers
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, is_given from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given, is_given
from pipecat.services.tts_service import InterruptibleTTSService, TextAggregationMode, TTSService from pipecat.services.tts_service import InterruptibleTTSService, TextAggregationMode, TTSService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -489,8 +489,8 @@ class SarvamHttpTTSService(TTSService):
default_settings.apply_update(settings) default_settings.apply_update(settings)
# Get model configuration (validates model exists) # Get model configuration (validates model exists)
resolved_model = default_settings.model resolved_model = assert_given(default_settings.model)
if resolved_model not in TTS_MODEL_CONFIGS: if resolved_model is None or resolved_model not in TTS_MODEL_CONFIGS:
allowed = ", ".join(sorted(TTS_MODEL_CONFIGS.keys())) allowed = ", ".join(sorted(TTS_MODEL_CONFIGS.keys()))
raise ValueError(f"Unsupported model '{resolved_model}'. Allowed values: {allowed}.") raise ValueError(f"Unsupported model '{resolved_model}'. Allowed values: {allowed}.")
@@ -505,7 +505,7 @@ class SarvamHttpTTSService(TTSService):
default_settings.voice = self._config.default_speaker default_settings.voice = self._config.default_speaker
# Validate and clamp pace to model's valid range # Validate and clamp pace to model's valid range
pace = default_settings.pace pace = assert_given(default_settings.pace)
pace_min, pace_max = self._config.pace_range pace_min, pace_max = self._config.pace_range
if pace is not None and (pace < pace_min or pace > pace_max): if pace is not None and (pace < pace_min or pace > pace_max):
logger.warning(f"Pace {pace} is outside model range ({pace_min}-{pace_max}). Clamping.") logger.warning(f"Pace {pace} is outside model range ({pace_min}-{pace_max}). Clamping.")
@@ -907,8 +907,8 @@ class SarvamTTSService(InterruptibleTTSService):
default_settings.apply_update(settings) default_settings.apply_update(settings)
# Get model configuration (validates model exists) # Get model configuration (validates model exists)
resolved_model = default_settings.model resolved_model = assert_given(default_settings.model)
if resolved_model not in TTS_MODEL_CONFIGS: if resolved_model is None or resolved_model not in TTS_MODEL_CONFIGS:
allowed = ", ".join(sorted(TTS_MODEL_CONFIGS.keys())) allowed = ", ".join(sorted(TTS_MODEL_CONFIGS.keys()))
raise ValueError(f"Unsupported model '{resolved_model}'. Allowed values: {allowed}.") raise ValueError(f"Unsupported model '{resolved_model}'. Allowed values: {allowed}.")
@@ -923,7 +923,7 @@ class SarvamTTSService(InterruptibleTTSService):
default_settings.voice = self._config.default_speaker default_settings.voice = self._config.default_speaker
# Validate and clamp pace to model's valid range # Validate and clamp pace to model's valid range
pace = default_settings.pace pace = assert_given(default_settings.pace)
pace_min, pace_max = self._config.pace_range pace_min, pace_max = self._config.pace_range
if pace is not None and (pace < pace_min or pace > pace_max): if pace is not None and (pace < pace_min or pace > pace_max):
logger.warning(f"Pace {pace} is outside model range ({pace_min}-{pace_max}). Clamping.") logger.warning(f"Pace {pace} is outside model range ({pace_min}-{pace_max}). Clamping.")

View File

@@ -25,7 +25,7 @@ from pipecat.frames.frames import (
VADUserStoppedSpeakingFrame, VADUserStoppedSpeakingFrame,
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, assert_given
from pipecat.services.stt_latency import SONIOX_TTFS_P99 from pipecat.services.stt_latency import SONIOX_TTFS_P99
from pipecat.services.stt_service import WebsocketSTTService from pipecat.services.stt_service import WebsocketSTTService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
@@ -504,7 +504,7 @@ class SonioxSTTService(WebsocketSTTService):
"num_channels": self._num_channels, "num_channels": self._num_channels,
"enable_endpoint_detection": enable_endpoint_detection, "enable_endpoint_detection": enable_endpoint_detection,
"sample_rate": self.sample_rate, "sample_rate": self.sample_rate,
"language_hints": _prepare_language_hints(s.language_hints), "language_hints": _prepare_language_hints(assert_given(s.language_hints)),
"language_hints_strict": s.language_hints_strict, "language_hints_strict": s.language_hints_strict,
"context": context, "context": context,
"enable_speaker_diarization": s.enable_speaker_diarization, "enable_speaker_diarization": s.enable_speaker_diarization,

View File

@@ -34,7 +34,7 @@ from pipecat.frames.frames import (
VADUserStoppedSpeakingFrame, VADUserStoppedSpeakingFrame,
) )
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, assert_given
from pipecat.services.stt_latency import SPEECHMATICS_TTFS_P99 from pipecat.services.stt_latency import SPEECHMATICS_TTFS_P99
from pipecat.services.stt_service import STTService from pipecat.services.stt_service import STTService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
@@ -719,22 +719,26 @@ class SpeechmaticsSTTService(STTService):
s = settings s = settings
# Preset from turn detection mode # Preset from turn detection mode
config = VoiceAgentConfigPreset.load(s.turn_detection_mode.value) turn_detection_mode = assert_given(s.turn_detection_mode)
config = VoiceAgentConfigPreset.load(turn_detection_mode.value)
# Audio encoding (init-only, stored as instance attribute) # Audio encoding (init-only, stored as instance attribute)
config.audio_encoding = self._audio_encoding config.audio_encoding = self._audio_encoding
# Language + domain # Language + domain
language = s.language language = assert_given(s.language)
config.language = self._language_to_speechmatics_language(language) config.language = self._language_to_speechmatics_language(language)
config.domain = s.domain if s.domain is not None else None config.domain = s.domain if s.domain is not None else None
config.output_locale = self._locale_to_speechmatics_locale(config.language, language) config.output_locale = self._locale_to_speechmatics_locale(config.language, language)
# Speaker config # Speaker config
focus_speakers = assert_given(s.focus_speakers)
ignore_speakers = assert_given(s.ignore_speakers)
focus_mode = assert_given(s.focus_mode)
config.speaker_config = SpeakerFocusConfig( config.speaker_config = SpeakerFocusConfig(
focus_speakers=s.focus_speakers if s.focus_speakers is not None else [], focus_speakers=focus_speakers if focus_speakers is not None else [],
ignore_speakers=s.ignore_speakers if s.ignore_speakers is not None else [], ignore_speakers=ignore_speakers if ignore_speakers is not None else [],
focus_mode=s.focus_mode if s.focus_mode is not None else SpeakerFocusMode.RETAIN, focus_mode=focus_mode if focus_mode is not None else SpeakerFocusMode.RETAIN,
) )
config.known_speakers = s.known_speakers if s.known_speakers is not None else [] config.known_speakers = s.known_speakers if s.known_speakers is not None else []
@@ -766,7 +770,8 @@ class SpeechmaticsSTTService(STTService):
setattr(config, key, value) setattr(config, key, value)
# Enable sentences # Enable sentences
split = s.split_sentences if s.split_sentences is not None else False split_sentences = assert_given(s.split_sentences)
split = split_sentences if split_sentences is not None else False
config.speech_segment_config = SpeechSegmentConfig(emit_sentences=split or False) config.speech_segment_config = SpeechSegmentConfig(emit_sentences=split or False)
return config return config
@@ -962,11 +967,9 @@ class SpeechmaticsSTTService(STTService):
# Create frame from segment # Create frame from segment
def attr_from_segment(segment: dict[str, Any]) -> dict[str, Any]: def attr_from_segment(segment: dict[str, Any]) -> dict[str, Any]:
# Formats the output text based on the speaker and defined formats from the config. # Formats the output text based on the speaker and defined formats from the config.
text = ( active_format = assert_given(self._settings.speaker_active_format)
self._settings.speaker_active_format passive_format = assert_given(self._settings.speaker_passive_format)
if segment.get("is_active", True) text = (active_format if segment.get("is_active", True) else passive_format).format(
else self._settings.speaker_passive_format
).format(
**{ **{
"speaker_id": segment.get("speaker_id", "UU"), "speaker_id": segment.get("speaker_id", "UU"),
"text": segment.get("text", ""), "text": segment.get("text", ""),

View File

@@ -20,7 +20,7 @@ from pipecat.frames.frames import (
Frame, Frame,
TTSAudioRawFrame, TTSAudioRawFrame,
) )
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven, assert_given
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
from pipecat.utils.network import exponential_backoff_time from pipecat.utils.network import exponential_backoff_time
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -183,7 +183,9 @@ class SpeechmaticsTTSService(TTSService):
} }
# Complete HTTP URL # Complete HTTP URL
url = _get_endpoint_url(self._base_url, self._settings.voice, self.sample_rate) url = _get_endpoint_url(
self._base_url, assert_given(self._settings.voice), self.sample_rate
)
try: try:
# Track attempt # Track attempt
@@ -208,7 +210,8 @@ class SpeechmaticsTTSService(TTSService):
attempt += 1 attempt += 1
# Check if we've exceeded the maximum number of attempts # Check if we've exceeded the maximum number of attempts
if attempt >= self._settings.max_retries: max_retries = assert_given(self._settings.max_retries)
if max_retries is not None and attempt >= max_retries:
raise ValueError() raise ValueError()
# Report error frame # Report error frame

View File

@@ -48,7 +48,7 @@ from pipecat.frames.frames import (
from pipecat.processors.aggregators.llm_context import LLMContext from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.llm_service import FunctionCallFromLLM, LLMService from pipecat.services.llm_service import FunctionCallFromLLM, LLMService
from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, LLMSettings, _NotGiven, assert_given
from pipecat.utils.time import time_now_iso8601 from pipecat.utils.time import time_now_iso8601
try: try:
@@ -377,7 +377,7 @@ class UltravoxRealtimeLLMService(LLMService):
async def _update_settings(self, delta: Settings): async def _update_settings(self, delta: Settings):
changed = await super()._update_settings(delta) changed = await super()._update_settings(delta)
if "output_medium" in changed: if "output_medium" in changed:
await self._update_output_medium(self._settings.output_medium) await self._update_output_medium(assert_given(self._settings.output_medium))
self._warn_unhandled_updated_settings(changed.keys() - {"output_medium"}) self._warn_unhandled_updated_settings(changed.keys() - {"output_medium"})
return changed return changed

View File

@@ -21,7 +21,7 @@ from loguru import logger
from typing_extensions import override from typing_extensions import override
from pipecat.frames.frames import ErrorFrame, Frame, TranscriptionFrame from pipecat.frames.frames import ErrorFrame, Frame, TranscriptionFrame
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, assert_given
from pipecat.services.stt_service import SegmentedSTTService from pipecat.services.stt_service import SegmentedSTTService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.time import time_now_iso8601 from pipecat.utils.time import time_now_iso8601
@@ -315,8 +315,11 @@ class WhisperSTTService(SegmentedSTTService):
from faster_whisper import WhisperModel from faster_whisper import WhisperModel
logger.debug("Loading Whisper model...") logger.debug("Loading Whisper model...")
model_name = assert_given(self._settings.model)
if model_name is None:
raise ValueError("Whisper model must be specified")
self._model = WhisperModel( self._model = WhisperModel(
self._settings.model, device=self._device, compute_type=self._compute_type model_name, 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:
@@ -354,24 +357,29 @@ class WhisperSTTService(SegmentedSTTService):
# Divide by 32768 because we have signed 16-bit data. # Divide by 32768 because we have signed 16-bit data.
audio_float = np.frombuffer(audio, dtype=np.int16).astype(np.float32) / 32768.0 audio_float = np.frombuffer(audio, dtype=np.int16).astype(np.float32) / 32768.0
language = assert_given(self._settings.language)
segments, _ = await asyncio.to_thread( segments, _ = await asyncio.to_thread(
self._model.transcribe, audio_float, language=self._settings.language self._model.transcribe, audio_float, language=language
) )
text: str = "" text: str = ""
no_speech_prob_threshold = assert_given(self._settings.no_speech_prob)
for segment in segments: for segment in segments:
if segment.no_speech_prob < self._settings.no_speech_prob: if (
no_speech_prob_threshold is not None
and segment.no_speech_prob < no_speech_prob_threshold
):
text += f"{segment.text} " text += f"{segment.text} "
await self.stop_processing_metrics() await self.stop_processing_metrics()
if text: if text:
await self._handle_transcription(text, True, self._settings.language) await self._handle_transcription(text, True, language)
logger.debug(f"Transcription: [{text}]") logger.debug(f"Transcription: [{text}]")
yield TranscriptionFrame( yield TranscriptionFrame(
text, text,
self._user_id, self._user_id,
time_now_iso8601(), time_now_iso8601(),
self._settings.language, language,
) )
@@ -494,20 +502,29 @@ class WhisperSTTServiceMLX(WhisperSTTService):
# Divide by 32768 because we have signed 16-bit data. # Divide by 32768 because we have signed 16-bit data.
audio_float = np.frombuffer(audio, dtype=np.int16).astype(np.float32) / 32768.0 audio_float = np.frombuffer(audio, dtype=np.int16).astype(np.float32) / 32768.0
model_path = assert_given(self._settings.model)
if model_path is None:
raise ValueError("Whisper model must be specified")
temperature = assert_given(self._settings.temperature)
language = assert_given(self._settings.language)
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._settings.model, path_or_hf_repo=model_path,
temperature=self._settings.temperature, temperature=temperature,
language=self._settings.language, language=language,
) )
text: str = "" text: str = ""
no_speech_prob_threshold = assert_given(self._settings.no_speech_prob)
for segment in chunk.get("segments", []): for segment in chunk.get("segments", []):
# Drop likely hallucinations # Drop likely hallucinations
if segment.get("compression_ratio", None) == 0.5555555555555556: if segment.get("compression_ratio", None) == 0.5555555555555556:
continue continue
if segment.get("no_speech_prob", 0.0) < self._settings.no_speech_prob: if (
no_speech_prob_threshold is not None
and segment.get("no_speech_prob", 0.0) < no_speech_prob_threshold
):
text += f"{segment.get('text', '')} " text += f"{segment.get('text', '')} "
if len(text.strip()) == 0: if len(text.strip()) == 0:
@@ -516,13 +533,13 @@ class WhisperSTTServiceMLX(WhisperSTTService):
await self.stop_processing_metrics() await self.stop_processing_metrics()
if text: if text:
await self._handle_transcription(text, True, self._settings.language) await self._handle_transcription(text, True, language)
logger.debug(f"Transcription: [{text}]") logger.debug(f"Transcription: [{text}]")
yield TranscriptionFrame( yield TranscriptionFrame(
text, text,
self._user_id, self._user_id,
time_now_iso8601(), time_now_iso8601(),
self._settings.language, language,
) )
except Exception as e: except Exception as e:

View File

@@ -53,6 +53,7 @@ from pipecat.services.settings import (
NOT_GIVEN, NOT_GIVEN,
LLMSettings, LLMSettings,
_NotGiven, _NotGiven,
assert_given,
is_given, is_given,
) )
from pipecat.utils.time import time_now_iso8601 from pipecat.utils.time import time_now_iso8601
@@ -319,13 +320,14 @@ class GrokRealtimeLLMService(LLMService):
Configured sample rate or None if not manually configured. Configured sample rate or None if not manually configured.
For PCMU/PCMA formats, returns 8000 Hz (G.711 standard). For PCMU/PCMA formats, returns 8000 Hz (G.711 standard).
""" """
if not self._settings.session_properties.audio: session_properties = assert_given(self._settings.session_properties)
if not session_properties.audio:
return None return None
audio_config = ( audio_config = (
self._settings.session_properties.audio.input session_properties.audio.input
if direction == "input" if direction == "input"
else self._settings.session_properties.audio.output else session_properties.audio.output
) )
if audio_config and audio_config.format: if audio_config and audio_config.format:
@@ -355,8 +357,9 @@ class GrokRealtimeLLMService(LLMService):
def _is_turn_detection_enabled(self) -> bool: def _is_turn_detection_enabled(self) -> bool:
"""Check if server-side VAD is enabled.""" """Check if server-side VAD is enabled."""
if self._settings.session_properties.turn_detection: session_properties = assert_given(self._settings.session_properties)
return self._settings.session_properties.turn_detection.type == "server_vad" if session_properties.turn_detection:
return session_properties.turn_detection.type == "server_vad"
return False return False
async def _handle_interruption(self): async def _handle_interruption(self):
@@ -423,7 +426,7 @@ class GrokRealtimeLLMService(LLMService):
input_sample_rate: Sample rate for audio input (Hz). input_sample_rate: Sample rate for audio input (Hz).
output_sample_rate: Sample rate for audio output (Hz). output_sample_rate: Sample rate for audio output (Hz).
""" """
props = self._settings.session_properties props = assert_given(self._settings.session_properties)
if not props.audio: if not props.audio:
props.audio = events.AudioConfiguration() props.audio = events.AudioConfiguration()
if not props.audio.input: if not props.audio.input:
@@ -592,12 +595,13 @@ class GrokRealtimeLLMService(LLMService):
async def _send_session_update(self): async def _send_session_update(self):
"""Update session settings on the server.""" """Update session settings on the server."""
settings = self._settings.session_properties settings = assert_given(self._settings.session_properties)
adapter: GrokRealtimeLLMAdapter = self.get_llm_adapter() adapter: GrokRealtimeLLMAdapter = self.get_llm_adapter()
if self._context: if self._context:
llm_invocation_params = adapter.get_llm_invocation_params( llm_invocation_params = adapter.get_llm_invocation_params(
self._context, system_instruction=self._settings.system_instruction self._context,
system_instruction=assert_given(self._settings.system_instruction),
) )
if llm_invocation_params["tools"]: if llm_invocation_params["tools"]:

View File

@@ -24,7 +24,7 @@ from pipecat.frames.frames import (
StartFrame, StartFrame,
TTSAudioRawFrame, TTSAudioRawFrame,
) )
from pipecat.services.settings import TTSSettings from pipecat.services.settings import TTSSettings, assert_given
from pipecat.services.tts_service import TTSService from pipecat.services.tts_service import TTSService
from pipecat.transcriptions.language import Language, resolve_language from pipecat.transcriptions.language import Language, resolve_language
from pipecat.utils.tracing.service_decorators import traced_tts from pipecat.utils.tracing.service_decorators import traced_tts
@@ -211,7 +211,7 @@ class XTTSService(TTSService):
logger.error(f"{self} no studio speakers available") logger.error(f"{self} no studio speakers available")
return return
embeddings = self._studio_speakers[self._settings.voice] embeddings = self._studio_speakers[assert_given(self._settings.voice)]
url = self._base_url + "/tts_stream" url = self._base_url + "/tts_stream"