feat: Add ErrorFrame emission to TTS/STT services for pipeline error detection (#2881)
* feat: Add ErrorFrame emission to TTS/STT services for pipeline error detection - Add ErrorFrame emission to all major TTS/STT services during initialization and runtime failures - Services updated: Cartesia, ElevenLabs, Deepgram, AssemblyAI, Rime, Azure - ErrorFrame objects emitted with fatal=False for graceful degradation - Enables on_pipeline_error event handler to detect service failures programmatically - Add comprehensive pytest test suite to verify ErrorFrame emission - Fixes issue where services failed gracefully but didn't emit ErrorFrame objects This allows developers to implement real-time error monitoring and alerting using the on_pipeline_error event handler introduced in v0.0.90. * Update STT and TTS services to use consistent error handling pattern - Improves error handling consistency across all services * Add changelog entry for STT/TTS error handling improvements * Linting issues Resolved * Azure STT ErrorFrames added with consistent patterns * Cartesia STT and Deepgram STT; additional fixes made * Removed Fatal Flags across services, removed duplication * Moving the changelog entry to the correct place. * Refactoring some classes to use yield instead of push_error directly. * Fixing ruff format. --------- Co-authored-by: Filipi Fuchter <filipi87@gmail.com>
This commit is contained in:
@@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Updated all STT and TTS services to use consistent error handling pattern with
|
||||||
|
`push_error()` method for better pipeline error event integration.
|
||||||
|
|
||||||
- Added Hindi support for Rime TTS services.
|
- Added Hindi support for Rime TTS services.
|
||||||
|
|
||||||
- Updated `GeminiTTSService` to use Google Cloud Text-to-Speech streaming API
|
- Updated `GeminiTTSService` to use Google Cloud Text-to-Speech streaming API
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from pipecat import __version__ as pipecat_version
|
|||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
EndFrame,
|
EndFrame,
|
||||||
|
ErrorFrame,
|
||||||
Frame,
|
Frame,
|
||||||
InterimTranscriptionFrame,
|
InterimTranscriptionFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
@@ -205,8 +206,9 @@ class AssemblyAISTTService(STTService):
|
|||||||
|
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to connect to AssemblyAI: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def _disconnect(self):
|
async def _disconnect(self):
|
||||||
@@ -231,7 +233,8 @@ class AssemblyAISTTService(STTService):
|
|||||||
logger.warning("Timed out waiting for termination message from server")
|
logger.warning("Timed out waiting for termination message from server")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error during termination handshake: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
if self._receive_task:
|
if self._receive_task:
|
||||||
await self.cancel_task(self._receive_task)
|
await self.cancel_task(self._receive_task)
|
||||||
@@ -239,7 +242,8 @@ class AssemblyAISTTService(STTService):
|
|||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error during disconnect: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
@@ -258,11 +262,13 @@ class AssemblyAISTTService(STTService):
|
|||||||
except websockets.exceptions.ConnectionClosedOK:
|
except websockets.exceptions.ConnectionClosedOK:
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing WebSocket message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
break
|
break
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fatal error in receive handler: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
def _parse_message(self, message: Dict[str, Any]) -> BaseMessage:
|
def _parse_message(self, message: Dict[str, Any]) -> BaseMessage:
|
||||||
"""Parse a raw message into the appropriate message type."""
|
"""Parse a raw message into the appropriate message type."""
|
||||||
@@ -291,7 +297,8 @@ class AssemblyAISTTService(STTService):
|
|||||||
elif isinstance(parsed_message, TerminationMessage):
|
elif isinstance(parsed_message, TerminationMessage):
|
||||||
await self._handle_termination(parsed_message)
|
await self._handle_termination(parsed_message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error handling message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
async def _handle_termination(self, message: TerminationMessage):
|
async def _handle_termination(self, message: TerminationMessage):
|
||||||
"""Handle termination message."""
|
"""Handle termination message."""
|
||||||
|
|||||||
@@ -237,7 +237,8 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
|
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
@@ -249,7 +250,8 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
logger.debug("Disconnecting from Async")
|
logger.debug("Disconnecting from Async")
|
||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
self._started = False
|
self._started = False
|
||||||
@@ -297,7 +299,7 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
logger.error(f"{self} error: {msg}")
|
logger.error(f"{self} error: {msg}")
|
||||||
await self.push_frame(TTSStoppedFrame())
|
await self.push_frame(TTSStoppedFrame())
|
||||||
await self.stop_all_metrics()
|
await self.stop_all_metrics()
|
||||||
await self.push_error(ErrorFrame(f"{self} error: {msg['message']}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {msg['message']}"))
|
||||||
else:
|
else:
|
||||||
logger.error(f"{self} error, unknown message type: {msg}")
|
logger.error(f"{self} error, unknown message type: {msg}")
|
||||||
|
|
||||||
@@ -342,7 +344,8 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
await self._get_websocket().send(msg)
|
await self._get_websocket().send(msg)
|
||||||
await self.start_tts_usage_metrics(text)
|
await self.start_tts_usage_metrics(text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error sending message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
@@ -350,6 +353,7 @@ class AsyncAITTSService(InterruptibleTTSService):
|
|||||||
yield None
|
yield None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
class AsyncAIHttpTTSService(TTSService):
|
class AsyncAIHttpTTSService(TTSService):
|
||||||
@@ -492,7 +496,7 @@ class AsyncAIHttpTTSService(TTSService):
|
|||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
error_text = await response.text()
|
error_text = await response.text()
|
||||||
logger.error(f"Async API error: {error_text}")
|
logger.error(f"Async API error: {error_text}")
|
||||||
await self.push_error(ErrorFrame(f"Async API error: {error_text}"))
|
await self.push_error(ErrorFrame(error=f"Async API error: {error_text}"))
|
||||||
raise Exception(f"Async API returned status {response.status}: {error_text}")
|
raise Exception(f"Async API returned status {response.status}: {error_text}")
|
||||||
|
|
||||||
audio_data = await response.read()
|
audio_data = await response.read()
|
||||||
@@ -509,7 +513,7 @@ class AsyncAIHttpTTSService(TTSService):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
await self.push_error(ErrorFrame(f"Error generating TTS: {e}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ class AWSTranscribeSTTService(STTService):
|
|||||||
return
|
return
|
||||||
logger.warning("WebSocket connection not established after connect")
|
logger.warning("WebSocket connection not established after connect")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to connect (attempt {retry_count + 1}/{max_retries}): {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
retry_count += 1
|
retry_count += 1
|
||||||
if retry_count < max_retries:
|
if retry_count < max_retries:
|
||||||
await asyncio.sleep(1) # Wait before retrying
|
await asyncio.sleep(1) # Wait before retrying
|
||||||
@@ -181,8 +182,8 @@ class AWSTranscribeSTTService(STTService):
|
|||||||
try:
|
try:
|
||||||
await self._connect()
|
await self._connect()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to reconnect: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame("Failed to reconnect to AWS Transcribe", fatal=False)
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Format the audio data according to AWS event stream format
|
# Format the audio data according to AWS event stream format
|
||||||
@@ -199,13 +200,13 @@ class AWSTranscribeSTTService(STTService):
|
|||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
# Don't yield error here - we'll retry on next frame
|
# Don't yield error here - we'll retry on next frame
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending audio: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"AWS Transcribe error: {str(e)}", fatal=False)
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in run_stt: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"AWS Transcribe error: {str(e)}", fatal=False)
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
|
|
||||||
async def _connect(self):
|
async def _connect(self):
|
||||||
@@ -288,7 +289,8 @@ class AWSTranscribeSTTService(STTService):
|
|||||||
|
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} Failed to connect to AWS Transcribe: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -308,7 +310,8 @@ class AWSTranscribeSTTService(STTService):
|
|||||||
await self._ws_client.send(json.dumps(end_stream))
|
await self._ws_client.send(json.dumps(end_stream))
|
||||||
await self._ws_client.close()
|
await self._ws_client.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"{self} Error closing WebSocket connection: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._ws_client = None
|
self._ws_client = None
|
||||||
await self._call_event_handler("on_disconnected")
|
await self._call_event_handler("on_disconnected")
|
||||||
@@ -527,9 +530,7 @@ class AWSTranscribeSTTService(STTService):
|
|||||||
elif headers.get(":message-type") == "exception":
|
elif headers.get(":message-type") == "exception":
|
||||||
error_msg = payload.get("Message", "Unknown error")
|
error_msg = payload.get("Message", "Unknown error")
|
||||||
logger.error(f"{self} Exception from AWS: {error_msg}")
|
logger.error(f"{self} Exception from AWS: {error_msg}")
|
||||||
await self.push_frame(
|
await self.push_frame(ErrorFrame(f"AWS Transcribe error: {error_msg}"))
|
||||||
ErrorFrame(f"AWS Transcribe error: {error_msg}", fatal=False)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.debug(f"{self} Other message type received: {headers}")
|
logger.debug(f"{self} Other message type received: {headers}")
|
||||||
logger.debug(f"{self} Payload: {payload}")
|
logger.debug(f"{self} Payload: {payload}")
|
||||||
@@ -537,5 +538,6 @@ class AWSTranscribeSTTService(STTService):
|
|||||||
logger.error(f"{self} WebSocket connection closed in receive loop: {e}")
|
logger.error(f"{self} WebSocket connection closed in receive loop: {e}")
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} Unexpected error in receive loop: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from loguru import logger
|
|||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
EndFrame,
|
EndFrame,
|
||||||
|
ErrorFrame,
|
||||||
Frame,
|
Frame,
|
||||||
InterimTranscriptionFrame,
|
InterimTranscriptionFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
@@ -111,13 +112,17 @@ class AzureSTTService(STTService):
|
|||||||
audio: Raw audio bytes to process.
|
audio: Raw audio bytes to process.
|
||||||
|
|
||||||
Yields:
|
Yields:
|
||||||
None - actual transcription frames are pushed via callbacks.
|
Frame: Either None for successful processing or ErrorFrame on failure.
|
||||||
"""
|
"""
|
||||||
await self.start_processing_metrics()
|
try:
|
||||||
await self.start_ttfb_metrics()
|
await self.start_processing_metrics()
|
||||||
if self._audio_stream:
|
await self.start_ttfb_metrics()
|
||||||
self._audio_stream.write(audio)
|
if self._audio_stream:
|
||||||
yield None
|
self._audio_stream.write(audio)
|
||||||
|
yield None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
"""Start the speech recognition service.
|
"""Start the speech recognition service.
|
||||||
@@ -133,17 +138,21 @@ class AzureSTTService(STTService):
|
|||||||
if self._audio_stream:
|
if self._audio_stream:
|
||||||
return
|
return
|
||||||
|
|
||||||
stream_format = AudioStreamFormat(samples_per_second=self.sample_rate, channels=1)
|
try:
|
||||||
self._audio_stream = PushAudioInputStream(stream_format)
|
stream_format = AudioStreamFormat(samples_per_second=self.sample_rate, channels=1)
|
||||||
|
self._audio_stream = PushAudioInputStream(stream_format)
|
||||||
|
|
||||||
audio_config = AudioConfig(stream=self._audio_stream)
|
audio_config = AudioConfig(stream=self._audio_stream)
|
||||||
|
|
||||||
self._speech_recognizer = SpeechRecognizer(
|
self._speech_recognizer = SpeechRecognizer(
|
||||||
speech_config=self._speech_config, audio_config=audio_config
|
speech_config=self._speech_config, audio_config=audio_config
|
||||||
)
|
)
|
||||||
self._speech_recognizer.recognizing.connect(self._on_handle_recognizing)
|
self._speech_recognizer.recognizing.connect(self._on_handle_recognizing)
|
||||||
self._speech_recognizer.recognized.connect(self._on_handle_recognized)
|
self._speech_recognizer.recognized.connect(self._on_handle_recognized)
|
||||||
self._speech_recognizer.start_continuous_recognition_async()
|
self._speech_recognizer.start_continuous_recognition_async()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"{self} exception during initialization: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
async def stop(self, frame: EndFrame):
|
async def stop(self, frame: EndFrame):
|
||||||
"""Stop the speech recognition service.
|
"""Stop the speech recognition service.
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ class AzureTTSService(AzureBaseTTSService):
|
|||||||
if self._speech_synthesizer is None:
|
if self._speech_synthesizer is None:
|
||||||
error_msg = "Speech synthesizer not initialized."
|
error_msg = "Speech synthesizer not initialized."
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
yield ErrorFrame(error_msg)
|
yield ErrorFrame(error=error_msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -364,13 +364,15 @@ class AzureTTSService(AzureBaseTTSService):
|
|||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error during synthesis: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
# Could add reconnection logic here if needed
|
# Could add reconnection logic here if needed
|
||||||
return
|
return
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
class AzureHttpTTSService(AzureBaseTTSService):
|
class AzureHttpTTSService(AzureBaseTTSService):
|
||||||
@@ -448,3 +450,4 @@ class AzureHttpTTSService(AzureBaseTTSService):
|
|||||||
logger.warning(f"Speech synthesis canceled: {cancellation_details.reason}")
|
logger.warning(f"Speech synthesis canceled: {cancellation_details.reason}")
|
||||||
if cancellation_details.reason == CancellationReason.Error:
|
if cancellation_details.reason == CancellationReason.Error:
|
||||||
logger.error(f"{self} error: {cancellation_details.error_details}")
|
logger.error(f"{self} error: {cancellation_details.error_details}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {cancellation_details.error_details}")
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from loguru import logger
|
|||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
EndFrame,
|
EndFrame,
|
||||||
|
ErrorFrame,
|
||||||
Frame,
|
Frame,
|
||||||
InterimTranscriptionFrame,
|
InterimTranscriptionFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
@@ -275,7 +276,8 @@ class CartesiaSTTService(WebsocketSTTService):
|
|||||||
self._websocket = await websocket_connect(ws_url, additional_headers=headers)
|
self._websocket = await websocket_connect(ws_url, additional_headers=headers)
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self}: unable to connect to Cartesia: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
async def _disconnect_websocket(self):
|
async def _disconnect_websocket(self):
|
||||||
try:
|
try:
|
||||||
@@ -284,6 +286,7 @@ class CartesiaSTTService(WebsocketSTTService):
|
|||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} error closing websocket: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_disconnected")
|
await self._call_event_handler("on_disconnected")
|
||||||
@@ -315,7 +318,9 @@ class CartesiaSTTService(WebsocketSTTService):
|
|||||||
await self._on_transcript(data)
|
await self._on_transcript(data)
|
||||||
|
|
||||||
elif data["type"] == "error":
|
elif data["type"] == "error":
|
||||||
logger.error(f"Cartesia error: {data.get('message', 'Unknown error')}")
|
error_msg = data.get("message", "Unknown error")
|
||||||
|
logger.error(f"Cartesia error: {error_msg}")
|
||||||
|
await self.push_error(ErrorFrame(error=error_msg))
|
||||||
|
|
||||||
@traced_stt
|
@traced_stt
|
||||||
async def _handle_transcription(
|
async def _handle_transcription(
|
||||||
|
|||||||
@@ -397,7 +397,8 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
|||||||
)
|
)
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
@@ -409,7 +410,8 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
|||||||
logger.debug("Disconnecting from Cartesia")
|
logger.debug("Disconnecting from Cartesia")
|
||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._context_id = None
|
self._context_id = None
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
@@ -465,7 +467,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
|||||||
logger.error(f"{self} error: {msg}")
|
logger.error(f"{self} error: {msg}")
|
||||||
await self.push_frame(TTSStoppedFrame())
|
await self.push_frame(TTSStoppedFrame())
|
||||||
await self.stop_all_metrics()
|
await self.stop_all_metrics()
|
||||||
await self.push_error(ErrorFrame(f"{self} error: {msg['error']}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {msg['error']}"))
|
||||||
self._context_id = None
|
self._context_id = None
|
||||||
else:
|
else:
|
||||||
logger.error(f"{self} error, unknown message type: {msg}")
|
logger.error(f"{self} error, unknown message type: {msg}")
|
||||||
@@ -506,7 +508,8 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
|||||||
await self._get_websocket().send(msg)
|
await self._get_websocket().send(msg)
|
||||||
await self.start_tts_usage_metrics(text)
|
await self.start_tts_usage_metrics(text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error sending message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
@@ -514,6 +517,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
|||||||
yield None
|
yield None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
class CartesiaHttpTTSService(TTSService):
|
class CartesiaHttpTTSService(TTSService):
|
||||||
@@ -705,7 +709,7 @@ class CartesiaHttpTTSService(TTSService):
|
|||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
error_text = await response.text()
|
error_text = await response.text()
|
||||||
logger.error(f"Cartesia API error: {error_text}")
|
logger.error(f"Cartesia API error: {error_text}")
|
||||||
await self.push_error(ErrorFrame(f"Cartesia API error: {error_text}"))
|
await self.push_error(ErrorFrame(error=f"Cartesia API error: {error_text}"))
|
||||||
raise Exception(f"Cartesia API returned status {response.status}: {error_text}")
|
raise Exception(f"Cartesia API returned status {response.status}: {error_text}")
|
||||||
|
|
||||||
audio_data = await response.read()
|
audio_data = await response.read()
|
||||||
@@ -722,7 +726,7 @@ class CartesiaHttpTTSService(TTSService):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
await self.push_error(ErrorFrame(f"Error generating TTS: {e}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|||||||
@@ -191,7 +191,8 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
|||||||
await self._disconnect_websocket()
|
await self._disconnect_websocket()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error during disconnect: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
# Reset state only after everything is cleaned up
|
# Reset state only after everything is cleaned up
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
@@ -214,7 +215,8 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
|||||||
logger.debug("Connected to Deepgram Flux Websocket")
|
logger.debug("Connected to Deepgram Flux Websocket")
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
@@ -233,6 +235,7 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
|||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} error closing websocket: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_disconnected")
|
await self._call_event_handler("on_disconnected")
|
||||||
@@ -333,14 +336,14 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
|||||||
"""
|
"""
|
||||||
if not self._websocket:
|
if not self._websocket:
|
||||||
logger.error("Not connected to Deepgram Flux.")
|
logger.error("Not connected to Deepgram Flux.")
|
||||||
yield ErrorFrame("Not connected to Deepgram Flux.", fatal=True)
|
yield ErrorFrame("Not connected to Deepgram Flux.")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._websocket.send(audio)
|
await self._websocket.send(audio)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send audio to Flux: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"Failed to send audio to Flux: {e}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
yield None
|
yield None
|
||||||
@@ -417,7 +420,8 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
|||||||
# Skip malformed messages
|
# Skip malformed messages
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
# Error will be handled inside WebsocketService->_receive_task_handler
|
# Error will be handled inside WebsocketService->_receive_task_handler
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ class DeepgramSTTService(STTService):
|
|||||||
async def _on_error(self, *args, **kwargs):
|
async def _on_error(self, *args, **kwargs):
|
||||||
error: ErrorResponse = kwargs["error"]
|
error: ErrorResponse = kwargs["error"]
|
||||||
logger.warning(f"{self} connection error, will retry: {error}")
|
logger.warning(f"{self} connection error, will retry: {error}")
|
||||||
await self.push_error(ErrorFrame(f"{error}"))
|
await self.push_error(ErrorFrame(error=f"{error}"))
|
||||||
await self.stop_all_metrics()
|
await self.stop_all_metrics()
|
||||||
# NOTE(aleix): we don't disconnect (i.e. call finish on the connection)
|
# NOTE(aleix): we don't disconnect (i.e. call finish on the connection)
|
||||||
# because this triggers more errors internally in the Deepgram SDK. So,
|
# because this triggers more errors internally in the Deepgram SDK. So,
|
||||||
|
|||||||
@@ -125,8 +125,8 @@ class DeepgramTTSService(TTSService):
|
|||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"Error getting audio: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
class DeepgramHttpTTSService(TTSService):
|
class DeepgramHttpTTSService(TTSService):
|
||||||
|
|||||||
@@ -351,8 +351,8 @@ class ElevenLabsSTTService(SegmentedSTTService):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"ElevenLabs STT error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"ElevenLabs STT error: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
def audio_format_from_sample_rate(sample_rate: int) -> str:
|
def audio_format_from_sample_rate(sample_rate: int) -> str:
|
||||||
|
|||||||
@@ -424,7 +424,8 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
|||||||
json.dumps({"context_id": self._context_id, "close_context": True})
|
json.dumps({"context_id": self._context_id, "close_context": True})
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error closing context for voice settings update: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._context_id = None
|
self._context_id = None
|
||||||
self._started = False
|
self._started = False
|
||||||
|
|
||||||
@@ -535,8 +536,9 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
|||||||
|
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
async def _disconnect_websocket(self):
|
async def _disconnect_websocket(self):
|
||||||
@@ -551,7 +553,8 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
|||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
logger.debug("Disconnected from ElevenLabs")
|
logger.debug("Disconnected from ElevenLabs")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._started = False
|
self._started = False
|
||||||
self._context_id = None
|
self._context_id = None
|
||||||
@@ -581,7 +584,8 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
|||||||
json.dumps({"context_id": self._context_id, "close_context": True})
|
json.dumps({"context_id": self._context_id, "close_context": True})
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error closing context on interruption: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._context_id = None
|
self._context_id = None
|
||||||
self._started = False
|
self._started = False
|
||||||
self._partial_word = ""
|
self._partial_word = ""
|
||||||
@@ -736,13 +740,15 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
|||||||
else:
|
else:
|
||||||
await self._send_text(text)
|
await self._send_text(text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error sending message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
self._started = False
|
self._started = False
|
||||||
return
|
return
|
||||||
yield None
|
yield None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
class ElevenLabsHttpTTSService(WordTTSService):
|
class ElevenLabsHttpTTSService(WordTTSService):
|
||||||
@@ -1085,7 +1091,8 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
|||||||
logger.warning(f"Failed to parse JSON from stream: {e}")
|
logger.warning(f"Failed to parse JSON from stream: {e}")
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing response: {e}", exc_info=True)
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# After processing all chunks, emit any remaining partial word
|
# After processing all chunks, emit any remaining partial word
|
||||||
@@ -1109,8 +1116,8 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
|||||||
self._previous_text = text
|
self._previous_text = text
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in run_tts: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(error=str(e))
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
finally:
|
finally:
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
# Let the parent class handle TTSStoppedFrame
|
# Let the parent class handle TTSStoppedFrame
|
||||||
|
|||||||
@@ -290,5 +290,5 @@ class FalSTTService(SegmentedSTTService):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fal Wizper error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"Fal Wizper error: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|||||||
@@ -237,7 +237,8 @@ class FishAudioTTSService(InterruptibleTTSService):
|
|||||||
|
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fish Audio initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
@@ -251,7 +252,8 @@ class FishAudioTTSService(InterruptibleTTSService):
|
|||||||
await self._websocket.send(ormsgpack.packb(stop_message))
|
await self._websocket.send(ormsgpack.packb(stop_message))
|
||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error closing websocket: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._request_id = None
|
self._request_id = None
|
||||||
self._started = False
|
self._started = False
|
||||||
@@ -293,7 +295,8 @@ class FishAudioTTSService(InterruptibleTTSService):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
@traced_tts
|
@traced_tts
|
||||||
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
|
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
|
||||||
@@ -329,7 +332,8 @@ class FishAudioTTSService(InterruptibleTTSService):
|
|||||||
flush_message = {"event": "flush"}
|
flush_message = {"event": "flush"}
|
||||||
await self._get_websocket().send(ormsgpack.packb(flush_message))
|
await self._get_websocket().send(ormsgpack.packb(flush_message))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error sending message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
@@ -337,5 +341,5 @@ class FishAudioTTSService(InterruptibleTTSService):
|
|||||||
yield None
|
yield None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error generating TTS: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"Error: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from pipecat import __version__ as pipecat_version
|
|||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
CancelFrame,
|
CancelFrame,
|
||||||
EndFrame,
|
EndFrame,
|
||||||
|
ErrorFrame,
|
||||||
Frame,
|
Frame,
|
||||||
InterimTranscriptionFrame,
|
InterimTranscriptionFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
@@ -467,7 +468,8 @@ class GladiaSTTService(STTService):
|
|||||||
break
|
break
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in connection handler: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._connection_active = False
|
self._connection_active = False
|
||||||
|
|
||||||
if not self._should_reconnect:
|
if not self._should_reconnect:
|
||||||
@@ -557,7 +559,8 @@ class GladiaSTTService(STTService):
|
|||||||
except websockets.exceptions.ConnectionClosed:
|
except websockets.exceptions.ConnectionClosed:
|
||||||
logger.debug("Connection closed during keepalive")
|
logger.debug("Connection closed during keepalive")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in Gladia keepalive task: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
async def _receive_task_handler(self):
|
async def _receive_task_handler(self):
|
||||||
try:
|
try:
|
||||||
@@ -620,7 +623,8 @@ class GladiaSTTService(STTService):
|
|||||||
# Expected when closing the connection
|
# Expected when closing the connection
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in Gladia WebSocket handler: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
async def _maybe_reconnect(self) -> bool:
|
async def _maybe_reconnect(self) -> bool:
|
||||||
"""Handle exponential backoff reconnection logic."""
|
"""Handle exponential backoff reconnection logic."""
|
||||||
|
|||||||
@@ -1174,7 +1174,7 @@ class GeminiLiveLLMService(LLMService):
|
|||||||
self._connection_task = self.create_task(self._connection_task_handler(config=config))
|
self._connection_task = self.create_task(self._connection_task_handler(config=config))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await self.push_error(ErrorFrame(error=f"{self} Initialization error: {e}", fatal=True))
|
await self.push_error(ErrorFrame(error=f"{self} Initialization error: {e}"))
|
||||||
|
|
||||||
async def _connection_task_handler(self, config: LiveConnectConfig):
|
async def _connection_task_handler(self, config: LiveConnectConfig):
|
||||||
async with self._client.aio.live.connect(model=self._model_name, config=config) as session:
|
async with self._client.aio.live.connect(model=self._model_name, config=config) as session:
|
||||||
@@ -1255,9 +1255,7 @@ class GeminiLiveLLMService(LLMService):
|
|||||||
f"Max consecutive failures ({MAX_CONSECUTIVE_FAILURES}) reached, "
|
f"Max consecutive failures ({MAX_CONSECUTIVE_FAILURES}) reached, "
|
||||||
"treating as fatal error"
|
"treating as fatal error"
|
||||||
)
|
)
|
||||||
await self.push_error(
|
await self.push_error(ErrorFrame(error=f"{self} Error in receive loop: {error}"))
|
||||||
ErrorFrame(error=f"{self} Error in receive loop: {error}", fatal=True)
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -774,7 +774,8 @@ class GoogleSTTService(STTService):
|
|||||||
yield cloud_speech.StreamingRecognizeRequest(audio=audio_data)
|
yield cloud_speech.StreamingRecognizeRequest(audio=audio_data)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in request generator: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def _stream_audio(self):
|
async def _stream_audio(self):
|
||||||
@@ -805,14 +806,15 @@ class GoogleSTTService(STTService):
|
|||||||
break
|
break
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"{self} Reconnecting: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
await asyncio.sleep(1) # Brief delay before reconnecting
|
await asyncio.sleep(1) # Brief delay before reconnecting
|
||||||
self._stream_start_time = int(time.time() * 1000)
|
self._stream_start_time = int(time.time() * 1000)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in streaming task: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
await self.push_frame(ErrorFrame(str(e)))
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
async def run_stt(self, audio: bytes) -> AsyncGenerator[Frame, None]:
|
async def run_stt(self, audio: bytes) -> AsyncGenerator[Frame, None]:
|
||||||
"""Process an audio chunk for STT transcription.
|
"""Process an audio chunk for STT transcription.
|
||||||
@@ -900,7 +902,8 @@ class GoogleSTTService(STTService):
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing Google STT responses: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
# Re-raise the exception to let it propagate (e.g. in the case of a
|
# Re-raise the exception to let it propagate (e.g. in the case of a
|
||||||
# timeout, propagate to _stream_audio to reconnect)
|
# timeout, propagate to _stream_audio to reconnect)
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -746,7 +746,7 @@ class GoogleHttpTTSService(TTSService):
|
|||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"{self} error generating TTS: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
error_message = f"TTS generation error: {str(e)}"
|
error_message = f"TTS generation error: {str(e)}"
|
||||||
yield ErrorFrame(error=error_message)
|
yield ErrorFrame(error=error_message)
|
||||||
|
|
||||||
@@ -1014,7 +1014,7 @@ class GoogleTTSService(GoogleBaseTTSService):
|
|||||||
yield frame
|
yield frame
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"{self} error generating TTS: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
error_message = f"TTS generation error: {str(e)}"
|
error_message = f"TTS generation error: {str(e)}"
|
||||||
yield ErrorFrame(error=error_message)
|
yield ErrorFrame(error=error_message)
|
||||||
|
|
||||||
@@ -1266,6 +1266,6 @@ class GeminiTTSService(GoogleBaseTTSService):
|
|||||||
yield frame
|
yield frame
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"{self} error generating TTS: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
error_message = f"Gemini TTS generation error: {str(e)}"
|
error_message = f"Gemini TTS generation error: {str(e)}"
|
||||||
yield ErrorFrame(error=error_message)
|
yield ErrorFrame(error=error_message)
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ from typing import AsyncGenerator, Optional
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from pipecat.frames.frames import Frame, TTSAudioRawFrame, TTSStartedFrame, TTSStoppedFrame
|
from pipecat.frames.frames import (
|
||||||
|
ErrorFrame,
|
||||||
|
Frame,
|
||||||
|
TTSAudioRawFrame,
|
||||||
|
TTSStartedFrame,
|
||||||
|
TTSStoppedFrame,
|
||||||
|
)
|
||||||
from pipecat.services.tts_service import TTSService
|
from pipecat.services.tts_service import TTSService
|
||||||
from pipecat.transcriptions.language import Language
|
from pipecat.transcriptions.language import Language
|
||||||
from pipecat.utils.tracing.service_decorators import traced_tts
|
from pipecat.utils.tracing.service_decorators import traced_tts
|
||||||
@@ -150,5 +156,6 @@ class GroqTTSService(TTSService):
|
|||||||
yield TTSAudioRawFrame(bytes, frame_rate, channels)
|
yield TTSAudioRawFrame(bytes, frame_rate, channels)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|||||||
@@ -225,8 +225,8 @@ class HumeTTSService(TTSService):
|
|||||||
self._audio_bytes = b""
|
self._audio_bytes = b""
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"{self} error generating TTS: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
await self.push_error(ErrorFrame(f"Error generating TTS: {e}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
# Ensure TTFB timer is stopped even on early failures
|
# Ensure TTFB timer is stopped even on early failures
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ class InworldTTSService(TTSService):
|
|||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
error_text = await response.text()
|
error_text = await response.text()
|
||||||
logger.error(f"Inworld API error: {error_text}")
|
logger.error(f"Inworld API error: {error_text}")
|
||||||
await self.push_error(ErrorFrame(f"Inworld API error: {error_text}"))
|
yield ErrorFrame(error=f"Inworld API error: {error_text}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
@@ -402,7 +402,7 @@ class InworldTTSService(TTSService):
|
|||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Log any unexpected errors and notify the pipeline
|
# Log any unexpected errors and notify the pipeline
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
await self.push_error(ErrorFrame(f"Error generating TTS: {e}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# STEP 8: CLEANUP AND COMPLETION
|
# STEP 8: CLEANUP AND COMPLETION
|
||||||
@@ -517,7 +517,7 @@ class InworldTTSService(TTSService):
|
|||||||
# Extract the base64-encoded audio content from response
|
# Extract the base64-encoded audio content from response
|
||||||
if "audioContent" not in response_data:
|
if "audioContent" not in response_data:
|
||||||
logger.error("No audioContent in Inworld API response")
|
logger.error("No audioContent in Inworld API response")
|
||||||
await self.push_error(ErrorFrame("No audioContent in response"))
|
await self.push_error(ErrorFrame(error="No audioContent in response"))
|
||||||
return
|
return
|
||||||
|
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
|
|||||||
@@ -223,7 +223,8 @@ class LmntTTSService(InterruptibleTTSService):
|
|||||||
|
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
@@ -239,7 +240,8 @@ class LmntTTSService(InterruptibleTTSService):
|
|||||||
# await self._websocket.send(json.dumps({"eof": True}))
|
# await self._websocket.send(json.dumps({"eof": True}))
|
||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._started = False
|
self._started = False
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
@@ -276,7 +278,7 @@ class LmntTTSService(InterruptibleTTSService):
|
|||||||
logger.error(f"{self} error: {msg['error']}")
|
logger.error(f"{self} error: {msg['error']}")
|
||||||
await self.push_frame(TTSStoppedFrame())
|
await self.push_frame(TTSStoppedFrame())
|
||||||
await self.stop_all_metrics()
|
await self.stop_all_metrics()
|
||||||
await self.push_error(ErrorFrame(f"{self} error: {msg['error']}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {msg['error']}"))
|
||||||
return
|
return
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logger.error(f"Invalid JSON message: {message}")
|
logger.error(f"Invalid JSON message: {message}")
|
||||||
@@ -309,7 +311,8 @@ class LmntTTSService(InterruptibleTTSService):
|
|||||||
await self._get_websocket().send(json.dumps({"flush": True}))
|
await self._get_websocket().send(json.dumps({"flush": True}))
|
||||||
await self.start_tts_usage_metrics(text)
|
await self.start_tts_usage_metrics(text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error sending message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
@@ -317,3 +320,4 @@ class LmntTTSService(InterruptibleTTSService):
|
|||||||
yield None
|
yield None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|||||||
@@ -347,8 +347,8 @@ class MiniMaxHttpTTSService(TTSService):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error generating TTS: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(error=f"MiniMax TTS error: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
finally:
|
finally:
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|||||||
@@ -294,7 +294,8 @@ class NeuphonicTTSService(InterruptibleTTSService):
|
|||||||
|
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
@@ -307,7 +308,8 @@ class NeuphonicTTSService(InterruptibleTTSService):
|
|||||||
logger.debug("Disconnecting from Neuphonic")
|
logger.debug("Disconnecting from Neuphonic")
|
||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._started = False
|
self._started = False
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
@@ -372,7 +374,8 @@ class NeuphonicTTSService(InterruptibleTTSService):
|
|||||||
await self._send_text(text)
|
await self._send_text(text)
|
||||||
await self.start_tts_usage_metrics(text)
|
await self.start_tts_usage_metrics(text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error sending message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
@@ -380,6 +383,7 @@ class NeuphonicTTSService(InterruptibleTTSService):
|
|||||||
yield None
|
yield None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
class NeuphonicHttpTTSService(TTSService):
|
class NeuphonicHttpTTSService(TTSService):
|
||||||
@@ -582,7 +586,8 @@ class NeuphonicHttpTTSService(TTSService):
|
|||||||
yield TTSAudioRawFrame(audio_bytes, self.sample_rate, 1)
|
yield TTSAudioRawFrame(audio_bytes, self.sample_rate, 1)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing SSE message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
# Don't yield error frame for individual message failures
|
# Don't yield error frame for individual message failures
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -590,8 +595,8 @@ class NeuphonicHttpTTSService(TTSService):
|
|||||||
logger.debug("TTS generation cancelled")
|
logger.debug("TTS generation cancelled")
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error in run_tts: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(error=f"Neuphonic TTS error: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
finally:
|
finally:
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ class OpenAIRealtimeLLMService(LLMService):
|
|||||||
# it is to recover from a send-side error with proper state management, and that exponential
|
# it is to recover from a send-side error with proper state management, and that exponential
|
||||||
# backoff for retries can have cost/stability implications for a service cluster, let's just
|
# backoff for retries can have cost/stability implications for a service cluster, let's just
|
||||||
# treat a send-side error as fatal.
|
# treat a send-side error as fatal.
|
||||||
await self.push_error(ErrorFrame(error=f"Error sending client event: {e}", fatal=True))
|
await self.push_error(ErrorFrame(error=f"Error sending client event: {e}"))
|
||||||
|
|
||||||
async def _update_settings(self):
|
async def _update_settings(self):
|
||||||
settings = self._session_properties
|
settings = self._session_properties
|
||||||
@@ -667,9 +667,7 @@ class OpenAIRealtimeLLMService(LLMService):
|
|||||||
self._current_assistant_response = None
|
self._current_assistant_response = None
|
||||||
# error handling
|
# error handling
|
||||||
if evt.response.status == "failed":
|
if evt.response.status == "failed":
|
||||||
await self.push_error(
|
await self.push_error(ErrorFrame(error=evt.response.status_details["error"]["message"]))
|
||||||
ErrorFrame(error=evt.response.status_details["error"]["message"], fatal=True)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
# response content
|
# response content
|
||||||
for item in evt.response.output:
|
for item in evt.response.output:
|
||||||
@@ -763,7 +761,7 @@ class OpenAIRealtimeLLMService(LLMService):
|
|||||||
|
|
||||||
async def _handle_evt_error(self, evt):
|
async def _handle_evt_error(self, evt):
|
||||||
# Errors are fatal to this connection. Send an ErrorFrame.
|
# Errors are fatal to this connection. Send an ErrorFrame.
|
||||||
await self.push_error(ErrorFrame(error=f"Error: {evt}", fatal=True))
|
await self.push_error(ErrorFrame(error=f"Error: {evt}"))
|
||||||
|
|
||||||
#
|
#
|
||||||
# state and client events for the current conversation
|
# state and client events for the current conversation
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ class OpenAITTSService(TTSService):
|
|||||||
f"{self} error getting audio (status: {r.status_code}, error: {error})"
|
f"{self} error getting audio (status: {r.status_code}, error: {error})"
|
||||||
)
|
)
|
||||||
yield ErrorFrame(
|
yield ErrorFrame(
|
||||||
f"Error getting audio (status: {r.status_code}, error: {error})"
|
error=f"Error getting audio (status: {r.status_code}, error: {error})"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -216,3 +216,4 @@ class OpenAITTSService(TTSService):
|
|||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
except BadRequestError as e:
|
except BadRequestError as e:
|
||||||
logger.exception(f"{self} error generating TTS: {e}")
|
logger.exception(f"{self} error generating TTS: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ class OpenAIRealtimeBetaLLMService(LLMService):
|
|||||||
# it is to recover from a send-side error with proper state management, and that exponential
|
# it is to recover from a send-side error with proper state management, and that exponential
|
||||||
# backoff for retries can have cost/stability implications for a service cluster, let's just
|
# backoff for retries can have cost/stability implications for a service cluster, let's just
|
||||||
# treat a send-side error as fatal.
|
# treat a send-side error as fatal.
|
||||||
await self.push_error(ErrorFrame(error=f"Error sending client event: {e}", fatal=True))
|
await self.push_error(ErrorFrame(error=f"Error sending client event: {e}"))
|
||||||
|
|
||||||
async def _update_settings(self):
|
async def _update_settings(self):
|
||||||
settings = self._session_properties
|
settings = self._session_properties
|
||||||
@@ -627,9 +627,7 @@ class OpenAIRealtimeBetaLLMService(LLMService):
|
|||||||
self._current_assistant_response = None
|
self._current_assistant_response = None
|
||||||
# error handling
|
# error handling
|
||||||
if evt.response.status == "failed":
|
if evt.response.status == "failed":
|
||||||
await self.push_error(
|
await self.push_error(ErrorFrame(error=evt.response.status_details["error"]["message"]))
|
||||||
ErrorFrame(error=evt.response.status_details["error"]["message"], fatal=True)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
# response content
|
# response content
|
||||||
for item in evt.response.output:
|
for item in evt.response.output:
|
||||||
@@ -687,7 +685,7 @@ class OpenAIRealtimeBetaLLMService(LLMService):
|
|||||||
|
|
||||||
async def _handle_evt_error(self, evt):
|
async def _handle_evt_error(self, evt):
|
||||||
# Errors are fatal to this connection. Send an ErrorFrame.
|
# Errors are fatal to this connection. Send an ErrorFrame.
|
||||||
await self.push_error(ErrorFrame(error=f"Error: {evt}", fatal=True))
|
await self.push_error(ErrorFrame(error=f"Error: {evt}"))
|
||||||
|
|
||||||
async def _handle_assistant_output(self, output):
|
async def _handle_assistant_output(self, output):
|
||||||
# We haven't seen intermixed audio and function_call items in the same response. But let's
|
# We haven't seen intermixed audio and function_call items in the same response. But let's
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class PiperTTSService(TTSService):
|
|||||||
f"{self} error getting audio (status: {response.status}, error: {error})"
|
f"{self} error getting audio (status: {response.status}, error: {error})"
|
||||||
)
|
)
|
||||||
yield ErrorFrame(
|
yield ErrorFrame(
|
||||||
f"Error getting audio (status: {response.status}, error: {error})"
|
error=f"Error getting audio (status: {response.status}, error: {error})"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -117,8 +117,8 @@ class PiperTTSService(TTSService):
|
|||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
yield frame
|
yield frame
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in run_tts: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(error=str(e))
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
finally:
|
finally:
|
||||||
logger.debug(f"{self}: Finished TTS [{text}]")
|
logger.debug(f"{self}: Finished TTS [{text}]")
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
|
|||||||
@@ -266,7 +266,8 @@ class PlayHTTTSService(InterruptibleTTSService):
|
|||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
@@ -279,7 +280,8 @@ class PlayHTTTSService(InterruptibleTTSService):
|
|||||||
logger.debug("Disconnecting from PlayHT")
|
logger.debug("Disconnecting from PlayHT")
|
||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._request_id = None
|
self._request_id = None
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
@@ -350,7 +352,7 @@ class PlayHTTTSService(InterruptibleTTSService):
|
|||||||
self._request_id = None
|
self._request_id = None
|
||||||
elif "error" in msg:
|
elif "error" in msg:
|
||||||
logger.error(f"{self} error: {msg}")
|
logger.error(f"{self} error: {msg}")
|
||||||
await self.push_error(ErrorFrame(f"{self} error: {msg['error']}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {msg['error']}"))
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logger.error(f"Invalid JSON message: {message}")
|
logger.error(f"Invalid JSON message: {message}")
|
||||||
|
|
||||||
@@ -392,7 +394,8 @@ class PlayHTTTSService(InterruptibleTTSService):
|
|||||||
await self._get_websocket().send(json.dumps(tts_command))
|
await self._get_websocket().send(json.dumps(tts_command))
|
||||||
await self.start_tts_usage_metrics(text)
|
await self.start_tts_usage_metrics(text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error sending message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
@@ -402,8 +405,8 @@ class PlayHTTTSService(InterruptibleTTSService):
|
|||||||
yield None
|
yield None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error generating TTS: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"{self} error: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
class PlayHTHttpTTSService(TTSService):
|
class PlayHTHttpTTSService(TTSService):
|
||||||
@@ -623,7 +626,8 @@ class PlayHTHttpTTSService(TTSService):
|
|||||||
yield frame
|
yield frame
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error generating TTS: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
finally:
|
finally:
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|||||||
@@ -259,7 +259,8 @@ class RimeTTSService(AudioContextWordTTSService):
|
|||||||
|
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
@@ -271,7 +272,8 @@ class RimeTTSService(AudioContextWordTTSService):
|
|||||||
await self._websocket.send(json.dumps(self._build_eos_msg()))
|
await self._websocket.send(json.dumps(self._build_eos_msg()))
|
||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._context_id = None
|
self._context_id = None
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
@@ -367,7 +369,7 @@ class RimeTTSService(AudioContextWordTTSService):
|
|||||||
logger.error(f"{self} error: {msg}")
|
logger.error(f"{self} error: {msg}")
|
||||||
await self.push_frame(TTSStoppedFrame())
|
await self.push_frame(TTSStoppedFrame())
|
||||||
await self.stop_all_metrics()
|
await self.stop_all_metrics()
|
||||||
await self.push_error(ErrorFrame(f"{self} error: {msg['message']}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {msg['message']}"))
|
||||||
self._context_id = None
|
self._context_id = None
|
||||||
|
|
||||||
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
||||||
@@ -409,7 +411,8 @@ class RimeTTSService(AudioContextWordTTSService):
|
|||||||
await self._get_websocket().send(json.dumps(msg))
|
await self._get_websocket().send(json.dumps(msg))
|
||||||
await self.start_tts_usage_metrics(text)
|
await self.start_tts_usage_metrics(text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error sending message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
@@ -417,6 +420,7 @@ class RimeTTSService(AudioContextWordTTSService):
|
|||||||
yield None
|
yield None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
class RimeHttpTTSService(TTSService):
|
class RimeHttpTTSService(TTSService):
|
||||||
@@ -574,8 +578,8 @@ class RimeHttpTTSService(TTSService):
|
|||||||
yield frame
|
yield frame
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error generating TTS: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(error=f"Rime TTS error: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
finally:
|
finally:
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
|
|||||||
@@ -659,8 +659,8 @@ class RivaSegmentedSTTService(SegmentedSTTService):
|
|||||||
yield ErrorFrame(f"Unexpected Riva response format: {str(ae)}")
|
yield ErrorFrame(f"Unexpected Riva response format: {str(ae)}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Riva Canary ASR error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"Riva Canary ASR error: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
|
|
||||||
class ParakeetSTTService(RivaSTTService):
|
class ParakeetSTTService(RivaSTTService):
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from loguru import logger
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
|
ErrorFrame,
|
||||||
Frame,
|
Frame,
|
||||||
TTSAudioRawFrame,
|
TTSAudioRawFrame,
|
||||||
TTSStartedFrame,
|
TTSStartedFrame,
|
||||||
@@ -165,6 +166,7 @@ class RivaTTSService(TTSService):
|
|||||||
add_response(None)
|
add_response(None)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
add_response(None)
|
add_response(None)
|
||||||
|
|
||||||
await self.start_ttfb_metrics()
|
await self.start_ttfb_metrics()
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ class SarvamHttpTTSService(TTSService):
|
|||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
error_text = await response.text()
|
error_text = await response.text()
|
||||||
logger.error(f"Sarvam API error: {error_text}")
|
logger.error(f"Sarvam API error: {error_text}")
|
||||||
await self.push_error(ErrorFrame(f"Sarvam API error: {error_text}"))
|
await self.push_error(ErrorFrame(error=f"Sarvam API error: {error_text}"))
|
||||||
return
|
return
|
||||||
|
|
||||||
response_data = await response.json()
|
response_data = await response.json()
|
||||||
@@ -274,7 +274,7 @@ class SarvamHttpTTSService(TTSService):
|
|||||||
# Decode base64 audio data
|
# Decode base64 audio data
|
||||||
if "audios" not in response_data or not response_data["audios"]:
|
if "audios" not in response_data or not response_data["audios"]:
|
||||||
logger.error("No audio data received from Sarvam API")
|
logger.error("No audio data received from Sarvam API")
|
||||||
await self.push_error(ErrorFrame("No audio data received"))
|
await self.push_error(ErrorFrame(error="No audio data received"))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the first audio (there should be only one for single text input)
|
# Get the first audio (there should be only one for single text input)
|
||||||
@@ -296,7 +296,7 @@ class SarvamHttpTTSService(TTSService):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
await self.push_error(ErrorFrame(f"Error generating TTS: {e}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
await self.stop_ttfb_metrics()
|
await self.stop_ttfb_metrics()
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
@@ -578,7 +578,8 @@ class SarvamTTSService(InterruptibleTTSService):
|
|||||||
await self._disconnect_websocket()
|
await self._disconnect_websocket()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error during disconnect: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
# Reset state only after everything is cleaned up
|
# Reset state only after everything is cleaned up
|
||||||
self._started = False
|
self._started = False
|
||||||
@@ -602,7 +603,8 @@ class SarvamTTSService(InterruptibleTTSService):
|
|||||||
|
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} initialization error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
await self._call_event_handler("on_connection_error", f"{e}")
|
await self._call_event_handler("on_connection_error", f"{e}")
|
||||||
|
|
||||||
@@ -618,8 +620,8 @@ class SarvamTTSService(InterruptibleTTSService):
|
|||||||
await self._websocket.send(json.dumps(config_message))
|
await self._websocket.send(json.dumps(config_message))
|
||||||
logger.debug("Configuration sent successfully")
|
logger.debug("Configuration sent successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send config: {str(e)}")
|
logger.error(f"{self} exception: {e}")
|
||||||
await self.push_frame(ErrorFrame(f"Failed to send config: {str(e)}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def _disconnect_websocket(self):
|
async def _disconnect_websocket(self):
|
||||||
@@ -632,6 +634,7 @@ class SarvamTTSService(InterruptibleTTSService):
|
|||||||
await self._websocket.close()
|
await self._websocket.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error closing websocket: {e}")
|
logger.error(f"{self} error closing websocket: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._started = False
|
self._started = False
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
@@ -661,7 +664,7 @@ class SarvamTTSService(InterruptibleTTSService):
|
|||||||
if "too long" in error_msg.lower() or "timeout" in error_msg.lower():
|
if "too long" in error_msg.lower() or "timeout" in error_msg.lower():
|
||||||
logger.warning("Connection timeout detected, service may need restart")
|
logger.warning("Connection timeout detected, service may need restart")
|
||||||
|
|
||||||
await self.push_frame(ErrorFrame(f"TTS Error: {error_msg}"))
|
await self.push_frame(ErrorFrame(error=f"TTS Error: {error_msg}"))
|
||||||
|
|
||||||
async def _keepalive_task_handler(self):
|
async def _keepalive_task_handler(self):
|
||||||
"""Handle keepalive messages to maintain WebSocket connection."""
|
"""Handle keepalive messages to maintain WebSocket connection."""
|
||||||
@@ -717,7 +720,8 @@ class SarvamTTSService(InterruptibleTTSService):
|
|||||||
await self._send_text(text)
|
await self._send_text(text)
|
||||||
await self.start_tts_usage_metrics(text)
|
await self.start_tts_usage_metrics(text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error sending message: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
yield TTSStoppedFrame()
|
yield TTSStoppedFrame()
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
@@ -725,3 +729,4 @@ class SarvamTTSService(InterruptibleTTSService):
|
|||||||
yield None
|
yield None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} exception: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|||||||
@@ -327,8 +327,8 @@ class SonioxSTTService(STTService):
|
|||||||
# Expected when closing the connection
|
# Expected when closing the connection
|
||||||
logger.debug("WebSocket connection closed, keepalive task stopped.")
|
logger.debug("WebSocket connection closed, keepalive task stopped.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error (_keepalive_task_handler): {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
await self.push_error(ErrorFrame(f"{self} error (_keepalive_task_handler): {e}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
async def _receive_task_handler(self):
|
async def _receive_task_handler(self):
|
||||||
if not self._websocket:
|
if not self._websocket:
|
||||||
@@ -409,7 +409,7 @@ class SonioxSTTService(STTService):
|
|||||||
)
|
)
|
||||||
await self.push_error(
|
await self.push_error(
|
||||||
ErrorFrame(
|
ErrorFrame(
|
||||||
f"{self} error: {error_code} (_receive_task_handler) - {error_message}"
|
error=f"{self} error: {error_code} (_receive_task_handler) - {error_message}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -425,5 +425,5 @@ class SonioxSTTService(STTService):
|
|||||||
# Expected when closing the connection.
|
# Expected when closing the connection.
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
await self.push_error(ErrorFrame(f"{self} error: {e}"))
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|||||||
@@ -467,8 +467,8 @@ class SpeechmaticsSTTService(STTService):
|
|||||||
await self._client.send_audio(audio)
|
await self._client.send_audio(audio)
|
||||||
yield None
|
yield None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Speechmatics error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"Speechmatics error: {e}", fatal=False)
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
|
|
||||||
def update_params(
|
def update_params(
|
||||||
@@ -514,6 +514,8 @@ class SpeechmaticsSTTService(STTService):
|
|||||||
self._client.send_message(payload), self.get_event_loop()
|
self._client.send_message(payload), self.get_event_loop()
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
raise RuntimeError(f"error sending message to STT: {e}")
|
raise RuntimeError(f"error sending message to STT: {e}")
|
||||||
|
|
||||||
async def _connect(self) -> None:
|
async def _connect(self) -> None:
|
||||||
@@ -579,7 +581,8 @@ class SpeechmaticsSTTService(STTService):
|
|||||||
logger.debug(f"{self} Connected to Speechmatics STT service")
|
logger.debug(f"{self} Connected to Speechmatics STT service")
|
||||||
await self._call_event_handler("on_connected")
|
await self._call_event_handler("on_connected")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} Error connecting to Speechmatics: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
self._client = None
|
self._client = None
|
||||||
|
|
||||||
async def _disconnect(self) -> None:
|
async def _disconnect(self) -> None:
|
||||||
@@ -593,7 +596,8 @@ class SpeechmaticsSTTService(STTService):
|
|||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.warning(f"{self} Timeout while closing Speechmatics client connection")
|
logger.warning(f"{self} Timeout while closing Speechmatics client connection")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self} Error closing Speechmatics client: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
finally:
|
finally:
|
||||||
self._client = None
|
self._client = None
|
||||||
await self._call_event_handler("on_disconnected")
|
await self._call_event_handler("on_disconnected")
|
||||||
|
|||||||
@@ -246,7 +246,8 @@ class UltravoxSTTService(AIService):
|
|||||||
|
|
||||||
logger.info("Model warm-up completed successfully")
|
logger.info("Model warm-up completed successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Model warm-up failed: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
|
await self.push_error(ErrorFrame(error=f"{self} error: {e}"))
|
||||||
|
|
||||||
def _generate_silent_audio(self, sample_rate=16000, duration_sec=1.0):
|
def _generate_silent_audio(self, sample_rate=16000, duration_sec=1.0):
|
||||||
"""Generate silent audio as a numpy array.
|
"""Generate silent audio as a numpy array.
|
||||||
@@ -376,7 +377,7 @@ class UltravoxSTTService(AIService):
|
|||||||
if arr.size > 0: # Check if array is not empty
|
if arr.size > 0: # Check if array is not empty
|
||||||
audio_arrays.append(arr)
|
audio_arrays.append(arr)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing bytes audio frame: {e}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
# Handle numpy array data
|
# Handle numpy array data
|
||||||
elif isinstance(f.audio, np.ndarray):
|
elif isinstance(f.audio, np.ndarray):
|
||||||
if f.audio.size > 0: # Check if array is not empty
|
if f.audio.size > 0: # Check if array is not empty
|
||||||
@@ -436,14 +437,14 @@ class UltravoxSTTService(AIService):
|
|||||||
yield LLMFullResponseEndFrame()
|
yield LLMFullResponseEndFrame()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error generating text from model: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"Error generating text: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
else:
|
else:
|
||||||
logger.warning("No model available for text generation")
|
logger.error("No model available for text generation")
|
||||||
yield ErrorFrame("No model available for text generation")
|
yield ErrorFrame("No model available for text generation")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing audio buffer: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class WebsocketService(ABC):
|
|||||||
if self._reconnect_on_error:
|
if self._reconnect_on_error:
|
||||||
retry_count += 1
|
retry_count += 1
|
||||||
if retry_count >= MAX_RETRIES:
|
if retry_count >= MAX_RETRIES:
|
||||||
await report_error(ErrorFrame(message, fatal=True))
|
await report_error(ErrorFrame(message))
|
||||||
break
|
break
|
||||||
|
|
||||||
logger.warning(f"{self} connection error, will retry: {e}")
|
logger.warning(f"{self} connection error, will retry: {e}")
|
||||||
|
|||||||
@@ -226,8 +226,8 @@ class BaseWhisperSTTService(SegmentedSTTService):
|
|||||||
logger.warning("Received empty transcription from API")
|
logger.warning("Received empty transcription from API")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Exception during transcription: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"Error during transcription: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|
||||||
async def _transcribe(self, audio: bytes) -> Transcription:
|
async def _transcribe(self, audio: bytes) -> Transcription:
|
||||||
"""Transcribe audio data to text.
|
"""Transcribe audio data to text.
|
||||||
|
|||||||
@@ -428,5 +428,5 @@ class WhisperSTTServiceMLX(WhisperSTTService):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"MLX Whisper transcription error: {e}")
|
logger.error(f"{self} exception: {e}")
|
||||||
yield ErrorFrame(f"MLX Whisper transcription error: {str(e)}")
|
yield ErrorFrame(error=f"{self} error: {e}")
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class XTTSService(TTSService):
|
|||||||
)
|
)
|
||||||
await self.push_error(
|
await self.push_error(
|
||||||
ErrorFrame(
|
ErrorFrame(
|
||||||
f"Error error getting studio speakers (status: {r.status}, error: {text})"
|
error=f"Error getting studio speakers (status: {r.status}, error: {text})"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -187,7 +187,7 @@ class XTTSService(TTSService):
|
|||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
text = await r.text()
|
text = await r.text()
|
||||||
logger.error(f"{self} error getting audio (status: {r.status}, error: {text})")
|
logger.error(f"{self} error getting audio (status: {r.status}, error: {text})")
|
||||||
yield ErrorFrame(f"Error getting audio (status: {r.status}, error: {text})")
|
yield ErrorFrame(error=f"Error getting audio (status: {r.status}, error: {text})")
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.start_tts_usage_metrics(text)
|
await self.start_tts_usage_metrics(text)
|
||||||
|
|||||||
Reference in New Issue
Block a user