Skip to content

fix(minimax-tts): surface real API error when response is not SSE#8064

Open
YonganZhang wants to merge 1 commit intoAstrBotDevs:masterfrom
YonganZhang:fix/minimax-tts-error-detection
Open

fix(minimax-tts): surface real API error when response is not SSE#8064
YonganZhang wants to merge 1 commit intoAstrBotDevs:masterfrom
YonganZhang:fix/minimax-tts-error-detection

Conversation

@YonganZhang
Copy link
Copy Markdown

@YonganZhang YonganZhang commented May 7, 2026

Bug

MiniMax TTS API returns a JSON error body (not an SSE stream) when:

  • quota / rate limit exceeded
  • invalid voice_id or model
  • API key issues

Current code calls raise_for_status() (which doesn't trigger on 200 OK with a JSON error body) and then parses everything as SSE. Users get a confusing parse error or empty audio with no indication of the real cause.

Fix

Before entering the SSE parse loop, check Content-Type. If it's not text/event-stream, parse the JSON base_resp and raise a RuntimeError carrying the actual status_code and status_msg.

Example

Before:

TTS failed: Cannot decode SSE event from b'{"base_resp":{"status_code":1027,"status_msg":"insufficient balance"}}'

After:

RuntimeError: MiniMax TTS API error (code=1027): insufficient balance

Test

Reproduced locally with a depleted MiniMax API key — error message now clearly identifies the cause; prior version produced a confusing SSE parse error.

Diff size

17 lines, pure error-handling. No new feature, no new dependency.

Summary by Sourcery

Bug Fixes:

  • Detect non-SSE MiniMax TTS responses via Content-Type and raise a RuntimeError with the API's status_code and status_msg instead of producing a confusing SSE parse failure.

## Bug

MiniMax TTS API returns a JSON error body (not an SSE stream) when:
- quota / rate limit exceeded
- invalid voice_id or model
- API key issues

Current code calls `raise_for_status()` (which doesn't trigger on 200 with
JSON body) then parses everything as SSE. Users get a confusing parse error
or empty audio with no indication of the real cause.

## Fix

Before entering the SSE parse loop, check `Content-Type`. If it's not
`text/event-stream`, parse the JSON `base_resp` and raise a `RuntimeError`
with the actual `status_code` and `status_msg`.

## Example

Before:
  TTS failed: Cannot decode SSE event from b'{"base_resp":{...

After:
  RuntimeError: MiniMax TTS API error (code=1027): insufficient balance

## Test

Reproduced locally by hitting a key with no balance — error message now
clearly identifies the cause.

## Diff size

17 lines, pure error-handling. No new feature, no new dependency.
@auto-assign auto-assign Bot requested review from LIghtJUNction and anka-afk May 7, 2026 15:45
@dosubot dosubot Bot added the size:S This PR changes 10-29 lines, ignoring generated files. label May 7, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Consider normalizing the Content-Type header to lowercase (e.g., response.headers.get("Content-Type", "").lower()) before checking for "text/event-stream" to avoid missing valid values due to casing differences.
  • When loading err_data = json.loads(body), it may be safer to ensure it’s a dict (e.g., if isinstance(err_data, dict): ...) before calling .get() on it, to avoid unexpected type errors if the API ever returns a non-object JSON payload.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider normalizing the `Content-Type` header to lowercase (e.g., `response.headers.get("Content-Type", "").lower()`) before checking for `"text/event-stream"` to avoid missing valid values due to casing differences.
- When loading `err_data = json.loads(body)`, it may be safer to ensure it’s a dict (e.g., `if isinstance(err_data, dict): ...`) before calling `.get()` on it, to avoid unexpected type errors if the API ever returns a non-object JSON payload.

## Individual Comments

### Comment 1
<location path="astrbot/core/provider/sources/minimax_tts_api_source.py" line_range="114-115" />
<code_context>
+                # Detect by Content-Type and surface the actual API error so callers
+                # see the real status_code / status_msg instead of a confusing
+                # parsing failure later in the SSE loop.
+                content_type = response.headers.get("Content-Type", "")
+                if "text/event-stream" not in content_type:
+                    body = await response.text()
+                    try:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Normalize Content-Type before checking for `text/event-stream`.

MIME types are case-insensitive, so checking the raw header string can fail if the server returns mixed case (e.g. `Text/Event-Stream`). Please normalize first, e.g. `content_type = response.headers.get("Content-Type", "").lower()`, then check `

```suggestion
                content_type = response.headers.get("Content-Type", "").lower()
                if "text/event-stream" not in content_type:
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +114 to +115
content_type = response.headers.get("Content-Type", "")
if "text/event-stream" not in content_type:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): Normalize Content-Type before checking for text/event-stream.

MIME types are case-insensitive, so checking the raw header string can fail if the server returns mixed case (e.g. Text/Event-Stream). Please normalize first, e.g. content_type = response.headers.get("Content-Type", "").lower(), then check `

Suggested change
content_type = response.headers.get("Content-Type", "")
if "text/event-stream" not in content_type:
content_type = response.headers.get("Content-Type", "").lower()
if "text/event-stream" not in content_type:

Copy link
Copy Markdown
Contributor

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

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 error handling for non-SSE responses from the MiniMax TTS API by checking the Content-Type header and parsing the JSON error body. Feedback suggests improving the robustness of the JSON parsing logic to prevent potential AttributeErrors and using Exception instead of RuntimeError to maintain consistency with the existing codebase.

Comment on lines +116 to +127
body = await response.text()
try:
err_data = json.loads(body)
base_resp = err_data.get("base_resp", {})
err_msg = base_resp.get("status_msg", body[:200])
err_code = base_resp.get("status_code", "unknown")
except json.JSONDecodeError:
err_msg = body[:200]
err_code = "unknown"
raise RuntimeError(
f"MiniMax TTS API error (code={err_code}): {err_msg}"
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current error parsing logic is somewhat fragile. It can raise an AttributeError if the response body is a valid JSON but not a dictionary (e.g., a list), or if base_resp is explicitly set to null in the JSON. Additionally, using Exception instead of RuntimeError would be more consistent with the rest of the codebase and the existing error handling in this file (see lines 159 and 194).

Suggested change
body = await response.text()
try:
err_data = json.loads(body)
base_resp = err_data.get("base_resp", {})
err_msg = base_resp.get("status_msg", body[:200])
err_code = base_resp.get("status_code", "unknown")
except json.JSONDecodeError:
err_msg = body[:200]
err_code = "unknown"
raise RuntimeError(
f"MiniMax TTS API error (code={err_code}): {err_msg}"
)
body = await response.text()
err_msg, err_code = body[:200], "unknown"
try:
err_data = json.loads(body)
if isinstance(err_data, dict):
base_resp = err_data.get("base_resp")
if isinstance(base_resp, dict):
err_msg = base_resp.get("status_msg") or err_msg
err_code = base_resp.get("status_code", "unknown")
except Exception:
pass
raise Exception(f"MiniMax TTS API error (code={err_code}): {err_msg}")
References
  1. Consistency with existing error handling patterns in the repository (using Exception over RuntimeError). (link)

@dosubot dosubot Bot added the area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. label May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant