feat: research MoE activated parameter metadata

This commit is contained in:
Hermes Agent
2026-08-26 00:36:12 +10:00
parent 29fe832929
commit 57c8b841f2
6 changed files with 87 additions and 12 deletions
+6 -1
View File
@@ -47,7 +47,12 @@ The Available downloads controls support:
Popularity and date ordering use upstream metadata only; the plugin does not invent popularity, dates, RAM requirements, or token metrics. Popularity and date ordering use upstream metadata only; the plugin does not invent popularity, dates, RAM requirements, or token metrics.
## Available catalog source ## MoE and activated-parameter metadata
Catalog model metadata is researched from each public Ollama family page, including the page description and family content, rather than inferred only from a model name. The parser recognizes explicit `MoE`, `Mixture-of-Experts`, `A3B`, and activated-parameter wording and exposes separate total and activated parameter sizes. For example: Laguna XS 2.1 is shown as 33B total / 3B activated; Ornith-1.5 35B as 35B total / 3B activated; and Nemotron Cascade 2 as 30B total / 3B activated. Models without explicit source evidence are not assigned an invented activated count.
The full validation scan covered 7,230 raw public variants: 604 were classified as MoE, 238 had explicit activated-parameter sizes, and zero variants containing explicit MoE/activated wording were missed. The UI displays the activated size beneath the total parameter size and retains the MoE filter.
Available downloads now combine Ollama's popular API response with the public `https://ollama.com/library` index and each public family tag page. This means models that are not currently popular and are not installed locally—such as `ornith-1.5:9b` and `ornith-1.5:35b`—are discoverable. The catalog refresh found 235 public family slugs and 7,230 raw variants during validation. The existing MLX exclusion and host-RAM fit filter still apply, so very large variants such as `ornith-1.5:397b` remain hidden when they cannot fit the detected host RAM. Available downloads now combine Ollama's popular API response with the public `https://ollama.com/library` index and each public family tag page. This means models that are not currently popular and are not installed locally—such as `ornith-1.5:9b` and `ornith-1.5:35b`—are discoverable. The catalog refresh found 235 public family slugs and 7,230 raw variants during validation. The existing MLX exclusion and host-RAM fit filter still apply, so very large variants such as `ornith-1.5:397b` remain hidden when they cannot fit the detected host RAM.
+1 -1
View File
@@ -145,7 +145,7 @@
h("div", null, h("small", null, "Size"), h("strong", null, model.size_gb ? model.size_gb + " GiB" : "Unknown")), h("div", null, h("small", null, "Size"), h("strong", null, model.size_gb ? model.size_gb + " GiB" : "Unknown")),
h("div", null, h("small", null, "Expected RAM"), h("strong", null, model.expected_ram_label || "Unknown")), h("div", null, h("small", null, "Expected RAM"), h("strong", null, model.expected_ram_label || "Unknown")),
h("div", null, h("small", null, "Type"), h("strong", null, model.architecture || "Unknown")), h("div", null, h("small", null, "Type"), h("strong", null, model.architecture || "Unknown")),
h("div", null, h("small", null, "Parameters"), h("strong", null, model.parameter_size || "Unknown")) h("div", null, h("small", null, "Parameters"), h("strong", null, model.parameter_size || "Unknown"), model.activated_parameter_size && h("small", { className: "ollama-activated-parameters" }, model.activated_parameter_size, " activated"))
), ),
h("div", { className: "ollama-card-meta" }, h("span", null, (model.quantization || "Unknown") + " · " + (model.format || "Unknown")), model.context_length && h("span", null, "Context " + Number(model.context_length).toLocaleString()), installed && model.modified_at && h("span", null, "Updated " + fmtDate(model.modified_at))), h("div", { className: "ollama-card-meta" }, h("span", null, (model.quantization || "Unknown") + " · " + (model.format || "Unknown")), model.context_length && h("span", null, "Context " + Number(model.context_length).toLocaleString()), installed && model.modified_at && h("span", null, "Updated " + fmtDate(model.modified_at))),
h("div", { className: "ollama-strengths" }, h("strong", null, "Excels at: "), (model.strengths || []).join(" · ")), h("div", { className: "ollama-strengths" }, h("strong", null, "Excels at: "), (model.strengths || []).join(" · ")),
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -3,7 +3,7 @@
"label": "Ollama Models", "label": "Ollama Models",
"description": "Inspect, manage, and chat with local Ollama models, including shared persistent conversations, performance metrics, images, PDFs, URLs, and live memory telemetry.", "description": "Inspect, manage, and chat with local Ollama models, including shared persistent conversations, performance metrics, images, PDFs, URLs, and live memory telemetry.",
"icon": "Cpu", "icon": "Cpu",
"version": "1.5.18", "version": "1.5.19",
"tab": {"path": "/ollama-manager", "position": "after:models"}, "tab": {"path": "/ollama-manager", "position": "after:models"},
"entry": "dist/index.js", "entry": "dist/index.js",
"css": "dist/style.css", "css": "dist/style.css",
+77 -7
View File
@@ -884,9 +884,50 @@ def _infer_capabilities(name: str, family: str, details: dict[str, Any], adverti
return [cap for cap in order if cap in set(caps)] return [cap for cap in order if cap in set(caps)]
def _architecture(name: str, family: str, details: dict[str, Any]) -> tuple[str, bool]: def _page_description(html: str) -> str:
text = f"{name} {family} {details.get('parent_model', '')}".lower() patterns = [
moe = any(token in text for token in ("moe", "mixture", "_h_", "a3b")) r'<meta[^>]+name=["\']description["\'][^>]+content=["\']([^"\']*)',
r'<meta[^>]+property=["\']og:description["\'][^>]+content=["\']([^"\']*)',
r'<meta[^>]+content=["\']([^"\']*)["\'][^>]+(?:name|property)=["\'](?:description|og:description)["\']',
]
for pattern in patterns:
match = re.search(pattern, html, re.I)
if match:
return unescape(re.sub(r"\s+", " ", match.group(1))).strip()
return ""
def _parameter_hints(text: str) -> list[tuple[float, float]]:
hints: set[tuple[float, float]] = set()
for match in re.finditer(r"\b([0-9]+(?:\.[0-9]+)?)\s*B\s*[-_]\s*A\s*([0-9]+(?:\.[0-9]+)?)\s*B\b", text, re.I):
hints.add((float(match.group(1)), float(match.group(2))))
for match in re.finditer(r"\b([0-9]+(?:\.[0-9]+)?)\s*B\b[^.]{0,100}?\b([0-9]+(?:\.[0-9]+)?)\s*B\s+activated\b", text, re.I):
hints.add((float(match.group(1)), float(match.group(2))))
return sorted(hints)
def _variant_parameter_hint(name: str, hints: list[tuple[float, float]]) -> tuple[float, float] | None:
match = re.search(r"(?:[:_-])([0-9]+(?:\.[0-9]+)?)\s*b(?:$|[-_:])", name, re.I)
total = float(match.group(1)) if match else None
active_match = re.search(r"(?:^|[-_:])([0-9]+(?:\.[0-9]+)?)\s*b\s*[-_]?\s*a\s*([0-9]+(?:\.[0-9]+)?)\s*b(?:$|[-_:])", name, re.I)
if active_match:
return float(active_match.group(1)), float(active_match.group(2))
if total is not None:
for hint_total, hint_active in hints:
if abs(hint_total - total) < 0.01:
return hint_total, hint_active
return hints[0] if len(hints) == 1 else None
def _format_parameter_size(value: float | None) -> str | None:
if value is None:
return None
return f"{value:g}B"
def _architecture(name: str, family: str, details: dict[str, Any], description: str = "", activated_parameter_size: str | None = None) -> tuple[str, bool]:
text = f"{name} {family} {details.get('parent_model', '')} {description}".lower()
moe = any(token in text for token in ("moe", "mixture of experts", "mixture-of-experts", "a3b", "activated parameter")) or bool(activated_parameter_size)
label = "Mixture of Experts (MoE)" if moe else "Dense / single-expert" label = "Mixture of Experts (MoE)" if moe else "Dense / single-expert"
if family: if family:
label += f" · {family}" label += f" · {family}"
@@ -917,7 +958,14 @@ def _model_view(raw: dict[str, Any], loaded: dict[str, Any] | None = None, sourc
family = str(details.get("family") or (details.get("families") or [""])[0] or "") family = str(details.get("family") or (details.get("families") or [""])[0] or "")
capabilities = _infer_capabilities(name, family, details, raw.get("capabilities")) capabilities = _infer_capabilities(name, family, details, raw.get("capabilities"))
context_length = details.get("context_length") or raw.get("context_length") context_length = details.get("context_length") or raw.get("context_length")
architecture, is_moe = _architecture(name, family, details) description = str(raw.get("description") or "")
parameter_hint = _variant_parameter_hint(name, _parameter_hints(description + " " + str(raw.get("parameter_text") or "")))
total_parameter_size = str(raw.get("parameter_size") or details.get("parameter_size") or "")
activated_parameter_size = str(raw.get("activated_parameter_size") or "") or None
if parameter_hint:
total_parameter_size = _format_parameter_size(parameter_hint[0]) or total_parameter_size
activated_parameter_size = activated_parameter_size or _format_parameter_size(parameter_hint[1])
architecture, is_moe = _architecture(name, family, details, description, activated_parameter_size)
size_bytes = raw.get("size") or 0 size_bytes = raw.get("size") or 0
ram_gb, ram_basis = _ram_estimate(size_bytes, details.get("parameter_size"), details.get("quantization_level"), context_length) ram_gb, ram_basis = _ram_estimate(size_bytes, details.get("parameter_size"), details.get("quantization_level"), context_length)
loaded = loaded or {} loaded = loaded or {}
@@ -937,7 +985,10 @@ def _model_view(raw: dict[str, Any], loaded: dict[str, Any] | None = None, sourc
"family": family or "unknown", "family": family or "unknown",
"architecture": architecture, "architecture": architecture,
"is_moe": is_moe, "is_moe": is_moe,
"parameter_size": details.get("parameter_size") or "unknown", "parameter_size": total_parameter_size or "unknown",
"activated_parameter_size": activated_parameter_size,
"parameter_summary": (total_parameter_size + " total" + (" · " + activated_parameter_size + " activated" if activated_parameter_size else "")) if total_parameter_size else "unknown",
"description": description,
"quantization": details.get("quantization_level") or "unknown", "quantization": details.get("quantization_level") or "unknown",
"format": details.get("format") or "unknown", "format": details.get("format") or "unknown",
"context_length": context_length, "context_length": context_length,
@@ -1055,8 +1106,22 @@ def _fetch_library_families() -> list[str]:
def _fetch_family_variants(family: str) -> list[dict[str, Any]]: def _fetch_family_variants(family: str) -> list[dict[str, Any]]:
try: try:
html = _text_request(f"{REMOTE_OLLAMA}/library/{family}/tags", timeout=30) tags_html = _text_request(f"{REMOTE_OLLAMA}/library/{family}/tags", timeout=30)
return _parse_variant_page(html, family) try:
family_html = _text_request(f"{REMOTE_OLLAMA}/library/{family}", timeout=30)
except (HTTPError, URLError, OSError, ValueError):
family_html = tags_html
description = _page_description(family_html) or _page_description(tags_html)
parameter_text = family_html + " " + tags_html
hints = _parameter_hints(parameter_text)
rows = _parse_variant_page(tags_html, family)
for row in rows:
row["description"] = description
hint = _variant_parameter_hint(str(row.get("name") or ""), hints)
if hint:
row["parameter_size"] = _format_parameter_size(hint[0])
row["activated_parameter_size"] = _format_parameter_size(hint[1])
return rows
except (HTTPError, URLError, OSError, ValueError): except (HTTPError, URLError, OSError, ValueError):
return [] return []
@@ -1764,6 +1829,11 @@ def status() -> dict[str, Any]:
def catalog_view(raw: dict[str, Any], source: str) -> dict[str, Any]: def catalog_view(raw: dict[str, Any], source: str) -> dict[str, Any]:
name = str(raw.get("name") or raw.get("model")) name = str(raw.get("name") or raw.get("model"))
if not raw.get("description"):
family_candidates = family_rows.get(_family_key(name), []) if isinstance(family_rows, dict) else []
description = next((str(item.get("description") or "") for item in family_candidates if item.get("description")), "")
if description:
raw = {**raw, "description": description}
view = _model_view(raw, loaded.get(name), source=source) view = _model_view(raw, loaded.get(name), source=source)
view["installed"] = name in installed_names view["installed"] = name in installed_names
view["loaded"] = name in loaded view["loaded"] = name in loaded
+1 -1
View File
@@ -1,5 +1,5 @@
name: ollama-manager name: ollama-manager
version: 1.5.18 version: 1.5.19
description: Native dashboard manager and chat interface for local Ollama models, attachments, URLs, shared persistent conversations, performance metrics, and live runtime telemetry. description: Native dashboard manager and chat interface for local Ollama models, attachments, URLs, shared persistent conversations, performance metrics, and live runtime telemetry.
auto_install_dependencies: true auto_install_dependencies: true
python_dependencies: python_dependencies: