Merge pull request #1857 from pipecat-ai/aleix/avoid-mutable-default-values

avoid mutable default constructor values
This commit is contained in:
Aleix Conchillo Flaqué
2025-05-20 13:10:24 -07:00
committed by GitHub
51 changed files with 209 additions and 124 deletions

View File

@@ -93,6 +93,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed an issue that would cause multiple instances of the same class to behave
incorrectly if any of the given constructor arguments defaulted to a mutable
value (e.g. lists, dictionaries, objects).
- Fixed an issue with `CartesiaTTSService` where `TTSTextFrame` messages weren't
being emitted when the model was set to `sonic`. This resulted in the
assistant context not being updated with assistant messages.

View File

@@ -5,7 +5,7 @@
#
from enum import Enum
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
from pipecat.adapters.schemas.function_schema import FunctionSchema
@@ -18,7 +18,7 @@ class ToolsSchema:
def __init__(
self,
standard_tools: List[FunctionSchema],
custom_tools: Dict[AdapterType, List[Dict[str, Any]]] = None,
custom_tools: Optional[Dict[AdapterType, List[Dict[str, Any]]]] = None,
) -> None:
"""
A schema for tools that includes both standardized function schemas

View File

@@ -36,10 +36,10 @@ class SmartTurnTimeoutException(Exception):
class BaseSmartTurn(BaseTurnAnalyzer):
def __init__(
self, *, sample_rate: Optional[int] = None, params: SmartTurnParams = SmartTurnParams()
self, *, sample_rate: Optional[int] = None, params: Optional[SmartTurnParams] = None
):
super().__init__(sample_rate=sample_rate)
self._params = params
self._params = params or SmartTurnParams()
# Configuration
self._stop_ms = self._params.stop_secs * 1000 # silence threshold in ms
# Inference state

View File

@@ -6,7 +6,7 @@
import asyncio
import io
from typing import Any, Dict
from typing import Any, Dict, Optional
import aiohttp
import numpy as np
@@ -21,12 +21,12 @@ class HttpSmartTurnAnalyzer(BaseSmartTurn):
*,
url: str,
aiohttp_session: aiohttp.ClientSession,
headers: Dict[str, str] = {},
headers: Optional[Dict[str, str]] = None,
**kwargs,
):
super().__init__(**kwargs)
self._url = url
self._headers = headers
self._headers = headers or {}
self._aiohttp_session = aiohttp_session
def _serialize_array(self, audio_array: np.ndarray) -> bytes:

View File

@@ -105,7 +105,7 @@ class SileroOnnxModel:
class SileroVADAnalyzer(VADAnalyzer):
def __init__(self, *, sample_rate: Optional[int] = None, params: VADParams = VADParams()):
def __init__(self, *, sample_rate: Optional[int] = None, params: Optional[VADParams] = None):
super().__init__(sample_rate=sample_rate, params=params)
logger.debug("Loading Silero VAD model...")

View File

@@ -34,10 +34,10 @@ class VADParams(BaseModel):
class VADAnalyzer(ABC):
def __init__(self, *, sample_rate: Optional[int] = None, params: VADParams):
def __init__(self, *, sample_rate: Optional[int] = None, params: Optional[VADParams] = None):
self._init_sample_rate = sample_rate
self._sample_rate = 0
self._params = params
self._params = params or VADParams()
self._num_channels = 1
self._vad_buffer = b""

View File

@@ -188,9 +188,9 @@ class PipelineTask(BaseTask):
self,
pipeline: BasePipeline,
*,
params: PipelineParams = PipelineParams(),
observers: List[BaseObserver] = [],
clock: BaseClock = SystemClock(),
params: Optional[PipelineParams] = None,
observers: Optional[List[BaseObserver]] = None,
clock: Optional[BaseClock] = None,
task_manager: Optional[BaseTaskManager] = None,
check_dangling_tasks: bool = True,
idle_timeout_secs: Optional[float] = 300,
@@ -205,8 +205,8 @@ class PipelineTask(BaseTask):
):
super().__init__()
self._pipeline = pipeline
self._clock = clock
self._params = params
self._clock = clock or SystemClock()
self._params = params or PipelineParams()
self._check_dangling_tasks = check_dangling_tasks
self._idle_timeout_secs = idle_timeout_secs
self._idle_timeout_frames = idle_timeout_frames
@@ -224,6 +224,7 @@ class PipelineTask(BaseTask):
DeprecationWarning,
)
observers = self._params.observers
observers = observers or []
if self._enable_turn_tracking:
self._turn_tracking_observer = TurnTrackingObserver()
observers = [self._turn_tracking_observer] + list(observers)

View File

@@ -6,7 +6,7 @@
import asyncio
import inspect
from typing import List
from typing import List, Optional
from attr import dataclass
@@ -39,8 +39,10 @@ class TaskObserver(BaseObserver):
"""
def __init__(self, *, observers: List[BaseObserver] = [], task_manager: BaseTaskManager):
self._observers = observers
def __init__(
self, *, observers: Optional[List[BaseObserver]] = None, task_manager: BaseTaskManager
):
self._observers = observers or []
self._task_manager = task_manager
self._proxies: List[Proxy] = []

