Skip to content

Add MiniMax text-to-speech processor - #169

Open
octo-patch wants to merge 1 commit into
google-gemini:mainfrom
octo-patch:octo/20260812-tts-tool-recvrYUDlTpTw9
Open

Add MiniMax text-to-speech processor#169
octo-patch wants to merge 1 commit into
google-gemini:mainfrom
octo-patch:octo/20260812-tts-tool-recvrYUDlTpTw9

Conversation

@octo-patch

Copy link
Copy Markdown

Reason: Add current MiniMax Text-to-Audio support through a reusable contributed processor.

This adds configurable global and China endpoints, the current speech model set,
streaming and non-streaming response parsing, and MP3, WAV, FLAC, and PCM output.
It also documents the processor and covers request construction, endpoint routing,
audio decoding, pass-through behavior, error handling, and validation.

Checks:

  • .venv/bin/pytest -q genai_processors/contrib/tests/minimax_text_to_speech_test.py
  • .venv/bin/pytest -q genai_processors/tests/text_to_speech_test.py
  • .venv/bin/pyink --check genai_processors/contrib/minimax_text_to_speech.py genai_processors/contrib/tests/minimax_text_to_speech_test.py
  • .venv/bin/python -m compileall -q genai_processors/contrib/minimax_text_to_speech.py genai_processors/contrib/tests/minimax_text_to_speech_test.py

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the MiniMaxTextToSpeech processor, which integrates with the MiniMax Text-to-Audio API to convert text parts into audio. It supports both global and China endpoints, streaming responses, and multiple audio formats, and includes a comprehensive test suite and documentation. The review feedback highlights two critical improvements: stripping the sensitive Authorization header when fetching audio from pre-signed URLs to prevent credential leakage, and making the SSE stream parser more robust by explicitly ignoring non-data metadata lines.


async def _audio_bytes(self, audio: str) -> bytes:
if self._output_format == 'url':
response = await self._client.get(audio)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

When output_format is set to 'url', the processor fetches the audio file from the pre-signed URL returned by the MiniMax API. Since self._client is initialized with default headers containing the sensitive Authorization header (MiniMax API key), calling self._client.get(audio) will forward this credential to the external storage provider (e.g., Aliyun OSS or AWS S3) hosting the audio file. This leads to credential leakage and can also cause the request to fail with a 400 Bad Request or 403 Forbidden error because object storage providers often reject requests containing unexpected authorization headers. To prevent this, strip the Authorization and Content-Type headers when fetching the audio URL.

      response = await self._client.get(
          audio,
          headers={'Authorization': None, 'Content-Type': None},
      )

Comment on lines +251 to +257
async for line in response.aiter_lines():
if not line or line.startswith(':'):
continue
if line.startswith('data:'):
line = line[5:].strip()
if line == '[DONE]':
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current SSE parser processes any line that does not start with : or is not empty. However, standard SSE streams can contain other metadata lines (such as event:, id:, or retry:). If the server sends any of these metadata lines, the parser will attempt to parse them as JSON and fail with a JSONDecodeError. To make the stream parsing robust and compliant with the SSE specification, explicitly ignore any lines that do not start with data:.

      async for line in response.aiter_lines():
        if not line or line.startswith(':'):
          continue
        if not line.startswith('data:'):
          continue
        line = line[5:].strip()
        if line == '[DONE]':
          continue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant