"""Model identity ontology — the rules that separate a MODEL (release) from its ARTIFACTS (checkpoints, conversions, quantisations), its EVALUATION VARIANTS (reasoning-effort settings of the same weights) and its FAMILY. MODEL FAMILY Llama 4 · Qwen3.6 · Claude · Gemini └ MODEL Llama 4 Maverick · Qwen3.6-35B-A3B · Claude Fable 5.1 └ ARTIFACT meta-llama/Llama-4-Maverick-17B-128E-Instruct (official checkpoint) · unsloth/…-GGUF (third-party quantisation) └ DEPLOYMENT OpenRouter meta-llama/llama-4-maverick · Together meta-llama/Llama-4-Maverick (= prices rows) Everything here is deterministic string analysis. It never *asserts* a relation on its own: the resolution service uses these signals together with identifiers, organisations, `base_model` metadata and dates, and parks anything ambiguous in the review queue. """ from __future__ import annotations import re from dataclasses import dataclass, field # ---------------------------------------------------------------------------------------------- quantisation / precision formats QUANT_FORMATS: dict[str, str] = { # token (lowercase, matched on word boundaries) → canonical format "gguf": "gguf", "ggml": "gguf", "q2_k": "gguf", "q3_k_m": "gguf", "q4_k_m": "gguf", "q4_k_s": "gguf", "q5_k_m": "gguf", "q6_k": "gguf", "q8_0": "gguf", "q4_0": "gguf", "q4_1": "gguf", "q5_0": "gguf", "iq4_xs": "gguf", "iq3_m": "gguf", "iq2_m": "gguf", "ud-q4_k_xl": "gguf", "awq": "awq", "w4a16": "awq", "gptq": "gptq", "exl2": "exl2", "exl3": "exl3", "mlx": "mlx", "onnx": "onnx", "openvino": "openvino", "coreml": "coreml", "tensorrt": "tensorrt", "trt": "tensorrt", "trt-llm": "tensorrt", "bnb": "bnb", "bitsandbytes": "bnb", "nf4": "bnb", "4bit": "int4", "8bit": "int8", "int4": "int4", "int8": "int8", "w8a8": "int8", "w8a16": "int8", "fp8": "fp8", "fp8-kv": "fp8", "e4m3": "fp8", "nvfp4": "nvfp4", "mxfp4": "mxfp4", "mxfp8": "mxfp8", "fp4": "fp4", "quark": "quark", "quantized": "quantized", "quant": "quantized", "qat": "qat", "hqq": "hqq", "aqlm": "aqlm", "eetq": "eetq", "smoothquant": "smoothquant", "compressed-tensors": "compressed-tensors", "marlin": "gptq", "gptq-int4": "gptq", "autoround": "autoround", "2bit": "int2", "3bit": "int3", "5bit": "int5", "6bit": "int6", "2-bit": "int2", "3-bit": "int3", "4-bit": "int4", "5-bit": "int5", "6-bit": "int6", "8-bit": "int8", } # Full-precision dtype tokens: a "-BF16" or "-FP16" repo is a *conversion/packaging* of the same weights, not a quantisation. PRECISION_FORMATS = {"bf16", "fp16", "fp32", "f16", "f32", "float16", "bfloat16", "half"} ARTIFACT_PACKAGING = {"safetensors", "pytorch", "pth", "ckpt", "jax", "flax", "tf", "tflite", "litert", "gguf", "mlx", "onnx", "coreml", "openvino", "tensorrt"} # Third-party organisations that (almost) only publish conversions/quantisations of other people's models. CONVERTER_ORGS = { "unsloth", "bartowski", "mlx-community", "thebloke", "lmstudio-community", "qwen-community", "mradermacher", "quantfactory", "nousresearch-quant", "mistral-community", "turboderp", "casperhansen", "neuralmagic", "redhatai", "amd", "intel", "nvidia-quant", "ggml-org", "second-state", "mlx-vision", "cortexso", "gaianet", "bunnycore", "dranger003", "ubergarm", "anthracite-org", "modelcloud", "jinaai-quant", "ai-forever-quant", "lmstudio", "ollama", "ggerganov", "mlc-ai", "onnx-community", "onnxmodelzoo", "kaitchup", "thedrummer-quant", } # ---------------------------------------------------------------------------------------------- evaluation-effort variants (same weights, different setting) # Suffixes appended by evaluators (Artificial Analysis, LiveBench…) to a model slug to denote a *configuration* of the model. EFFORT_SUFFIXES: dict[str, dict[str, str]] = { "xhigh": {"reasoning_effort": "xhigh"}, "x-high": {"reasoning_effort": "xhigh"}, "extra-high": {"reasoning_effort": "xhigh"}, "high": {"reasoning_effort": "high"}, "medium": {"reasoning_effort": "medium"}, "low": {"reasoning_effort": "low"}, "minimal": {"reasoning_effort": "minimal"}, "max-effort": {"reasoning_effort": "max"}, "high-effort": {"reasoning_effort": "high"}, "low-effort": {"reasoning_effort": "low"}, "medium-effort": {"reasoning_effort": "medium"}, "xhigh-effort": {"reasoning_effort": "xhigh"}, "thinking": {"reasoning": "on"}, "reasoning": {"reasoning": "on"}, "think": {"reasoning": "on"}, "thinking-on": {"reasoning": "on"}, "non-reasoning": {"reasoning": "off"}, "no-reasoning": {"reasoning": "off"}, "non-thinking": {"reasoning": "off"}, "nothink": {"reasoning": "off"}, "no-think": {"reasoning": "off"}, "no-thinking": {"reasoning": "off"}, "thinking-off": {"reasoning": "off"}, "without-thinking": {"reasoning": "off"}, "with-thinking": {"reasoning": "on"}, "adaptive": {"reasoning": "adaptive"}, "adaptive-reasoning": {"reasoning": "adaptive"}, # NOT effort suffixes although evaluators sometimes use them: "max" (Qwen3-Max, GPT-5.1-Codex-Max are models), "fast" (Grok 4.1 Fast), # "instant" (Claude Instant) — they name a model tier, so they are only recognised in the explicit "-max-effort" form above. "thinking-16k": {"reasoning": "on", "thinking_budget": "16k"}, "thinking-32k": {"reasoning": "on", "thinking_budget": "32k"}, "thinking-64k": {"reasoning": "on", "thinking_budget": "64k"}, "thinking-128k": {"reasoning": "on", "thinking_budget": "128k"}, "thinking-8k": {"reasoning": "on", "thinking_budget": "8k"}, "thinking-4k": {"reasoning": "on", "thinking_budget": "4k"}, "thinking-1k": {"reasoning": "on", "thinking_budget": "1k"}, } # order matters: try the longest compound suffixes first _EFFORT_ORDERED = sorted(EFFORT_SUFFIXES, key=len, reverse=True) _EFFORT_RE = re.compile(r"[-_ ](" + "|".join(re.escape(s) for s in _EFFORT_ORDERED) + r")$", re.I) # LiveBench-style budgets: "32k-thinking", "24k-think", "default-think(ing)" _BUDGET_RE = re.compile(r"[-_ ]((\d+)k)[-_ ]?(thinking|think)$", re.I) _DEFAULT_THINK_RE = re.compile(r"[-_ ](default[-_ ]?(thinking|think))$", re.I) _MAX_EFFORT_SUFFIXES = 3 # "claude-opus-4-5-20251101-thinking-64k-high-effort" → thinking-64k + high-effort # Words that are *part of a model name*, never an effort suffix, when they precede the suffix (e.g. "Kimi K2 Thinking" is a distinct release). OFFICIAL_THINKING_RELEASES = {"kimi-k2-thinking", "qwen3-235b-a22b-thinking-2507", "qwen3-30b-a3b-thinking-2507", "qwen3-4b-thinking-2507", "glm-4.5-air-thinking", "gemini-2.5-flash-thinking", "grok-3-mini-thinking", "gemini-2-0-flash-thinking-exp-1219", "gemini-2-0-flash-thinking-exp-01-21", "qwen3-next-80b-a3b-thinking", "trinity-large-thinking", "trinity-mini-thinking", "kimi-k2-thinking-turbo", "devstral-medium", "mistral-medium", "codestral-medium", "magistral-medium", "mistral-small", "mistral-large"} # families whose "Thinking" checkpoints are separate weights (own hub repo), never an evaluation setting _OFFICIAL_THINKING_PATTERNS = [re.compile(p) for p in (r"^qwen3-vl-.*-thinking$", r"^qwen3(\.\d+)?-.*-thinking-\d{4}$", r"^qwen3-next-.*-thinking$", r"^glm-4\.[1-9]v?-.*thinking$", r"^deepseek-v3\.1-terminus-thinking$", r"^trinity-.*-thinking$")] # tier words that are only an *effort* when the stem carries a version or size digit ("claude-opus-5-medium", "o3-mini-high", "gpt-5-4-mini-low") # — never on a bare product name ("devstral-medium", "mistral-medium" are model tiers) _DIGIT_GATED_SUFFIXES = {"medium", "low", "high", "minimal"} def is_official_thinking_release(low: str) -> bool: return low in OFFICIAL_THINKING_RELEASES or any(p.match(low) for p in _OFFICIAL_THINKING_PATTERNS) # ---------------------------------------------------------------------------------------------- name analysis _SIZE_RE = re.compile(r"(? bool: return self.is_quantized or self.is_conversion @property def is_effort_variant(self) -> bool: return bool(self.effort) def _to_count(num: str, unit: str) -> int: mult = {"m": 1e6, "b": 1e9, "t": 1e12}[unit.lower()] return int(round(float(num) * mult)) def analyze_model_name(raw: str) -> NameAnalysis: """Deterministic analysis of a model / repository name.""" a = NameAnalysis(raw=raw.strip()) name = a.raw if "/" in name and " " not in name.split("/")[0]: org, _, rest = name.partition("/") a.repo_org, a.repo_name = org.strip(), rest.strip() name = rest a.from_converter_org = org.strip().lower() in CONVERTER_ORGS low = re.sub(r"[\s_]+", "-", name.lower().strip()) low = re.sub(r"[()\[\]]+", "-", low).strip("-") low = re.sub(r"-{2,}", "-", low) # evaluator effort suffixes (up to three, e.g. "-thinking-64k-high-effort"), only when the stem is not itself an official "Thinking" release stripped: list[str] = [] for _ in range(_MAX_EFFORT_SUFFIXES): if is_official_thinking_release(low): break m = _BUDGET_RE.search(low) # "-32k-thinking" before the bare "-thinking" if m: a.effort = {"reasoning": "on", "thinking_budget": m.group(1).lower(), **a.effort} stripped.insert(0, m.group(0)[1:].lower()) low = low[: m.start()] continue m = _DEFAULT_THINK_RE.search(low) if m: a.effort = {"reasoning": "on", **a.effort} stripped.insert(0, m.group(1).lower()) low = low[: m.start()] continue m = _EFFORT_RE.search(low) if m: suffix = m.group(1).lower() if suffix in _DIGIT_GATED_SUFFIXES and not re.search(r"\d", low[: m.start()]): break a.effort = {**EFFORT_SUFFIXES[suffix], **a.effort} stripped.insert(0, suffix) low = low[: m.start()] continue break if stripped: a.effort_suffix = "-".join(stripped) # sizes am = _ACTIVE_RE.search(low) if am: a.parameter_count = _to_count(am.group(1), low[am.start(1) + len(am.group(1)):].strip()[0]) a.active_parameter_count = _to_count(am.group(2), am.group(3)) else: sizes = _SIZE_RE.findall(low) if sizes: counts = [_to_count(n, u) for n, u in sizes] a.parameter_count = max(counts) # dates embedded in the name dm = _DATE_RE.search(low) if dm: a.snapshot_date = f"{dm.group(1)}-{dm.group(2)}-{dm.group(3)}" else: ym = _YYMM_RE.search(low) if ym and not _SIZE_RE.search(ym.group(0)): a.snapshot_date = f"20{ym.group(1)}-{ym.group(2)}" # quantisation / precision tokens tokens = [t for t in re.split(r"[-_\s./()\[\]]+", low) if t] quant: list[str] = [] precision = None kept: list[str] = [] for t in tokens: if t in QUANT_FORMATS: quant.append(QUANT_FORMATS[t]) continue if t in PRECISION_FORMATS: precision = t continue if re.fullmatch(r"(w\d+a\d+|q\d(_[a-z0-9]+)*|iq\d(_[a-z0-9]+)*|\d-?bit|int\d|fp\d|nvfp\d|mxfp\d)", t): quant.append(QUANT_FORMATS.get(t, "quantized")) continue kept.append(t) # "GGUF" / "MLX" packaging counts as artifact even without a bit-width; "MLX-8bit" is quantised a.quant_formats = sorted(set(quant)) a.precision = precision a.is_quantized = any(q not in ("onnx", "coreml", "openvino", "tensorrt", "mlx", "gguf") for q in a.quant_formats) or "gguf" in a.quant_formats a.is_conversion = (not a.is_quantized) and (bool(a.quant_formats) or precision is not None or (a.from_converter_org and bool(a.repo_org))) if "mlx" in a.quant_formats and any(q.startswith("int") for q in a.quant_formats): a.is_quantized = True a.base_key = "-".join(kept).strip("-") a.family_hint = family_hint(name) return a # ---------------------------------------------------------------------------------------------- family inference _FAMILY_PATTERNS: list[tuple[re.Pattern[str], str]] = [ (re.compile(r"\bclaude\b", re.I), "Claude"), (re.compile(r"\bgpt[- ]?(oss)\b", re.I), "gpt-oss"), (re.compile(r"\b(chat)?gpt[- ]?\d", re.I), "GPT"), (re.compile(r"\bo[1-9](-| |$|mini|pro)", re.I), "OpenAI o-series"), (re.compile(r"\bgemini\b", re.I), "Gemini"), (re.compile(r"\bgemma(?=\d|\b)", re.I), "Gemma"), (re.compile(r"\bpalm\b", re.I), "PaLM"), (re.compile(r"\bllama(?=\d|\b)", re.I), "Llama"), (re.compile(r"\bmistral\b", re.I), "Mistral"), (re.compile(r"\bmixtral\b", re.I), "Mixtral"), (re.compile(r"\bministral\b", re.I), "Ministral"), (re.compile(r"\b(codestral|devstral|magistral|pixtral|voxtral)\b", re.I), None), # own families, name = family (re.compile(r"\bqwen(?=\d|\b)|\bqwq\b|\bqvq\b", re.I), "Qwen"), (re.compile(r"\bdeepseek\b", re.I), "DeepSeek"), (re.compile(r"\bkimi\b", re.I), "Kimi"), (re.compile(r"\bglm(?=\d|\b)|\bchatglm\b", re.I), "GLM"), (re.compile(r"\bgrok\b", re.I), "Grok"), (re.compile(r"\bcommand\b", re.I), "Command"), (re.compile(r"\baya\b", re.I), "Aya"), (re.compile(r"\bphi(?=\d|\b)", re.I), "Phi"), (re.compile(r"\bnemotron\b", re.I), "Nemotron"), (re.compile(r"\bgranite\b", re.I), "Granite"), (re.compile(r"\bolmo(?=\d|\b)", re.I), "OLMo"), (re.compile(r"\bmolmo\b", re.I), "Molmo"), (re.compile(r"\bfalcon\b", re.I), "Falcon"), (re.compile(r"\byi\b", re.I), "Yi"), (re.compile(r"\bminimax\b", re.I), "MiniMax"), (re.compile(r"\bhunyuan\b", re.I), "Hunyuan"), (re.compile(r"\bernie\b", re.I), "ERNIE"), (re.compile(r"\bseed\b", re.I), "Seed"), (re.compile(r"\bdoubao\b", re.I), "Doubao"), (re.compile(r"\bstep\b", re.I), "Step"), (re.compile(r"\binternlm(?=\d|\b)|\binternvl(?=\d|\b)", re.I), "InternLM"), (re.compile(r"\bjamba\b", re.I), "Jamba"), (re.compile(r"\blfm(?=\d|\b)", re.I), "LFM"), (re.compile(r"\bexaone(?=\d|\b)", re.I), "EXAONE"), (re.compile(r"\bsolar\b", re.I), "Solar"), (re.compile(r"\bnova\b", re.I), "Nova"), (re.compile(r"\btitan\b", re.I), "Titan"), (re.compile(r"\bsonar\b", re.I), "Sonar"), (re.compile(r"\bstable[- ]?diffusion\b|\bsdxl\b|\bsd3\b", re.I), "Stable Diffusion"), (re.compile(r"\bflux\b", re.I), "FLUX"), (re.compile(r"\bwhisper\b", re.I), "Whisper"), (re.compile(r"\bdall[- ]?e\b", re.I), "DALL·E"), (re.compile(r"\bsora\b", re.I), "Sora"), (re.compile(r"\bveo\b", re.I), "Veo"), (re.compile(r"\bimagen\b", re.I), "Imagen"), (re.compile(r"\bcogito\b", re.I), "Cogito"), (re.compile(r"\bhermes\b", re.I), "Hermes"), (re.compile(r"\bsmol(lm|vlm)\b", re.I), "SmolLM"), (re.compile(r"\bbert\b", re.I), "BERT"), (re.compile(r"\bt5\b", re.I), "T5"), (re.compile(r"\bclip\b", re.I), "CLIP"), (re.compile(r"\bembed(ding)?\b", re.I), None), (re.compile(r"\brerank\b", re.I), None), ] _FAMILY_VERSION_RE = re.compile(r"^(?P[A-Za-z][A-Za-z·\-]*?)[\s\-]?(?P\d+(?:\.\d+)?)", re.I) def family_hint(name: str) -> str | None: """Family label without a version ("Qwen", "Llama", "Claude"). Versioned families ("Llama 3.1") are `family_release_hint`.""" n = name.split("/")[-1] for pat, fam in _FAMILY_PATTERNS: if pat.search(n): if fam is None: m = pat.search(n) return m.group(0).title() if m else None return fam return None def family_release_hint(name: str) -> str | None: """Versioned family ("Llama 3.1", "Qwen3.6", "Gemini 2.5", "Claude 4") when the name carries a version right after the family word.""" fam = family_hint(name) if not fam: return None n = name.split("/")[-1] # version = digits right after the family word, optionally ".minor" or "-minor" (AA/OpenRouter slugs write 5.4 as 5-4); # a number followed by a size unit (38B, 235B, 1.5T) is a parameter count, never a version. m = re.search(re.escape(fam.split(" ")[0]) + r"[\s\-]?(\d+)(?![0-9]*\.?\d*[bmt](?![a-z]))(?:[.\-](\d+)(?![0-9]*[bmt](?![a-z])))?(?![0-9])", n, re.I) if m: version = m.group(1) + (f".{m.group(2)}" if m.group(2) else "") joined = fam in ("Qwen", "GLM", "Phi", "Yi", "Step") or re.search(re.escape(fam) + r"\d", n, re.I) return f"{fam}{version}" if joined else f"{fam} {version}" return fam def variant_key(name: str) -> str: """Grouping key for near-duplicates: analysed base key + parameter count, ignoring org prefixes, quant/precision/effort tokens, separators and trailing 'instruct/chat/it' words. `Qwen3.6-35B-A3B`, `unsloth/Qwen3.6-35B-A3B-GGUF`, `Qwen3.6 35B A3B FP8` → same key.""" a = analyze_model_name(name) toks = [t for t in a.base_key.split("-") if t and t not in _TRAILING_VARIANT_WORDS] return "-".join(toks) # ---------------------------------------------------------------------------------------------- official organisations per family # family root (lowercase `family_hint`) → organisation slugs / hub org names (lowercase) that publish the *official* checkpoints. # A repository under one of these orgs is the model (or its official checkpoint), never a third-party artifact. FAMILY_ORGS: dict[str, tuple[str, ...]] = { "llama": ("meta-llama", "meta", "meta-ai", "facebook"), "qwen": ("qwen", "alibaba", "alibaba-cloud", "alibaba-qwen"), "gemma": ("google", "google-deepmind"), "gemini": ("google", "google-deepmind"), "palm": ("google",), "claude": ("anthropic",), "gpt": ("openai",), "gpt-oss": ("openai",), "openai o-series": ("openai",), "whisper": ("openai",), "dall·e": ("openai",), "sora": ("openai",), "deepseek": ("deepseek-ai", "deepseek"), "kimi": ("moonshotai", "moonshot-ai", "moonshot"), "glm": ("zai-org", "thudm", "zhipu-ai", "z-ai", "zhipu"), "mistral": ("mistralai", "mistral-ai", "mistral"), "mixtral": ("mistralai", "mistral-ai", "mistral"), "ministral": ("mistralai", "mistral-ai", "mistral"), "codestral": ("mistralai", "mistral-ai", "mistral"), "devstral": ("mistralai", "mistral-ai", "mistral"), "magistral": ("mistralai", "mistral-ai", "mistral"), "pixtral": ("mistralai", "mistral-ai", "mistral"), "voxtral": ("mistralai", "mistral-ai", "mistral"), "phi": ("microsoft",), "grok": ("xai", "x-ai", "xai-org"), "command": ("cohereforai", "cohere", "coherelabs", "cohere-labs"), "aya": ("cohereforai", "cohere", "coherelabs"), "nemotron": ("nvidia",), "granite": ("ibm-granite", "ibm"), "olmo": ("allenai", "ai2"), "molmo": ("allenai", "ai2"), "falcon": ("tiiuae", "tii"), "yi": ("01-ai",), "minimax": ("minimaxai", "minimax"), "hunyuan": ("tencent", "tencent-hunyuan"), "ernie": ("baidu",), "seed": ("bytedance-seed", "bytedance"), "doubao": ("bytedance",), "step": ("stepfun-ai", "stepfun"), "internlm": ("internlm", "shanghai-ai-laboratory", "opengvlab"), "jamba": ("ai21labs", "ai21"), "lfm": ("liquidai", "liquid-ai"), "exaone": ("lgai-exaone", "lg-ai-research"), "solar": ("upstage",), "nova": ("amazon", "aws"), "titan": ("amazon", "aws"), "sonar": ("perplexity-ai", "perplexity"), "stable diffusion": ("stabilityai", "stability-ai"), "flux": ("black-forest-labs",), "veo": ("google",), "imagen": ("google",), "cogito": ("deepcogito",), "hermes": ("nousresearch",), "smollm": ("huggingfacetb", "hugging-face"), "bert": ("google", "google-bert"), "t5": ("google", "google-t5"), "clip": ("openai",), } def official_orgs(name: str) -> tuple[str, ...]: """Official organisation slugs for the family the name belongs to (empty when unknown).""" fam = family_hint(name) return FAMILY_ORGS.get(fam.lower(), ()) if fam else () def is_official_org(repo_org: str | None, name: str) -> bool: return bool(repo_org) and repo_org.lower() in official_orgs(name) def effort_config(name: str, config: dict | None) -> dict: """Result configuration for an evaluation-effort variant: the effort dict (`reasoning_effort`, `reasoning`, `thinking_budget`) is merged into the config and the evaluator's variant slug is kept as `aa_variant_slug` so folded results stay distinguishable. Names that are not effort variants return the config unchanged.""" cfg = dict(config or {}) a = analyze_model_name(name) if not a.is_effort_variant: return cfg for k, v in a.effort.items(): cfg.setdefault(k, v) slug = cfg.get("aa_slug") or a.raw.split("/")[-1].strip().lower().replace(" ", "-") cfg.setdefault("aa_variant_slug", slug) return cfg def base_name(name: str) -> str: """The name with the evaluator effort suffix removed ("gpt-5-4-mini-medium" → "gpt-5-4-mini", "Claude Sonnet 4 (no thinking)" → "Claude Sonnet 4"); unchanged when not a variant.""" a = analyze_model_name(name) if not a.is_effort_variant or not a.effort_suffix: return name.strip() n = name.strip() # the suffix was detected on a normalised form (spaces/underscores/parentheses → "-"): strip it from the raw name with the same tolerance tokens = [re.escape(t) for t in a.effort_suffix.split("-") if t] pattern = r"[\s\-_(]+" + r"[\s\-_]*".join(tokens) + r"[)\s]*$" out = re.sub(pattern, "", n, flags=re.I) return out.strip().rstrip("-_ (").strip() or n __all__ = ["ARTIFACT_PACKAGING", "CONVERTER_ORGS", "EFFORT_SUFFIXES", "FAMILY_ORGS", "OFFICIAL_THINKING_RELEASES", "NameAnalysis", "PRECISION_FORMATS", "QUANT_FORMATS", "analyze_model_name", "base_name", "effort_config", "family_hint", "family_release_hint", "is_official_org", "is_official_thinking_release", "official_orgs", "variant_key"]