Studio: tag MLX loaded models as MLX instead of Base in chat (#6067)

* Studio: tag MLX loaded models as MLX instead of Base in chat

* Studio: tag MLX named hub defaults via name heuristic
This commit is contained in:
Michael Han 2026-06-07 01:57:55 -07:00 committed by GitHub
commit 1e811acd62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 36 additions and 2 deletions

View file

@ -705,6 +705,7 @@ class InferenceOrchestrator:
self.models[self.active_model_name] = {
"is_vision": model_info.get("is_vision", False),
"is_lora": model_info.get("is_lora", False),
"is_mlx": model_info.get("is_mlx", False),
"display_name": model_info.get("display_name", model_name),
"is_audio": model_info.get("is_audio", False),
"audio_type": model_info.get("audio_type"),

View file

@ -342,6 +342,8 @@ def _handle_load(backend, config: dict, resp_queue: Any) -> None:
"is_vision": mc.is_vision,
"is_lora": mc.is_lora,
"is_gguf": False,
# MLX backend sets device="mlx"; lets the UI tag MLX models.
"is_mlx": getattr(backend, "device", None) == "mlx",
"is_audio": getattr(mc, "is_audio", False),
"audio_type": getattr(mc, "audio_type", None),
"has_audio_input": getattr(mc, "has_audio_input", False),

View file

@ -76,6 +76,9 @@ class ModelDetails(BaseModel):
is_gguf: bool = Field(
False, description = "Whether model is a GGUF model (llama.cpp format)"
)
is_mlx: bool = Field(
False, description = "Whether model is served via the MLX backend (Apple Silicon)"
)
is_audio: bool = Field(False, description = "Whether model is a TTS audio model")
audio_type: Optional[str] = Field(
None, description = "Audio codec type: snac, csm, bicodec, dac"

View file

@ -1446,6 +1446,15 @@ async def browse_folders(
)
def _looks_like_mlx_repo(model_id: str) -> bool:
"""Name heuristic for unloaded models, mirrors the -GGUF suffix check.
Tokenized so MLX only matches as a whole name segment."""
if model_id.lower().startswith("mlx-community/"):
return True
tail = model_id.split("/")[-1]
return "MLX" in _re.split(r"[-_.]", tail.upper())
@router.get("/list")
async def list_models(
current_subject: str = Depends(get_current_subject),
@ -1471,6 +1480,7 @@ async def list_models(
name = model_name.split("/")[-1] if "/" in model_name else model_name,
is_vision = _is_vision,
is_lora = model_data.get("is_lora", False),
is_mlx = model_data.get("is_mlx", False),
is_audio = model_data.get("is_audio", False),
audio_type = _audio_type,
has_audio_input = model_data.get("has_audio_input", False),
@ -1498,13 +1508,18 @@ async def list_models(
all_models = []
seen_ids = set()
# Prefer loaded entries for duplicate ids so runtime flags
# (is_mlx, is_vision, is_audio, ...) are not lost.
loaded_by_id = {model_info.id: model_info for model_info in loaded_models}
# Add default models
for model_id in default_models:
if model_id not in seen_ids:
model_info = ModelDetails(
model_info = loaded_by_id.get(model_id) or ModelDetails(
id = model_id,
name = model_id.split("/")[-1] if "/" in model_id else model_id,
is_gguf = model_id.upper().endswith("-GGUF"),
is_mlx = _looks_like_mlx_repo(model_id),
)
all_models.append(model_info)
seen_ids.add(model_id)

View file

@ -94,16 +94,25 @@ function describeModel(model: {
is_lora?: boolean;
is_vision?: boolean;
is_gguf?: boolean;
is_mlx?: boolean;
is_audio?: boolean;
has_audio_input?: boolean;
}): string | undefined {
const tags: string[] = [];
if (model.is_gguf) tags.push("GGUF");
if (model.is_mlx) tags.push("MLX");
if (model.is_lora) tags.push("LoRA");
if (model.is_vision) tags.push("Vision");
if (model.is_audio) tags.push("Audio");
if (model.has_audio_input) tags.push("Audio Input");
if (!model.is_lora && !model.is_vision && !model.is_gguf && !model.is_audio && !model.has_audio_input)
if (
!model.is_lora &&
!model.is_vision &&
!model.is_gguf &&
!model.is_mlx &&
!model.is_audio &&
!model.has_audio_input
)
tags.push("Base");
return tags.join(" · ");
}
@ -114,6 +123,7 @@ function toChatModelSummary(model: {
is_lora?: boolean;
is_vision?: boolean;
is_gguf?: boolean;
is_mlx?: boolean;
is_audio?: boolean;
audio_type?: string | null;
has_audio_input?: boolean;
@ -125,6 +135,7 @@ function toChatModelSummary(model: {
isLora: Boolean(model.is_lora),
isVision: Boolean(model.is_vision),
isGguf: Boolean(model.is_gguf),
isMlx: Boolean(model.is_mlx),
isAudio: Boolean(model.is_audio),
audioType: model.audio_type ?? null,
hasAudioInput: Boolean(model.has_audio_input),

View file

@ -7,6 +7,7 @@ export interface BackendModelDetails {
is_vision?: boolean;
is_lora?: boolean;
is_gguf?: boolean;
is_mlx?: boolean;
is_audio?: boolean;
audio_type?: string | null;
has_audio_input?: boolean;

View file

@ -44,6 +44,7 @@ export interface ChatModelSummary {
isVision: boolean;
isLora: boolean;
isGguf?: boolean;
isMlx?: boolean;
isAudio?: boolean;
audioType?: string | null;
hasAudioInput?: boolean;