mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(desktop): avoid probing custom providers on model picker open
This commit is contained in:
parent
bd480b4c8c
commit
fe5054bccf
7 changed files with 72 additions and 2 deletions
|
|
@ -118,6 +118,7 @@ def build_models_payload(
|
|||
capabilities: bool = False,
|
||||
force_fresh_nous_tier: bool = False,
|
||||
refresh: bool = False,
|
||||
probe_custom_providers: bool = True,
|
||||
max_models: int | None = None,
|
||||
) -> dict:
|
||||
"""Build the ``{providers, model, provider}`` shape every consumer
|
||||
|
|
@ -149,6 +150,11 @@ def build_models_payload(
|
|||
re-fetches its live catalog. Set only for an explicit user-triggered
|
||||
"refresh models" action; normal picker opens leave it false to stay
|
||||
snappy on the 1h cache.
|
||||
- ``probe_custom_providers``: allow saved custom/provider endpoints to
|
||||
run live ``/models`` discovery while building the payload. GUI picker
|
||||
opens should leave this false unless the user explicitly refreshes; the
|
||||
row can still render its configured model immediately, and slow/offline
|
||||
local endpoints no longer block the dialog.
|
||||
"""
|
||||
from hermes_cli.model_switch import list_authenticated_providers
|
||||
|
||||
|
|
@ -161,6 +167,7 @@ def build_models_payload(
|
|||
force_fresh_nous_tier=force_fresh_nous_tier,
|
||||
max_models=max_models,
|
||||
refresh=refresh,
|
||||
probe_custom_providers=probe_custom_providers,
|
||||
)
|
||||
|
||||
moa_row = _moa_provider_row(ctx.current_provider)
|
||||
|
|
|
|||
|
|
@ -1426,6 +1426,7 @@ def list_authenticated_providers(
|
|||
max_models: int | None = None,
|
||||
current_model: str = "",
|
||||
refresh: bool = False,
|
||||
probe_custom_providers: bool = True,
|
||||
) -> List[dict]:
|
||||
"""Detect which providers have credentials and list their curated models.
|
||||
|
||||
|
|
@ -1452,6 +1453,11 @@ def list_authenticated_providers(
|
|||
live catalog. Use for an explicit user-triggered "refresh models" action
|
||||
(e.g. the desktop picker's refresh control); leave false for normal picker
|
||||
opens so they stay snappy on the 1h cache.
|
||||
|
||||
``probe_custom_providers`` controls live ``/models`` discovery for saved
|
||||
custom OpenAI-compatible endpoints. Keep the default true for CLI parity;
|
||||
GUI picker opens can pass false to show configured models immediately
|
||||
without waiting on offline local endpoints.
|
||||
"""
|
||||
import os
|
||||
from agent.models_dev import (
|
||||
|
|
@ -1994,7 +2000,7 @@ def list_authenticated_providers(
|
|||
if isinstance(discover, str):
|
||||
discover = discover.lower() not in {"false", "no", "0"}
|
||||
has_explicit_models = bool(models_list)
|
||||
should_probe = bool(api_url) and discover and (
|
||||
should_probe = probe_custom_providers and bool(api_url) and discover and (
|
||||
bool(api_key) or not has_explicit_models
|
||||
)
|
||||
if should_probe:
|
||||
|
|
@ -2255,7 +2261,8 @@ def list_authenticated_providers(
|
|||
# full aggregator catalog via /models but only serve a subset
|
||||
# (parity with section 3's user ``providers:`` behaviour).
|
||||
should_probe = (
|
||||
bool(api_url)
|
||||
probe_custom_providers
|
||||
and bool(api_url)
|
||||
and (bool(api_key) or not grp["models"])
|
||||
and grp.get("discover_models", True)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4296,6 +4296,7 @@ def get_model_options(profile: Optional[str] = None, refresh: bool = False):
|
|||
pricing=True,
|
||||
capabilities=True,
|
||||
refresh=bool(refresh),
|
||||
probe_custom_providers=bool(refresh),
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -219,6 +219,19 @@ def test_build_models_payload_can_force_fresh_nous_tier():
|
|||
assert mock_list.call_args.kwargs["force_fresh_nous_tier"] is True
|
||||
|
||||
|
||||
def test_build_models_payload_can_skip_custom_provider_probes():
|
||||
ctx = _empty_ctx()
|
||||
rows = []
|
||||
with patch(
|
||||
"hermes_cli.model_switch.list_authenticated_providers",
|
||||
return_value=rows,
|
||||
) as mock_list:
|
||||
build_models_payload(ctx, probe_custom_providers=False)
|
||||
|
||||
mock_list.assert_called_once()
|
||||
assert mock_list.call_args.kwargs["probe_custom_providers"] is False
|
||||
|
||||
|
||||
def test_list_authenticated_providers_force_fresh_is_keyword_only():
|
||||
"""``force_fresh_nous_tier`` must be keyword-only on the public listing API.
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,30 @@ def test_list_authenticated_providers_includes_custom_providers(monkeypatch):
|
|||
)
|
||||
|
||||
|
||||
def test_list_authenticated_providers_can_skip_custom_provider_live_probe(monkeypatch):
|
||||
monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {})
|
||||
monkeypatch.setattr(providers_mod, "HERMES_OVERLAYS", {})
|
||||
fetch = lambda *a, **k: (_ for _ in ()).throw(AssertionError("unexpected probe"))
|
||||
monkeypatch.setattr("hermes_cli.models.fetch_api_models", fetch)
|
||||
|
||||
providers = list_authenticated_providers(
|
||||
user_providers={},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "Slow Local",
|
||||
"base_url": "http://127.0.0.1:8080/v1",
|
||||
"api_key": "sk-local",
|
||||
"model": "local-model",
|
||||
}
|
||||
],
|
||||
probe_custom_providers=False,
|
||||
)
|
||||
|
||||
row = next(p for p in providers if p["slug"] == "custom:slow-local")
|
||||
assert row["models"] == ["local-model"]
|
||||
assert row["total_models"] == 1
|
||||
|
||||
|
||||
def test_resolve_provider_full_finds_named_custom_provider():
|
||||
"""Explicit /model --provider should resolve saved custom_providers entries."""
|
||||
resolved = resolve_provider_full(
|
||||
|
|
|
|||
|
|
@ -5845,6 +5845,7 @@ def test_model_options_does_not_overwrite_curated_models(monkeypatch):
|
|||
live_fetch.assert_not_called()
|
||||
# list_authenticated_providers is the single source.
|
||||
assert listing.call_count == 1
|
||||
assert listing.call_args.kwargs["probe_custom_providers"] is False
|
||||
|
||||
|
||||
def test_model_options_propagates_list_exception(monkeypatch):
|
||||
|
|
@ -5870,6 +5871,22 @@ def test_model_options_propagates_list_exception(monkeypatch):
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_model_options_refresh_allows_custom_provider_probes(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
server,
|
||||
"_load_cfg",
|
||||
lambda: {"providers": {}, "custom_providers": []},
|
||||
)
|
||||
with patch(
|
||||
"hermes_cli.model_switch.list_authenticated_providers",
|
||||
return_value=[],
|
||||
) as listing:
|
||||
resp = server._methods["model.options"](78, {"session_id": "", "refresh": True})
|
||||
|
||||
assert "result" in resp, resp
|
||||
assert listing.call_args.kwargs["probe_custom_providers"] is True
|
||||
|
||||
|
||||
class _ImmediateThread:
|
||||
"""Runs the target callable synchronously so assertions can follow."""
|
||||
|
||||
|
|
|
|||
|
|
@ -12325,6 +12325,7 @@ def _(rid, params: dict) -> dict:
|
|||
pricing=True,
|
||||
capabilities=True,
|
||||
refresh=bool(params.get("refresh")),
|
||||
probe_custom_providers=bool(params.get("refresh")),
|
||||
)
|
||||
return _ok(rid, payload)
|
||||
except Exception as e:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue