minimax updates

some `debug`s -> `trace`s

add western US base_url to docs

ensure error_message is defined

add deprecation warning for `english_normalization` param
This commit is contained in:
vipyne
2025-11-04 15:40:38 -06:00
parent 616e6ba351
commit 17cf6c56cf
3 changed files with 48 additions and 83 deletions

View File

@@ -241,6 +241,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed `needs_mcp_alternate_schema()` from `LLMService`. The mechanism that - Removed `needs_mcp_alternate_schema()` from `LLMService`. The mechanism that
relied on it went away. relied on it went away.
- In `MiniMaxHttpTTSService`:
-- Added support for speech-2.6-hd and speech-2.6-turbo models
-- Added languages: Afrikaans, Bulgarian, Catalan, Danish, Persian, Filipino, Hebrew,
Croatian, Hungarian, Malay, Norwegian, Nynorsk, Slovak, Slovenian, Swedish, and Tamil
-- Added new emotions: calm and fluent
### Fixed ### Fixed
- Restore backwards compatibility for vision/image features (broken in 0.0.92) - Restore backwards compatibility for vision/image features (broken in 0.0.92)

View File

@@ -5,14 +5,11 @@
# #
import sys import sys
from importlib.metadata import version, PackageNotFoundError from importlib.metadata import version
from loguru import logger from loguru import logger
try: __version__ = version("pipecat-ai")
__version__ = version("pipecat-ai")
except PackageNotFoundError:
__version__ = "0.0.0.dev" # Development version
logger.info(f"ᓚᘏᗢ Pipecat {__version__} (Python {sys.version}) ᓚᘏᗢ") logger.info(f"ᓚᘏᗢ Pipecat {__version__} (Python {sys.version}) ᓚᘏᗢ")

View File

@@ -110,6 +110,7 @@ class MiniMaxHttpTTSService(TTSService):
pitch: Pitch adjustment (range: -12 to 12). pitch: Pitch adjustment (range: -12 to 12).
emotion: Emotional tone (options: "happy", "sad", "angry", "fearful", emotion: Emotional tone (options: "happy", "sad", "angry", "fearful",
"disgusted", "surprised", "calm", "fluent"). "disgusted", "surprised", "calm", "fluent").
english_normalization: Deprecated; use `text_normalization` instead
text_normalization: Enable text normalization (Chinese/English). text_normalization: Enable text normalization (Chinese/English).
latex_read: Enable LaTeX formula reading. latex_read: Enable LaTeX formula reading.
force_cbr: Enable Constant Bitrate (CBR) for audio encoding (MP3 only). force_cbr: Enable Constant Bitrate (CBR) for audio encoding (MP3 only).
@@ -124,6 +125,7 @@ class MiniMaxHttpTTSService(TTSService):
volume: Optional[float] = 1.0 volume: Optional[float] = 1.0
pitch: Optional[int] = 0 pitch: Optional[int] = 0
emotion: Optional[str] = None emotion: Optional[str] = None
english_normalization: Optional[bool] = None # Deprecated
text_normalization: Optional[bool] = None text_normalization: Optional[bool] = None
latex_read: Optional[bool] = None latex_read: Optional[bool] = None
force_cbr: Optional[bool] = None force_cbr: Optional[bool] = None
@@ -136,8 +138,6 @@ class MiniMaxHttpTTSService(TTSService):
*, *,
api_key: str, api_key: str,
base_url: str = "https://api.minimax.io/v1/t2a_v2", base_url: str = "https://api.minimax.io/v1/t2a_v2",
# https://api-uw.minimax.io/v1/t2a_v2
# support west of unite state
group_id: str, group_id: str,
model: str = "speech-02-turbo", model: str = "speech-02-turbo",
voice_id: str = "Calm_Woman", voice_id: str = "Calm_Woman",
@@ -153,6 +153,7 @@ class MiniMaxHttpTTSService(TTSService):
base_url: API base URL, defaults to MiniMax's T2A endpoint. base_url: API base URL, defaults to MiniMax's T2A endpoint.
Global: https://api.minimax.io/v1/t2a_v2 Global: https://api.minimax.io/v1/t2a_v2
Mainland China: https://api.minimaxi.chat/v1/t2a_v2 Mainland China: https://api.minimaxi.chat/v1/t2a_v2
Western United States: https://api-uw.minimax.io/v1/t2a_v2
group_id: MiniMax Group ID to identify project. group_id: MiniMax Group ID to identify project.
model: TTS model name. Defaults to "speech-02-turbo". Options include: model: TTS model name. Defaults to "speech-02-turbo". Options include:
"speech-2.6-hd", "speech-2.6-turbo" (latest, supports Filipino/Tamil/Persian), "speech-2.6-hd", "speech-2.6-turbo" (latest, supports Filipino/Tamil/Persian),
@@ -174,7 +175,6 @@ class MiniMaxHttpTTSService(TTSService):
self._session = aiohttp_session self._session = aiohttp_session
self._model_name = model self._model_name = model
self._voice_id = voice_id self._voice_id = voice_id
self._current_trace_id: Optional[str] = None
# Create voice settings # Create voice settings
self._settings = { self._settings = {
@@ -238,6 +238,13 @@ class MiniMaxHttpTTSService(TTSService):
f"Unsupported emotion: {params.emotion}. Supported emotions: {supported_emotions}" f"Unsupported emotion: {params.emotion}. Supported emotions: {supported_emotions}"
) )
# If `english_normalization`, add `text_normalization` and print warning
if params.english_normalization is not None:
logger.warning(
"Parameter `english_normalization` is deprecated and will be removed in a future version. Use `text_normalization` instead."
)
self._settings["voice_setting"]["text_normalization"] = params.english_normalization
# Add text_normalization if provided (corrected parameter name) # Add text_normalization if provided (corrected parameter name)
if params.text_normalization is not None: if params.text_normalization is not None:
self._settings["voice_setting"]["text_normalization"] = params.text_normalization self._settings["voice_setting"]["text_normalization"] = params.text_normalization
@@ -341,9 +348,6 @@ class MiniMaxHttpTTSService(TTSService):
""" """
logger.debug(f"{self}: Generating TTS [{text}]") logger.debug(f"{self}: Generating TTS [{text}]")
# Reset trace_id for new request
self._current_trace_id = None
headers = { headers = {
"accept": "application/json, text/plain, */*", "accept": "application/json, text/plain, */*",
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -361,14 +365,10 @@ class MiniMaxHttpTTSService(TTSService):
async with self._session.post( async with self._session.post(
self._base_url, headers=headers, json=payload self._base_url, headers=headers, json=payload
) as response: ) as response:
# Extract trace_id from response header (available in all responses) logger.trace(f"MiniMax TTS request status={response.status}")
trace_id = response.headers.get("Trace-Id") or response.headers.get("trace-id") or response.headers.get("X-Trace-Id") or "unknown"
self._current_trace_id = trace_id
# Log trace_id for all requests
logger.info(f"MiniMax TTS request trace_id={trace_id}, status={response.status}")
if response.status != 200: if response.status != 200:
error_message = "MiniMax TTS error"
# Try to read error response body # Try to read error response body
try: try:
error_body = await response.text() error_body = await response.text()
@@ -381,25 +381,13 @@ class MiniMaxHttpTTSService(TTSService):
error_message = ( error_message = (
f"MiniMax TTS error: HTTP {response.status}, " f"MiniMax TTS error: HTTP {response.status}, "
f"status_code={status_code}, status_msg={status_msg}, " f"status_code={status_code}, status_msg={status_msg}"
f"trace_id={trace_id}"
)
logger.error(
error_message,
extra={
"trace_id": trace_id,
"http_status": response.status,
"status_code": status_code,
"text_length": len(text),
},
) )
logger.error(error_message)
except Exception as parse_error: except Exception as parse_error:
# If parsing fails, use basic error message # If parsing fails, use basic error message
error_message = f"MiniMax TTS error: HTTP {response.status}, trace_id={trace_id}" error_message = f"MiniMax TTS error: HTTP {response.status}"
logger.error( logger.error(error_message)
error_message,
extra={"http_status": response.status, "trace_id": trace_id, "parse_error": str(parse_error)},
)
yield ErrorFrame(error=error_message) yield ErrorFrame(error=error_message)
return return
@@ -408,7 +396,7 @@ class MiniMaxHttpTTSService(TTSService):
yield TTSStartedFrame() yield TTSStartedFrame()
# Process the streaming response # Process the streaming response
logger.debug(f"Starting to read streaming response, status={response.status}, trace_id={trace_id}") logger.trace(f"Starting to read streaming response, status={response.status}")
buffer = bytearray() buffer = bytearray()
CHUNK_SIZE = self.chunk_size CHUNK_SIZE = self.chunk_size
@@ -416,7 +404,7 @@ class MiniMaxHttpTTSService(TTSService):
async for chunk in response.content.iter_chunked(CHUNK_SIZE): async for chunk in response.content.iter_chunked(CHUNK_SIZE):
chunk_count += 1 chunk_count += 1
logger.debug(f"Received chunk #{chunk_count}, size={len(chunk)} bytes") logger.trace(f"Received chunk #{chunk_count}, size={len(chunk)} bytes")
if not chunk: if not chunk:
continue continue
@@ -424,7 +412,7 @@ class MiniMaxHttpTTSService(TTSService):
# Log raw buffer content for debugging # Log raw buffer content for debugging
if chunk_count == 1: if chunk_count == 1:
logger.debug(f"Raw buffer content: {buffer[:200]}") # First 200 bytes logger.trace(f"Raw buffer content: {buffer[:200]}") # First 200 bytes
# Check if first chunk is a direct JSON error (not streaming format) # Check if first chunk is a direct JSON error (not streaming format)
if not buffer.startswith(b"data:"): if not buffer.startswith(b"data:"):
@@ -435,17 +423,13 @@ class MiniMaxHttpTTSService(TTSService):
if status_code != 0: if status_code != 0:
# This is a non-streaming error response # This is a non-streaming error response
# Use trace_id from header (already extracted above)
status_msg = base_resp.get("status_msg", "Unknown error") status_msg = base_resp.get("status_msg", "Unknown error")
error_message = ( error_message = (
f"MiniMax TTS API error: status_code={status_code}, " f"MiniMax TTS API error: status_code={status_code}"
f"status_msg={status_msg}, trace_id={self._current_trace_id}" f"status_msg={status_msg}"
)
logger.error(
error_message,
extra={"trace_id": self._current_trace_id, "status_code": status_code},
) )
logger.error(error_message)
yield ErrorFrame(error=error_message) yield ErrorFrame(error=error_message)
return return
except (json.JSONDecodeError, UnicodeDecodeError): except (json.JSONDecodeError, UnicodeDecodeError):
@@ -469,7 +453,9 @@ class MiniMaxHttpTTSService(TTSService):
try: try:
data_str = data_block[5:].decode("utf-8") data_str = data_block[5:].decode("utf-8")
logger.debug(f"Parsing data block: {data_str[:200]}...") # Log first 200 chars logger.trace(
f"Parsing data block: {data_str[:200]}..."
) # Log first 200 chars
data = json.loads(data_str) data = json.loads(data_str)
# Check for business errors in base_resp # Check for business errors in base_resp
@@ -481,28 +467,16 @@ class MiniMaxHttpTTSService(TTSService):
status_msg = base_resp.get("status_msg", "Unknown error") status_msg = base_resp.get("status_msg", "Unknown error")
error_message = ( error_message = (
f"MiniMax TTS API error: status_code={status_code}, " f"MiniMax TTS API error: status_code={status_code}, "
f"status_msg={status_msg}, trace_id={self._current_trace_id}" f"status_msg={status_msg}"
)
logger.error(
error_message,
extra={"trace_id": self._current_trace_id, "status_code": status_code},
) )
logger.error(error_message)
yield ErrorFrame(error=error_message) yield ErrorFrame(error=error_message)
return return
# Handle final chunk with extra_info # Handle final chunk with extra_info
if "extra_info" in data: if "extra_info" in data:
extra_info = data.get("extra_info", {}) extra_info = data.get("extra_info", {})
logger.info( logger.debug(f"Received final chunk with extra info: {extra_info}")
f"MiniMax TTS completed successfully, trace_id={self._current_trace_id}",
extra={
"trace_id": self._current_trace_id,
"audio_length": extra_info.get("audio_length"),
"audio_size": extra_info.get("audio_size"),
"usage_characters": extra_info.get("usage_characters"),
"word_count": extra_info.get("word_count"),
},
)
continue # No audio data in this block continue # No audio data in this block
# Extract audio data # Extract audio data
@@ -513,10 +487,7 @@ class MiniMaxHttpTTSService(TTSService):
# Check for subtitle file (if subtitle generation is enabled) # Check for subtitle file (if subtitle generation is enabled)
subtitle_file = chunk_data.get("subtitle_file") subtitle_file = chunk_data.get("subtitle_file")
if subtitle_file: if subtitle_file:
logger.info( logger.info(f"Subtitle file available: {subtitle_file}")
f"Subtitle file available: {subtitle_file}",
extra={"trace_id": self._current_trace_id, "subtitle_url": subtitle_file},
)
audio_data = chunk_data.get("audio") audio_data = chunk_data.get("audio")
if not audio_data: if not audio_data:
@@ -542,31 +513,22 @@ class MiniMaxHttpTTSService(TTSService):
except ValueError as e: except ValueError as e:
logger.error( logger.error(
f"Error converting hex to binary: {e}", f"Error converting hex to binary: {e}",
extra={"trace_id": self._current_trace_id},
) )
continue continue
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logger.error( logger.error(
f"Error decoding JSON: {e}, data: {data_block[:100]}", f"Error decoding JSON: {e}, data: {data_block[:100]}",
extra={"trace_id": self._current_trace_id or "unknown"},
) )
continue continue
except aiohttp.ClientError as e: except aiohttp.ClientError as e:
error_msg = f"MiniMax TTS network error: {str(e)}" error_msg = f"MiniMax TTS network error: {str(e)}"
logger.exception(error_msg, extra={"trace_id": self._current_trace_id or "unknown"}) logger.exception(error_msg)
yield ErrorFrame(error=error_msg) yield ErrorFrame(error=error_msg)
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 ErrorFrame(error=f"{self} error: {e}")
finally: finally:
if self._current_trace_id:
logger.debug(
f"MiniMax TTS request finished, trace_id={self._current_trace_id}, "
f"received {chunk_count if 'chunk_count' in locals() else 0} chunks"
)
else:
logger.debug(f"MiniMax TTS request finished with no trace_id (no data received)")
await self.stop_ttfb_metrics() await self.stop_ttfb_metrics()
yield TTSStoppedFrame() yield TTSStoppedFrame()