Update STT service settings
This commit is contained in:
@@ -22,9 +22,12 @@ from pipecat.processors.aggregators.llm_response_universal import (
|
||||
)
|
||||
from pipecat.runner.types import RunnerArguments
|
||||
from pipecat.runner.utils import create_transport
|
||||
from pipecat.services.aws.llm import AWSBedrockLLMService
|
||||
from pipecat.services.aws.llm import AWSBedrockLLMService, AWSBedrockLLMSettings
|
||||
from pipecat.services.deepgram.sagemaker.stt import DeepgramSageMakerSTTService
|
||||
from pipecat.services.deepgram.sagemaker.tts import DeepgramSageMakerTTSService
|
||||
from pipecat.services.deepgram.sagemaker.tts import (
|
||||
DeepgramSageMakerTTSService,
|
||||
DeepgramSageMakerTTSSettings,
|
||||
)
|
||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||
from pipecat.transports.daily.transport import DailyParams
|
||||
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
||||
@@ -69,14 +72,18 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
||||
tts = DeepgramSageMakerTTSService(
|
||||
endpoint_name=os.getenv("SAGEMAKER_TTS_ENDPOINT_NAME"),
|
||||
region=os.getenv("AWS_REGION"),
|
||||
voice="aura-2-andromeda-en",
|
||||
settings=DeepgramSageMakerTTSSettings(
|
||||
voice="aura-2-andromeda-en",
|
||||
),
|
||||
)
|
||||
|
||||
llm = AWSBedrockLLMService(
|
||||
aws_region=os.getenv("AWS_REGION"),
|
||||
model="us.amazon.nova-pro-v1:0",
|
||||
params=AWSBedrockLLMService.InputParams(temperature=0.8),
|
||||
system_instruction="You are a helpful LLM in a WebRTC call. Your goal is to demonstrate your capabilities in a succinct way. Your output will be spoken aloud, so avoid special characters that can't easily be spoken, such as emojis or bullet points. Respond to what the user said in a creative and helpful way.",
|
||||
settings=AWSBedrockLLMSettings(
|
||||
model="us.amazon.nova-pro-v1:0",
|
||||
temperature=0.8,
|
||||
),
|
||||
)
|
||||
|
||||
context = LLMContext()
|
||||
|
||||
@@ -23,8 +23,8 @@ from pipecat.processors.aggregators.llm_response_universal import (
|
||||
from pipecat.runner.types import RunnerArguments
|
||||
from pipecat.runner.utils import create_transport
|
||||
from pipecat.services.openai.llm import OpenAILLMService
|
||||
from pipecat.services.openai.stt import OpenAISTTService
|
||||
from pipecat.services.openai.tts import OpenAITTSService
|
||||
from pipecat.services.openai.stt import OpenAISTTService, OpenAISTTSettings
|
||||
from pipecat.services.openai.tts import OpenAITTSService, OpenAITTSSettings
|
||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||
from pipecat.transports.daily.transport import DailyParams
|
||||
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
||||
@@ -54,11 +54,18 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
||||
|
||||
stt = OpenAISTTService(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
model="gpt-4o-transcribe",
|
||||
prompt="Expect words related to dogs, such as breed names.",
|
||||
settings=OpenAISTTSettings(
|
||||
model="gpt-4o-transcribe",
|
||||
prompt="Expect words related to dogs, such as breed names.",
|
||||
),
|
||||
)
|
||||
|
||||
tts = OpenAITTSService(api_key=os.getenv("OPENAI_API_KEY"), voice="ballad")
|
||||
tts = OpenAITTSService(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
settings=OpenAITTSSettings(
|
||||
voice="ballad",
|
||||
),
|
||||
)
|
||||
|
||||
llm = OpenAILLMService(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
|
||||
@@ -124,6 +124,9 @@ AnyMessage = BeginMessage | TurnMessage | SpeechStartedMessage | TerminationMess
|
||||
class AssemblyAIConnectionParams(BaseModel):
|
||||
"""Configuration parameters for AssemblyAI WebSocket connection.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=AssemblyAISTTSettings(foo=...)`` instead.
|
||||
|
||||
Parameters:
|
||||
sample_rate: Audio sample rate in Hz. Defaults to 16000.
|
||||
encoding: Audio encoding format. Defaults to "pcm_s16le".
|
||||
|
||||
@@ -13,7 +13,7 @@ WebSocket API for streaming audio transcription.
|
||||
import asyncio
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, AsyncGenerator, Dict, Optional
|
||||
from typing import Any, AsyncGenerator, Dict, List, Optional
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from loguru import logger
|
||||
@@ -83,15 +83,38 @@ def map_language_from_assemblyai(language_code: str) -> Language:
|
||||
class AssemblyAISTTSettings(STTSettings):
|
||||
"""Settings for the AssemblyAI STT service.
|
||||
|
||||
See :class:`AssemblyAIConnectionParams` for detailed parameter descriptions.
|
||||
|
||||
Parameters:
|
||||
connection_params: Connection configuration parameters.
|
||||
formatted_finals: Whether to enable transcript formatting.
|
||||
word_finalization_max_wait_time: Maximum time to wait for word
|
||||
finalization in milliseconds.
|
||||
end_of_turn_confidence_threshold: Confidence threshold for
|
||||
end-of-turn detection.
|
||||
min_turn_silence: Minimum silence duration when confident about
|
||||
end-of-turn.
|
||||
max_turn_silence: Maximum silence duration before forcing
|
||||
end-of-turn.
|
||||
keyterms_prompt: List of key terms to guide transcription.
|
||||
prompt: Optional text prompt to guide the transcription. Only
|
||||
used when model is "u3-rt-pro".
|
||||
language_detection: Enable automatic language detection.
|
||||
format_turns: Whether to format transcript turns.
|
||||
speaker_labels: Enable speaker diarization.
|
||||
"""
|
||||
|
||||
connection_params: AssemblyAIConnectionParams | _NotGiven = field(
|
||||
formatted_finals: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
word_finalization_max_wait_time: int | None | _NotGiven = field(
|
||||
default_factory=lambda: NOT_GIVEN
|
||||
)
|
||||
end_of_turn_confidence_threshold: float | None | _NotGiven = field(
|
||||
default_factory=lambda: NOT_GIVEN
|
||||
)
|
||||
min_turn_silence: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
max_turn_silence: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
keyterms_prompt: List[str] | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
prompt: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
language_detection: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
format_turns: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
speaker_labels: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
|
||||
|
||||
class AssemblyAISTTService(WebsocketSTTService):
|
||||
@@ -110,6 +133,8 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
api_key: str,
|
||||
language: Optional[Language] = None,
|
||||
api_endpoint_base_url: str = "wss://streaming.assemblyai.com/v3/ws",
|
||||
sample_rate: int = 16000,
|
||||
encoding: str = "pcm_s16le",
|
||||
connection_params: Optional[AssemblyAIConnectionParams] = None,
|
||||
vad_force_turn_endpoint: bool = True,
|
||||
should_interrupt: bool = True,
|
||||
@@ -123,8 +148,18 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
Args:
|
||||
api_key: AssemblyAI API key for authentication.
|
||||
language: Language code for transcription. Defaults to English (Language.EN).
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=AssemblyAISTTSettings(language=...)`` instead.
|
||||
|
||||
api_endpoint_base_url: WebSocket endpoint URL. Defaults to AssemblyAI's streaming endpoint.
|
||||
connection_params: Connection configuration parameters. Defaults to AssemblyAIConnectionParams().
|
||||
sample_rate: Audio sample rate in Hz. Defaults to 16000.
|
||||
encoding: Audio encoding format. Defaults to "pcm_s16le".
|
||||
connection_params: Connection configuration parameters.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=AssemblyAISTTSettings(...)`` instead.
|
||||
|
||||
vad_force_turn_endpoint: Controls turn detection mode.
|
||||
When True (Pipecat mode, default): Forces AssemblyAI to return finals ASAP
|
||||
so Pipecat's turn detection (e.g., Smart Turn) decides when the user is done.
|
||||
@@ -135,7 +170,6 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
When False (AssemblyAI turn detection mode, u3-rt-pro only): AssemblyAI's model
|
||||
controls turn endings using built-in turn detection.
|
||||
- Uses AssemblyAI API defaults for all parameters (unless user explicitly sets them)
|
||||
- Respects all user-provided connection_params as-is
|
||||
- Emits UserStarted/StoppedSpeakingFrame from STT
|
||||
- No ForceEndpoint on VAD stop
|
||||
should_interrupt: Whether to interrupt the bot when the user starts speaking
|
||||
@@ -145,39 +179,80 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
Use {speaker} for speaker label and {text} for transcript text.
|
||||
Example: "<{speaker}>{text}</{speaker}>" or "{speaker}: {text}"
|
||||
If None, transcript text is not modified. Defaults to None.
|
||||
settings: Runtime-updatable settings. When provided alongside other
|
||||
settings: Runtime-updatable settings. When provided alongside deprecated
|
||||
parameters, ``settings`` values take precedence.
|
||||
ttfs_p99_latency: P99 latency from speech end to final transcript in seconds.
|
||||
Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark
|
||||
**kwargs: Additional arguments passed to parent STTService class.
|
||||
"""
|
||||
# Resolve connection_params early — needed for validation and turn mode config
|
||||
_connection_params = connection_params or AssemblyAIConnectionParams()
|
||||
# 1. Initialize default_settings with hardcoded defaults
|
||||
default_settings = AssemblyAISTTSettings(
|
||||
model="u3-rt-pro",
|
||||
language=Language.EN,
|
||||
formatted_finals=True,
|
||||
word_finalization_max_wait_time=None,
|
||||
end_of_turn_confidence_threshold=None,
|
||||
min_turn_silence=None,
|
||||
max_turn_silence=None,
|
||||
keyterms_prompt=None,
|
||||
prompt=None,
|
||||
language_detection=None,
|
||||
format_turns=True,
|
||||
speaker_labels=None,
|
||||
)
|
||||
|
||||
# AssemblyAI turn detection mode (vad_force_turn_endpoint=False) requires the
|
||||
# SpeechStarted event for reliable barge-in. Only u3-rt-pro supports
|
||||
# this. Other models must use Pipecat turn detection.
|
||||
is_u3_pro = _connection_params.speech_model == "u3-rt-pro"
|
||||
# 2. Apply direct init arg overrides (deprecated)
|
||||
if language is not None:
|
||||
_warn_deprecated_param("language", AssemblyAISTTSettings, "language")
|
||||
default_settings.language = language
|
||||
|
||||
# 3. Apply connection_params overrides (deprecated) — only if settings not provided
|
||||
if connection_params is not None:
|
||||
_warn_deprecated_param("connection_params", AssemblyAISTTSettings)
|
||||
if not settings:
|
||||
sample_rate = connection_params.sample_rate
|
||||
encoding = connection_params.encoding
|
||||
default_settings.model = connection_params.speech_model
|
||||
default_settings.formatted_finals = connection_params.formatted_finals
|
||||
default_settings.word_finalization_max_wait_time = (
|
||||
connection_params.word_finalization_max_wait_time
|
||||
)
|
||||
default_settings.end_of_turn_confidence_threshold = (
|
||||
connection_params.end_of_turn_confidence_threshold
|
||||
)
|
||||
default_settings.min_turn_silence = connection_params.min_turn_silence
|
||||
default_settings.max_turn_silence = connection_params.max_turn_silence
|
||||
default_settings.keyterms_prompt = connection_params.keyterms_prompt
|
||||
default_settings.prompt = connection_params.prompt
|
||||
default_settings.language_detection = connection_params.language_detection
|
||||
default_settings.format_turns = connection_params.format_turns
|
||||
default_settings.speaker_labels = connection_params.speaker_labels
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
if settings is not None:
|
||||
default_settings.apply_update(settings)
|
||||
|
||||
# 5. Validate final settings
|
||||
is_u3_pro = default_settings.model == "u3-rt-pro"
|
||||
if not vad_force_turn_endpoint and not is_u3_pro:
|
||||
raise ValueError(
|
||||
f"AssemblyAI turn detection mode (vad_force_turn_endpoint=False) requires "
|
||||
f"u3-rt-pro for SpeechStarted support. Either set "
|
||||
f"vad_force_turn_endpoint=True for {_connection_params.speech_model}, "
|
||||
f"or use speech_model='u3-rt-pro'."
|
||||
f"vad_force_turn_endpoint=True for {default_settings.model}, "
|
||||
f"or use model='u3-rt-pro'."
|
||||
)
|
||||
|
||||
# Validate that prompt and keyterms_prompt are not both set
|
||||
if _connection_params.prompt is not None and _connection_params.keyterms_prompt is not None:
|
||||
if default_settings.prompt is not None and default_settings.keyterms_prompt is not None:
|
||||
raise ValueError(
|
||||
"The prompt and keyterms_prompt parameters cannot be used in the same request. "
|
||||
"Please choose either one or the other based on your use case. When you use "
|
||||
"keyterms_prompt, your boosted words are appended to the default prompt automatically. "
|
||||
"Or to boost within prompt: <prompt> + Make sure to boost the words <keyterms> in the audio. "
|
||||
"Or to boost within prompt: <prompt> + Make sure to boost the words <keyterms> "
|
||||
"in the audio. "
|
||||
"For more info go to: https://www.assemblyai.com/docs/streaming/universal-3-pro"
|
||||
)
|
||||
|
||||
# Warn if user sets a custom prompt (recommend testing without one first)
|
||||
if _connection_params.prompt is not None:
|
||||
if default_settings.prompt is not None:
|
||||
logger.warning(
|
||||
"Custom prompt detected. Prompting is a beta feature. We recommend testing "
|
||||
"with no prompt first, as this will use our optimized default prompt for "
|
||||
@@ -186,35 +261,12 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
"https://www.assemblyai.com/docs/streaming/prompting"
|
||||
)
|
||||
|
||||
# When vad_force_turn_endpoint is enabled, configure connection params
|
||||
# for Pipecat turn detection mode (fast finals for smart turn analyzer)
|
||||
# 6. Configure pipecat turn mode (mutates default_settings)
|
||||
if vad_force_turn_endpoint:
|
||||
_connection_params = self._configure_pipecat_turn_mode(_connection_params, is_u3_pro)
|
||||
|
||||
# 1. Initialize default_settings with hardcoded defaults
|
||||
default_settings = AssemblyAISTTSettings(
|
||||
model=None,
|
||||
language=Language.EN,
|
||||
connection_params=AssemblyAIConnectionParams(),
|
||||
)
|
||||
|
||||
# 2. Apply direct init arg overrides (deprecated)
|
||||
if language is not None:
|
||||
_warn_deprecated_param("language", AssemblyAISTTSettings, "language")
|
||||
default_settings.language = language
|
||||
|
||||
# 3. Apply connection_params overrides — only if settings not provided
|
||||
if connection_params is not None:
|
||||
_warn_deprecated_param("connection_params", AssemblyAISTTSettings)
|
||||
if not settings:
|
||||
default_settings.connection_params = _connection_params
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
if settings is not None:
|
||||
default_settings.apply_update(settings)
|
||||
self._configure_pipecat_turn_mode(default_settings, is_u3_pro)
|
||||
|
||||
super().__init__(
|
||||
sample_rate=_connection_params.sample_rate,
|
||||
sample_rate=sample_rate,
|
||||
ttfs_p99_latency=ttfs_p99_latency,
|
||||
settings=default_settings,
|
||||
**kwargs,
|
||||
@@ -226,6 +278,9 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
self._should_interrupt = should_interrupt
|
||||
self._speaker_format = speaker_format
|
||||
|
||||
# Init-only audio config (not runtime-updatable)
|
||||
self._encoding = encoding
|
||||
|
||||
self._termination_event = asyncio.Event()
|
||||
self._received_termination = False
|
||||
self._connected = False
|
||||
@@ -238,10 +293,8 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
|
||||
self._user_speaking = False
|
||||
|
||||
def _configure_pipecat_turn_mode(
|
||||
self, connection_params: AssemblyAIConnectionParams, is_u3_pro: bool
|
||||
) -> AssemblyAIConnectionParams:
|
||||
"""Configure connection params for Pipecat turn detection mode.
|
||||
def _configure_pipecat_turn_mode(self, settings: AssemblyAISTTSettings, is_u3_pro: bool):
|
||||
"""Configure settings for Pipecat turn detection mode.
|
||||
|
||||
When vad_force_turn_endpoint is enabled, force AssemblyAI to return
|
||||
finals as fast as possible so Pipecat's smart turn analyzer can decide
|
||||
@@ -260,46 +313,31 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
- max_turn_silence: not set (API default)
|
||||
|
||||
Args:
|
||||
connection_params: The user-provided connection parameters.
|
||||
settings: The settings to configure in place.
|
||||
is_u3_pro: Whether using u3-rt-pro model.
|
||||
|
||||
Returns:
|
||||
Updated connection parameters configured for Pipecat turn mode.
|
||||
"""
|
||||
updates = {}
|
||||
|
||||
if is_u3_pro:
|
||||
# u3-rt-pro: Synchronize max_turn_silence with min_turn_silence
|
||||
min_silence = connection_params.min_turn_silence
|
||||
min_silence = settings.min_turn_silence
|
||||
if min_silence is None:
|
||||
min_silence = 100
|
||||
|
||||
# Warn if user set max_turn_silence (will be overridden)
|
||||
if connection_params.max_turn_silence is not None:
|
||||
if settings.max_turn_silence is not None:
|
||||
logger.warning(
|
||||
f"Your max_turn_silence value ({connection_params.max_turn_silence}ms) will be "
|
||||
f"Your max_turn_silence value ({settings.max_turn_silence}ms) will be "
|
||||
f"OVERRIDDEN in Pipecat mode (vad_force_turn_endpoint=True). It will be set to "
|
||||
f"{min_silence}ms (matching min_turn_silence) and SENT to "
|
||||
f"AssemblyAI to avoid double turn detection. To use your max_turn_silence as-is, "
|
||||
f"switch to AssemblyAI turn detection mode (vad_force_turn_endpoint=False)."
|
||||
)
|
||||
|
||||
updates = {
|
||||
"min_turn_silence": min_silence,
|
||||
"max_turn_silence": min_silence,
|
||||
}
|
||||
settings.min_turn_silence = min_silence
|
||||
settings.max_turn_silence = min_silence
|
||||
else:
|
||||
# universal-streaming: Different configuration (works differently)
|
||||
updates = {
|
||||
"end_of_turn_confidence_threshold": 1.0,
|
||||
"min_turn_silence": 160,
|
||||
}
|
||||
|
||||
# Apply updates if any
|
||||
if updates:
|
||||
connection_params = connection_params.model_copy(update=updates)
|
||||
|
||||
return connection_params
|
||||
settings.end_of_turn_confidence_threshold = 1.0
|
||||
settings.min_turn_silence = 160
|
||||
|
||||
def can_generate_metrics(self) -> bool:
|
||||
"""Check if the service can generate metrics.
|
||||
@@ -309,18 +347,11 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, delta: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings delta and send UpdateConfiguration if connected.
|
||||
|
||||
Stores settings changes and sends UpdateConfiguration message to AssemblyAI
|
||||
without reconnecting. Supports updating:
|
||||
- keyterms_prompt: List of terms to boost (can be empty array to clear)
|
||||
- prompt: Custom prompt text (u3-rt-pro only)
|
||||
- max_turn_silence: Maximum silence before forcing turn end
|
||||
- min_turn_silence: Silence before EOT check
|
||||
async def _update_settings(self, delta: AssemblyAISTTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings delta and reconnect to apply changes.
|
||||
|
||||
Args:
|
||||
delta: A :class:`STTSettings` (or ``AssemblyAISTTSettings``) delta.
|
||||
delta: A settings delta with updated values.
|
||||
|
||||
Returns:
|
||||
Dict mapping changed field names to their previous values.
|
||||
@@ -330,72 +361,9 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
if not changed:
|
||||
return changed
|
||||
|
||||
# If websocket is connected, send UpdateConfiguration for supported params
|
||||
if (
|
||||
self._websocket
|
||||
and self._websocket.state is State.OPEN
|
||||
and "connection_params" in changed
|
||||
):
|
||||
# Build UpdateConfiguration message
|
||||
update_config = {"type": "UpdateConfiguration"}
|
||||
conn_params = self._settings.connection_params
|
||||
|
||||
# Get the old connection_params to see what changed
|
||||
old_conn_params = changed.get("connection_params")
|
||||
|
||||
# Check each potentially changed parameter
|
||||
if (
|
||||
old_conn_params is None
|
||||
or conn_params.keyterms_prompt != old_conn_params.keyterms_prompt
|
||||
):
|
||||
if conn_params.keyterms_prompt is not None:
|
||||
update_config["keyterms_prompt"] = conn_params.keyterms_prompt
|
||||
logger.info(f"Updating keyterms_prompt to: {conn_params.keyterms_prompt}")
|
||||
|
||||
if old_conn_params is None or conn_params.prompt != old_conn_params.prompt:
|
||||
if conn_params.prompt is not None:
|
||||
if conn_params.speech_model != "u3-rt-pro":
|
||||
logger.warning(
|
||||
f"prompt parameter is only supported with u3-rt-pro model, "
|
||||
f"current model is {conn_params.speech_model}"
|
||||
)
|
||||
else:
|
||||
update_config["prompt"] = conn_params.prompt
|
||||
logger.info(f"Updating prompt")
|
||||
|
||||
if (
|
||||
old_conn_params is None
|
||||
or conn_params.max_turn_silence != old_conn_params.max_turn_silence
|
||||
):
|
||||
if conn_params.max_turn_silence is not None:
|
||||
update_config["max_turn_silence"] = conn_params.max_turn_silence
|
||||
logger.info(f"Updating max_turn_silence to: {conn_params.max_turn_silence}ms")
|
||||
|
||||
if (
|
||||
old_conn_params is None
|
||||
or conn_params.min_turn_silence != old_conn_params.min_turn_silence
|
||||
):
|
||||
if conn_params.min_turn_silence is not None:
|
||||
update_config["min_turn_silence"] = conn_params.min_turn_silence
|
||||
logger.info(f"Updating min_turn_silence to: {conn_params.min_turn_silence}ms")
|
||||
|
||||
# Send update if we have parameters to update
|
||||
if len(update_config) > 1: # More than just "type"
|
||||
try:
|
||||
await self._websocket.send(json.dumps(update_config))
|
||||
logger.info(f"Sent UpdateConfiguration: {update_config}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send UpdateConfiguration: {e}")
|
||||
elif "connection_params" in changed:
|
||||
logger.warning(
|
||||
"Connection params changed but WebSocket not connected. "
|
||||
"Settings will be applied on next connection."
|
||||
)
|
||||
|
||||
# Warn about other settings that can't be changed dynamically
|
||||
other_changes = {k: v for k, v in changed.items() if k not in ["connection_params"]}
|
||||
if other_changes:
|
||||
self._warn_unhandled_updated_settings(other_changes)
|
||||
# Reconnect to apply updated settings (they become WS query params)
|
||||
await self._disconnect()
|
||||
await self._connect()
|
||||
|
||||
return changed
|
||||
|
||||
@@ -473,19 +441,41 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
|
||||
def _build_ws_url(self) -> str:
|
||||
"""Build WebSocket URL with query parameters using urllib.parse.urlencode."""
|
||||
params = {}
|
||||
for k, v in self._settings.connection_params.model_dump().items():
|
||||
# Skip deprecated parameter - it's been migrated to min_turn_silence
|
||||
if k == "min_end_of_turn_silence_when_confident":
|
||||
continue
|
||||
s = self._settings
|
||||
params: dict[str, Any] = {}
|
||||
|
||||
# Init-only audio config
|
||||
params["sample_rate"] = self.sample_rate
|
||||
params["encoding"] = self._encoding
|
||||
|
||||
# Map model → speech_model (AssemblyAI API naming)
|
||||
if s.model is not None:
|
||||
params["speech_model"] = s.model
|
||||
|
||||
# Settings fields (skip None values)
|
||||
optional_fields = {
|
||||
"formatted_finals": s.formatted_finals,
|
||||
"word_finalization_max_wait_time": s.word_finalization_max_wait_time,
|
||||
"end_of_turn_confidence_threshold": s.end_of_turn_confidence_threshold,
|
||||
"min_turn_silence": s.min_turn_silence,
|
||||
"max_turn_silence": s.max_turn_silence,
|
||||
"prompt": s.prompt,
|
||||
"language_detection": s.language_detection,
|
||||
"format_turns": s.format_turns,
|
||||
"speaker_labels": s.speaker_labels,
|
||||
}
|
||||
|
||||
for k, v in optional_fields.items():
|
||||
if v is not None:
|
||||
if k == "keyterms_prompt":
|
||||
params[k] = json.dumps(v)
|
||||
elif isinstance(v, bool):
|
||||
if isinstance(v, bool):
|
||||
params[k] = str(v).lower()
|
||||
else:
|
||||
params[k] = v
|
||||
|
||||
# Special handling for keyterms_prompt (needs JSON encoding)
|
||||
if s.keyterms_prompt is not None:
|
||||
params["keyterms_prompt"] = json.dumps(s.keyterms_prompt)
|
||||
|
||||
if params:
|
||||
query_string = urlencode(params)
|
||||
return f"{self._api_endpoint_base_url}?{query_string}"
|
||||
@@ -717,7 +707,7 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
|
||||
# Determine if this is a final turn from AssemblyAI
|
||||
is_final_turn = message.end_of_turn and (
|
||||
not self._settings.connection_params.format_turns or message.turn_is_formatted
|
||||
not self._settings.format_turns or message.turn_is_formatted
|
||||
)
|
||||
|
||||
if self._vad_force_turn_endpoint:
|
||||
|
||||
@@ -14,7 +14,7 @@ import json
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
@@ -29,7 +29,7 @@ from pipecat.frames.frames import (
|
||||
TranscriptionFrame,
|
||||
)
|
||||
from pipecat.services.aws.utils import build_event_message, decode_event, get_presigned_url
|
||||
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.settings import STTSettings, _warn_deprecated_param
|
||||
from pipecat.services.stt_latency import AWS_TRANSCRIBE_TTFS_P99
|
||||
from pipecat.services.stt_service import WebsocketSTTService
|
||||
from pipecat.transcriptions.language import Language, resolve_language
|
||||
@@ -47,21 +47,9 @@ except ModuleNotFoundError as e:
|
||||
|
||||
@dataclass
|
||||
class AWSTranscribeSTTSettings(STTSettings):
|
||||
"""Settings for the AWS Transcribe STT service.
|
||||
"""Settings for the AWS Transcribe STT service."""
|
||||
|
||||
Parameters:
|
||||
sample_rate: Audio sample rate in Hz (8000 or 16000).
|
||||
media_encoding: Audio encoding format (e.g. "linear16").
|
||||
number_of_channels: Number of audio channels.
|
||||
show_speaker_label: Whether to show speaker labels.
|
||||
enable_channel_identification: Whether to enable channel identification.
|
||||
"""
|
||||
|
||||
sample_rate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
media_encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
number_of_channels: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
show_speaker_label: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
enable_channel_identification: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
pass
|
||||
|
||||
|
||||
class AWSTranscribeSTTService(WebsocketSTTService):
|
||||
@@ -94,11 +82,9 @@ class AWSTranscribeSTTService(WebsocketSTTService):
|
||||
aws_access_key_id: AWS access key ID. If None, uses AWS_ACCESS_KEY_ID environment variable.
|
||||
aws_session_token: AWS session token for temporary credentials. If None, uses AWS_SESSION_TOKEN environment variable.
|
||||
region: AWS region for the service.
|
||||
sample_rate: Audio sample rate in Hz. Must be 8000 or 16000.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=AWSTranscribeSTTSettings(sample_rate=...)`` instead.
|
||||
|
||||
sample_rate: Audio sample rate in Hz. If None, uses the pipeline sample rate.
|
||||
AWS Transcribe only supports 8000 or 16000 Hz; other values are
|
||||
clamped to 16000 Hz at connect time.
|
||||
language: Language for transcription.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
@@ -113,17 +99,9 @@ class AWSTranscribeSTTService(WebsocketSTTService):
|
||||
# 1. Initialize default_settings with hardcoded defaults
|
||||
default_settings = AWSTranscribeSTTSettings(
|
||||
language=self.language_to_service_language(Language.EN) or "en-US",
|
||||
sample_rate=16000,
|
||||
media_encoding="linear16",
|
||||
number_of_channels=1,
|
||||
show_speaker_label=False,
|
||||
enable_channel_identification=False,
|
||||
)
|
||||
|
||||
# 2. Apply direct init arg overrides (deprecated)
|
||||
if sample_rate is not None:
|
||||
_warn_deprecated_param("sample_rate", AWSTranscribeSTTSettings, "sample_rate")
|
||||
default_settings.sample_rate = sample_rate
|
||||
if language is not None:
|
||||
_warn_deprecated_param("language", AWSTranscribeSTTSettings, "language")
|
||||
default_settings.language = self.language_to_service_language(language) or "en-US"
|
||||
@@ -135,17 +113,17 @@ class AWSTranscribeSTTService(WebsocketSTTService):
|
||||
default_settings.apply_update(settings)
|
||||
|
||||
super().__init__(
|
||||
sample_rate=sample_rate,
|
||||
ttfs_p99_latency=ttfs_p99_latency,
|
||||
settings=default_settings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Validate sample rate - AWS Transcribe only supports 8000 Hz or 16000 Hz
|
||||
if default_settings.sample_rate not in [8000, 16000]:
|
||||
logger.warning(
|
||||
f"AWS Transcribe only supports 8000 Hz or 16000 Hz sample rates. Converting from {default_settings.sample_rate} Hz to 16000 Hz."
|
||||
)
|
||||
self._settings.sample_rate = 16000
|
||||
# Init-only connection config (not runtime-updatable).
|
||||
self._media_encoding = "linear16"
|
||||
self._number_of_channels = 1
|
||||
self._show_speaker_label = False
|
||||
self._enable_channel_identification = False
|
||||
|
||||
self._credentials = {
|
||||
"aws_access_key_id": aws_access_key_id or os.getenv("AWS_ACCESS_KEY_ID"),
|
||||
@@ -293,6 +271,15 @@ class AWSTranscribeSTTService(WebsocketSTTService):
|
||||
if not language_code:
|
||||
raise ValueError(f"Unsupported language: {language_code}")
|
||||
|
||||
# Validate sample rate — AWS Transcribe only supports 8000 or 16000 Hz
|
||||
connect_sample_rate = self.sample_rate
|
||||
if connect_sample_rate not in (8000, 16000):
|
||||
logger.warning(
|
||||
f"AWS Transcribe only supports 8000 Hz or 16000 Hz sample rates. "
|
||||
f"Converting from {connect_sample_rate} Hz to 16000 Hz."
|
||||
)
|
||||
connect_sample_rate = 16000
|
||||
|
||||
# Generate random websocket key
|
||||
websocket_key = "".join(
|
||||
random.choices(
|
||||
@@ -318,14 +305,14 @@ class AWSTranscribeSTTService(WebsocketSTTService):
|
||||
},
|
||||
language_code=language_code,
|
||||
media_encoding=self.get_service_encoding(
|
||||
self._settings.media_encoding
|
||||
self._media_encoding
|
||||
), # Convert to AWS format
|
||||
sample_rate=self._settings.sample_rate,
|
||||
number_of_channels=self._settings.number_of_channels,
|
||||
sample_rate=connect_sample_rate,
|
||||
number_of_channels=self._number_of_channels,
|
||||
enable_partial_results_stabilization=True,
|
||||
partial_results_stability="high",
|
||||
show_speaker_label=self._settings.show_speaker_label,
|
||||
enable_channel_identification=self._settings.enable_channel_identification,
|
||||
show_speaker_label=self._show_speaker_label,
|
||||
enable_channel_identification=self._enable_channel_identification,
|
||||
)
|
||||
|
||||
logger.debug(f"{self} Connecting to WebSocket with URL: {presigned_url[:100]}...")
|
||||
|
||||
@@ -11,7 +11,7 @@ Speech SDK for real-time audio transcription.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
@@ -26,7 +26,7 @@ from pipecat.frames.frames import (
|
||||
TranscriptionFrame,
|
||||
)
|
||||
from pipecat.services.azure.common import language_to_azure_language
|
||||
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.settings import STTSettings, _warn_deprecated_param
|
||||
from pipecat.services.stt_latency import AZURE_TTFS_P99
|
||||
from pipecat.services.stt_service import STTService
|
||||
from pipecat.transcriptions.language import Language
|
||||
@@ -53,15 +53,9 @@ except ModuleNotFoundError as e:
|
||||
|
||||
@dataclass
|
||||
class AzureSTTSettings(STTSettings):
|
||||
"""Settings for the Azure STT service.
|
||||
"""Settings for the Azure STT service."""
|
||||
|
||||
Parameters:
|
||||
region: Azure region for the Speech service.
|
||||
sample_rate: Audio sample rate in Hz.
|
||||
"""
|
||||
|
||||
region: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
sample_rate: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
pass
|
||||
|
||||
|
||||
class AzureSTTService(STTService):
|
||||
@@ -110,9 +104,7 @@ class AzureSTTService(STTService):
|
||||
# 1. Initialize default_settings with hardcoded defaults
|
||||
default_settings = AzureSTTSettings(
|
||||
model=None,
|
||||
region=region,
|
||||
language=language_to_azure_language(Language.EN_US),
|
||||
sample_rate=sample_rate,
|
||||
)
|
||||
|
||||
# 2. Apply direct init arg overrides (deprecated)
|
||||
|
||||
@@ -12,7 +12,7 @@ the Cartesia Live transcription API for real-time speech recognition.
|
||||
|
||||
import json
|
||||
import urllib.parse
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
@@ -28,7 +28,7 @@ from pipecat.frames.frames import (
|
||||
VADUserStoppedSpeakingFrame,
|
||||
)
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.settings import STTSettings, _warn_deprecated_param
|
||||
from pipecat.services.stt_latency import CARTESIA_TTFS_P99
|
||||
from pipecat.services.stt_service import WebsocketSTTService
|
||||
from pipecat.transcriptions.language import Language
|
||||
@@ -46,20 +46,17 @@ except ModuleNotFoundError as e:
|
||||
|
||||
@dataclass
|
||||
class CartesiaSTTSettings(STTSettings):
|
||||
"""Settings for the Cartesia STT service.
|
||||
"""Settings for the Cartesia STT service."""
|
||||
|
||||
Parameters:
|
||||
encoding: Audio encoding format (e.g. ``"pcm_s16le"``).
|
||||
"""
|
||||
|
||||
encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
pass
|
||||
|
||||
|
||||
class CartesiaLiveOptions:
|
||||
"""Configuration options for Cartesia Live STT service.
|
||||
|
||||
Manages transcription parameters including model selection, language,
|
||||
audio encoding format, and sample rate settings.
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=CartesiaSTTSettings(...)`` for model/language and
|
||||
direct ``__init__`` parameters for encoding/sample_rate instead.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -156,7 +153,8 @@ class CartesiaSTTService(WebsocketSTTService):
|
||||
*,
|
||||
api_key: str,
|
||||
base_url: str = "",
|
||||
sample_rate: int = 16000,
|
||||
encoding: str = "pcm_s16le",
|
||||
sample_rate: Optional[int] = None,
|
||||
live_options: Optional[CartesiaLiveOptions] = None,
|
||||
settings: Optional[CartesiaSTTSettings] = None,
|
||||
ttfs_p99_latency: Optional[float] = CARTESIA_TTFS_P99,
|
||||
@@ -167,44 +165,42 @@ class CartesiaSTTService(WebsocketSTTService):
|
||||
Args:
|
||||
api_key: Authentication key for Cartesia API.
|
||||
base_url: Custom API endpoint URL. If empty, uses default.
|
||||
sample_rate: Audio sample rate in Hz. Defaults to 16000.
|
||||
encoding: Audio encoding format. Defaults to "pcm_s16le".
|
||||
sample_rate: Audio sample rate in Hz. If None, uses the pipeline
|
||||
sample rate.
|
||||
live_options: Configuration options for transcription service.
|
||||
settings: Runtime-updatable settings. When provided alongside
|
||||
``live_options``, ``settings`` values take precedence.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=CartesiaSTTSettings(...)`` for model/language
|
||||
and direct init parameters for encoding/sample_rate instead.
|
||||
|
||||
settings: Runtime-updatable settings. When provided alongside deprecated
|
||||
parameters, ``settings`` values take precedence.
|
||||
ttfs_p99_latency: P99 latency from speech end to final transcript in seconds.
|
||||
Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark
|
||||
**kwargs: Additional arguments passed to parent STTService.
|
||||
"""
|
||||
sample_rate = sample_rate or (live_options.sample_rate if live_options else None)
|
||||
|
||||
# 1. Initialize default_settings with hardcoded defaults
|
||||
default_settings = CartesiaSTTSettings(
|
||||
model="ink-whisper",
|
||||
language=Language.EN.value,
|
||||
encoding="pcm_s16le",
|
||||
)
|
||||
|
||||
# 2. (no deprecated direct args for this service)
|
||||
|
||||
# 3. Apply live_options overrides — only if settings not provided
|
||||
# 2. Apply live_options overrides — only if settings not provided
|
||||
if live_options is not None:
|
||||
_warn_deprecated_param("live_options", CartesiaSTTSettings)
|
||||
if not settings:
|
||||
lo_dict = live_options.to_dict()
|
||||
# Filter out "None" string values
|
||||
lo_dict = {
|
||||
k: v
|
||||
for k, v in lo_dict.items()
|
||||
if (not isinstance(v, str) or v != "None") and k != "sample_rate"
|
||||
}
|
||||
if "model" in lo_dict:
|
||||
default_settings.model = lo_dict["model"]
|
||||
if "language" in lo_dict:
|
||||
default_settings.language = lo_dict["language"]
|
||||
if "encoding" in lo_dict:
|
||||
default_settings.encoding = lo_dict["encoding"]
|
||||
if live_options.sample_rate and sample_rate is None:
|
||||
sample_rate = live_options.sample_rate
|
||||
if live_options.encoding:
|
||||
encoding = live_options.encoding
|
||||
if live_options.model:
|
||||
default_settings.model = live_options.model
|
||||
if live_options.language:
|
||||
lang = live_options.language
|
||||
default_settings.language = lang.value if isinstance(lang, Language) else lang
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
# 3. Apply settings delta (canonical API, always wins)
|
||||
if settings is not None:
|
||||
default_settings.apply_update(settings)
|
||||
|
||||
@@ -221,6 +217,9 @@ class CartesiaSTTService(WebsocketSTTService):
|
||||
self._base_url = base_url or "api.cartesia.ai"
|
||||
self._receive_task = None
|
||||
|
||||
# Init-only audio config (not runtime-updatable).
|
||||
self._encoding = encoding
|
||||
|
||||
def can_generate_metrics(self) -> bool:
|
||||
"""Check if the service can generate processing metrics.
|
||||
|
||||
@@ -339,7 +338,7 @@ class CartesiaSTTService(WebsocketSTTService):
|
||||
params = {
|
||||
"model": self._settings.model,
|
||||
"language": self._settings.language,
|
||||
"encoding": self._settings.encoding,
|
||||
"encoding": self._encoding,
|
||||
"sample_rate": str(self.sample_rate),
|
||||
}
|
||||
ws_url = f"wss://{self._base_url}/stt/websocket?{urllib.parse.urlencode(params)}"
|
||||
|
||||
@@ -81,20 +81,16 @@ class DeepgramFluxSTTSettings(STTSettings):
|
||||
eot_timeout_ms: Time in ms after speech to finish a turn regardless of EOT
|
||||
confidence (default 5000).
|
||||
keyterm: Keyterms to boost recognition accuracy for specialized terminology.
|
||||
mip_opt_out: Opt out of the Deepgram Model Improvement Program (default False).
|
||||
tag: Tags to label requests for identification during usage reporting.
|
||||
min_confidence: Minimum confidence required to create a TranscriptionFrame.
|
||||
encoding: Audio encoding format (e.g. ``"linear16"``).
|
||||
"""
|
||||
|
||||
eager_eot_threshold: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
eot_threshold: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
eot_timeout_ms: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
keyterm: list | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
mip_opt_out: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
tag: list | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
min_confidence: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
|
||||
|
||||
class DeepgramFluxSTTService(WebsocketSTTService):
|
||||
@@ -158,6 +154,7 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
||||
api_key: str,
|
||||
url: str = "wss://api.deepgram.com/v2/listen",
|
||||
sample_rate: Optional[int] = None,
|
||||
mip_opt_out: Optional[bool] = None,
|
||||
model: Optional[str] = None,
|
||||
flux_encoding: str = "linear16",
|
||||
params: Optional[InputParams] = None,
|
||||
@@ -170,7 +167,9 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
||||
Args:
|
||||
api_key: Deepgram API key for authentication. Required for API access.
|
||||
url: WebSocket URL for the Deepgram Flux API. Defaults to the preview endpoint.
|
||||
sample_rate: Audio sample rate in Hz. If None, uses the rate from params or 16000.
|
||||
sample_rate: Audio sample rate in Hz. If None, uses the pipeline
|
||||
sample rate.
|
||||
mip_opt_out: Opt out of the Deepgram Model Improvement Program.
|
||||
model: Deepgram Flux model to use for transcription.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
@@ -221,12 +220,10 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
||||
default_settings = DeepgramFluxSTTSettings(
|
||||
model="flux-general-en",
|
||||
language=Language.EN,
|
||||
encoding=flux_encoding,
|
||||
eager_eot_threshold=None,
|
||||
eot_threshold=None,
|
||||
eot_timeout_ms=None,
|
||||
keyterm=[],
|
||||
mip_opt_out=None,
|
||||
tag=[],
|
||||
min_confidence=None,
|
||||
)
|
||||
@@ -244,9 +241,10 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
||||
default_settings.eot_threshold = params.eot_threshold
|
||||
default_settings.eot_timeout_ms = params.eot_timeout_ms
|
||||
default_settings.keyterm = params.keyterm or []
|
||||
default_settings.mip_opt_out = params.mip_opt_out
|
||||
default_settings.tag = params.tag or []
|
||||
default_settings.min_confidence = params.min_confidence
|
||||
if params.mip_opt_out is not None:
|
||||
mip_opt_out = params.mip_opt_out
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
if settings is not None:
|
||||
@@ -261,8 +259,11 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
||||
self._api_key = api_key
|
||||
self._url = url
|
||||
self._should_interrupt = should_interrupt
|
||||
self._encoding = flux_encoding
|
||||
self._mip_opt_out = mip_opt_out
|
||||
self._websocket_url = None
|
||||
self._receive_task = None
|
||||
|
||||
# Flux event handlers
|
||||
self._register_event_handler("on_start_of_turn")
|
||||
self._register_event_handler("on_turn_resumed")
|
||||
@@ -448,7 +449,7 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
||||
url_params = [
|
||||
f"model={self._settings.model}",
|
||||
f"sample_rate={self.sample_rate}",
|
||||
f"encoding={self._settings.encoding}",
|
||||
f"encoding={self._encoding}",
|
||||
]
|
||||
|
||||
if self._settings.eager_eot_threshold is not None:
|
||||
@@ -460,8 +461,8 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
||||
if self._settings.eot_timeout_ms is not None:
|
||||
url_params.append(f"eot_timeout_ms={self._settings.eot_timeout_ms}")
|
||||
|
||||
if self._settings.mip_opt_out is not None:
|
||||
url_params.append(f"mip_opt_out={str(self._settings.mip_opt_out).lower()}")
|
||||
if self._mip_opt_out is not None:
|
||||
url_params.append(f"mip_opt_out={str(self._mip_opt_out).lower()}")
|
||||
|
||||
# Add keyterm parameters (can have multiple)
|
||||
for keyterm in self._settings.keyterm:
|
||||
|
||||
@@ -14,7 +14,7 @@ languages, and various Deepgram features.
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass, fields
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
@@ -32,32 +32,23 @@ from pipecat.frames.frames import (
|
||||
)
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.aws.sagemaker.bidi_client import SageMakerBidiClient
|
||||
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.deepgram.stt import DeepgramSTTSettings, LiveOptions
|
||||
from pipecat.services.settings import STTSettings, _warn_deprecated_param, is_given
|
||||
from pipecat.services.stt_latency import DEEPGRAM_SAGEMAKER_TTFS_P99
|
||||
from pipecat.services.stt_service import STTService
|
||||
from pipecat.transcriptions.language import Language
|
||||
from pipecat.utils.time import time_now_iso8601
|
||||
from pipecat.utils.tracing.service_decorators import traced_stt
|
||||
|
||||
try:
|
||||
from deepgram import LiveOptions
|
||||
except ModuleNotFoundError as e:
|
||||
logger.error(f"Exception: {e}")
|
||||
logger.error(
|
||||
"In order to use DeepgramSageMakerSTTService, you need to `pip install pipecat-ai[deepgram,sagemaker]`."
|
||||
)
|
||||
raise Exception(f"Missing module: {e}")
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeepgramSageMakerSTTSettings(STTSettings):
|
||||
class DeepgramSageMakerSTTSettings(DeepgramSTTSettings):
|
||||
"""Settings for the Deepgram SageMaker STT service.
|
||||
|
||||
Parameters:
|
||||
live_options: Deepgram LiveOptions for the SageMaker connection.
|
||||
Inherits all fields from :class:`DeepgramSTTSettings`.
|
||||
"""
|
||||
|
||||
live_options: Any | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
pass
|
||||
|
||||
|
||||
class DeepgramSageMakerSTTService(STTService):
|
||||
@@ -72,14 +63,13 @@ class DeepgramSageMakerSTTService(STTService):
|
||||
|
||||
- AWS credentials configured (via environment variables, AWS CLI, or instance metadata)
|
||||
- A deployed SageMaker endpoint with Deepgram model: https://developers.deepgram.com/docs/deploy-amazon-sagemaker
|
||||
- Deepgram SDK for LiveOptions configuration
|
||||
|
||||
Example::
|
||||
|
||||
stt = DeepgramSageMakerSTTService(
|
||||
endpoint_name="my-deepgram-endpoint",
|
||||
region="us-east-2",
|
||||
live_options=LiveOptions(
|
||||
settings=DeepgramSageMakerSTTSettings(
|
||||
model="nova-3",
|
||||
language="en",
|
||||
interim_results=True,
|
||||
@@ -95,7 +85,11 @@ class DeepgramSageMakerSTTService(STTService):
|
||||
*,
|
||||
endpoint_name: str,
|
||||
region: str,
|
||||
encoding: str = "linear16",
|
||||
channels: int = 1,
|
||||
multichannel: bool = False,
|
||||
sample_rate: Optional[int] = None,
|
||||
mip_opt_out: Optional[bool] = None,
|
||||
live_options: Optional[LiveOptions] = None,
|
||||
settings: Optional[DeepgramSageMakerSTTSettings] = None,
|
||||
ttfs_p99_latency: Optional[float] = DEEPGRAM_SAGEMAKER_TTFS_P99,
|
||||
@@ -107,11 +101,20 @@ class DeepgramSageMakerSTTService(STTService):
|
||||
endpoint_name: Name of the SageMaker endpoint with Deepgram model
|
||||
deployed (e.g., "my-deepgram-nova-3-endpoint").
|
||||
region: AWS region where the endpoint is deployed (e.g., "us-east-2").
|
||||
sample_rate: Audio sample rate in Hz. If None, uses value from
|
||||
live_options or defaults to the value from StartFrame.
|
||||
live_options: Deepgram LiveOptions configuration. Treated as a
|
||||
delta from a set of sensible defaults — only the fields you
|
||||
set are overridden; all others keep their default values.
|
||||
encoding: Audio encoding format. Defaults to "linear16".
|
||||
channels: Number of audio channels. Defaults to 1.
|
||||
multichannel: Transcribe each audio channel independently.
|
||||
Defaults to False.
|
||||
sample_rate: Audio sample rate in Hz. If None, uses the pipeline
|
||||
sample rate.
|
||||
mip_opt_out: Opt out of Deepgram model improvement program.
|
||||
live_options: Legacy configuration options.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=DeepgramSageMakerSTTSettings(...)`` for
|
||||
runtime-updatable fields and direct init parameters for
|
||||
connection-level config.
|
||||
|
||||
settings: Runtime-updatable settings. When provided alongside
|
||||
``live_options``, ``settings`` values take precedence (applied
|
||||
after the ``live_options`` merge).
|
||||
@@ -119,43 +122,63 @@ class DeepgramSageMakerSTTService(STTService):
|
||||
Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark
|
||||
**kwargs: Additional arguments passed to the parent STTService.
|
||||
"""
|
||||
sample_rate = sample_rate or (live_options.sample_rate if live_options else None)
|
||||
|
||||
default_options = LiveOptions(
|
||||
encoding="linear16",
|
||||
language=Language.EN,
|
||||
model="nova-3",
|
||||
channels=1,
|
||||
interim_results=True,
|
||||
punctuate=True,
|
||||
)
|
||||
|
||||
# 1. Initialize default_settings with hardcoded defaults
|
||||
default_settings = DeepgramSageMakerSTTSettings(
|
||||
model="nova-3",
|
||||
language=Language.EN,
|
||||
live_options=default_options,
|
||||
detect_entities=False,
|
||||
diarize=False,
|
||||
dictation=False,
|
||||
endpointing=None,
|
||||
interim_results=True,
|
||||
keyterm=None,
|
||||
keywords=None,
|
||||
numerals=False,
|
||||
profanity_filter=True,
|
||||
punctuate=True,
|
||||
redact=None,
|
||||
replace=None,
|
||||
search=None,
|
||||
smart_format=False,
|
||||
utterance_end_ms=None,
|
||||
vad_events=False,
|
||||
)
|
||||
|
||||
# 2. (no deprecated direct args like model= for this service)
|
||||
|
||||
# 3. Apply live_options overrides — only if settings not provided
|
||||
# 2. Apply live_options overrides — only if settings not provided
|
||||
if live_options is not None:
|
||||
_warn_deprecated_param("live_options", DeepgramSageMakerSTTSettings)
|
||||
if not settings:
|
||||
# Merge user live_options onto defaults
|
||||
merged_dict = {**default_options.to_dict(), **live_options.to_dict()}
|
||||
merged_live_options = LiveOptions(**merged_dict)
|
||||
default_settings.live_options = merged_live_options
|
||||
if hasattr(live_options, "model") and live_options.model is not None:
|
||||
default_settings.model = live_options.model
|
||||
if hasattr(live_options, "language") and live_options.language is not None:
|
||||
default_settings.language = live_options.language
|
||||
# Extract init-only fields from live_options
|
||||
if live_options.sample_rate is not None and sample_rate is None:
|
||||
sample_rate = live_options.sample_rate
|
||||
if live_options.encoding is not None:
|
||||
encoding = live_options.encoding
|
||||
if live_options.channels is not None:
|
||||
channels = live_options.channels
|
||||
if live_options.multichannel is not None:
|
||||
multichannel = live_options.multichannel
|
||||
if live_options.mip_opt_out is not None:
|
||||
mip_opt_out = live_options.mip_opt_out
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
# Build settings delta from remaining fields
|
||||
init_only = {
|
||||
"sample_rate",
|
||||
"encoding",
|
||||
"channels",
|
||||
"multichannel",
|
||||
"mip_opt_out",
|
||||
}
|
||||
lo_dict = {k: v for k, v in live_options.to_dict().items() if k not in init_only}
|
||||
delta = DeepgramSageMakerSTTSettings.from_mapping(lo_dict)
|
||||
default_settings.apply_update(delta)
|
||||
|
||||
# 3. Apply settings delta (canonical API, always wins)
|
||||
if settings is not None:
|
||||
default_settings.apply_update(settings)
|
||||
|
||||
# Sync extra to top-level fields so self._settings is unambiguous
|
||||
default_settings._sync_extra_to_fields()
|
||||
|
||||
super().__init__(
|
||||
sample_rate=sample_rate,
|
||||
ttfs_p99_latency=ttfs_p99_latency,
|
||||
@@ -166,6 +189,12 @@ class DeepgramSageMakerSTTService(STTService):
|
||||
self._endpoint_name = endpoint_name
|
||||
self._region = region
|
||||
|
||||
# Init-only connection config (not runtime-updatable).
|
||||
self._encoding = encoding
|
||||
self._channels = channels
|
||||
self._multichannel = multichannel
|
||||
self._mip_opt_out = mip_opt_out
|
||||
|
||||
self._client: Optional[SageMakerBidiClient] = None
|
||||
self._response_task: Optional[asyncio.Task] = None
|
||||
self._keepalive_task: Optional[asyncio.Task] = None
|
||||
@@ -185,6 +214,10 @@ class DeepgramSageMakerSTTService(STTService):
|
||||
if not changed:
|
||||
return changed
|
||||
|
||||
# Sync extra to fields after the update so self._settings stays unambiguous
|
||||
if isinstance(self._settings, DeepgramSTTSettings):
|
||||
self._settings._sync_extra_to_fields()
|
||||
|
||||
# TODO: someday we could reconnect here to apply updated settings.
|
||||
# Code might look something like the below:
|
||||
# await self._disconnect()
|
||||
@@ -237,6 +270,43 @@ class DeepgramSageMakerSTTService(STTService):
|
||||
yield ErrorFrame(error=f"Unknown error occurred: {e}")
|
||||
yield None
|
||||
|
||||
def _build_query_string(self) -> str:
|
||||
"""Build query string from current settings and init-only connection config."""
|
||||
params = {}
|
||||
s = self._settings
|
||||
|
||||
# Declared Deepgram-specific fields from settings
|
||||
for f in fields(s):
|
||||
if f.name in ("model", "language", "extra") or f.name.startswith("_"):
|
||||
continue
|
||||
value = getattr(s, f.name)
|
||||
if not is_given(value) or value is None:
|
||||
continue
|
||||
params[f.name] = str(value).lower() if isinstance(value, bool) else str(value)
|
||||
|
||||
# model and language
|
||||
if is_given(s.model) and s.model is not None:
|
||||
params["model"] = str(s.model)
|
||||
if is_given(s.language) and s.language is not None:
|
||||
params["language"] = str(s.language)
|
||||
|
||||
# Init-only connection config
|
||||
params["encoding"] = self._encoding
|
||||
params["channels"] = str(self._channels)
|
||||
params["multichannel"] = str(self._multichannel).lower()
|
||||
params["sample_rate"] = str(self.sample_rate)
|
||||
|
||||
if self._mip_opt_out is not None:
|
||||
params["mip_opt_out"] = str(self._mip_opt_out).lower()
|
||||
|
||||
# Any remaining values in extra
|
||||
if s.extra:
|
||||
for key, value in s.extra.items():
|
||||
if value is not None:
|
||||
params[key] = str(value).lower() if isinstance(value, bool) else str(value)
|
||||
|
||||
return "&".join(f"{k}={v}" for k, v in params.items())
|
||||
|
||||
async def _connect(self):
|
||||
"""Connect to the SageMaker endpoint and start the BiDi session.
|
||||
|
||||
@@ -246,21 +316,7 @@ class DeepgramSageMakerSTTService(STTService):
|
||||
"""
|
||||
logger.debug("Connecting to Deepgram on SageMaker...")
|
||||
|
||||
live_options = LiveOptions(
|
||||
**{**self._settings.live_options.to_dict(), "sample_rate": self.sample_rate}
|
||||
)
|
||||
|
||||
# Build query string from live_options, converting booleans to strings
|
||||
query_params = {}
|
||||
for key, value in live_options.to_dict().items():
|
||||
if value is not None:
|
||||
# Convert boolean values to lowercase strings for Deepgram API
|
||||
if isinstance(value, bool):
|
||||
query_params[key] = str(value).lower()
|
||||
else:
|
||||
query_params[key] = str(value)
|
||||
|
||||
query_string = "&".join(f"{k}={v}" for k, v in query_params.items())
|
||||
query_string = self._build_query_string()
|
||||
|
||||
# Create BiDi client
|
||||
self._client = SageMakerBidiClient(
|
||||
|
||||
@@ -187,8 +187,7 @@ class DeepgramSageMakerTTSService(TTSService):
|
||||
logger.debug("Connecting to Deepgram TTS on SageMaker...")
|
||||
|
||||
query_string = (
|
||||
f"model={self._settings.voice}&encoding={self._settings.encoding}"
|
||||
f"&sample_rate={self.sample_rate}"
|
||||
f"model={self._settings.voice}&encoding={self._encoding}&sample_rate={self.sample_rate}"
|
||||
)
|
||||
|
||||
self._client = SageMakerBidiClient(
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import Any, AsyncGenerator, Dict, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -26,7 +26,6 @@ from pipecat.frames.frames import (
|
||||
)
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.settings import (
|
||||
_S,
|
||||
NOT_GIVEN,
|
||||
STTSettings,
|
||||
_NotGiven,
|
||||
@@ -57,8 +56,11 @@ class LiveOptions:
|
||||
"""Deepgram live transcription options.
|
||||
|
||||
Compatibility wrapper that mirrors the ``LiveOptions`` class removed in
|
||||
deepgram-sdk v6. Pass this to :class:`DeepgramSTTService` via the
|
||||
``live_options`` constructor argument.
|
||||
deepgram-sdk v6.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=DeepgramSTTSettings(...)`` for runtime-updatable fields
|
||||
and direct ``__init__`` parameters for connection-level config instead.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -179,29 +181,42 @@ class DeepgramSTTSettings(STTSettings):
|
||||
|
||||
``model`` and ``language`` are inherited from ``STTSettings`` /
|
||||
``ServiceSettings``. Additional Deepgram connection params may
|
||||
be passed in through extra ``extra`` (also inherited).
|
||||
be passed in through ``extra`` (also inherited).
|
||||
|
||||
Parameters:
|
||||
channels: Number of audio channels.
|
||||
detect_entities: Enable named entity detection.
|
||||
diarize: Enable speaker diarization.
|
||||
encoding: Audio encoding (e.g. ``"linear16"``).
|
||||
dictation: Enable dictation mode (converts commands to punctuation).
|
||||
endpointing: Endpointing sensitivity in ms, or ``False`` to disable.
|
||||
interim_results: Whether to emit interim transcriptions.
|
||||
keyterm: Keyterms to boost (str or list of str).
|
||||
keywords: Keywords to boost (str or list of str).
|
||||
numerals: Convert spoken numbers to numerals.
|
||||
profanity_filter: Filter profanity from transcripts.
|
||||
punctuate: Add punctuation to transcripts.
|
||||
redact: Redact sensitive information (str or list of redaction types).
|
||||
replace: Word replacement rules (str or list).
|
||||
search: Search terms to highlight (str or list of str).
|
||||
smart_format: Apply smart formatting to transcripts.
|
||||
utterance_end_ms: Silence duration in ms before an utterance-end event.
|
||||
vad_events: Enable Deepgram VAD speech-started / utterance-end events.
|
||||
extra: Additional Deepgram query parameters not covered by the fields above.
|
||||
"""
|
||||
|
||||
channels: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
detect_entities: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
diarize: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
dictation: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
endpointing: Any | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
interim_results: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
keyterm: Any | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
keywords: Any | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
numerals: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
profanity_filter: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
punctuate: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
redact: Any | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
replace: Any | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
search: Any | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
smart_format: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
utterance_end_ms: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
vad_events: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
|
||||
def _sync_extra_to_fields(self) -> None:
|
||||
@@ -259,9 +274,16 @@ class DeepgramSTTService(STTService):
|
||||
api_key: str,
|
||||
url: str = "",
|
||||
base_url: str = "",
|
||||
encoding: str = "linear16",
|
||||
channels: int = 1,
|
||||
multichannel: bool = False,
|
||||
sample_rate: Optional[int] = None,
|
||||
callback: Optional[str] = None,
|
||||
callback_method: Optional[str] = None,
|
||||
tag: Optional[Any] = None,
|
||||
mip_opt_out: Optional[bool] = None,
|
||||
live_options: Optional[LiveOptions] = None,
|
||||
addons: Optional[Dict] = None,
|
||||
addons: Optional[dict] = None,
|
||||
should_interrupt: bool = True,
|
||||
settings: Optional[DeepgramSTTSettings] = None,
|
||||
ttfs_p99_latency: Optional[float] = DEEPGRAM_TTFS_P99,
|
||||
@@ -277,12 +299,25 @@ class DeepgramSTTService(STTService):
|
||||
Parameter `url` is deprecated, use `base_url` instead.
|
||||
|
||||
base_url: Custom Deepgram API base URL.
|
||||
sample_rate: Audio sample rate. If None, uses default or live_options value.
|
||||
live_options: :class: LiveOptions configuration. Treated as a
|
||||
delta from a set of sensible defaults — only the fields you
|
||||
set are overridden; all others keep their default values.
|
||||
encoding: Audio encoding format. Defaults to "linear16".
|
||||
channels: Number of audio channels. Defaults to 1.
|
||||
multichannel: Transcribe each audio channel independently.
|
||||
Defaults to False.
|
||||
sample_rate: Audio sample rate in Hz. If None, uses the pipeline
|
||||
sample rate.
|
||||
callback: Callback URL for async transcription delivery.
|
||||
callback_method: HTTP method for the callback (``"GET"`` or ``"POST"``).
|
||||
tag: Custom billing tag.
|
||||
mip_opt_out: Opt out of Deepgram model improvement program.
|
||||
live_options: Legacy configuration options.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=DeepgramSTTSettings(...)`` for runtime-updatable
|
||||
fields and direct init parameters for connection-level config.
|
||||
|
||||
addons: Additional Deepgram features to enable.
|
||||
should_interrupt: Determine whether the bot should be interrupted when Deepgram VAD events are enabled and the system detects that the user is speaking.
|
||||
should_interrupt: Whether to interrupt the bot when Deepgram VAD
|
||||
detects the user is speaking.
|
||||
|
||||
.. deprecated:: 0.0.99
|
||||
This parameter will be removed along with `vad_events` support.
|
||||
@@ -297,8 +332,6 @@ class DeepgramSTTService(STTService):
|
||||
Note:
|
||||
The `vad_events` option in LiveOptions is deprecated as of version 0.0.99 and will be removed in a future version. Please use the Silero VAD instead.
|
||||
"""
|
||||
sample_rate = sample_rate or (live_options.sample_rate if live_options else None)
|
||||
|
||||
if url:
|
||||
import warnings
|
||||
|
||||
@@ -314,30 +347,62 @@ class DeepgramSTTService(STTService):
|
||||
default_settings = DeepgramSTTSettings(
|
||||
model="nova-3-general",
|
||||
language=Language.EN,
|
||||
encoding="linear16",
|
||||
channels=1,
|
||||
interim_results=True,
|
||||
smart_format=False,
|
||||
punctuate=True,
|
||||
profanity_filter=True,
|
||||
vad_events=False,
|
||||
detect_entities=False,
|
||||
diarize=False,
|
||||
dictation=False,
|
||||
endpointing=None,
|
||||
interim_results=True,
|
||||
keyterm=None,
|
||||
keywords=None,
|
||||
numerals=False,
|
||||
profanity_filter=True,
|
||||
punctuate=True,
|
||||
redact=None,
|
||||
replace=None,
|
||||
search=None,
|
||||
smart_format=False,
|
||||
utterance_end_ms=None,
|
||||
vad_events=False,
|
||||
)
|
||||
|
||||
# 2. (no deprecated direct args like model= for this service)
|
||||
|
||||
# 3. Apply live_options overrides — only if settings not provided
|
||||
# 2. Apply live_options overrides — only if settings not provided
|
||||
if live_options is not None:
|
||||
_warn_deprecated_param("live_options", DeepgramSTTSettings)
|
||||
if not settings:
|
||||
lo_dict = live_options.to_dict()
|
||||
delta = DeepgramSTTSettings.from_mapping(
|
||||
{k: v for k, v in lo_dict.items() if k != "sample_rate"}
|
||||
)
|
||||
# Extract init-only fields from live_options
|
||||
if live_options.sample_rate is not None and sample_rate is None:
|
||||
sample_rate = live_options.sample_rate
|
||||
if live_options.encoding is not None:
|
||||
encoding = live_options.encoding
|
||||
if live_options.channels is not None:
|
||||
channels = live_options.channels
|
||||
if live_options.callback is not None:
|
||||
callback = live_options.callback
|
||||
if live_options.callback_method is not None:
|
||||
callback_method = live_options.callback_method
|
||||
if live_options.tag is not None:
|
||||
tag = live_options.tag
|
||||
if live_options.mip_opt_out is not None:
|
||||
mip_opt_out = live_options.mip_opt_out
|
||||
if live_options.multichannel is not None:
|
||||
multichannel = live_options.multichannel
|
||||
|
||||
# Build settings delta from remaining fields
|
||||
init_only = {
|
||||
"sample_rate",
|
||||
"encoding",
|
||||
"channels",
|
||||
"multichannel",
|
||||
"callback",
|
||||
"callback_method",
|
||||
"tag",
|
||||
"mip_opt_out",
|
||||
}
|
||||
lo_dict = {k: v for k, v in live_options.to_dict().items() if k not in init_only}
|
||||
delta = DeepgramSTTSettings.from_mapping(lo_dict)
|
||||
default_settings.apply_update(delta)
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
# 3. Apply settings delta (canonical API, always wins)
|
||||
if settings is not None:
|
||||
default_settings.apply_update(settings)
|
||||
|
||||
@@ -353,6 +418,13 @@ class DeepgramSTTService(STTService):
|
||||
|
||||
self._addons = addons
|
||||
self._should_interrupt = should_interrupt
|
||||
self._encoding = encoding
|
||||
self._channels = channels
|
||||
self._multichannel = multichannel
|
||||
self._callback = callback
|
||||
self._callback_method = callback_method
|
||||
self._tag = tag
|
||||
self._mip_opt_out = mip_opt_out
|
||||
|
||||
if self._settings.vad_events:
|
||||
import warnings
|
||||
@@ -487,14 +559,26 @@ class DeepgramSTTService(STTService):
|
||||
if is_given(s.language) and s.language is not None:
|
||||
kwargs["language"] = str(s.language)
|
||||
|
||||
# Init-only connection config
|
||||
kwargs["encoding"] = self._encoding
|
||||
kwargs["channels"] = str(self._channels)
|
||||
kwargs["multichannel"] = str(self._multichannel).lower()
|
||||
kwargs["sample_rate"] = str(self.sample_rate)
|
||||
|
||||
if self._callback is not None:
|
||||
kwargs["callback"] = self._callback
|
||||
if self._callback_method is not None:
|
||||
kwargs["callback_method"] = self._callback_method
|
||||
if self._tag is not None:
|
||||
kwargs["tag"] = str(self._tag)
|
||||
if self._mip_opt_out is not None:
|
||||
kwargs["mip_opt_out"] = str(self._mip_opt_out).lower()
|
||||
|
||||
# Any remaining values in extra (that didn't map to declared fields)
|
||||
for key, value in s.extra.items():
|
||||
if value is not None:
|
||||
kwargs[key] = str(value).lower() if isinstance(value, bool) else str(value)
|
||||
|
||||
# Always inject sample_rate from service level.
|
||||
kwargs["sample_rate"] = str(self.sample_rate)
|
||||
|
||||
if self._addons:
|
||||
for key, value in self._addons.items():
|
||||
kwargs[key] = str(value)
|
||||
|
||||
@@ -182,7 +182,8 @@ class ElevenLabsSTTSettings(STTSettings):
|
||||
"""Settings for the ElevenLabs file-based STT service.
|
||||
|
||||
Parameters:
|
||||
tag_audio_events: Whether to include audio event tags in transcription.
|
||||
tag_audio_events: Whether to include audio events like (laughter),
|
||||
(coughing) in the transcription.
|
||||
"""
|
||||
|
||||
tag_audio_events: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
@@ -195,7 +196,6 @@ class ElevenLabsRealtimeSTTSettings(STTSettings):
|
||||
See ``ElevenLabsRealtimeSTTService.InputParams`` for detailed descriptions.
|
||||
|
||||
Parameters:
|
||||
commit_strategy: How to segment speech - manual (Pipecat VAD) or vad (ElevenLabs VAD).
|
||||
vad_silence_threshold_secs: Seconds of silence before VAD commits (0.3-3.0).
|
||||
vad_threshold: VAD sensitivity (0.1-0.9, lower is more sensitive).
|
||||
min_speech_duration_ms: Minimum speech duration for VAD (50-2000ms).
|
||||
@@ -205,7 +205,6 @@ class ElevenLabsRealtimeSTTSettings(STTSettings):
|
||||
include_language_detection: Whether to include language detection in transcripts.
|
||||
"""
|
||||
|
||||
commit_strategy: CommitStrategy | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
vad_silence_threshold_secs: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
vad_threshold: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
min_speech_duration_ms: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
@@ -495,6 +494,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
*,
|
||||
api_key: str,
|
||||
base_url: str = "api.elevenlabs.io",
|
||||
commit_strategy: CommitStrategy = CommitStrategy.MANUAL,
|
||||
model: Optional[str] = None,
|
||||
sample_rate: Optional[int] = None,
|
||||
params: Optional[InputParams] = None,
|
||||
@@ -507,6 +507,9 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
Args:
|
||||
api_key: ElevenLabs API key for authentication.
|
||||
base_url: Base URL for ElevenLabs WebSocket API.
|
||||
commit_strategy: How to segment speech — ``CommitStrategy.MANUAL``
|
||||
(Pipecat VAD) or ``CommitStrategy.VAD`` (ElevenLabs VAD).
|
||||
Defaults to ``CommitStrategy.MANUAL``.
|
||||
model: Model ID for transcription.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
@@ -528,7 +531,6 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
default_settings = ElevenLabsRealtimeSTTSettings(
|
||||
model="scribe_v2_realtime",
|
||||
language=None,
|
||||
commit_strategy=CommitStrategy.MANUAL,
|
||||
vad_silence_threshold_secs=None,
|
||||
vad_threshold=None,
|
||||
min_speech_duration_ms=None,
|
||||
@@ -548,7 +550,8 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
_warn_deprecated_param("params", ElevenLabsRealtimeSTTSettings)
|
||||
if not settings:
|
||||
default_settings.language = params.language_code
|
||||
default_settings.commit_strategy = params.commit_strategy
|
||||
if params.commit_strategy != CommitStrategy.MANUAL:
|
||||
commit_strategy = params.commit_strategy
|
||||
default_settings.vad_silence_threshold_secs = params.vad_silence_threshold_secs
|
||||
default_settings.vad_threshold = params.vad_threshold
|
||||
default_settings.min_speech_duration_ms = params.min_speech_duration_ms
|
||||
@@ -575,6 +578,9 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
self._audio_format = "" # initialized in start()
|
||||
self._receive_task = None
|
||||
|
||||
# Init-only config (not runtime-updatable).
|
||||
self._commit_strategy = commit_strategy
|
||||
|
||||
self._connected_event = asyncio.Event()
|
||||
self._connected_event.set()
|
||||
|
||||
@@ -651,7 +657,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
await self._start_metrics()
|
||||
elif isinstance(frame, VADUserStoppedSpeakingFrame):
|
||||
# Send commit when user stops speaking (manual commit mode)
|
||||
if self._settings.commit_strategy == CommitStrategy.MANUAL:
|
||||
if self._commit_strategy == CommitStrategy.MANUAL:
|
||||
if self._websocket and self._websocket.state is State.OPEN:
|
||||
try:
|
||||
commit_message = {
|
||||
@@ -754,7 +760,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
params.append(f"language_code={self._settings.language}")
|
||||
|
||||
params.append(f"audio_format={self._audio_format}")
|
||||
params.append(f"commit_strategy={self._settings.commit_strategy.value}")
|
||||
params.append(f"commit_strategy={self._commit_strategy.value}")
|
||||
|
||||
# Add optional parameters
|
||||
if self._settings.include_timestamps:
|
||||
@@ -771,7 +777,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
)
|
||||
|
||||
# Add VAD parameters if using VAD commit strategy and values are specified
|
||||
if self._settings.commit_strategy == CommitStrategy.VAD:
|
||||
if self._commit_strategy == CommitStrategy.VAD:
|
||||
if self._settings.vad_silence_threshold_secs is not None:
|
||||
params.append(
|
||||
f"vad_silence_threshold_secs={self._settings.vad_silence_threshold_secs}"
|
||||
@@ -931,7 +937,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
|
||||
await self._handle_transcription(text, True, language)
|
||||
|
||||
finalized = self._settings.commit_strategy == CommitStrategy.MANUAL
|
||||
finalized = self._commit_strategy == CommitStrategy.MANUAL
|
||||
|
||||
await self.push_frame(
|
||||
TranscriptionFrame(
|
||||
@@ -975,7 +981,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
|
||||
await self._handle_transcription(text, True, language)
|
||||
|
||||
finalized = self._settings.commit_strategy == CommitStrategy.MANUAL
|
||||
finalized = self._commit_strategy == CommitStrategy.MANUAL
|
||||
|
||||
# This message is sent after committed_transcript when include_timestamps=true.
|
||||
# It contains the full transcript data including text and word-level timestamps.
|
||||
|
||||
@@ -12,7 +12,7 @@ transcription using segmented audio processing.
|
||||
|
||||
import base64
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
import aiohttp
|
||||
@@ -20,7 +20,7 @@ from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
|
||||
from pipecat.frames.frames import ErrorFrame, Frame, TranscriptionFrame
|
||||
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.settings import STTSettings, _warn_deprecated_param
|
||||
from pipecat.services.stt_latency import FAL_TTFS_P99
|
||||
from pipecat.services.stt_service import SegmentedSTTService
|
||||
from pipecat.transcriptions.language import Language, resolve_language
|
||||
@@ -143,18 +143,9 @@ def language_to_fal_language(language: Language) -> Optional[str]:
|
||||
|
||||
@dataclass
|
||||
class FalSTTSettings(STTSettings):
|
||||
"""Settings for the Fal Wizper STT service.
|
||||
"""Settings for the Fal Wizper STT service."""
|
||||
|
||||
Parameters:
|
||||
task: Task to perform ('transcribe' or 'translate'). Defaults to
|
||||
'transcribe'.
|
||||
chunk_level: Level of chunking ('segment'). Defaults to 'segment'.
|
||||
version: Version of Wizper model to use. Defaults to '3'.
|
||||
"""
|
||||
|
||||
task: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
chunk_level: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
version: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
pass
|
||||
|
||||
|
||||
class FalSTTService(SegmentedSTTService):
|
||||
@@ -189,6 +180,9 @@ class FalSTTService(SegmentedSTTService):
|
||||
*,
|
||||
api_key: Optional[str] = None,
|
||||
aiohttp_session: Optional[aiohttp.ClientSession] = None,
|
||||
task: str = "transcribe",
|
||||
chunk_level: str = "segment",
|
||||
version: str = "3",
|
||||
sample_rate: Optional[int] = None,
|
||||
params: Optional[InputParams] = None,
|
||||
settings: Optional[FalSTTSettings] = None,
|
||||
@@ -201,11 +195,16 @@ class FalSTTService(SegmentedSTTService):
|
||||
api_key: Fal API key. If not provided, will check FAL_KEY environment variable.
|
||||
aiohttp_session: Optional aiohttp ClientSession for HTTP requests.
|
||||
If not provided, a session will be created and managed internally.
|
||||
task: Task to perform (``"transcribe"`` or ``"translate"``).
|
||||
Defaults to ``"transcribe"``.
|
||||
chunk_level: Level of chunking (``"segment"``). Defaults to ``"segment"``.
|
||||
version: Version of Wizper model to use. Defaults to ``"3"``.
|
||||
sample_rate: Audio sample rate in Hz. If not provided, uses the pipeline's rate.
|
||||
params: Configuration parameters for the Wizper API.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=FalSTTSettings(...)`` instead.
|
||||
Use ``settings=FalSTTSettings(...)`` for model/language and
|
||||
direct init parameters for task/chunk_level/version instead.
|
||||
|
||||
settings: Runtime-updatable settings. When provided alongside deprecated
|
||||
parameters, ``settings`` values take precedence.
|
||||
@@ -217,9 +216,6 @@ class FalSTTService(SegmentedSTTService):
|
||||
default_settings = FalSTTSettings(
|
||||
model=None,
|
||||
language=language_to_fal_language(Language.EN) or "en",
|
||||
task="transcribe",
|
||||
chunk_level="segment",
|
||||
version="3",
|
||||
)
|
||||
|
||||
# 2. (no deprecated direct args for this service)
|
||||
@@ -231,9 +227,12 @@ class FalSTTService(SegmentedSTTService):
|
||||
default_settings.language = (
|
||||
language_to_fal_language(params.language) if params.language else "en"
|
||||
)
|
||||
default_settings.task = params.task
|
||||
default_settings.chunk_level = params.chunk_level
|
||||
default_settings.version = params.version
|
||||
if params.task != "transcribe":
|
||||
task = params.task
|
||||
if params.chunk_level != "segment":
|
||||
chunk_level = params.chunk_level
|
||||
if params.version != "3":
|
||||
version = params.version
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
if settings is not None:
|
||||
@@ -246,6 +245,10 @@ class FalSTTService(SegmentedSTTService):
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self._task = task
|
||||
self._chunk_level = chunk_level
|
||||
self._version = version
|
||||
|
||||
self._api_key = api_key or os.getenv("FAL_KEY", "")
|
||||
if not self._api_key:
|
||||
raise ValueError(
|
||||
@@ -301,7 +304,15 @@ class FalSTTService(SegmentedSTTService):
|
||||
self._session = aiohttp.ClientSession()
|
||||
|
||||
data_uri = f"data:audio/x-wav;base64,{base64.b64encode(audio).decode()}"
|
||||
payload = {"audio_url": data_uri, **self._settings.given_fields()}
|
||||
payload: dict = {"audio_url": data_uri}
|
||||
if self._settings.language is not None:
|
||||
payload["language"] = self._settings.language
|
||||
if self._task is not None:
|
||||
payload["task"] = self._task
|
||||
if self._chunk_level is not None:
|
||||
payload["chunk_level"] = self._chunk_level
|
||||
if self._version is not None:
|
||||
payload["version"] = self._version
|
||||
headers = {
|
||||
"Authorization": f"Key {self._api_key}",
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -152,6 +152,10 @@ class MessagesConfig(BaseModel):
|
||||
class GladiaInputParams(BaseModel):
|
||||
"""Configuration parameters for the Gladia STT service.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=GladiaSTTSettings(...)`` for runtime-updatable
|
||||
fields and direct init parameters for encoding/bit_depth/channels.
|
||||
|
||||
Parameters:
|
||||
encoding: Audio encoding format
|
||||
bit_depth: Audio bit depth
|
||||
|
||||
@@ -15,7 +15,7 @@ import base64
|
||||
import json
|
||||
import warnings
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, AsyncGenerator, Dict, Literal, Optional
|
||||
from typing import Any, AsyncGenerator, Literal, Optional
|
||||
|
||||
import aiohttp
|
||||
from loguru import logger
|
||||
@@ -191,28 +191,22 @@ class GladiaSTTSettings(STTSettings):
|
||||
"""Settings for Gladia STT service.
|
||||
|
||||
Parameters:
|
||||
encoding: Audio encoding format.
|
||||
bit_depth: Audio bit depth.
|
||||
channels: Number of audio channels.
|
||||
language_config: Language detection and handling configuration.
|
||||
custom_metadata: Additional metadata to include with requests.
|
||||
endpointing: Silence duration in seconds to mark end of speech.
|
||||
maximum_duration_without_endpointing: Maximum utterance duration without silence.
|
||||
language_config: Detailed language configuration.
|
||||
pre_processing: Audio pre-processing options.
|
||||
realtime_processing: Real-time processing features.
|
||||
messages_config: WebSocket message filtering options.
|
||||
enable_vad: Enable VAD to trigger end of utterance detection.
|
||||
"""
|
||||
|
||||
encoding: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
bit_depth: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
channels: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
custom_metadata: Dict[str, Any] | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
language_config: LanguageConfig | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
custom_metadata: dict[str, Any] | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
endpointing: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
maximum_duration_without_endpointing: int | None | _NotGiven = field(
|
||||
default_factory=lambda: NOT_GIVEN
|
||||
)
|
||||
language_config: LanguageConfig | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
pre_processing: PreProcessingConfig | None | _NotGiven = field(
|
||||
default_factory=lambda: NOT_GIVEN
|
||||
)
|
||||
@@ -247,6 +241,9 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
api_key: str,
|
||||
region: Literal["us-west", "eu-west"] | None = None,
|
||||
url: str = "https://api.gladia.io/v2/live",
|
||||
encoding: str = "wav/pcm",
|
||||
bit_depth: int = 16,
|
||||
channels: int = 1,
|
||||
confidence: Optional[float] = None,
|
||||
sample_rate: Optional[int] = None,
|
||||
model: Optional[str] = None,
|
||||
@@ -263,6 +260,9 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
api_key: Gladia API key for authentication.
|
||||
region: Region used to process audio. eu-west or us-west. Defaults to eu-west.
|
||||
url: Gladia API URL. Defaults to "https://api.gladia.io/v2/live".
|
||||
encoding: Audio encoding format. Defaults to ``"wav/pcm"``.
|
||||
bit_depth: Audio bit depth. Defaults to 16.
|
||||
channels: Number of audio channels. Defaults to 1.
|
||||
confidence: Minimum confidence threshold for transcriptions (0.0-1.0).
|
||||
|
||||
.. deprecated:: 0.0.86
|
||||
@@ -278,7 +278,8 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
params: Additional configuration parameters for Gladia service.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=GladiaSTTSettings(...)`` instead.
|
||||
Use ``settings=GladiaSTTSettings(...)`` for runtime-updatable
|
||||
fields and direct init parameters for encoding/bit_depth/channels.
|
||||
|
||||
max_buffer_size: Maximum size of audio buffer in bytes. Defaults to 20MB.
|
||||
should_interrupt: Determine whether the bot should be interrupted when
|
||||
@@ -303,13 +304,10 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
default_settings = GladiaSTTSettings(
|
||||
model="solaria-1",
|
||||
language=None,
|
||||
encoding="wav/pcm",
|
||||
bit_depth=16,
|
||||
channels=1,
|
||||
language_config=None,
|
||||
custom_metadata=None,
|
||||
endpointing=None,
|
||||
maximum_duration_without_endpointing=5,
|
||||
language_config=None,
|
||||
pre_processing=None,
|
||||
realtime_processing=None,
|
||||
messages_config=None,
|
||||
@@ -334,9 +332,13 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
stacklevel=2,
|
||||
)
|
||||
if not settings:
|
||||
default_settings.encoding = params.encoding
|
||||
default_settings.bit_depth = params.bit_depth
|
||||
default_settings.channels = params.channels
|
||||
# Extract init-only fields from params
|
||||
if params.encoding is not None:
|
||||
encoding = params.encoding
|
||||
if params.bit_depth is not None:
|
||||
bit_depth = params.bit_depth
|
||||
if params.channels is not None:
|
||||
channels = params.channels
|
||||
default_settings.custom_metadata = params.custom_metadata
|
||||
default_settings.endpointing = params.endpointing
|
||||
default_settings.maximum_duration_without_endpointing = (
|
||||
@@ -347,14 +349,14 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
default_settings.messages_config = params.messages_config
|
||||
default_settings.enable_vad = params.enable_vad
|
||||
# Resolve deprecated language → language_config at init time
|
||||
language_config = params.language_config
|
||||
if not language_config and params.language:
|
||||
if params.language_config:
|
||||
default_settings.language_config = params.language_config
|
||||
elif params.language:
|
||||
language_code = self.language_to_service_language(params.language)
|
||||
if language_code:
|
||||
language_config = LanguageConfig(
|
||||
default_settings.language_config = LanguageConfig(
|
||||
languages=[language_code], code_switching=False
|
||||
)
|
||||
default_settings.language_config = language_config
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
if settings is not None:
|
||||
@@ -374,6 +376,11 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
self._url = url
|
||||
self._receive_task = None
|
||||
|
||||
# Init-only connection config
|
||||
self._encoding = encoding
|
||||
self._bit_depth = bit_depth
|
||||
self._channels = channels
|
||||
|
||||
# Session management
|
||||
self._session_url = None
|
||||
self._session_id = None
|
||||
@@ -411,14 +418,14 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
"""
|
||||
return language_to_gladia_language(language)
|
||||
|
||||
def _prepare_settings(self) -> Dict[str, Any]:
|
||||
def _prepare_settings(self) -> dict[str, Any]:
|
||||
s = self._settings
|
||||
|
||||
settings = {
|
||||
"encoding": s.encoding or "wav/pcm",
|
||||
"bit_depth": s.bit_depth or 16,
|
||||
"encoding": self._encoding or "wav/pcm",
|
||||
"bit_depth": self._bit_depth or 16,
|
||||
"sample_rate": self.sample_rate,
|
||||
"channels": s.channels or 1,
|
||||
"channels": self._channels or 1,
|
||||
"model": s.model,
|
||||
}
|
||||
|
||||
@@ -610,7 +617,7 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
self._websocket = None
|
||||
await self._call_event_handler("on_disconnected")
|
||||
|
||||
async def _setup_gladia(self, settings: Dict[str, Any]):
|
||||
async def _setup_gladia(self, settings: dict[str, Any]):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
params = {}
|
||||
if self._region:
|
||||
|
||||
@@ -12,7 +12,7 @@ WebSocket API for streaming audio transcription.
|
||||
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
@@ -28,7 +28,7 @@ from pipecat.frames.frames import (
|
||||
VADUserStoppedSpeakingFrame,
|
||||
)
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.settings import STTSettings, _warn_deprecated_param
|
||||
from pipecat.services.stt_latency import GRADIUM_TTFS_P99
|
||||
from pipecat.services.stt_service import WebsocketSTTService
|
||||
from pipecat.transcriptions.language import Language, resolve_language
|
||||
@@ -68,14 +68,9 @@ def language_to_gradium_language(language: Language) -> Optional[str]:
|
||||
|
||||
@dataclass
|
||||
class GradiumSTTSettings(STTSettings):
|
||||
"""Settings for the Gradium STT service.
|
||||
"""Settings for the Gradium STT service."""
|
||||
|
||||
Parameters:
|
||||
delay_in_frames: Delay in audio frames (80ms each) before text is
|
||||
generated. Higher delays allow more context but increase latency.
|
||||
"""
|
||||
|
||||
delay_in_frames: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
pass
|
||||
|
||||
|
||||
class GradiumSTTService(WebsocketSTTService):
|
||||
@@ -112,6 +107,7 @@ class GradiumSTTService(WebsocketSTTService):
|
||||
*,
|
||||
api_key: str,
|
||||
api_endpoint_base_url: str = "wss://eu.api.gradium.ai/api/speech/asr",
|
||||
delay_in_frames: Optional[int] = None,
|
||||
params: Optional[InputParams] = None,
|
||||
json_config: Optional[str] = None,
|
||||
settings: Optional[GradiumSTTSettings] = None,
|
||||
@@ -123,6 +119,9 @@ class GradiumSTTService(WebsocketSTTService):
|
||||
Args:
|
||||
api_key: Gradium API key for authentication.
|
||||
api_endpoint_base_url: WebSocket endpoint URL. Defaults to Gradium's streaming endpoint.
|
||||
delay_in_frames: Delay in audio frames (80ms each) before text is
|
||||
generated. Higher delays allow more context but increase latency.
|
||||
Allowed values: 7, 8, 10, 12, 14, 16, 20, 24, 36, 48.
|
||||
params: Configuration parameters for language and delay settings.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
@@ -152,7 +151,6 @@ class GradiumSTTService(WebsocketSTTService):
|
||||
default_settings = GradiumSTTSettings(
|
||||
model=None,
|
||||
language=None,
|
||||
delay_in_frames=None,
|
||||
)
|
||||
|
||||
# 2. (no deprecated direct args for this service)
|
||||
@@ -162,7 +160,8 @@ class GradiumSTTService(WebsocketSTTService):
|
||||
_warn_deprecated_param("params", GradiumSTTSettings)
|
||||
if not settings:
|
||||
default_settings.language = params.language
|
||||
default_settings.delay_in_frames = params.delay_in_frames
|
||||
if params.delay_in_frames is not None:
|
||||
delay_in_frames = params.delay_in_frames
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
if settings is not None:
|
||||
@@ -179,6 +178,7 @@ class GradiumSTTService(WebsocketSTTService):
|
||||
self._api_endpoint_base_url = api_endpoint_base_url
|
||||
self._websocket = None
|
||||
self._json_config = json_config
|
||||
self._config_delay_in_frames = delay_in_frames
|
||||
|
||||
self._receive_task = None
|
||||
|
||||
@@ -358,8 +358,8 @@ class GradiumSTTService(WebsocketSTTService):
|
||||
gradium_language = language_to_gradium_language(self._settings.language)
|
||||
if gradium_language:
|
||||
json_config["language"] = gradium_language
|
||||
if self._settings.delay_in_frames:
|
||||
json_config["delay_in_frames"] = self._settings.delay_in_frames
|
||||
if self._config_delay_in_frames:
|
||||
json_config["delay_in_frames"] = self._config_delay_in_frames
|
||||
if json_config:
|
||||
setup_msg["json_config"] = json_config
|
||||
await self._websocket.send(json.dumps(setup_msg))
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
"""Groq speech-to-text service implementation using Whisper models."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from pipecat.services.settings import _warn_deprecated_param
|
||||
@@ -18,6 +19,17 @@ from pipecat.services.whisper.base_stt import (
|
||||
from pipecat.transcriptions.language import Language
|
||||
|
||||
|
||||
@dataclass
|
||||
class GroqSTTSettings(BaseWhisperSTTSettings):
|
||||
"""Settings for the Groq STT service.
|
||||
|
||||
Parameters:
|
||||
prompt: Optional prompt text to guide transcription style.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GroqSTTService(BaseWhisperSTTService):
|
||||
"""Groq Whisper speech-to-text service.
|
||||
|
||||
@@ -25,6 +37,8 @@ class GroqSTTService(BaseWhisperSTTService):
|
||||
set via the api_key parameter or GROQ_API_KEY environment variable.
|
||||
"""
|
||||
|
||||
_settings: GroqSTTSettings
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -34,7 +48,7 @@ class GroqSTTService(BaseWhisperSTTService):
|
||||
language: Optional[Language] = None,
|
||||
prompt: Optional[str] = None,
|
||||
temperature: Optional[float] = None,
|
||||
settings: Optional[BaseWhisperSTTSettings] = None,
|
||||
settings: Optional[GroqSTTSettings] = None,
|
||||
ttfs_p99_latency: Optional[float] = GROQ_TTFS_P99,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -44,24 +58,24 @@ class GroqSTTService(BaseWhisperSTTService):
|
||||
model: Whisper model to use.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=BaseWhisperSTTSettings(model=...)`` instead.
|
||||
Use ``settings=GroqSTTSettings(model=...)`` instead.
|
||||
|
||||
api_key: Groq API key. Defaults to None.
|
||||
base_url: API base URL. Defaults to "https://api.groq.com/openai/v1".
|
||||
language: Language of the audio input.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=BaseWhisperSTTSettings(language=...)`` instead.
|
||||
Use ``settings=GroqSTTSettings(language=...)`` instead.
|
||||
|
||||
prompt: Optional text to guide the model's style or continue a previous segment.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=BaseWhisperSTTSettings(prompt=...)`` instead.
|
||||
Use ``settings=GroqSTTSettings(prompt=...)`` instead.
|
||||
|
||||
temperature: Optional sampling temperature between 0 and 1.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=BaseWhisperSTTSettings(temperature=...)`` instead.
|
||||
Use ``settings=GroqSTTSettings(temperature=...)`` instead.
|
||||
|
||||
settings: Runtime-updatable settings. When provided alongside deprecated
|
||||
parameters, ``settings`` values take precedence.
|
||||
@@ -70,24 +84,25 @@ class GroqSTTService(BaseWhisperSTTService):
|
||||
**kwargs: Additional arguments passed to BaseWhisperSTTService.
|
||||
"""
|
||||
# --- 1. Hardcoded defaults ---
|
||||
default_settings = BaseWhisperSTTSettings(
|
||||
default_settings = GroqSTTSettings(
|
||||
model="whisper-large-v3-turbo",
|
||||
language=self.language_to_service_language(Language.EN),
|
||||
base_url=base_url,
|
||||
prompt=None,
|
||||
temperature=None,
|
||||
)
|
||||
|
||||
# --- 2. Deprecated direct-arg overrides ---
|
||||
if model is not None:
|
||||
_warn_deprecated_param("model", BaseWhisperSTTSettings, "model")
|
||||
_warn_deprecated_param("model", GroqSTTSettings, "model")
|
||||
default_settings.model = model
|
||||
if language is not None:
|
||||
_warn_deprecated_param("language", BaseWhisperSTTSettings, "language")
|
||||
_warn_deprecated_param("language", GroqSTTSettings, "language")
|
||||
default_settings.language = self.language_to_service_language(language)
|
||||
if prompt is not None:
|
||||
_warn_deprecated_param("prompt", BaseWhisperSTTSettings, "prompt")
|
||||
_warn_deprecated_param("prompt", GroqSTTSettings, "prompt")
|
||||
default_settings.prompt = prompt
|
||||
if temperature is not None:
|
||||
_warn_deprecated_param("temperature", BaseWhisperSTTSettings, "temperature")
|
||||
_warn_deprecated_param("temperature", GroqSTTSettings, "temperature")
|
||||
default_settings.temperature = temperature
|
||||
|
||||
# --- 3. (no params object for this service) ---
|
||||
@@ -105,7 +120,7 @@ class GroqSTTService(BaseWhisperSTTService):
|
||||
)
|
||||
|
||||
async def _transcribe(self, audio: bytes) -> Transcription:
|
||||
assert self._language is not None # Assigned in the BaseWhisperSTTService class
|
||||
assert self._settings.language is not None
|
||||
|
||||
# Build kwargs dict with only set parameters
|
||||
kwargs = {
|
||||
@@ -113,13 +128,13 @@ class GroqSTTService(BaseWhisperSTTService):
|
||||
"model": self._settings.model,
|
||||
# Use verbose_json to get probability metrics
|
||||
"response_format": "verbose_json" if self._include_prob_metrics else "json",
|
||||
"language": self._language,
|
||||
"language": self._settings.language,
|
||||
}
|
||||
|
||||
if self._prompt is not None:
|
||||
kwargs["prompt"] = self._prompt
|
||||
if self._settings.prompt is not None:
|
||||
kwargs["prompt"] = self._settings.prompt
|
||||
|
||||
if self._temperature is not None:
|
||||
kwargs["temperature"] = self._temperature
|
||||
if self._settings.temperature is not None:
|
||||
kwargs["temperature"] = self._settings.temperature
|
||||
|
||||
return await self._client.audio.transcriptions.create(**kwargs)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import asyncio
|
||||
from concurrent.futures import CancelledError as FuturesCancelledError
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, AsyncGenerator, List, Mapping, Optional
|
||||
|
||||
from loguru import logger
|
||||
@@ -23,7 +23,7 @@ from pipecat.frames.frames import (
|
||||
StartFrame,
|
||||
TranscriptionFrame,
|
||||
)
|
||||
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.settings import STTSettings, _warn_deprecated_param
|
||||
from pipecat.services.stt_latency import NVIDIA_TTFS_P99
|
||||
from pipecat.services.stt_service import SegmentedSTTService, STTService
|
||||
from pipecat.transcriptions.language import Language, resolve_language
|
||||
@@ -110,11 +110,11 @@ class NvidiaSegmentedSTTSettings(STTSettings):
|
||||
boosted_lm_score: Score boost for specified words.
|
||||
"""
|
||||
|
||||
profanity_filter: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
automatic_punctuation: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
verbatim_transcripts: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
boosted_lm_words: List[str] | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
boosted_lm_score: float | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
profanity_filter: bool = False
|
||||
automatic_punctuation: bool = True
|
||||
verbatim_transcripts: bool = False
|
||||
boosted_lm_words: Optional[List[str]] = None
|
||||
boosted_lm_score: float = 4.0
|
||||
|
||||
|
||||
class NvidiaSTTService(STTService):
|
||||
@@ -586,19 +586,18 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
|
||||
def _create_recognition_config(self):
|
||||
"""Create the NVIDIA Riva ASR recognition configuration."""
|
||||
# Create base configuration
|
||||
s = self._settings
|
||||
config = riva.client.RecognitionConfig(
|
||||
language_code=self._get_language_code(),
|
||||
max_alternatives=1,
|
||||
profanity_filter=self._settings.profanity_filter,
|
||||
enable_automatic_punctuation=self._settings.automatic_punctuation,
|
||||
verbatim_transcripts=self._settings.verbatim_transcripts,
|
||||
profanity_filter=s.profanity_filter,
|
||||
enable_automatic_punctuation=s.automatic_punctuation,
|
||||
verbatim_transcripts=s.verbatim_transcripts,
|
||||
)
|
||||
|
||||
# Add word boosting if specified
|
||||
if self._settings.boosted_lm_words:
|
||||
riva.client.add_word_boosting_to_config(
|
||||
config, self._settings.boosted_lm_words, self._settings.boosted_lm_score
|
||||
)
|
||||
if s.boosted_lm_words:
|
||||
riva.client.add_word_boosting_to_config(config, s.boosted_lm_words, s.boosted_lm_score)
|
||||
|
||||
# Add voice activity detection parameters
|
||||
riva.client.add_endpoint_parameters_to_config(
|
||||
|
||||
@@ -16,7 +16,7 @@ Provides two STT services:
|
||||
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, AsyncGenerator, Literal, Optional, Union
|
||||
|
||||
from loguru import logger
|
||||
@@ -35,7 +35,7 @@ from pipecat.frames.frames import (
|
||||
VADUserStoppedSpeakingFrame,
|
||||
)
|
||||
from pipecat.processors.frame_processor import FrameDirection
|
||||
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.settings import STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.stt_latency import OPENAI_REALTIME_TTFS_P99, OPENAI_TTFS_P99
|
||||
from pipecat.services.stt_service import WebsocketSTTService
|
||||
from pipecat.services.whisper.base_stt import (
|
||||
@@ -55,6 +55,13 @@ except ModuleNotFoundError:
|
||||
State = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class OpenAISTTSettings(BaseWhisperSTTSettings):
|
||||
"""Settings for the OpenAI STT service."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OpenAISTTService(BaseWhisperSTTService):
|
||||
"""OpenAI Speech-to-Text service that generates text from audio.
|
||||
|
||||
@@ -62,6 +69,8 @@ class OpenAISTTService(BaseWhisperSTTService):
|
||||
set via the api_key parameter or OPENAI_API_KEY environment variable.
|
||||
"""
|
||||
|
||||
_settings: OpenAISTTSettings
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -71,7 +80,7 @@ class OpenAISTTService(BaseWhisperSTTService):
|
||||
language: Optional[Language] = Language.EN,
|
||||
prompt: Optional[str] = None,
|
||||
temperature: Optional[float] = None,
|
||||
settings: Optional[BaseWhisperSTTSettings] = None,
|
||||
settings: Optional[OpenAISTTSettings] = None,
|
||||
ttfs_p99_latency: Optional[float] = OPENAI_TTFS_P99,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -81,13 +90,25 @@ class OpenAISTTService(BaseWhisperSTTService):
|
||||
model: Model to use — either gpt-4o or Whisper.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=BaseWhisperSTTSettings(model=...)`` instead.
|
||||
Use ``settings=OpenAISTTSettings(model=...)`` instead.
|
||||
|
||||
api_key: OpenAI API key. Defaults to None.
|
||||
base_url: API base URL. Defaults to None.
|
||||
language: Language of the audio input. Defaults to English.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=OpenAISTTSettings(language=...)`` instead.
|
||||
|
||||
prompt: Optional text to guide the model's style or continue a previous segment.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=OpenAISTTSettings(prompt=...)`` instead.
|
||||
|
||||
temperature: Optional sampling temperature between 0 and 1. Defaults to 0.0.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=OpenAISTTSettings(temperature=...)`` instead.
|
||||
|
||||
settings: Runtime-updatable settings. When provided alongside deprecated
|
||||
parameters, ``settings`` values take precedence.
|
||||
ttfs_p99_latency: P99 latency from speech end to final transcript in seconds.
|
||||
@@ -96,18 +117,23 @@ class OpenAISTTService(BaseWhisperSTTService):
|
||||
"""
|
||||
# --- 1. Hardcoded defaults ---
|
||||
_language = language or Language.EN
|
||||
default_settings = BaseWhisperSTTSettings(
|
||||
default_settings = OpenAISTTSettings(
|
||||
model="gpt-4o-transcribe",
|
||||
language=self.language_to_service_language(_language),
|
||||
base_url=base_url,
|
||||
prompt=prompt,
|
||||
temperature=temperature,
|
||||
prompt=None,
|
||||
temperature=None,
|
||||
)
|
||||
|
||||
# --- 2. Deprecated direct-arg overrides ---
|
||||
if model is not None:
|
||||
_warn_deprecated_param("model", BaseWhisperSTTSettings, "model")
|
||||
_warn_deprecated_param("model", OpenAISTTSettings, "model")
|
||||
default_settings.model = model
|
||||
if prompt is not None:
|
||||
_warn_deprecated_param("prompt", OpenAISTTSettings, "prompt")
|
||||
default_settings.prompt = prompt
|
||||
if temperature is not None:
|
||||
_warn_deprecated_param("temperature", OpenAISTTSettings, "temperature")
|
||||
default_settings.temperature = temperature
|
||||
|
||||
# --- 3. (no params object for this service) ---
|
||||
|
||||
@@ -124,7 +150,7 @@ class OpenAISTTService(BaseWhisperSTTService):
|
||||
)
|
||||
|
||||
async def _transcribe(self, audio: bytes) -> Transcription:
|
||||
assert self._language is not None # Assigned in the BaseWhisperSTTService class
|
||||
assert self._settings.language is not None
|
||||
|
||||
# Build kwargs dict with only set parameters
|
||||
kwargs = {
|
||||
@@ -162,7 +188,7 @@ class OpenAIRealtimeSTTSettings(STTSettings):
|
||||
prompt: Optional prompt text to guide transcription style.
|
||||
"""
|
||||
|
||||
prompt: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
prompt: str | None | _NotGiven = None
|
||||
|
||||
|
||||
class OpenAIRealtimeSTTService(WebsocketSTTService):
|
||||
@@ -228,8 +254,16 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
||||
base_url: WebSocket base URL for the Realtime API.
|
||||
Defaults to ``"wss://api.openai.com/v1/realtime"``.
|
||||
language: Language of the audio input. Defaults to English.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=OpenAIRealtimeSTTSettings(language=...)`` instead.
|
||||
|
||||
prompt: Optional prompt text to guide transcription style
|
||||
or provide keyword hints.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=OpenAIRealtimeSTTSettings(prompt=...)`` instead.
|
||||
|
||||
turn_detection: Server-side VAD configuration. Defaults to
|
||||
``False`` (disabled), which relies on a local VAD
|
||||
processor in the pipeline. Pass ``None`` to use server
|
||||
@@ -257,14 +291,20 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
||||
# --- 1. Hardcoded defaults ---
|
||||
default_settings = OpenAIRealtimeSTTSettings(
|
||||
model="gpt-4o-transcribe",
|
||||
language=language,
|
||||
prompt=prompt,
|
||||
language=Language.EN,
|
||||
prompt=None,
|
||||
)
|
||||
|
||||
# --- 2. Deprecated direct-arg overrides ---
|
||||
if model is not None:
|
||||
_warn_deprecated_param("model", OpenAIRealtimeSTTSettings, "model")
|
||||
default_settings.model = model
|
||||
if language is not None and language != Language.EN:
|
||||
_warn_deprecated_param("language", OpenAIRealtimeSTTSettings, "language")
|
||||
default_settings.language = language
|
||||
if prompt is not None:
|
||||
_warn_deprecated_param("prompt", OpenAIRealtimeSTTSettings, "prompt")
|
||||
default_settings.prompt = prompt
|
||||
|
||||
# --- 3. (no params object for this service) ---
|
||||
|
||||
@@ -281,7 +321,6 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
||||
self._api_key = api_key
|
||||
self._base_url = base_url
|
||||
|
||||
self._prompt = self._settings.prompt
|
||||
self._turn_detection = turn_detection
|
||||
self._noise_reduction = noise_reduction
|
||||
self._should_interrupt = should_interrupt
|
||||
@@ -318,8 +357,7 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
||||
async def _update_settings(self, delta: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings delta and send session update if needed.
|
||||
|
||||
Keeps ``_language_code`` and ``_prompt`` in sync with settings
|
||||
and sends a ``session.update`` to the server when the session is active.
|
||||
Sends a ``session.update`` to the server when the session is active.
|
||||
|
||||
Args:
|
||||
delta: A :class:`STTSettings` (or ``OpenAIRealtimeSTTSettings``) delta.
|
||||
@@ -329,13 +367,7 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
||||
"""
|
||||
changed = await super()._update_settings(delta)
|
||||
|
||||
if not changed:
|
||||
return changed
|
||||
|
||||
if "prompt" in changed and isinstance(self._settings, OpenAIRealtimeSTTSettings):
|
||||
self._prompt = self._settings.prompt
|
||||
|
||||
if self._session_ready:
|
||||
if changed and self._session_ready:
|
||||
await self._send_session_update()
|
||||
|
||||
return changed
|
||||
@@ -492,8 +524,8 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
||||
if language_code:
|
||||
transcription["language"] = language_code
|
||||
|
||||
if self._prompt:
|
||||
transcription["prompt"] = self._prompt
|
||||
if self._settings.prompt:
|
||||
transcription["prompt"] = self._settings.prompt
|
||||
|
||||
input_audio: dict = {
|
||||
"format": {
|
||||
|
||||
@@ -161,6 +161,8 @@ class OpenAITTSService(TTSService):
|
||||
model="gpt-4o-mini-tts",
|
||||
voice="alloy",
|
||||
language=None,
|
||||
instructions=None,
|
||||
speed=None,
|
||||
)
|
||||
|
||||
# 2. Apply direct init arg overrides (deprecated)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
"""SambaNova's Speech-to-Text service implementation for real-time transcription."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional
|
||||
|
||||
from loguru import logger
|
||||
@@ -20,6 +21,13 @@ from pipecat.services.whisper.base_stt import (
|
||||
from pipecat.transcriptions.language import Language
|
||||
|
||||
|
||||
@dataclass
|
||||
class SambaNovaSTTSettings(BaseWhisperSTTSettings):
|
||||
"""Settings for the SambaNova STT service."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SambaNovaSTTService(BaseWhisperSTTService): # type: ignore
|
||||
"""SambaNova Whisper speech-to-text service.
|
||||
|
||||
@@ -36,7 +44,7 @@ class SambaNovaSTTService(BaseWhisperSTTService): # type: ignore
|
||||
language: Optional[Language] = None,
|
||||
prompt: Optional[str] = None,
|
||||
temperature: Optional[float] = None,
|
||||
settings: Optional[BaseWhisperSTTSettings] = None,
|
||||
settings: Optional[SambaNovaSTTSettings] = None,
|
||||
ttfs_p99_latency: Optional[float] = SAMBANOVA_TTFS_P99,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
@@ -46,24 +54,24 @@ class SambaNovaSTTService(BaseWhisperSTTService): # type: ignore
|
||||
model: Whisper model to use.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=BaseWhisperSTTSettings(model=...)`` instead.
|
||||
Use ``settings=SambaNovaSTTSettings(model=...)`` instead.
|
||||
|
||||
api_key: SambaNova API key. Defaults to None.
|
||||
base_url: API base URL. Defaults to "https://api.sambanova.ai/v1".
|
||||
language: Language of the audio input.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=BaseWhisperSTTSettings(language=...)`` instead.
|
||||
Use ``settings=SambaNovaSTTSettings(language=...)`` instead.
|
||||
|
||||
prompt: Optional text to guide the model's style or continue a previous segment.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=BaseWhisperSTTSettings(prompt=...)`` instead.
|
||||
Use ``settings=SambaNovaSTTSettings(prompt=...)`` instead.
|
||||
|
||||
temperature: Optional sampling temperature between 0 and 1.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=BaseWhisperSTTSettings(temperature=...)`` instead.
|
||||
Use ``settings=SambaNovaSTTSettings(temperature=...)`` instead.
|
||||
|
||||
settings: Runtime-updatable settings. When provided alongside deprecated
|
||||
parameters, ``settings`` values take precedence.
|
||||
@@ -72,24 +80,25 @@ class SambaNovaSTTService(BaseWhisperSTTService): # type: ignore
|
||||
**kwargs: Additional arguments passed to `pipecat.services.whisper.base_stt.BaseWhisperSTTService`.
|
||||
"""
|
||||
# --- 1. Hardcoded defaults ---
|
||||
default_settings = BaseWhisperSTTSettings(
|
||||
default_settings = SambaNovaSTTSettings(
|
||||
model="Whisper-Large-v3",
|
||||
language=self.language_to_service_language(Language.EN),
|
||||
base_url=base_url,
|
||||
prompt=None,
|
||||
temperature=None,
|
||||
)
|
||||
|
||||
# --- 2. Deprecated direct-arg overrides ---
|
||||
if model is not None:
|
||||
_warn_deprecated_param("model", BaseWhisperSTTSettings, "model")
|
||||
_warn_deprecated_param("model", SambaNovaSTTSettings, "model")
|
||||
default_settings.model = model
|
||||
if language is not None:
|
||||
_warn_deprecated_param("language", BaseWhisperSTTSettings, "language")
|
||||
_warn_deprecated_param("language", SambaNovaSTTSettings, "language")
|
||||
default_settings.language = self.language_to_service_language(language)
|
||||
if prompt is not None:
|
||||
_warn_deprecated_param("prompt", BaseWhisperSTTSettings, "prompt")
|
||||
_warn_deprecated_param("prompt", SambaNovaSTTSettings, "prompt")
|
||||
default_settings.prompt = prompt
|
||||
if temperature is not None:
|
||||
_warn_deprecated_param("temperature", BaseWhisperSTTSettings, "temperature")
|
||||
_warn_deprecated_param("temperature", SambaNovaSTTSettings, "temperature")
|
||||
default_settings.temperature = temperature
|
||||
|
||||
# --- 3. (no params object for this service) ---
|
||||
@@ -107,7 +116,7 @@ class SambaNovaSTTService(BaseWhisperSTTService): # type: ignore
|
||||
)
|
||||
|
||||
async def _transcribe(self, audio: bytes) -> Transcription:
|
||||
assert self._language is not None # Assigned in the BaseWhisperSTTService class
|
||||
assert self._settings.language is not None
|
||||
|
||||
if self._include_prob_metrics:
|
||||
# https://docs.sambanova.ai/docs/en/features/audio#request-parameters
|
||||
@@ -122,13 +131,13 @@ class SambaNovaSTTService(BaseWhisperSTTService): # type: ignore
|
||||
"file": ("audio.wav", audio, "audio/wav"),
|
||||
"model": self._settings.model,
|
||||
"response_format": "json",
|
||||
"language": self._language,
|
||||
"language": self._settings.language,
|
||||
}
|
||||
|
||||
if self._prompt is not None:
|
||||
kwargs["prompt"] = self._prompt
|
||||
if self._settings.prompt is not None:
|
||||
kwargs["prompt"] = self._settings.prompt
|
||||
|
||||
if self._temperature is not None:
|
||||
kwargs["temperature"] = self._temperature
|
||||
if self._settings.temperature is not None:
|
||||
kwargs["temperature"] = self._settings.temperature
|
||||
|
||||
return await self._client.audio.transcriptions.create(**kwargs)
|
||||
|
||||
@@ -142,14 +142,13 @@ class SarvamSTTSettings(STTSettings):
|
||||
"""Settings for the Sarvam STT service.
|
||||
|
||||
Parameters:
|
||||
prompt: Optional prompt to guide transcription/translation style.
|
||||
mode: Mode of operation (transcribe, translate, verbatim, etc.).
|
||||
prompt: Optional prompt to guide transcription/translation style/context.
|
||||
Only applicable to models that support prompts (e.g., saaras:v2.5).
|
||||
vad_signals: Enable VAD signals in response.
|
||||
high_vad_sensitivity: Enable high VAD sensitivity.
|
||||
"""
|
||||
|
||||
prompt: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
mode: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
vad_signals: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
high_vad_sensitivity: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
|
||||
@@ -204,6 +203,9 @@ class SarvamSTTService(STTService):
|
||||
*,
|
||||
api_key: str,
|
||||
model: Optional[str] = None,
|
||||
mode: Optional[
|
||||
Literal["transcribe", "translate", "verbatim", "translit", "codemix"]
|
||||
] = None,
|
||||
sample_rate: Optional[int] = None,
|
||||
input_audio_codec: str = "wav",
|
||||
params: Optional[InputParams] = None,
|
||||
@@ -222,6 +224,9 @@ class SarvamSTTService(STTService):
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=SarvamSTTSettings(model=...)`` instead.
|
||||
|
||||
mode: Mode of operation. Options: transcribe, translate, verbatim,
|
||||
translit, codemix. Only applicable to models that support it
|
||||
(e.g., saaras:v3). Defaults to the model's default mode.
|
||||
sample_rate: Audio sample rate. Defaults to 16000 if not specified.
|
||||
input_audio_codec: Audio codec/format of the input file. Defaults to "wav".
|
||||
params: Configuration parameters for Sarvam STT service.
|
||||
@@ -238,32 +243,32 @@ class SarvamSTTService(STTService):
|
||||
keepalive_interval: Seconds between idle checks when keepalive is enabled.
|
||||
**kwargs: Additional arguments passed to the parent STTService.
|
||||
"""
|
||||
# 1. Initialize default_settings with hardcoded defaults
|
||||
# --- 1. Hardcoded defaults ---
|
||||
default_settings = SarvamSTTSettings(
|
||||
model="saarika:v2.5",
|
||||
language=None,
|
||||
prompt=None,
|
||||
mode=None,
|
||||
vad_signals=None,
|
||||
high_vad_sensitivity=None,
|
||||
)
|
||||
|
||||
# 2. Apply direct init arg overrides (deprecated)
|
||||
# --- 2. Deprecated direct-arg overrides ---
|
||||
if model is not None:
|
||||
_warn_deprecated_param("model", SarvamSTTSettings, "model")
|
||||
default_settings.model = model
|
||||
|
||||
# 3. Apply params overrides — only if settings not provided
|
||||
# --- 3. Deprecated params overrides ---
|
||||
if params is not None:
|
||||
_warn_deprecated_param("params", SarvamSTTSettings)
|
||||
if not settings:
|
||||
default_settings.language = params.language
|
||||
default_settings.prompt = params.prompt
|
||||
default_settings.mode = params.mode
|
||||
if params.mode is not None:
|
||||
mode = params.mode
|
||||
default_settings.vad_signals = params.vad_signals
|
||||
default_settings.high_vad_sensitivity = params.high_vad_sensitivity
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
# --- 4. Settings delta (canonical API, always wins) ---
|
||||
if settings is not None:
|
||||
default_settings.apply_update(settings)
|
||||
|
||||
@@ -278,7 +283,7 @@ class SarvamSTTService(STTService):
|
||||
# Validate parameters against model capabilities
|
||||
if default_settings.prompt is not None and not self._config.supports_prompt:
|
||||
raise ValueError(f"Model '{resolved_model}' does not support prompt parameter.")
|
||||
if default_settings.mode is not None and not self._config.supports_mode:
|
||||
if mode is not None and not self._config.supports_mode:
|
||||
raise ValueError(f"Model '{resolved_model}' does not support mode parameter.")
|
||||
if default_settings.language is not None and not self._config.supports_language:
|
||||
raise ValueError(
|
||||
@@ -286,8 +291,8 @@ class SarvamSTTService(STTService):
|
||||
)
|
||||
|
||||
# Resolve mode default from model config
|
||||
if default_settings.mode is None:
|
||||
default_settings.mode = self._config.default_mode
|
||||
if mode is None:
|
||||
mode = self._config.default_mode
|
||||
|
||||
super().__init__(
|
||||
sample_rate=sample_rate,
|
||||
@@ -300,6 +305,9 @@ class SarvamSTTService(STTService):
|
||||
|
||||
self._api_key = api_key
|
||||
|
||||
# Init-only connection config (not runtime-updatable)
|
||||
self._mode = mode
|
||||
|
||||
# Store connection parameters
|
||||
self._input_audio_codec = input_audio_codec
|
||||
|
||||
@@ -380,30 +388,26 @@ class SarvamSTTService(STTService):
|
||||
f"Model '{self._settings.model}' does not support language parameter "
|
||||
"(auto-detects language)."
|
||||
)
|
||||
|
||||
if isinstance(delta, SarvamSTTSettings):
|
||||
if is_given(delta.prompt) and delta.prompt is not None:
|
||||
if not self._config.supports_prompt:
|
||||
raise ValueError(
|
||||
f"Model '{self._settings.model}' does not support prompt parameter."
|
||||
)
|
||||
if is_given(delta.mode) and delta.mode is not None:
|
||||
if not self._config.supports_mode:
|
||||
raise ValueError(
|
||||
f"Model '{self._settings.model}' does not support mode parameter."
|
||||
)
|
||||
if (
|
||||
isinstance(delta, SarvamSTTSettings)
|
||||
and is_given(delta.prompt)
|
||||
and delta.prompt is not None
|
||||
):
|
||||
if not self._config.supports_prompt:
|
||||
raise ValueError(
|
||||
f"Model '{self._settings.model}' does not support prompt parameter."
|
||||
)
|
||||
|
||||
changed = await super()._update_settings(delta)
|
||||
|
||||
# TODO: someday we could reconnect here to apply updated settings.
|
||||
# Code might look something like the below:
|
||||
# if not changed:
|
||||
# return changed
|
||||
# Prompt is a WebSocket connect-time parameter; reconnect to apply.
|
||||
if "prompt" in changed:
|
||||
await self._disconnect()
|
||||
await self._connect()
|
||||
|
||||
# await self._disconnect()
|
||||
# await self._connect()
|
||||
|
||||
self._warn_unhandled_updated_settings(changed)
|
||||
unhandled = {k: v for k, v in changed.items() if k != "prompt"}
|
||||
if unhandled:
|
||||
self._warn_unhandled_updated_settings(unhandled)
|
||||
|
||||
return changed
|
||||
|
||||
@@ -542,8 +546,8 @@ class SarvamSTTService(STTService):
|
||||
connect_kwargs["language_code"] = language_string
|
||||
|
||||
# Add mode for models that support it
|
||||
if self._config.supports_mode and self._settings.mode is not None:
|
||||
connect_kwargs["mode"] = self._settings.mode
|
||||
if self._config.supports_mode and self._mode is not None:
|
||||
connect_kwargs["mode"] = self._mode
|
||||
|
||||
# Prompt support differs across sarvamai versions. Prefer connect-time prompt
|
||||
# when available and gracefully degrade if the SDK doesn't accept it.
|
||||
|
||||
@@ -144,8 +144,6 @@ class SonioxSTTSettings(STTSettings):
|
||||
"""Settings for Soniox STT service.
|
||||
|
||||
Parameters:
|
||||
audio_format: Audio format to use for transcription.
|
||||
num_channels: Number of channels to use for transcription.
|
||||
language_hints: List of language hints to use for transcription.
|
||||
language_hints_strict: If true, strictly enforce language hints.
|
||||
context: Customization for transcription. String for models with
|
||||
@@ -156,8 +154,6 @@ class SonioxSTTSettings(STTSettings):
|
||||
client_reference_id: Client reference ID to use for transcription.
|
||||
"""
|
||||
|
||||
audio_format: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
num_channels: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
language_hints: List[Language] | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
language_hints_strict: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
context: SonioxContextObject | str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
@@ -187,6 +183,8 @@ class SonioxSTTService(WebsocketSTTService):
|
||||
url: str = "wss://stt-rt.soniox.com/transcribe-websocket",
|
||||
sample_rate: Optional[int] = None,
|
||||
model: Optional[str] = None,
|
||||
audio_format: str = "pcm_s16le",
|
||||
num_channels: int = 1,
|
||||
params: Optional[SonioxInputParams] = None,
|
||||
vad_force_turn_endpoint: bool = True,
|
||||
settings: Optional[SonioxSTTSettings] = None,
|
||||
@@ -204,6 +202,8 @@ class SonioxSTTService(WebsocketSTTService):
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=SonioxSTTSettings(model=...)`` instead.
|
||||
|
||||
audio_format: Audio format for transcription. Defaults to ``"pcm_s16le"``.
|
||||
num_channels: Number of audio channels. Defaults to 1.
|
||||
params: Additional configuration parameters, such as language hints, context and
|
||||
speaker diarization.
|
||||
|
||||
@@ -218,12 +218,10 @@ class SonioxSTTService(WebsocketSTTService):
|
||||
Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark
|
||||
**kwargs: Additional arguments passed to the STTService.
|
||||
"""
|
||||
# 1. Initialize default_settings with hardcoded defaults
|
||||
# --- 1. Hardcoded defaults ---
|
||||
default_settings = SonioxSTTSettings(
|
||||
model="stt-rt-v4",
|
||||
language=None,
|
||||
audio_format="pcm_s16le",
|
||||
num_channels=1,
|
||||
language_hints=None,
|
||||
language_hints_strict=None,
|
||||
context=None,
|
||||
@@ -232,18 +230,20 @@ class SonioxSTTService(WebsocketSTTService):
|
||||
client_reference_id=None,
|
||||
)
|
||||
|
||||
# 2. Apply direct init arg overrides (deprecated)
|
||||
# --- 2. Deprecated direct-arg overrides ---
|
||||
if model is not None:
|
||||
_warn_deprecated_param("model", SonioxSTTSettings, "model")
|
||||
default_settings.model = model
|
||||
|
||||
# 3. Apply params overrides — only if settings not provided
|
||||
# --- 3. Deprecated params overrides ---
|
||||
if params is not None:
|
||||
_warn_deprecated_param("params", SonioxSTTSettings)
|
||||
if not settings:
|
||||
default_settings.model = params.model
|
||||
default_settings.audio_format = params.audio_format
|
||||
default_settings.num_channels = params.num_channels
|
||||
if params.audio_format is not None:
|
||||
audio_format = params.audio_format
|
||||
if params.num_channels is not None:
|
||||
num_channels = params.num_channels
|
||||
default_settings.language_hints = params.language_hints
|
||||
default_settings.language_hints_strict = params.language_hints_strict
|
||||
default_settings.context = params.context
|
||||
@@ -253,7 +253,7 @@ class SonioxSTTService(WebsocketSTTService):
|
||||
)
|
||||
default_settings.client_reference_id = params.client_reference_id
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
# --- 4. Settings delta (canonical API, always wins) ---
|
||||
if settings is not None:
|
||||
default_settings.apply_update(settings)
|
||||
|
||||
@@ -270,6 +270,10 @@ class SonioxSTTService(WebsocketSTTService):
|
||||
self._url = url
|
||||
self._vad_force_turn_endpoint = vad_force_turn_endpoint
|
||||
|
||||
# Init-only audio config
|
||||
self._audio_format = audio_format
|
||||
self._num_channels = num_channels
|
||||
|
||||
self._final_transcription_buffer = []
|
||||
self._last_tokens_received: Optional[float] = None
|
||||
|
||||
@@ -438,8 +442,8 @@ class SonioxSTTService(WebsocketSTTService):
|
||||
config = {
|
||||
"api_key": self._api_key,
|
||||
"model": s.model,
|
||||
"audio_format": s.audio_format,
|
||||
"num_channels": s.num_channels or 1,
|
||||
"audio_format": self._audio_format,
|
||||
"num_channels": self._num_channels,
|
||||
"enable_endpoint_detection": enable_endpoint_detection,
|
||||
"sample_rate": self.sample_rate,
|
||||
"language_hints": _prepare_language_hints(s.language_hints),
|
||||
|
||||
@@ -100,7 +100,6 @@ class SpeechmaticsSTTSettings(STTSettings):
|
||||
focus_mode: Speaker focus mode for diarization.
|
||||
known_speakers: List of known speaker labels and identifiers.
|
||||
additional_vocab: List of additional vocabulary entries.
|
||||
audio_encoding: Audio encoding format.
|
||||
operating_point: Operating point for accuracy vs. latency.
|
||||
max_delay: Maximum delay in seconds for transcription.
|
||||
end_of_utterance_silence_trigger: Maximum delay for end of utterance trigger.
|
||||
@@ -126,7 +125,6 @@ class SpeechmaticsSTTSettings(STTSettings):
|
||||
additional_vocab: list[AdditionalVocabEntry] | _NotGiven = field(
|
||||
default_factory=lambda: NOT_GIVEN
|
||||
)
|
||||
audio_encoding: AudioEncoding | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
operating_point: OperatingPoint | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
max_delay: float | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
end_of_utterance_silence_trigger: float | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
@@ -344,8 +342,8 @@ class SpeechmaticsSTTService(STTService):
|
||||
class UpdateParams(BaseModel):
|
||||
"""Update parameters for Speechmatics STT service.
|
||||
|
||||
These are the only parameters that can be changed once a session has started. If you need to
|
||||
change the language, etc., then you must create a new instance of the service.
|
||||
.. deprecated:: 0.0.104
|
||||
Use ``SpeechmaticsSTTSettings`` with ``STTUpdateSettingsFrame`` instead.
|
||||
|
||||
Parameters:
|
||||
focus_speakers: List of speaker IDs to focus on. When enabled, only these speakers are
|
||||
@@ -379,6 +377,7 @@ class SpeechmaticsSTTService(STTService):
|
||||
api_key: str | None = None,
|
||||
base_url: str | None = None,
|
||||
sample_rate: int | None = None,
|
||||
encoding: AudioEncoding = AudioEncoding.PCM_S16LE,
|
||||
params: InputParams | None = None,
|
||||
should_interrupt: bool = True,
|
||||
settings: SpeechmaticsSTTSettings | None = None,
|
||||
@@ -393,6 +392,7 @@ class SpeechmaticsSTTService(STTService):
|
||||
base_url: Base URL for Speechmatics API. Uses environment variable `SPEECHMATICS_RT_URL`
|
||||
or defaults to `wss://eu2.rt.speechmatics.com/v2`.
|
||||
sample_rate: Optional audio sample rate in Hz.
|
||||
encoding: Audio encoding format. Defaults to ``AudioEncoding.PCM_S16LE``.
|
||||
params: Input parameters for the service.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
@@ -423,7 +423,7 @@ class SpeechmaticsSTTService(STTService):
|
||||
_params = params or SpeechmaticsSTTService.InputParams()
|
||||
self._check_deprecated_args(kwargs, _params)
|
||||
|
||||
# 1. Initialize default_settings with hardcoded defaults
|
||||
# --- 1. Hardcoded defaults ---
|
||||
default_settings = SpeechmaticsSTTSettings(
|
||||
model=None, # Will be resolved from operating_point after config is built
|
||||
language=Language.EN,
|
||||
@@ -436,7 +436,6 @@ class SpeechmaticsSTTService(STTService):
|
||||
focus_mode=SpeakerFocusMode.RETAIN,
|
||||
known_speakers=[],
|
||||
additional_vocab=[],
|
||||
audio_encoding=AudioEncoding.PCM_S16LE,
|
||||
operating_point=None,
|
||||
max_delay=None,
|
||||
end_of_utterance_silence_trigger=None,
|
||||
@@ -451,9 +450,9 @@ class SpeechmaticsSTTService(STTService):
|
||||
extra_params=None,
|
||||
)
|
||||
|
||||
# 2. No direct init arg overrides
|
||||
# --- 2. No direct init arg overrides ---
|
||||
|
||||
# 3. Apply params overrides — only if settings not provided
|
||||
# --- 3. Deprecated params overrides ---
|
||||
if params is not None:
|
||||
_warn_deprecated_param("params", SpeechmaticsSTTSettings)
|
||||
if not settings:
|
||||
@@ -475,7 +474,7 @@ class SpeechmaticsSTTService(STTService):
|
||||
default_settings.focus_mode = _params.focus_mode
|
||||
default_settings.known_speakers = _params.known_speakers
|
||||
default_settings.additional_vocab = _params.additional_vocab
|
||||
default_settings.audio_encoding = _params.audio_encoding
|
||||
encoding = _params.audio_encoding
|
||||
default_settings.operating_point = _params.operating_point
|
||||
default_settings.max_delay = _params.max_delay
|
||||
default_settings.end_of_utterance_silence_trigger = (
|
||||
@@ -493,10 +492,11 @@ class SpeechmaticsSTTService(STTService):
|
||||
|
||||
# Build SDK config from settings, then resolve model from operating_point
|
||||
self._client: VoiceAgentClient | None = None
|
||||
self._audio_encoding = encoding
|
||||
self._config: VoiceAgentConfig = self._build_config(default_settings)
|
||||
default_settings.model = self._config.operating_point.value
|
||||
|
||||
# 4. Apply settings delta (canonical API, always wins)
|
||||
# --- 4. Settings delta (canonical API, always wins) ---
|
||||
if settings is not None:
|
||||
default_settings.apply_update(settings)
|
||||
|
||||
@@ -720,6 +720,9 @@ class SpeechmaticsSTTService(STTService):
|
||||
# Preset from turn detection mode
|
||||
config = VoiceAgentConfigPreset.load(s.turn_detection_mode.value)
|
||||
|
||||
# Audio encoding (init-only, stored as instance attribute)
|
||||
config.audio_encoding = self._audio_encoding
|
||||
|
||||
# Language + domain
|
||||
language = s.language
|
||||
config.language = self._language_to_speechmatics_language(language)
|
||||
@@ -773,7 +776,7 @@ class SpeechmaticsSTTService(STTService):
|
||||
) -> None:
|
||||
"""Updates the speaker configuration.
|
||||
|
||||
.. deprecated::
|
||||
.. deprecated:: 0.0.104
|
||||
Use ``STTUpdateSettingsFrame`` with
|
||||
``SpeechmaticsSTTSettings(...)`` instead.
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@ This module provides common functionality for services implementing the Whisper
|
||||
interface, including language mapping, metrics generation, and error handling.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
from dataclasses import dataclass
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
from openai import AsyncOpenAI
|
||||
from openai.types.audio import Transcription
|
||||
|
||||
from pipecat.frames.frames import ErrorFrame, Frame, TranscriptionFrame
|
||||
from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.settings import STTSettings, _NotGiven, _warn_deprecated_param
|
||||
from pipecat.services.stt_latency import WHISPER_TTFS_P99
|
||||
from pipecat.services.stt_service import SegmentedSTTService
|
||||
from pipecat.transcriptions.language import Language, resolve_language
|
||||
@@ -31,15 +31,13 @@ class BaseWhisperSTTSettings(STTSettings):
|
||||
"""Settings for Whisper API-based STT services.
|
||||
|
||||
Parameters:
|
||||
base_url: API base URL.
|
||||
prompt: Optional text to guide the model's style or continue
|
||||
a previous segment.
|
||||
temperature: Sampling temperature between 0 and 1.
|
||||
"""
|
||||
|
||||
base_url: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
prompt: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
temperature: float | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
prompt: str | None | _NotGiven = None
|
||||
temperature: float | None | _NotGiven = None
|
||||
|
||||
|
||||
def language_to_whisper_language(language: Language) -> Optional[str]:
|
||||
@@ -185,7 +183,6 @@ class BaseWhisperSTTService(SegmentedSTTService):
|
||||
default_settings = BaseWhisperSTTSettings(
|
||||
model=None,
|
||||
language=None,
|
||||
base_url=base_url,
|
||||
)
|
||||
|
||||
# --- 2. Deprecated direct-arg overrides ---
|
||||
@@ -214,32 +211,12 @@ class BaseWhisperSTTService(SegmentedSTTService):
|
||||
**kwargs,
|
||||
)
|
||||
self._client = self._create_client(api_key, base_url)
|
||||
self._language = self._settings.language
|
||||
self._prompt = self._settings.prompt
|
||||
self._temperature = self._settings.temperature
|
||||
self._include_prob_metrics = include_prob_metrics
|
||||
self._push_empty_transcripts = push_empty_transcripts
|
||||
|
||||
def _create_client(self, api_key: Optional[str], base_url: Optional[str]):
|
||||
return AsyncOpenAI(api_key=api_key, base_url=base_url)
|
||||
|
||||
async def _update_settings(self, delta: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings delta, syncing instance variables.
|
||||
|
||||
Keeps ``_language``, ``_prompt``, and ``_temperature`` in sync with
|
||||
the settings fields.
|
||||
"""
|
||||
changed = await super()._update_settings(delta)
|
||||
|
||||
if "language" in changed:
|
||||
self._language = self._settings.language
|
||||
if "prompt" in changed:
|
||||
self._prompt = self._settings.prompt
|
||||
if "temperature" in changed:
|
||||
self._temperature = self._settings.temperature
|
||||
|
||||
return changed
|
||||
|
||||
def can_generate_metrics(self) -> bool:
|
||||
"""Whether this service can generate processing metrics.
|
||||
|
||||
@@ -289,7 +266,7 @@ class BaseWhisperSTTService(SegmentedSTTService):
|
||||
logger.warning("Received empty transcription from API")
|
||||
|
||||
if text or self._push_empty_transcripts:
|
||||
await self._handle_transcription(text, True, self._language)
|
||||
await self._handle_transcription(text, True, self._settings.language)
|
||||
logger.debug(f"Transcription: [{text}]")
|
||||
yield TranscriptionFrame(
|
||||
text,
|
||||
|
||||
@@ -179,13 +179,9 @@ class WhisperSTTSettings(STTSettings):
|
||||
"""Settings for the local Whisper (Faster Whisper) STT service.
|
||||
|
||||
Parameters:
|
||||
device: Inference device ('cpu', 'cuda', or 'auto').
|
||||
compute_type: Compute type for inference ('default', 'int8', etc.).
|
||||
no_speech_prob: Probability threshold for filtering non-speech segments.
|
||||
"""
|
||||
|
||||
device: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
compute_type: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
no_speech_prob: float | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
|
||||
|
||||
|
||||
@@ -217,8 +213,8 @@ class WhisperSTTService(SegmentedSTTService):
|
||||
self,
|
||||
*,
|
||||
model: Optional[str | Model] = None,
|
||||
device: Optional[str] = None,
|
||||
compute_type: Optional[str] = None,
|
||||
device: str = "auto",
|
||||
compute_type: str = "default",
|
||||
no_speech_prob: Optional[float] = None,
|
||||
language: Optional[Language] = None,
|
||||
settings: Optional[WhisperSTTSettings] = None,
|
||||
@@ -233,15 +229,9 @@ class WhisperSTTService(SegmentedSTTService):
|
||||
Use ``settings=WhisperSTTSettings(model=...)`` instead.
|
||||
|
||||
device: The device to run inference on ('cpu', 'cuda', or 'auto').
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=WhisperSTTSettings(device=...)`` instead.
|
||||
|
||||
compute_type: The compute type for inference ('default', 'int8', 'int8_float16', etc.).
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
Use ``settings=WhisperSTTSettings(compute_type=...)`` instead.
|
||||
|
||||
Defaults to ``"auto"``.
|
||||
compute_type: The compute type for inference ('default', 'int8',
|
||||
'int8_float16', etc.). Defaults to ``"default"``.
|
||||
no_speech_prob: Probability threshold for filtering out non-speech segments.
|
||||
|
||||
.. deprecated:: 0.0.105
|
||||
@@ -260,8 +250,6 @@ class WhisperSTTService(SegmentedSTTService):
|
||||
default_settings = WhisperSTTSettings(
|
||||
model=Model.DISTIL_MEDIUM_EN.value,
|
||||
language=Language.EN,
|
||||
device="auto",
|
||||
compute_type="default",
|
||||
no_speech_prob=0.4,
|
||||
)
|
||||
|
||||
@@ -269,12 +257,6 @@ class WhisperSTTService(SegmentedSTTService):
|
||||
if model is not None:
|
||||
_warn_deprecated_param("model", WhisperSTTSettings, "model")
|
||||
default_settings.model = model if isinstance(model, str) else model.value
|
||||
if device is not None:
|
||||
_warn_deprecated_param("device", WhisperSTTSettings, "device")
|
||||
default_settings.device = device
|
||||
if compute_type is not None:
|
||||
_warn_deprecated_param("compute_type", WhisperSTTSettings, "compute_type")
|
||||
default_settings.compute_type = compute_type
|
||||
if no_speech_prob is not None:
|
||||
_warn_deprecated_param("no_speech_prob", WhisperSTTSettings, "no_speech_prob")
|
||||
default_settings.no_speech_prob = no_speech_prob
|
||||
@@ -292,9 +274,11 @@ class WhisperSTTService(SegmentedSTTService):
|
||||
settings=default_settings,
|
||||
**kwargs,
|
||||
)
|
||||
self._device: str = self._settings.device
|
||||
self._compute_type = self._settings.compute_type
|
||||
self._no_speech_prob = self._settings.no_speech_prob
|
||||
|
||||
# Init-only inference config
|
||||
self._device = device
|
||||
self._compute_type = compute_type
|
||||
|
||||
self._model: Optional[WhisperModel] = None
|
||||
|
||||
self._load()
|
||||
@@ -373,7 +357,7 @@ class WhisperSTTService(SegmentedSTTService):
|
||||
)
|
||||
text: str = ""
|
||||
for segment in segments:
|
||||
if segment.no_speech_prob < self._no_speech_prob:
|
||||
if segment.no_speech_prob < self._settings.no_speech_prob:
|
||||
text += f"{segment.text} "
|
||||
|
||||
await self.stop_processing_metrics()
|
||||
@@ -471,9 +455,6 @@ class WhisperSTTServiceMLX(WhisperSTTService):
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self._no_speech_prob = self._settings.no_speech_prob
|
||||
self._temperature = self._settings.temperature
|
||||
|
||||
# No need to call _load() as MLX Whisper loads models on demand
|
||||
|
||||
@override
|
||||
@@ -514,7 +495,7 @@ class WhisperSTTServiceMLX(WhisperSTTService):
|
||||
mlx_whisper.transcribe,
|
||||
audio_float,
|
||||
path_or_hf_repo=self._settings.model,
|
||||
temperature=self._temperature,
|
||||
temperature=self._settings.temperature,
|
||||
language=self._settings.language,
|
||||
)
|
||||
text: str = ""
|
||||
@@ -523,7 +504,7 @@ class WhisperSTTServiceMLX(WhisperSTTService):
|
||||
if segment.get("compression_ratio", None) == 0.5555555555555556:
|
||||
continue
|
||||
|
||||
if segment.get("no_speech_prob", 0.0) < self._no_speech_prob:
|
||||
if segment.get("no_speech_prob", 0.0) < self._settings.no_speech_prob:
|
||||
text += f"{segment.get('text', '')} "
|
||||
|
||||
if len(text.strip()) == 0:
|
||||
|
||||
@@ -328,8 +328,6 @@ class TestDeepgramSTTSettingsApplyUpdate:
|
||||
defaults = dict(
|
||||
model="nova-3-general",
|
||||
language="en",
|
||||
encoding="linear16",
|
||||
channels=1,
|
||||
interim_results=True,
|
||||
smart_format=False,
|
||||
punctuate=True,
|
||||
@@ -350,8 +348,8 @@ class TestDeepgramSTTSettingsApplyUpdate:
|
||||
assert current.punctuate is False
|
||||
assert "punctuate" in changed
|
||||
# Other fields are untouched
|
||||
assert current.encoding == "linear16"
|
||||
assert current.channels == 1
|
||||
assert current.model == "nova-3-general"
|
||||
assert current.language == "en"
|
||||
|
||||
def test_apply_update_model(self):
|
||||
"""model field is updated directly."""
|
||||
@@ -427,8 +425,6 @@ class TestDeepgramSTTSettingsFromMapping:
|
||||
current = DeepgramSTTSettings(
|
||||
model="nova-3-general",
|
||||
language="en",
|
||||
encoding="linear16",
|
||||
channels=1,
|
||||
interim_results=True,
|
||||
punctuate=True,
|
||||
profanity_filter=True,
|
||||
@@ -442,7 +438,6 @@ class TestDeepgramSTTSettingsFromMapping:
|
||||
assert current.punctuate is False
|
||||
assert current.diarize is True
|
||||
# Unchanged fields stay put
|
||||
assert current.encoding == "linear16"
|
||||
assert current.model == "nova-3-general"
|
||||
assert "punctuate" in changed
|
||||
|
||||
@@ -451,8 +446,6 @@ class TestDeepgramSTTSettingsFromMapping:
|
||||
current = DeepgramSTTSettings(
|
||||
model="nova-3-general",
|
||||
language="en",
|
||||
encoding="linear16",
|
||||
channels=1,
|
||||
)
|
||||
|
||||
raw = {"model": "nova-2"}
|
||||
@@ -474,16 +467,13 @@ class TestDeepgramSageMakerSTTSettings:
|
||||
store = DeepgramSageMakerSTTSettings(
|
||||
model="nova-3",
|
||||
language="en",
|
||||
encoding="linear16",
|
||||
channels=1,
|
||||
punctuate=True,
|
||||
)
|
||||
delta = DeepgramSageMakerSTTSettings(punctuate=False)
|
||||
delta = DeepgramSageMakerSTTSettings(model="nova-2")
|
||||
changed = store.apply_update(delta)
|
||||
|
||||
assert store.punctuate is False
|
||||
assert store.encoding == "linear16"
|
||||
assert "punctuate" in changed
|
||||
assert store.model == "nova-2"
|
||||
assert store.language == "en"
|
||||
assert "model" in changed
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -499,17 +489,17 @@ class TestDeepgramSTTSettingsExtraSync:
|
||||
return DeepgramSTTService(api_key="test-key", sample_rate=16000, **kwargs)
|
||||
|
||||
def test_extra_synced_to_declared_field_at_init(self):
|
||||
"""If LiveOptions has unknown params in _extra, they can be synced if they match fields."""
|
||||
"""LiveOptions params that match declared fields are synced at init."""
|
||||
from pipecat.services.deepgram.stt import LiveOptions
|
||||
|
||||
# Use **kwargs to pass undeclared params
|
||||
live_options = LiveOptions(numerals=True) # 'numerals' goes into _extra
|
||||
live_options = LiveOptions(numerals=True)
|
||||
|
||||
svc = self._make_service(live_options=live_options)
|
||||
|
||||
# 'numerals' doesn't match a declared DeepgramSTTSettings field,
|
||||
# so it should stay in extra
|
||||
assert svc._settings.extra["numerals"] is True
|
||||
# 'numerals' is a declared DeepgramSTTSettings field,
|
||||
# so it should be promoted from extra to the declared field
|
||||
assert svc._settings.numerals is True
|
||||
assert "numerals" not in svc._settings.extra
|
||||
|
||||
def test_declared_field_from_live_options(self):
|
||||
"""LiveOptions fields that match DeepgramSTTSettings fields are applied."""
|
||||
@@ -532,7 +522,7 @@ class TestDeepgramSTTSettingsExtraSync:
|
||||
raw_dict = {
|
||||
"diarize": True, # matches declared field
|
||||
"punctuate": False, # matches declared field
|
||||
"numerals": True, # doesn't match - stays in extra
|
||||
"custom_param": "value", # doesn't match - stays in extra
|
||||
}
|
||||
|
||||
delta = DeepgramSTTSettings.from_mapping(raw_dict)
|
||||
@@ -541,7 +531,7 @@ class TestDeepgramSTTSettingsExtraSync:
|
||||
assert delta.diarize is True
|
||||
assert delta.punctuate is False
|
||||
# Unknown stays in extra
|
||||
assert delta.extra["numerals"] is True
|
||||
assert delta.extra["custom_param"] == "value"
|
||||
|
||||
# Now simulate syncing (though from_mapping already routes correctly)
|
||||
delta._sync_extra_to_fields()
|
||||
@@ -549,7 +539,7 @@ class TestDeepgramSTTSettingsExtraSync:
|
||||
# Still the same - from_mapping already put them in the right place
|
||||
assert delta.diarize is True
|
||||
assert delta.punctuate is False
|
||||
assert delta.extra["numerals"] is True
|
||||
assert delta.extra["custom_param"] == "value"
|
||||
|
||||
def test_sync_promotes_extra_to_field_when_not_given(self):
|
||||
"""_sync_extra_to_fields promotes extra dict entries to declared fields."""
|
||||
@@ -611,16 +601,17 @@ class TestDeepgramSTTSettingsExtraSync:
|
||||
"""Unknown params (not matching fields) stay in extra and get forwarded."""
|
||||
from pipecat.services.deepgram.stt import LiveOptions
|
||||
|
||||
# numerals isn't a declared field in DeepgramSTTSettings
|
||||
# 'numerals' is now a declared field; 'custom_param' is not
|
||||
live_options = LiveOptions(numerals=True, custom_param="test")
|
||||
|
||||
svc = self._make_service(live_options=live_options)
|
||||
|
||||
# Should be in extra
|
||||
assert svc._settings.extra["numerals"] is True
|
||||
# 'numerals' is a declared field, so it should be promoted
|
||||
assert svc._settings.numerals is True
|
||||
# 'custom_param' is unknown, so it stays in extra
|
||||
assert svc._settings.extra["custom_param"] == "test"
|
||||
|
||||
# And forwarded to kwargs
|
||||
# Both forwarded to kwargs
|
||||
kwargs = svc._build_connect_kwargs()
|
||||
assert kwargs["numerals"] == "true"
|
||||
assert kwargs["custom_param"] == "test"
|
||||
|
||||
Reference in New Issue
Block a user