"""Optional `model: auto` routing. Never used when the client names a model explicitly.""" from __future__ import annotations import re CODE_RE = re.compile(r"```|\bdef \b|\bclass \b|\bimport \b|\bfunction\b|=>|#include|SELECT .* FROM", re.I) REASON_RE = re.compile(r"\b(prove|proof|step by step|reason|derive|why|analy[sz]e|plan|strategy|compare|evaluate|explain in depth)\b", re.I) def _text_of(body: dict) -> tuple[str, bool]: text = "" has_image = False for m in body.get("messages") or []: c = m.get("content") if isinstance(c, str): text += c + "\n" elif isinstance(c, list): for p in c: if isinstance(p, dict): if p.get("type") == "text": text += p.get("text", "") + "\n" elif p.get("type") in ("image_url", "input_image", "image"): has_image = True if body.get("prompt"): text += str(body["prompt"]) return text, has_image async def choose_auto(state, body: dict, endpoint: str) -> str: registry = state.manager.registry manager = state.manager aliases = await registry.aliases() text, has_image = _text_of(body) if endpoint.endswith("/embeddings"): return aliases.get("embedding") or await _first(registry, lambda m: m["embedding"]) if endpoint.endswith("/rerank"): return aliases.get("reranker") or await _first(registry, lambda m: m["reranker"]) if has_image: return aliases.get("vision") or await _first(registry, lambda m: m["vision"] and not m["embedding"]) long_prompt = len(text) > 12000 if CODE_RE.search(text) and "coder" in aliases: return aliases["coder"] if (REASON_RE.search(text) or long_prompt) and "reasoning" in aliases: return aliases["reasoning"] # prefer the currently loaded text model to avoid a switch cur = manager.current_model() if cur and not (cur.model.get("embedding") or cur.model.get("reranker") or cur.model.get("vision") and not text): return cur.model["id"] if "default" in aliases: return aliases["default"] if "fast" in aliases: return aliases["fast"] default = await state.db.get_setting("default_model") if default: return default return await _first(registry, lambda m: not (m["embedding"] or m["reranker"]) and m["compatible"]) async def _first(registry, pred) -> str: models = await registry.list_models() fav = [m for m in models if pred(m) and m["favorite"] and m["enabled"] and m["compatible"]] ok = fav or [m for m in models if pred(m) and m["enabled"] and m["compatible"]] if not ok: from .errors import ModelNotFound raise ModelNotFound("No suitable model installed for automatic routing.") # smallest that is reasonably capable: prefer the largest under 20 GB, else smallest mid = sorted(ok, key=lambda m: (m["estimated_ram_gb"] or 0)) under = [m for m in mid if (m["estimated_ram_gb"] or 0) <= 20] return (under[-1] if under else mid[0])["id"]