fix(minimax-tts): surface real API error when response is not SSE#8064
fix(minimax-tts): surface real API error when response is not SSE#8064YonganZhang wants to merge 1 commit intoAstrBotDevs:masterfrom
Conversation
## 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.
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- Consider normalizing the
Content-Typeheader 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| content_type = response.headers.get("Content-Type", "") | ||
| if "text/event-stream" not in content_type: |
There was a problem hiding this comment.
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 `
| 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: |
There was a problem hiding this comment.
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.
| 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}" | ||
| ) |
There was a problem hiding this comment.
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).
| 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
- Consistency with existing error handling patterns in the repository (using Exception over RuntimeError). (link)
Bug
MiniMax TTS API returns a JSON error body (not an SSE stream) when:
voice_idor modelCurrent code calls
raise_for_status()(which doesn't trigger on200 OKwith 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 nottext/event-stream, parse the JSONbase_respand raise aRuntimeErrorcarrying the actualstatus_codeandstatus_msg.Example
Before:
After:
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: