Skip to content

fix(mlx): pass the voice description the design model requires (#1405) - #1407

Merged
debpalash merged 2 commits into
mainfrom
fix/1405-mlx-voicedesign-instruct
Aug 7, 2026
Merged

fix(mlx): pass the voice description the design model requires (#1405)#1407
debpalash merged 2 commits into
mainfrom
fix/1405-mlx-voicedesign-instruct

Conversation

@debpalash

@debpalash debpalash commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Closes #1405.

Reported on macOS / M4 Pro with the mlx-audio engine: generating returned

400 Bad Request: VoiceDesign model requires 'instruct' to describe the voice
(e.g., 'A cheerful young female voice with high pitch')

Root cause

MLXAudioBackend.generate assembles its kwargs by hand:

kwargs = {"text": text, "speed": speed}
if voice:     kwargs["voice"] = voice
if ref_audio: kwargs["ref_audio"] = ref_audio
if ref_audio and ref_text: kwargs["ref_text"] = ref_text
if language ...: kwargs["lang_code"] = ...

instruct is never forwarded — while the comment immediately above that block asserts that it is:

Different engines accept different kwargs (voice for Kokoro, ref_audio for CSM, instruct for Qwen3) — we pass them all and let the engine ignore what it doesn't use.

The curated qwen3-tts model is the VoiceDesign variant (Qwen3-TTS-12Hz-1.7B-VoiceDesign-4bit), and mlx_audio/tts/models/qwen3_tts/qwen3_tts.py:919 raises when that variant runs without an instruct.

So the engine could not produce audio under any input, and the user got a raw 400 quoting a library message that names an internal parameter and no action. The comment is why it went unnoticed — it documented behaviour the code was missing.

Fix

  • forward instruct to the library;
  • when a design model has no description, fail with something actionable instead of the library's wording.

Detection asks the model's own tts_model_type — the exact field mlx-audio branches on, so it cannot drift — and falls back to the VoiceDesign id convention only when a config doesn't expose the field.

One pre-existing test changed

test_mlx_audio_generate_non_kokoro_model_ignores_kokoro_validation generated with the VoiceDesign model and no description while asserting lang_code behaviour. That scenario could never have worked against the real library; it passed only because instruct was dropped before reaching it. It now supplies one, and additionally asserts the forwarding — so the gap that hid this bug is itself covered.

Tests

5 new, 4 fail before — including the core one, that the description reaches the model at all.

Backend suite: 4397 passed, 32 skipped.

The MLX audio backend now forwards voice descriptions as instruct for Qwen3 VoiceDesign models and raises an actionable error when the description is missing. Model detection uses tts_model_type with a model-ID fallback, fixing speech generation on Apple Silicon. Tests cover forwarding, validation, and detection behavior; human review should verify compatibility with model configurations that omit or misreport tts_model_type.

`MLXAudioBackend.generate` assembles its kwargs by hand and forwards
`voice`, `ref_audio`, `ref_text` and `lang_code`. It never forwarded
`instruct` — while the comment immediately above the block asserted that it
did ("voice for Kokoro, ref_audio for CSM, instruct for Qwen3 — we pass
them all").

The curated `qwen3-tts` model IS the VoiceDesign variant, and mlx-audio
raises outright when that variant runs without an instruct. So the engine
could not produce audio under any input, and the reporter got a bare 400
quoting a library message that names an internal parameter and no action.
The comment is why this went unnoticed: it documented the behaviour the
code was missing.

Now the description is forwarded, and a design model without one fails with
something a user can act on instead of the library's wording.

Detection asks the model's own `tts_model_type` — the exact field mlx-audio
branches on, so it cannot drift from the library — and falls back to the id
convention only when a config does not expose it.

One pre-existing test had to change: it generated with the VoiceDesign
model and no description while asserting lang_code behaviour. That scenario
could never have worked against the real library; it passed only because
instruct was being dropped before it got there. It now supplies one, and
additionally asserts the forwarding.

5 new tests, 4 fail before. Backend suite: 4397 passed, 32 skipped.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 00c48a85-2da0-4ea8-951c-a53caf3421f2

📥 Commits

Reviewing files that changed from the base of the PR and between 4711656 and 8d34f83.

📒 Files selected for processing (2)
  • backend/services/tts_backend.py
  • tests/test_mlx_audio_instruct_1405.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/services/tts_backend.py
  • tests/test_mlx_audio_instruct_1405.py

📝 Walkthrough

Walkthrough

Changes

VoiceDesign support

Layer / File(s) Summary
MLX-Audio VoiceDesign generation
backend/services/tts_backend.py, CHANGELOG.md
MLX-Audio detects VoiceDesign models from configuration or model ID, forwards instruct, and raises a clear error when the description is missing. The changelog records the fix.
VoiceDesign behavior tests
tests/test_mlx_audio_instruct_1405.py, tests/test_engines.py
Tests cover instruction forwarding, validation errors, non-VoiceDesign models, detection precedence, and the Qwen3-TTS integration path.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 8 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
I18n Completeness (21 Locales) ⚠️ Warning No frontend t() keys changed, but tts_backend.py:1441-1445 adds a user-facing hardcoded error that /generate returns directly to the UI, bypassing all 21 locales. Return a stable error code and translate the message in the frontend, adding the key to all 21 locale JSON files; keep backend text for logs only.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional-commit format with scope and references issue #1405 while accurately describing the fix.
Description check ✅ Passed The description clearly documents the bug, root cause, fix, affected test, and test results, although it omits some template checkboxes.
Linked Issues check ✅ Passed The changes address issue #1405 by forwarding voice descriptions, validating missing descriptions, and adding regression coverage.
Out of Scope Changes check ✅ Passed The changelog, backend changes, and tests directly support the linked VoiceDesign bug fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Cross-Platform Default Parity ✅ Passed Default TTS remains OmniVoice and MLX defaults to Kokoro; VoiceDesign logic activates only for an explicitly selected model or env override, while MLX is unavailable on Windows/Linux.
Local-First Guarantee ✅ Passed The PR adds no endpoints, credentials, telemetry, or dependencies; it only validates locally and forwards data to the existing MLX loader/model, with HuggingFace downloads remaining the only permit...
Backward Compatibility ✅ Passed The PR changes only MLX generation, tests, and CHANGELOG; DB, migrations, settings, prefs, dependency, and model-loading files are unchanged, so existing data and installed weights need no migratio...

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/services/tts_backend.py`:
- Line 1437: In the instruct handling near the kwargs construction, replace the
inline conditional assignment with a properly indented if block containing
kwargs["instruct"] = instruct, preserving the existing condition and behavior so
Ruff E701 is resolved.

In `@tests/test_mlx_audio_instruct_1405.py`:
- Line 25: Move the module-level services import into the backend fixture or
each test, resolving tts_backend at runtime so the regression suite uses the
current implementation despite sys.modules pollution. Keep only imports that are
safe at module scope in tests/test_mlx_audio_instruct_1405.py.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 88250417-11d5-4c82-8ad8-ed1b47583d96

📥 Commits

Reviewing files that changed from the base of the PR and between 0c82167 and 4711656.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • backend/services/tts_backend.py
  • tests/test_engines.py
  • tests/test_mlx_audio_instruct_1405.py

Comment thread backend/services/tts_backend.py Outdated
Comment thread tests/test_mlx_audio_instruct_1405.py Outdated
CodeRabbit on #1407: a module-level `from services import tts_backend`
keeps whatever object sys.modules held when the file was imported. Other
suites rebind that name, so the binding can go stale and leave these
tests exercising a different implementation than the one under test —
passing alone and proving nothing in the full run. Resolve it in a
fixture instead.

Also split the one-line `if instruct:` now that it carries an elif.
@debpalash
debpalash merged commit dd8143c into main Aug 7, 2026
15 checks passed
@debpalash
debpalash deleted the fix/1405-mlx-voicedesign-instruct branch August 7, 2026 10:45
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.

[Bug] 400 Bad Request: VoiceDesign model requires 'instruct' to describe the voice (e.

1 participant