View File

@@ -7,7 +7,7 @@
import asyncio
from abc import abstractmethod
from dataclasses import dataclass
from typing import Dict, List, Literal, Set
from typing import Dict, List, Literal, Optional, Set
from loguru import logger
@@ -243,11 +243,11 @@ class LLMUserContextAggregator(LLMContextResponseAggregator):
self,
context: OpenAILLMContext,
*,
params: LLMUserAggregatorParams = LLMUserAggregatorParams(),
params: Optional[LLMUserAggregatorParams] = None,
**kwargs,
):
super().__init__(context=context, role="user", **kwargs)
self._params = params
self._params = params or LLMUserAggregatorParams()
if "aggregation_timeout" in kwargs:
import warnings
@@ -446,11 +446,11 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
self,
context: OpenAILLMContext,
*,
params: LLMAssistantAggregatorParams = LLMAssistantAggregatorParams(),
params: Optional[LLMAssistantAggregatorParams] = None,
**kwargs,
):
super().__init__(context=context, role="assistant", **kwargs)
self._params = params
self._params = params or LLMAssistantAggregatorParams()
if "expect_stripped_words" in kwargs:
import warnings
@@ -640,9 +640,9 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
class LLMUserResponseAggregator(LLMUserContextAggregator):
def __init__(
self,
messages: List[dict] = [],
messages: Optional[List[dict]] = None,
*,
params: LLMUserAggregatorParams = LLMUserAggregatorParams(),
params: Optional[LLMUserAggregatorParams] = None,
**kwargs,
):
super().__init__(context=OpenAILLMContext(messages), params=params, **kwargs)
@@ -662,9 +662,9 @@ class LLMUserResponseAggregator(LLMUserContextAggregator):
class LLMAssistantResponseAggregator(LLMAssistantContextAggregator):
def __init__(
self,
messages: List[dict] = [],
messages: Optional[List[dict]] = None,
*,
params: LLMAssistantAggregatorParams = LLMAssistantAggregatorParams(),
params: Optional[LLMAssistantAggregatorParams] = None,
**kwargs,
):
super().__init__(context=OpenAILLMContext(messages), params=params, **kwargs)

View File

@@ -7,6 +7,7 @@
import re
import time
from enum import Enum
from typing import List
from loguru import logger
@@ -31,7 +32,7 @@ class WakeCheckFilter(FrameProcessor):
self.wake_timer = 0.0
self.accumulator = ""
def __init__(self, wake_phrases: list[str], keepalive_timeout: float = 3):
def __init__(self, wake_phrases: List[str], keepalive_timeout: float = 3):
super().__init__()
self._participant_states = {}
self._keepalive_timeout = keepalive_timeout

View File

@@ -22,7 +22,7 @@ class WakeNotifierFilter(FrameProcessor):
self,
notifier: BaseNotifier,
*,
types: Tuple[Type[Frame]],
types: Tuple[Type[Frame], ...],
filter: Callable[[Frame], Awaitable[bool]],
**kwargs,
):

View File

@@ -437,10 +437,10 @@ class RTVIObserver(BaseObserver):
params (RTVIObserverParams): Settings to enable/disable specific messages.
"""
def __init__(self, rtvi: "RTVIProcessor", *, params: RTVIObserverParams = RTVIObserverParams()):
def __init__(self, rtvi: "RTVIProcessor", *, params: Optional[RTVIObserverParams] = None):
super().__init__()
self._rtvi = rtvi
self._params = params
self._params = params or RTVIObserverParams()
self._bot_transcription = ""
self._frames_seen = set()
rtvi.set_errors_enabled(self._params.errors_enabled)
@@ -632,12 +632,12 @@ class RTVIProcessor(FrameProcessor):
def __init__(
self,
*,
config: RTVIConfig = RTVIConfig(config=[]),
config: Optional[RTVIConfig] = None,
transport: Optional[BaseTransport] = None,
**kwargs,
):
super().__init__(**kwargs)
self._config = config
self._config = config or RTVIConfig(config=[])
self._bot_ready = False
self._client_ready = False

View File

@@ -43,10 +43,10 @@ class GStreamerPipelineSource(FrameProcessor):
audio_channels: int = 1
clock_sync: bool = True
def __init__(self, *, pipeline: str, out_params: OutputParams = OutputParams(), **kwargs):
def __init__(self, *, pipeline: str, out_params: Optional[OutputParams] = None, **kwargs):
super().__init__(**kwargs)
self._out_params = out_params
self._out_params = out_params or GStreamerPipelineSource.OutputParams()
self._sample_rate = 0
Gst.init()

View File

@@ -5,7 +5,7 @@
#
import asyncio
from typing import Awaitable, Callable, List
from typing import Awaitable, Callable, List, Optional
from pipecat.frames.frames import Frame, StartFrame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
@@ -22,14 +22,14 @@ class IdleFrameProcessor(FrameProcessor):
*,
callback: Callable[["IdleFrameProcessor"], Awaitable[None]],
timeout: float,
types: List[type] = [],
types: Optional[List[type]] = None,
**kwargs,
):
super().__init__(**kwargs)
self._callback = callback
self._timeout = timeout
self._types = types
self._types = types or []
self._idle_task = None
async def process_frame(self, frame: Frame, direction: FrameDirection):

View File

@@ -4,11 +4,17 @@
# SPDX-License-Identifier: BSD 2-Clause License
#
from typing import Optional
from typing import Optional, Tuple, Type
from loguru import logger
from pipecat.frames.frames import AudioRawFrame, BotSpeakingFrame, Frame, TransportMessageFrame
from pipecat.frames.frames import (
BotSpeakingFrame,
Frame,
InputAudioRawFrame,
OutputAudioRawFrame,
TransportMessageFrame,
)
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
logger = logger.opt(ansi=True)
@@ -19,16 +25,17 @@ class FrameLogger(FrameProcessor):
self,
prefix="Frame",
color: Optional[str] = None,
ignored_frame_types: Optional[list] = [
ignored_frame_types: Tuple[Type[Frame], ...] = (
BotSpeakingFrame,
AudioRawFrame,
InputAudioRawFrame,
OutputAudioRawFrame,
TransportMessageFrame,
],
),
):
super().__init__()
self._prefix = prefix
self._color = color
self._ignored_frame_types = tuple(ignored_frame_types) if ignored_frame_types else None
self._ignored_frame_types = ignored_frame_types
async def process_frame(self, frame: Frame, direction: FrameDirection):
await super().process_frame(frame, direction)

View File

@@ -79,7 +79,7 @@ class TelnyxFrameSerializer(FrameSerializer):
inbound_encoding: str,
call_control_id: Optional[str] = None,
api_key: Optional[str] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
):
"""Initialize the TelnyxFrameSerializer.
@@ -92,11 +92,11 @@ class TelnyxFrameSerializer(FrameSerializer):
params: Configuration parameters.
"""
self._stream_id = stream_id
params.outbound_encoding = outbound_encoding
params.inbound_encoding = inbound_encoding
self._call_control_id = call_control_id
self._api_key = api_key
self._params = params
self._params = params or TelnyxFrameSerializer.InputParams()
self._params.outbound_encoding = outbound_encoding
self._params.inbound_encoding = inbound_encoding
self._telnyx_sample_rate = self._params.telnyx_sample_rate
self._sample_rate = 0 # Pipeline input rate

View File

@@ -69,7 +69,7 @@ class TwilioFrameSerializer(FrameSerializer):
call_sid: Optional[str] = None,
account_sid: Optional[str] = None,
auth_token: Optional[str] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
):
"""Initialize the TwilioFrameSerializer.
@@ -84,7 +84,7 @@ class TwilioFrameSerializer(FrameSerializer):
self._call_sid = call_sid
self._account_sid = account_sid
self._auth_token = auth_token
self._params = params
self._params = params or TwilioFrameSerializer.InputParams()
self._twilio_sample_rate = self._params.twilio_sample_rate
self._sample_rate = 0 # Pipeline input rate

View File

@@ -91,11 +91,12 @@ class AnthropicLLMService(LLMService):
*,
api_key: str,
model: str = "claude-3-7-sonnet-20250219",
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
client=None,
**kwargs,
):
super().__init__(**kwargs)
params = params or AnthropicLLMService.InputParams()
self._client = client or AsyncAnthropic(
api_key=api_key
) # if the client is provided, use it and remove it, otherwise create a new one

View File

@@ -38,12 +38,13 @@ class AssemblyAISTTService(STTService):
*,
api_key: str,
sample_rate: Optional[int] = None,
encoding: AudioEncoding = AudioEncoding("pcm_s16le"),
encoding: Optional[AudioEncoding] = None,
language=Language.EN, # Only English is supported for Realtime
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
encoding = encoding or AudioEncoding("pcm_s16le")
aai.settings.api_key = api_key
self._transcriber: Optional[aai.RealtimeTranscriber] = None

View File

@@ -530,17 +530,19 @@ class AWSBedrockLLMService(LLMService):
def __init__(
self,
*,
model: str,
aws_access_key: Optional[str] = None,
aws_secret_key: Optional[str] = None,
aws_session_token: Optional[str] = None,
aws_region: str = "us-east-1",
model: str,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
client_config: Optional[Config] = None,
**kwargs,
):
super().__init__(**kwargs)
params = params or AWSBedrockLLMService.InputParams()
# Initialize the AWS Bedrock client
if not client_config:
client_config = Config(

View File

@@ -125,11 +125,13 @@ class AWSPollyTTSService(TTSService):
region: Optional[str] = None,
voice_id: str = "Joanna",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or AWSPollyTTSService.InputParams()
self._polly_client = boto3.client(
"polly",
aws_access_key_id=aws_access_key_id,

View File

@@ -142,7 +142,7 @@ class AWSNovaSonicLLMService(LLMService):
region: str,
model: str = "amazon.nova-sonic-v1:0",
voice_id: str = "matthew", # matthew, tiffany, amy
params: Params = Params(),
params: Optional[Params] = None,
system_instruction: Optional[str] = None,
tools: Optional[ToolsSchema] = None,
send_transcription_frames: bool = True,
@@ -155,7 +155,7 @@ class AWSNovaSonicLLMService(LLMService):
self._model = model
self._client: Optional[BedrockRuntimeClient] = None
self._voice_id = voice_id
self._params = params
self._params = params or Params()
self._system_instruction = system_instruction
self._tools = tools
self._send_transcription_frames = send_transcription_frames

View File

@@ -68,11 +68,13 @@ class AzureBaseTTSService(TTSService):
region: str,
voice="en-US-SaraNeural",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or AzureBaseTTSService.InputParams()
self._settings = {
"emphasis": params.emphasis,
"language": self.language_to_service_language(params.language)

View File

@@ -11,7 +11,7 @@ import warnings
from typing import AsyncGenerator, List, Optional, Union
from loguru import logger
from pydantic import BaseModel
from pydantic import BaseModel, Field
from pipecat.frames.frames import (
CancelFrame,
@@ -90,7 +90,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
sample_rate: Optional[int] = None,
encoding: str = "pcm_s16le",
container: str = "raw",
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
text_aggregator: Optional[BaseTextAggregator] = None,
**kwargs,
):
@@ -113,6 +113,8 @@ class CartesiaTTSService(AudioContextWordTTSService):
**kwargs,
)
params = params or CartesiaTTSService.InputParams()
self._api_key = api_key
self._cartesia_version = cartesia_version
self._url = url
@@ -317,7 +319,7 @@ class CartesiaHttpTTSService(TTSService):
class InputParams(BaseModel):
language: Optional[Language] = Language.EN
speed: Optional[Union[str, float]] = ""
emotion: Optional[List[str]] = []
emotion: Optional[List[str]] = Field(default_factory=list)
def __init__(
self,
@@ -330,11 +332,13 @@ class CartesiaHttpTTSService(TTSService):
sample_rate: Optional[int] = None,
encoding: str = "pcm_s16le",
container: str = "raw",
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or CartesiaHttpTTSService.InputParams()
self._api_key = api_key
self._base_url = base_url
self._cartesia_version = cartesia_version

View File

@@ -184,7 +184,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
model: str = "eleven_flash_v2_5",
url: str = "wss://api.elevenlabs.io",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
# Aggregating sentences still gives cleaner-sounding results and fewer
@@ -210,6 +210,8 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
**kwargs,
)
params = params or ElevenLabsTTSService.InputParams()
self._api_key = api_key
self._url = url
self._settings = {
@@ -512,7 +514,7 @@ class ElevenLabsHttpTTSService(WordTTSService):
model: str = "eleven_flash_v2_5",
base_url: str = "https://api.elevenlabs.io",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(
@@ -523,6 +525,8 @@ class ElevenLabsHttpTTSService(WordTTSService):
**kwargs,
)
params = params or ElevenLabsHttpTTSService.InputParams()
self._api_key = api_key
self._base_url = base_url
self._params = params

View File

@@ -173,7 +173,7 @@ class FalSTTService(SegmentedSTTService):
*,
api_key: Optional[str] = None,
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(
@@ -181,6 +181,8 @@ class FalSTTService(SegmentedSTTService):
**kwargs,
)
params = params or FalSTTService.InputParams()
if api_key:
os.environ["FAL_KEY"] = api_key
elif "FAL_KEY" not in os.environ:

View File

@@ -52,7 +52,7 @@ class FishAudioTTSService(InterruptibleTTSService):
model: str, # This is the reference_id
output_format: FishAudioOutputFormat = "pcm",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(
@@ -62,6 +62,8 @@ class FishAudioTTSService(InterruptibleTTSService):
**kwargs,
)
params = params or FishAudioTTSService.InputParams()
self._api_key = api_key
self._base_url = "wss://api.fish.audio/v1/tts/live"
self._websocket = None

View File

@@ -341,11 +341,14 @@ class GeminiMultimodalLiveLLMService(LLMService):
start_video_paused: bool = False,
system_instruction: Optional[str] = None,
tools: Optional[Union[List[dict], ToolsSchema]] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
inference_on_context_initialization: bool = True,
**kwargs,
):
super().__init__(base_url=base_url, **kwargs)
params = params or InputParams()
self._last_sent_time = 0
self._api_key = api_key
self._base_url = base_url

View File

@@ -194,7 +194,7 @@ class GladiaSTTService(STTService):
confidence: float = 0.5,
sample_rate: Optional[int] = None,
model: str = "solaria-1",
params: GladiaInputParams = GladiaInputParams(),
params: Optional[GladiaInputParams] = None,
**kwargs,
):
"""Initialize the Gladia STT service.
@@ -211,6 +211,8 @@ class GladiaSTTService(STTService):
"""
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or GladiaInputParams()
# Warn about deprecated language parameter if it's used
if params.language is not None:
warnings.warn(

View File

@@ -10,7 +10,7 @@ import os
# Suppress gRPC fork warnings
os.environ["GRPC_ENABLE_FORK_SUPPORT"] = "false"
from typing import AsyncGenerator
from typing import AsyncGenerator, Optional
from loguru import logger
from PIL import Image
@@ -32,19 +32,19 @@ class GoogleImageGenService(ImageGenService):
class InputParams(BaseModel):
number_of_images: int = Field(default=1, ge=1, le=8)
model: str = Field(default="imagen-3.0-generate-002")
negative_prompt: str = Field(default=None)
negative_prompt: Optional[str] = Field(default=None)
def __init__(
self,
*,
params: InputParams = InputParams(),
api_key: str,
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(**kwargs)
self.set_model_name(params.model)
self._params = params
self._params = params or GoogleImageGenService.InputParams()
self._client = genai.Client(api_key=api_key)
self.set_model_name(self._params.model)
def can_generate_metrics(self) -> bool:
return True

View File

@@ -467,13 +467,16 @@ class GoogleLLMService(LLMService):
*,
api_key: str,
model: str = "gemini-2.0-flash",
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
system_instruction: Optional[str] = None,
tools: Optional[List[Dict[str, Any]]] = None,
tool_config: Optional[Dict[str, Any]] = None,
**kwargs,
):
super().__init__(**kwargs)
params = params or GoogleLLMService.InputParams()
self.set_model_name(model)
self._api_key = api_key
self._system_instruction = system_instruction

View File

@@ -52,7 +52,7 @@ class GoogleVertexLLMService(OpenAILLMService):
credentials: Optional[str] = None,
credentials_path: Optional[str] = None,
model: str = "google/gemini-2.0-flash-001",
params: InputParams = OpenAILLMService.InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
"""Initializes the VertexLLMService.
@@ -64,6 +64,7 @@ class GoogleVertexLLMService(OpenAILLMService):
params (InputParams): Vertex AI input parameters.
**kwargs: Additional arguments for OpenAILLMService.
"""
params = params or OpenAILLMService.InputParams()
base_url = self._get_base_url(params)
self._api_key = self._get_api_token(credentials, credentials_path)

View File

@@ -412,7 +412,7 @@ class GoogleSTTService(STTService):
credentials_path: Optional[str] = None,
location: str = "global",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
"""Initialize the Google STT service.
@@ -431,6 +431,8 @@ class GoogleSTTService(STTService):
"""
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or GoogleSTTService.InputParams()
self._location = location
self._stream = None
self._config = None

View File

@@ -219,11 +219,13 @@ class GoogleTTSService(TTSService):
credentials_path: Optional[str] = None,
voice_id: str = "en-US-Neural2-A",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or GoogleTTSService.InputParams()
self._settings = {
"pitch": params.pitch,
"rate": params.rate,

View File

@@ -34,7 +34,7 @@ class GroqTTSService(TTSService):
*,
api_key: str,
output_format: str = "wav",
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
model_name: str = "playai-tts",
voice_id: str = "Celeste-PlayAI",
sample_rate: Optional[int] = GROQ_SAMPLE_RATE,
@@ -42,12 +42,15 @@ class GroqTTSService(TTSService):
):
if sample_rate != self.GROQ_SAMPLE_RATE:
logger.warning(f"Groq TTS only supports {self.GROQ_SAMPLE_RATE}Hz sample rate. ")
super().__init__(
pause_frame_processing=True,
sample_rate=sample_rate,
**kwargs,
)
params = params or GroqTTSService.InputParams()
self._api_key = api_key
self._model_name = model_name
self._output_format = output_format

View File

@@ -4,7 +4,7 @@
# SPDX-License-Identifier: BSD 2-Clause License
#
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
from loguru import logger
from pydantic import BaseModel, Field
@@ -49,16 +49,19 @@ class Mem0MemoryService(FrameProcessor):
def __init__(
self,
*,
api_key: str = None,
local_config: Dict[str, Any] = {},
user_id: str = None,
agent_id: str = None,
run_id: str = None,
params: InputParams = InputParams(),
api_key: Optional[str] = None,
local_config: Optional[Dict[str, Any]] = None,
user_id: Optional[str] = None,
agent_id: Optional[str] = None,
run_id: Optional[str] = None,
params: Optional[InputParams] = None,
):
# Important: Call the parent class __init__ first
super().__init__()
local_config = local_config or {}
params = params or Mem0MemoryService.InputParams()
if local_config:
self.memory_client = Memory.from_config(local_config)
else:

View File

@@ -114,11 +114,13 @@ class MiniMaxHttpTTSService(TTSService):
voice_id: str = "Calm_Woman",
aiohttp_session: aiohttp.ClientSession,
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or MiniMaxHttpTTSService.InputParams()
self._api_key = api_key
self._group_id = group_id
self._base_url = f"https://api.minimaxi.chat/v1/t2a_v2?GroupId={group_id}"

View File

@@ -80,7 +80,7 @@ class NeuphonicTTSService(InterruptibleTTSService):
url: str = "wss://api.neuphonic.com",
sample_rate: Optional[int] = 22050,
encoding: str = "pcm_linear",
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(
@@ -92,6 +92,8 @@ class NeuphonicTTSService(InterruptibleTTSService):
**kwargs,
)
params = params or NeuphonicTTSService.InputParams()
self._api_key = api_key
self._url = url
self._settings = {
@@ -293,11 +295,13 @@ class NeuphonicHttpTTSService(TTSService):
url: str = "https://api.neuphonic.com",
sample_rate: Optional[int] = 22050,
encoding: str = "pcm_linear",
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or NeuphonicHttpTTSService.InputParams()
self._api_key = api_key
self._url = url
self._settings = {

View File

@@ -77,11 +77,14 @@ class BaseOpenAILLMService(LLMService):
base_url=None,
organization=None,
project=None,
default_headers: Mapping[str, str] | None = None,
params: InputParams = InputParams(),
default_headers: Optional[Mapping[str, str]] = None,
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(**kwargs)
params = params or BaseOpenAILLMService.InputParams()
self._settings = {
"frequency_penalty": params.frequency_penalty,
"presence_penalty": params.presence_penalty,

View File

@@ -6,7 +6,7 @@
import json
from dataclasses import dataclass
from typing import Any
from typing import Any, Optional
from pipecat.frames.frames import (
FunctionCallCancelFrame,
@@ -41,7 +41,7 @@ class OpenAILLMService(BaseOpenAILLMService):
self,
*,
model: str = "gpt-4.1",
params: BaseOpenAILLMService.InputParams = BaseOpenAILLMService.InputParams(),
params: Optional[BaseOpenAILLMService.InputParams] = None,
**kwargs,
):
super().__init__(model=model, params=params, **kwargs)

View File

@@ -8,6 +8,7 @@ import base64
import json
import time
from dataclasses import dataclass
from typing import Optional
from loguru import logger
@@ -89,17 +90,20 @@ class OpenAIRealtimeBetaLLMService(LLMService):
api_key: str,
model: str = "gpt-4o-realtime-preview-2024-12-17",
base_url: str = "wss://api.openai.com/v1/realtime",
session_properties: events.SessionProperties = events.SessionProperties(),
session_properties: Optional[events.SessionProperties] = None,
start_audio_paused: bool = False,
send_transcription_frames: bool = True,
**kwargs,
):
full_url = f"{base_url}?model={model}"
super().__init__(base_url=full_url, **kwargs)
self.api_key = api_key
self.base_url = full_url
self._session_properties: events.SessionProperties = session_properties
self._session_properties: events.SessionProperties = (
session_properties or events.SessionProperties()
)
self._audio_input_paused = start_audio_paused
self._send_transcription_frames = send_transcription_frames
self._websocket = None

View File

@@ -110,7 +110,7 @@ class PlayHTTTSService(InterruptibleTTSService):
voice_engine: str = "Play3.0-mini",
sample_rate: Optional[int] = None,
output_format: str = "wav",
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(
@@ -119,6 +119,8 @@ class PlayHTTTSService(InterruptibleTTSService):
**kwargs,
)
params = params or PlayHTTTSService.InputParams()
self._api_key = api_key
self._user_id = user_id
self._websocket_url = None
@@ -328,11 +330,13 @@ class PlayHTHttpTTSService(TTSService):
voice_engine: str = "Play3.0-mini",
protocol: str = "http", # Options: http, ws
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or PlayHTHttpTTSService.InputParams()
self._user_id = user_id
self._api_key = api_key

View File

@@ -80,7 +80,7 @@ class RimeTTSService(AudioContextWordTTSService):
url: str = "wss://users.rime.ai/ws2",
model: str = "mistv2",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
text_aggregator: Optional[BaseTextAggregator] = None,
**kwargs,
):
@@ -105,6 +105,8 @@ class RimeTTSService(AudioContextWordTTSService):
**kwargs,
)
params = params or RimeTTSService.InputParams()
# Store service configuration
self._api_key = api_key
self._url = url
@@ -364,11 +366,13 @@ class RimeHttpTTSService(TTSService):
aiohttp_session: aiohttp.ClientSession,
model: str = "mistv2",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or RimeHttpTTSService.InputParams()
self._api_key = api_key
self._session = aiohttp_session
self._base_url = "https://users.rime.ai/v1/rime-tts"

View File

@@ -99,10 +99,13 @@ class RivaSTTService(STTService):
"model_name": "parakeet-ctc-1.1b-asr",
},
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or RivaSTTService.InputParams()
self._api_key = api_key
self._profanity_filter = False
self._automatic_punctuation = True
@@ -322,11 +325,13 @@ class RivaSegmentedSTTService(SegmentedSTTService):
"model_name": "canary-1b-asr",
},
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or RivaSegmentedSTTService.InputParams()
# Set model name
self.set_model_name(model_function_map.get("model_name"))
@@ -533,7 +538,7 @@ class ParakeetSTTService(RivaSTTService):
"model_name": "parakeet-ctc-1.1b-asr",
},
sample_rate: Optional[int] = None,
params: RivaSTTService.InputParams = RivaSTTService.InputParams(), # Use parent class's type
params: Optional[RivaSTTService.InputParams] = None, # Use parent class's type
**kwargs,
):
super().__init__(

View File

@@ -44,7 +44,7 @@ class RivaTTSService(TTSService):
def __init__(
self,
*,
api_key: str = None,
api_key: str,
server: str = "grpc.nvcf.nvidia.com:443",
voice_id: str = "Magpie-Multilingual.EN-US.Ray",
sample_rate: Optional[int] = None,
@@ -52,10 +52,13 @@ class RivaTTSService(TTSService):
"function_id": "877104f7-e885-42b9-8de8-f6e4c6303969",
"model_name": "magpie-tts-multilingual",
},
params: InputParams = InputParams(),
params: Optional[InputParams] = None,
**kwargs,
):
super().__init__(sample_rate=sample_rate, **kwargs)
params = params or RivaTTSService.InputParams()
self._api_key = api_key
self._voice_id = voice_id
self._language_code = params.language
@@ -136,14 +139,10 @@ class RivaTTSService(TTSService):
class FastPitchTTSService(RivaTTSService):
class InputParams(BaseModel):
language: Optional[Language] = Language.EN_US
quality: Optional[int] = 20
def __init__(
self,
*,
api_key: str = None,
api_key: str,
server: str = "grpc.nvcf.nvidia.com:443",
voice_id: str = "English-US.Female-1",
sample_rate: Optional[int] = None,
@@ -151,11 +150,12 @@ class FastPitchTTSService(RivaTTSService):
"function_id": "0149dedb-2be8-4195-b9a0-e57e0e14f972",
"model_name": "fastpitch-hifigan-tts",
},
params: InputParams = InputParams(),
params: Optional[RivaTTSService.InputParams] = None,
**kwargs,
):
super().__init__(
api_key=api_key,
server=server,
voice_id=voice_id,
sample_rate=sample_rate,
model_function_map=model_function_map,

View File

@@ -64,7 +64,7 @@ class TTSService(AIService):
# Text aggregator to aggregate incoming tokens and decide when to push to the TTS.
text_aggregator: Optional[BaseTextAggregator] = None,
# Text filter executed after text has been aggregated.
text_filters: Sequence[BaseTextFilter] = [],
text_filters: Optional[Sequence[BaseTextFilter]] = None,
text_filter: Optional[BaseTextFilter] = None,
# Audio transport destination of the generated frames.
transport_destination: Optional[str] = None,
@@ -83,7 +83,7 @@ class TTSService(AIService):
self._voice_id: str = ""
self._settings: Dict[str, Any] = {}
self._text_aggregator: BaseTextAggregator = text_aggregator or SimpleTextAggregator()
self._text_filters: Sequence[BaseTextFilter] = text_filters
self._text_filters: Sequence[BaseTextFilter] = text_filters or []
self._transport_destination: Optional[str] = transport_destination
if text_filter:

View File

@@ -79,10 +79,13 @@ async def run_test(
expected_down_frames: Optional[Sequence[type]] = None,
expected_up_frames: Optional[Sequence[type]] = None,
ignore_start: bool = True,
observers: List[BaseObserver] = [],
start_metadata: Dict[str, Any] = {},
observers: Optional[List[BaseObserver]] = None,
start_metadata: Optional[Dict[str, Any]] = None,
send_end_frame: bool = True,
) -> Tuple[Sequence[Frame], Sequence[Frame]]:
observers = observers or []
start_metadata = start_metadata or {}
received_up = asyncio.Queue()
received_down = asyncio.Queue()
source = QueuedFrameProcessor(

View File

@@ -250,11 +250,11 @@ class WebsocketClientTransport(BaseTransport):
def __init__(
self,
uri: str,
params: WebsocketClientParams = WebsocketClientParams(),
params: Optional[WebsocketClientParams] = None,
):
super().__init__()
self._params = params
self._params = params or WebsocketClientParams()
callbacks = WebsocketClientCallbacks(
on_connected=self._on_connected,
@@ -262,7 +262,7 @@ class WebsocketClientTransport(BaseTransport):
on_message=self._on_message,
)
self._session = WebsocketClientSession(uri, params, callbacks, self.name)
self._session = WebsocketClientSession(uri, self._params, callbacks, self.name)
self._input: Optional[WebsocketClientInputTransport] = None
self._output: Optional[WebsocketClientOutputTransport] = None

View File

@@ -95,7 +95,7 @@ class WebRTCVADAnalyzer(VADAnalyzer):
params: VAD configuration parameters (VADParams).
"""
def __init__(self, *, sample_rate: Optional[int] = None, params: VADParams = VADParams()):
def __init__(self, *, sample_rate: Optional[int] = None, params: Optional[VADParams] = None):
super().__init__(sample_rate=sample_rate, params=params)
self._webrtc_vad = Daily.create_native_vad(
@@ -1223,7 +1223,7 @@ class DailyTransport(BaseTransport):
room_url: str,
token: Optional[str],
bot_name: str,
params: DailyParams = DailyParams(),
params: Optional[DailyParams] = None,
input_name: Optional[str] = None,
output_name: Optional[str] = None,
):
@@ -1256,9 +1256,11 @@ class DailyTransport(BaseTransport):
on_recording_stopped=self._on_recording_stopped,
on_recording_error=self._on_recording_error,
)
self._params = params
self._params = params or DailyParams()
self._client = DailyTransportClient(room_url, token, bot_name, params, callbacks, self.name)
self._client = DailyTransportClient(
room_url, token, bot_name, self._params, callbacks, self.name
)
self._input: Optional[DailyInputTransport] = None
self._output: Optional[DailyOutputTransport] = None

View File

@@ -499,7 +499,7 @@ class LiveKitTransport(BaseTransport):
url: str,
token: str,
room_name: str,
params: LiveKitParams = LiveKitParams(),
params: Optional[LiveKitParams] = None,
input_name: Optional[str] = None,
output_name: Optional[str] = None,
):
@@ -515,7 +515,7 @@ class LiveKitTransport(BaseTransport):
on_data_received=self._on_data_received,
on_first_participant_joined=self._on_first_participant_joined,
)
self._params = params
self._params = params or LiveKitParams()
self._client = LiveKitTransportClient(
url, token, room_name, self._params, callbacks, self.name

View File

@@ -26,9 +26,9 @@ class MarkdownTextFilter(BaseTextFilter):
filter_code: Optional[bool] = False
filter_tables: Optional[bool] = False
def __init__(self, params: InputParams = InputParams(), **kwargs):
def __init__(self, params: Optional[InputParams] = None, **kwargs):
super().__init__(**kwargs)
self._settings = params
self._settings = params or MarkdownTextFilter.InputParams()
self._in_code_block = False
self._in_table = False
self._interrupted = False