Add GET /v1/audio/voices endpoint for TTS voice discovery - #743
Conversation
OpenAI-compatible clients that want to populate a voice picker have
no way to ask the server which voices a given TTS model supports.
For voice-pack-based models like Kokoro, the catalog is determined
by the .safetensors files in the model's voices/ directory — readily
discoverable but not exposed over HTTP today.
This adds a small GET /v1/audio/voices?model=<repo_id> handler that
resolves the model's HF snapshot, enumerates voices/*.safetensors,
and returns {id, name} entries in an OpenAI-style { object, data }
envelope. Models without a voices/ subdirectory return an empty
data list rather than an error, so callers can fall back gracefully.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
This API isn't standard but it's pretty self-contained so I'm ok with it. Note it won't work with most models, but it sounds like you have a specific use case in mind. |
|
@lucasnewman Is there a standard API for this metadata from openai spec? |
No, it's usually just docs and isn't applicable to voice matching models. |
|
Thanks both for taking a look! On the standard-API point — yeah, agree it's non-standard. The motivation is that a number of OpenAI-compatible TTS clients already probe To @lucasnewman's point about most models not supporting it — totally agree, which is why the handler degrades gracefully rather than failing:
So clients always get a well-formed response and can fall back to whatever default UX they want for models that don't expose a voice list. Effectively opt-in by model architecture — surfaces voices only when the model actually has discrete voices to surface. Happy to also run the test against a non-Kokoro model on my end before merge if that'd help. |
Summary
Adds a small
GET /v1/audio/voices?model=<repo_id>handler to the mlx-audio HTTP server so OpenAI-compatible clients can populate a voice picker for voice-pack-based TTS models (Kokoro and similar). Resolves #742.The implementation is ~34 lines, no new dependencies. Returns OpenAI-style
{ object, data }shape so existing clients' voice-list parsers work without changes.Behavior
GET /v1/audio/voices?model=mlx-community/Kokoro-82M-bf16→ enumeratesvoices/*.safetensorsfrom the HF snapshot, returns 54 entries.GET /v1/audio/voices?model=<model without voices/ subdir>→ returns{ data: [] }so clients can fall back gracefully (relevant for voice-cloning models like Higgs Audio that ship reference WAVs in a different layout).GET /v1/audio/voices(nomodel) → HTTP 400.GET /v1/audio/voices?model=<bogus repo id>→ returns{ data: [], error: "..." }rather than 500, again so clients fall back instead of crashing.Why
Marinara Engine and several other OpenAI-compatible clients already hit
${baseUrl}/audio/voicesto populate their per-character voice pickers. Without this endpoint, those clients see a 404 and fall back to their hardcoded OpenAI 9-voice list — so users running mlx-audio with Kokoro (or any voice-pack TTS model) can't actually see the voices their server supports.With this endpoint plus the small client-side change to pass the model id (Pasta-Devs/Marinara-Engine#1131), Marinara's voice picker correctly shows all 54 Kokoro voices instead of the OpenAI fallback.
Test plan
mlx_audio.serverlocally with default flags.mlx-community/Kokoro-82M-bf16to populate the HF cache.curl 'http://127.0.0.1:8080/v1/audio/voices?model=mlx-community/Kokoro-82M-bf16'→ expect 54 voice entries.curl 'http://127.0.0.1:8080/v1/audio/voices'→ expect HTTP 400.curl 'http://127.0.0.1:8080/v1/audio/voices?model=mlx-community/higgs-audio-v2-3B-mlx-q6'→ expect{ data: [] }(novoices/dir in Higgs repo).I verified the first three myself against a local mlx-audio run. The Higgs case is untested by me but follows from the
voices_dir.is_dir()check returning False; happy to also test it if you'd like before merge.🤖 Generated with Claude Code