"""Numbers as they appear in AI documentation: `70B`, `1.5T`, `128K context`, `$3.00 / 1M tokens`, `72.4%`.""" from __future__ import annotations import re _MULT = {"k": 1e3, "m": 1e6, "b": 1e9, "t": 1e12, "g": 1e9} _PARAMS = re.compile(r"(? float: return float(s.replace(",", ".")) def parse_param_count(text: str) -> int | None: """'Qwen3-235B-A22B' → 235e9 ; '7.6 billion parameters' → 7.6e9. Returns total parameters (not active).""" m = _PARAMS_WORD.search(text) if m: return int(_num(m.group(1)) * {"million": 1e6, "billion": 1e9, "trillion": 1e12}[m.group(2).lower()]) best: int | None = None for m in _PARAMS.finditer(text): val = _num(m.group(1)) * _MULT[m.group(2).lower()] if 1e6 <= val <= 5e13 and (best is None or val > best): best = int(val) return best def parse_active_params(text: str) -> int | None: """'235B-A22B' / '30B-A3B' → active parameters of an MoE model.""" m = re.search(r"-A(\d+(?:\.\d+)?)([bBmM])\b", text) if not m: m = re.search(r"(\d+(?:\.\d+)?)\s*([bB])\s*active", text, re.IGNORECASE) if not m: return None return int(_num(m.group(1)) * _MULT[m.group(2).lower()]) def parse_context_length(text: str) -> int | None: """'128K' → 128000 ; '1M' → 1000000 ; '200,000 tokens' → 200000 ; '32768' → 32768.""" t = text.strip().replace("tokens", "").replace("token", "") m = re.search(r"(\d{1,3}(?:,\d{3})+|\d+(?:\.\d+)?)\s*([kKmM])?\b", t) if not m: return None raw = m.group(1) if "," in raw and not m.group(2): val = float(raw.replace(",", "")) else: val = float(raw.replace(",", ".")) if m.group(2): unit = m.group(2).lower() val *= 1024 if (unit == "k" and val in (8, 16, 32, 64, 128, 256, 512)) and "1024" in text else _MULT[unit] if val < 256 or val > 1e8: return None return int(val) def parse_tokens(text: str) -> int | None: return parse_context_length(text) def parse_money_per_mtok(text: str) -> float | None: """'$3.00 / 1M tokens' → 3.0 ; '$0.15/M' → 0.15 ; '$2 per 1K tokens' → 2000.0 (normalised to per million).""" m = _MONEY.search(text) if m: val = _num(m.group(1)) unit = m.group(2).lower() return val * 1000 if unit == "k" else val m = _MONEY_SIMPLE.search(text) if m and "token" in text.lower(): return float(m.group(1)) return None def parse_money(text: str) -> float | None: m = _MONEY_SIMPLE.search(text.replace(",", "")) return float(m.group(1)) if m else None def parse_percent(text: str) -> float | None: m = _PCT.search(text) if m: return float(m.group(1)) m = re.search(r"(? int | None: m = re.search(r"-?\d[\d,]*", text) if not m: return None try: return int(m.group(0).replace(",", "")) except ValueError: return None __all__ = [ "parse_active_params", "parse_context_length", "parse_int", "parse_money", "parse_money_per_mtok", "parse_param_count", "parse_percent", "parse_tokens", ]