endpoints: filter /v1/models and /v1/voices by engine availability flags

This commit is contained in:
russell@unturf.com 2026-05-24 11:58:35 -04:00
parent f25731ca08
commit e9c978b051
No known key found for this signature in database

View file

@ -66,6 +66,17 @@ except ImportError:
torch = None
F5TTS = None
def is_engine_available(model_id):
"""Whether the TTS engine for a given model_id is loadable in this process.
Used to filter advertised models/voices so we don't 503 on requests for
engines whose Python deps weren't installed. tts-1/tts-1-hd/tts-1-silero/
tts-1-kokoro are assumed available they surface their own load errors."""
if model_id == 'tts-1-qwen':
return QWEN_TTS_AVAILABLE
if model_id == 'tts-1-f5':
return F5_TTS_AVAILABLE
return True
@contextlib.asynccontextmanager
async def lifespan(app):
# Startup: Initialize voice caches in each worker process
@ -74,10 +85,12 @@ async def lifespan(app):
default_exists('config/pre_process_map.yaml')
default_exists('config/voice_to_speaker.yaml')
# Build voice-to-model cache for fast lookups
# Build voice-to-model cache for fast lookups (skip engines we can't load)
with open('config/voice_to_speaker.yaml', 'r', encoding='utf8') as file:
voice_map = yaml.safe_load(file)
for model_id, voices in voice_map.items():
if not is_engine_available(model_id):
continue
if isinstance(voices, dict):
for voice_name in voices.keys():
# First match wins (for duplicate voice names across models)
@ -85,9 +98,11 @@ async def lifespan(app):
voice_to_model_cache[voice_name] = model_id
print(f"Voice-to-model cache initialized with {len(voice_to_model_cache)} voices")
# Build voices cache for /v1/voices endpoint
# Build voices cache for /v1/voices endpoint (skip engines we can't load)
models_data = []
for model_id, voices in voice_map.items():
if not is_engine_available(model_id):
continue
if isinstance(voices, dict):
voice_list = list(voices.keys())
@ -731,51 +746,21 @@ def build_ffmpeg_args(response_format, input_format, sample_rate):
@app.get("/v1/models")
async def list_models():
"""List all available TTS models (OpenAI-compatible format)"""
# Return minimal OpenAI-compatible model list (no extra fields)
# tts-1-qwen and tts-1-f5 enabled by default
return {
"object": "list",
"data": [
{
"id": "tts-1-qwen",
"""List all available TTS models (OpenAI-compatible format).
Only advertise engines whose Python deps are actually loadable otherwise
clients try a model and get 503, which they treat as a transient outage.
"""
available = []
for model_id in ('tts-1-qwen', 'tts-1-f5'):
if is_engine_available(model_id):
available.append({
"id": model_id,
"object": "model",
"created": 1700000000,
"owned_by": "uncloseai"
},
{
"id": "tts-1-f5",
"object": "model",
"created": 1700000000,
"owned_by": "uncloseai"
}
# Other models disabled by default:
# {
# "id": "tts-1",
# "object": "model",
# "created": 1700000000,
# "owned_by": "uncloseai"
# },
# {
# "id": "tts-1-hd",
# "object": "model",
# "created": 1700000000,
# "owned_by": "uncloseai"
# },
# {
# "id": "tts-1-silero",
# "object": "model",
# "created": 1700000000,
# "owned_by": "uncloseai"
# },
# {
# "id": "tts-1-kokoro",
# "object": "model",
# "created": 1700000000,
# "owned_by": "uncloseai"
# }
]
}
})
return {"object": "list", "data": available}
@app.get("/v1/voices")
async def list_voices():