fix(model-switch): treat models dict as metadata, not allowlist
hermes model saves custom_providers models: {default: {context_length}} for
local Ollama. That dict shape was treated as an explicit catalog, so no-key
endpoints skipped live /v1/models probing and Desktop/Telegram only showed
the saved default — Refresh could not help. Keep list/string shapes as
allowlists; pin dict catalogs with discover_models: false.
This commit is contained in:
parent
f5be9236e0
commit
f66319097e
|
|
@ -102,6 +102,30 @@ def _declared_model_ids(value: Any) -> list[str]:
|
||||||
return ids
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
def _models_config_is_allowlist(value: Any) -> bool:
|
||||||
|
"""Return True when ``models:`` is an intentional ID allowlist.
|
||||||
|
|
||||||
|
A mapping like ``{model_id: {context_length: N}}`` is per-model *metadata*
|
||||||
|
written by ``_save_custom_provider`` / the ``hermes model`` wizard — not a
|
||||||
|
catalog narrow. Treating that shape as an allowlist made Desktop/Telegram
|
||||||
|
pickers show only the saved default for local Ollama (no ``api_key``),
|
||||||
|
while ``hermes model`` still live-probed the full ``/v1/models`` list.
|
||||||
|
Refresh could not help because the same gate skipped probing.
|
||||||
|
|
||||||
|
List/string shapes remain allowlists for no-key endpoints. To pin a
|
||||||
|
dict-shaped catalog, set ``discover_models: false``.
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return False
|
||||||
|
if isinstance(value, str):
|
||||||
|
return bool(value.strip())
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return False
|
||||||
|
if isinstance(value, (list, tuple)):
|
||||||
|
return bool(_declared_model_ids(value))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _save_discovered_models_to_config(
|
def _save_discovered_models_to_config(
|
||||||
api_url: str, model_ids: list[str]
|
api_url: str, model_ids: list[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
@ -2631,12 +2655,13 @@ def list_authenticated_providers(
|
||||||
for _m in entry_models:
|
for _m in entry_models:
|
||||||
if _m and _m not in ep_groups[group_key]["models"]:
|
if _m and _m not in ep_groups[group_key]["models"]:
|
||||||
ep_groups[group_key]["models"].append(_m)
|
ep_groups[group_key]["models"].append(_m)
|
||||||
# Track explicit ``models:`` declarations separately from the
|
# Track allowlist-shaped ``models:`` separately from the merged
|
||||||
# merged list: a singular ``default_model``/``model`` is only the
|
# list: a singular ``default_model``/``model`` is only the active
|
||||||
# active selection and must not be mistaken for the user narrowing
|
# selection and must not suppress discovery (see #40542 / PR
|
||||||
# the endpoint to a curated subset (mirrors section 4's
|
# #61928). Dict-shaped ``models:`` is context_length metadata from
|
||||||
# declaration-tracking; see #40542 / PR #61928).
|
# ``hermes model``, not an allowlist — see
|
||||||
if entry_declared_models:
|
# ``_models_config_is_allowlist``.
|
||||||
|
if _models_config_is_allowlist(ep_cfg.get("models")):
|
||||||
ep_groups[group_key]["has_explicit_models"] = True
|
ep_groups[group_key]["has_explicit_models"] = True
|
||||||
ep_groups[group_key]["raw_names"].append(display_name)
|
ep_groups[group_key]["raw_names"].append(display_name)
|
||||||
ep_groups[group_key]["aliases"].update(
|
ep_groups[group_key]["aliases"].update(
|
||||||
|
|
@ -2663,14 +2688,16 @@ def list_authenticated_providers(
|
||||||
# unless the provider explicitly opts out via discover_models: false.
|
# unless the provider explicitly opts out via discover_models: false.
|
||||||
# Policy mirrors Section 4's should_probe logic:
|
# Policy mirrors Section 4's should_probe logic:
|
||||||
# - With an api_key: always probe (user opted into the endpoint).
|
# - With an api_key: always probe (user opted into the endpoint).
|
||||||
# - Without an api_key but with an explicit ``models:`` list:
|
# - Without an api_key but with an allowlist-shaped ``models:``
|
||||||
# skip — the user is narrowing a public endpoint to a specific
|
# (list/string): skip — the user narrowed a public endpoint.
|
||||||
# subset. A singular ``default_model``/``model`` does NOT count
|
# A singular ``default_model``/``model`` does NOT count as
|
||||||
# as narrowing (it's just the active selection) and must not
|
# narrowing (mirrors section 4 / #40542).
|
||||||
# suppress discovery — mirrors section 4 / #40542.
|
# - A dict-shaped ``models:`` is per-model metadata
|
||||||
# - Without an api_key AND no explicit models: probe anyway so
|
# (context_length), not an allowlist — still probe so local
|
||||||
# bare-endpoint providers (local llama.cpp / Ollama servers)
|
# Ollama/llama.cpp match ``hermes model``. Pin with
|
||||||
# still show their full model catalog.
|
# ``discover_models: false`` instead.
|
||||||
|
# - Without an api_key AND no allowlist: probe anyway so bare
|
||||||
|
# local endpoints still show their full model catalog.
|
||||||
api_key = str(ep_cfg.get("api_key", "") or "").strip()
|
api_key = str(ep_cfg.get("api_key", "") or "").strip()
|
||||||
if not api_key:
|
if not api_key:
|
||||||
key_env = str(ep_cfg.get("key_env", "") or "").strip()
|
key_env = str(ep_cfg.get("key_env", "") or "").strip()
|
||||||
|
|
@ -2911,8 +2938,12 @@ def list_authenticated_providers(
|
||||||
if default_model and default_model not in groups[group_key]["models"]:
|
if default_model and default_model not in groups[group_key]["models"]:
|
||||||
groups[group_key]["models"].append(default_model)
|
groups[group_key]["models"].append(default_model)
|
||||||
|
|
||||||
declared_models = _declared_model_ids(entry.get("models", {}))
|
models_field = entry.get("models", {})
|
||||||
if declared_models:
|
declared_models = _declared_model_ids(models_field)
|
||||||
|
# Dict-shaped models: is context_length metadata from
|
||||||
|
# ``_save_custom_provider``, not an allowlist — see
|
||||||
|
# ``_models_config_is_allowlist``.
|
||||||
|
if _models_config_is_allowlist(models_field):
|
||||||
groups[group_key]["has_explicit_models"] = True
|
groups[group_key]["has_explicit_models"] = True
|
||||||
for model_id in declared_models:
|
for model_id in declared_models:
|
||||||
if model_id not in groups[group_key]["models"]:
|
if model_id not in groups[group_key]["models"]:
|
||||||
|
|
@ -2974,24 +3005,20 @@ def list_authenticated_providers(
|
||||||
# Live-discovery policy:
|
# Live-discovery policy:
|
||||||
# - With an api_key, the user has explicitly opted into the
|
# - With an api_key, the user has explicitly opted into the
|
||||||
# endpoint and live /models is the source of truth — replace
|
# endpoint and live /models is the source of truth — replace
|
||||||
# the (possibly partial) ``models:`` subset configured for
|
# the (possibly partial) ``models:`` subset with the full
|
||||||
# context-length overrides with the full live catalog.
|
# live catalog (Bifrost / aggregator-gateway case).
|
||||||
# This is the Bifrost / aggregator-gateway case.
|
# - Without an api_key but with an allowlist-shaped ``models:``
|
||||||
# - Without an api_key but with an explicit ``models:`` list,
|
# (list/string), the user narrowed a public endpoint (e.g.
|
||||||
# the user is narrowing a public endpoint to a specific subset
|
# ollama.com). Preserve that list and skip live discovery.
|
||||||
# (e.g. ollama.com /v1/models returns 35 models but the user
|
# - A dict-shaped ``models:`` is per-model metadata written by
|
||||||
# only wants 4). Preserve the explicit list and skip live
|
# ``_save_custom_provider`` for context_length — not an
|
||||||
# discovery. The singular ``model:`` field is only the current
|
# allowlist. Still probe so Desktop/Telegram match
|
||||||
# active selection and must not suppress discovery on local
|
# ``hermes model``. Pin a dict catalog with
|
||||||
# no-key endpoints.
|
# ``discover_models: false``.
|
||||||
# - Without an api_key AND no explicit models, fall through to
|
# - The singular ``model:`` field is only the current active
|
||||||
# live discovery so bare-endpoint custom providers (local
|
# selection and must not suppress discovery.
|
||||||
# llama.cpp / Ollama servers) still appear populated.
|
|
||||||
# - When discover_models: false is set, skip live discovery and
|
# - When discover_models: false is set, skip live discovery and
|
||||||
# keep the explicit ``models:`` list regardless of whether an
|
# keep the configured ``models:`` list regardless of api_key.
|
||||||
# api_key is present. This supports endpoints that expose a
|
|
||||||
# full aggregator catalog via /models but only serve a subset
|
|
||||||
# (parity with section 3's user ``providers:`` behaviour).
|
|
||||||
_grp_is_current = (
|
_grp_is_current = (
|
||||||
slug.lower() == _current_provider_norm
|
slug.lower() == _current_provider_norm
|
||||||
or _current_provider_norm in {
|
or _current_provider_norm in {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue