Skip to content

Commit bb6e3d3

Browse files
AAClauseclementb49claude
authored
feat(provider): switch to dynamic model metadata loading (#1149)
* feat(provider): switch to dynamic model metadata loading - Replace hardcoded provider model catalogs with dynamic loading from model-metadata JSON (with caching and provider-specific postprocessing) - Remove Gemini custom ordering to respect metadata order. - Add text-output filtering so unsupported generation-only models are not shown in the UI - Clean up dead reasoning code paths - Add regression tests for loader parsing/caching and Anthropic thinking rendering/parity. * fix: Address review comments * refactor(loader): tighten model-metadata parsing with typed pydantic schemas Replace ad-hoc dict handling with typed nested Pydantic models and centralized normalization/validation for metadata rows, while preserving tolerant parsing behavior and existing loader outputs. * feat(models): load account models in background and surface load errors * refactor: improve pydantic usage to parse JSON provider info * refactor: finalize pydantic refactor * style: correct ruff issue * refactor(models): move async model loading from view to presenter Relocate threading, error handling, cache-invalidation logic, and the deferred-model-selection state machine from BaseConversation (view) to BaseConversationPresenter, restoring MVP separation. The view now only renders received models and delegates all orchestration to the presenter. Also fix a typo (clas → class) in dynamic_model_loader.py and update ConversationTab._restore_draft_block to use set_pending_model instead of calling engine.get_model() directly, which would fail before async loading completes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(models): address code review issues in dynamic model loading - Fix type mismatch: store str(e) in _LAST_LOAD_ERROR instead of Exception - Add threading.Lock to protect _CACHE and _LAST_LOAD_ERROR from races - Remove import wx from presenter; view wraps callback with wx.CallAfter - Show error modal only when model list is empty (not on stale cache hit) - Remove bare except Exception around SetStatusText - Annotate _pending_model_account_id and account_id params as UUID Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: clear cache in test * feat(models): add model list refresh action and F5 shortcut Adds a “Refresh model list” item to the model context menu and binds F5 in the model list to reload models by invalidating cached engine models first. * fix(openrouter): sort models by created timestamp like other providers Parse OpenRouter created into ProviderAIModel.created and sort descending by created date instead of model name, with tests for ordering and invalid timestamp fallback. * refactor(models): move TTL cache to BaseEngine for all providers Centralizes model-list caching/invalidation and error fallback in BaseEngine so OpenRouter/Ollama also honor TTL; simplifies dynamic_model_loader to fetch/parse only and updates cache behavior tests. * refactor(cache): centralize engine model cache with persistent registry cleanup - Move model-list caching to BaseEngine with per-account disk persistence, TTL expiry, and stale fallback on refresh failure. - Add registry-based cache tracking/pruning plus account-removal cache cleanup, with tests for restart reuse, expiry reload, version mismatch, write failures, and registry maintenance. * refactor(provider): unify OpenRouter model conversion with dynamic loader - extract shared model-row conversion and modality fallback logic into dynamic_model_loader - route OpenRouter /models parsing through shared converter and support pricing metadata - expand unit tests for modality precedence/fallback and context-length edge cases * fix(provider-engine): address review feedback on model cache safety Catch disk-cache read OSErrors for graceful fallback, avoid deleting cache files still referenced by other accounts, and add a 30s timeout to OpenRouter model discovery calls. * fix: address review feedback on model/cache loading reliability Reorder account removal cleanup to be best-effort, harden model-loading concurrency and cache invalidation atomicity, and propagate provider metadata fetch failures with explicit fallbacks where needed (OpenRouter/DeepSeek/Anthropic). Also align related tests with the new behavior. * fix(models): address review comments on refresh, timestamps, and model UI Serialize BaseEngine model-list refresh, handle invalid created timestamps in dynamic metadata, guard model-list callback on destroy with translator context, and tighten parse_model_metadata tests to surface validation errors. * refactor(models): simplify model details text and trim loader/test noise * fix(accounts,ui): invalidate default on remove, provider-id draft lookup, defer pending model pop * feat(ui): model list cache TTL in preferences and model-loader join on shutdown * feat(models): filter completion params and UI by catalog sampling metadata * refactor(models): centralize catalog sampling policy and engine strip hook - Add basilisk/model_catalog_sampling.py for supported_parameters / unsupported_parameters rules, main-tab visibility map, and OpenAI-style param stripping. - BaseEngine._strip_catalog_sampling_params(); chat engines call it instead of importing strip helpers directly. - UI: replace _sampling_visibility_ignore_advanced with gate_on_advanced_mode on refresh_sampling_controls_visibility; align widget rows with MAIN_UI_SAMPLING_PARAM_KEYS + zip(..., strict=True). - Tests: tests/test_model_catalog_sampling.py (replaces old completion-params test file). * refactor(models): centralize SigmaNight metadata URLs and catalog tagging - Add model_metadata_catalog constants for data/*.json URLs and source labels - Point OpenAI/Anthropic/Gemini/Mistral/xAI/DeepSeek MODELS_JSON_URL at catalog - Tag ProviderAIModel.extra_info[metadata_catalog] (SigmaNight vs OpenRouter) - Show metadata catalog in model details; document in model_catalog_sampling - Extend loader/OpenRouter/reasoning tests; fix get_provider_models monkeypatch * refactor(sampling): decouple catalog stripping from default chat kwargs * refactor(provider_engine): extract model list disk cache from BaseEngine Move JSON payload I/O, path hashing, directory prune, and registry-backed delete/register helpers into engine_model_list_cache. BaseEngine keeps RAM cache, refresh coordination, and TTL-driven orchestration. * refactor(models): extract provider_ai_model_display and thin base conversation view * refactor: re-consolidate model catalog modules and engine metadata URLs * refactor(deepseek): drop bundled model fallback; load catalog JSON only * chore(provider-engine): document model-list cache * test(provider_engine): avoid GeminiEngine import for Win ARM64 CI --------- Co-authored-by: clemetb49 <clement.boussiron@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 97a3a84 commit bb6e3d3

37 files changed

Lines changed: 4140 additions & 987 deletions

basilisk/config/account_config.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
import basilisk.global_vars as global_vars
3030
from basilisk.consts import APP_NAME
3131
from basilisk.provider import Provider, get_provider, providers
32+
from basilisk.provider_engine.model_cache_registry import (
33+
remove_account_model_cache,
34+
)
3235

3336
from .config_enums import AccountSource, KeyStorageMethodEnum
3437
from .config_helper import (
@@ -568,6 +571,21 @@ def get_accounts_by_provider(
568571
"""
569572
return filter(lambda x: x.provider.name == provider_name, self.accounts)
570573

574+
def get_accounts_by_provider_id(
575+
self, provider_id: Optional[str] = None
576+
) -> Iterable[Account]:
577+
"""Get accounts whose provider id matches (e.g. ``openai``, ``anthropic``).
578+
579+
Args:
580+
provider_id: ``Provider.id`` string to filter accounts.
581+
582+
Returns:
583+
Iterable of accounts for that provider id.
584+
"""
585+
if provider_id is None:
586+
return iter(())
587+
return filter(lambda x: x.provider.id == provider_id, self.accounts)
588+
571589
def remove(self, account: Account):
572590
"""Remove an account from the configuration.
573591
@@ -577,8 +595,33 @@ def remove(self, account: Account):
577595
Raises:
578596
ValueError: If the account is not found in the configuration.
579597
"""
580-
account.delete_keyring_password()
581598
self.accounts.remove(account)
599+
account_id = str(account.id)
600+
try:
601+
account.delete_keyring_password()
602+
except Exception as exc:
603+
log.warning(
604+
"Failed to delete keyring password for account %s: %s",
605+
account_id,
606+
exc,
607+
)
608+
try:
609+
remove_account_model_cache(account_id)
610+
except Exception as exc:
611+
log.warning(
612+
"Failed to remove model cache for account %s: %s",
613+
account_id,
614+
exc,
615+
)
616+
info = self.default_account_info
617+
if isinstance(info, UUID) and info == account.id:
618+
self.default_account_info = None
619+
self.__dict__.pop("default_account", None)
620+
elif self.__dict__.get("default_account") is not None:
621+
cached = self.__dict__["default_account"]
622+
if getattr(cached, "id", None) == account.id:
623+
self.default_account_info = None
624+
self.__dict__.pop("default_account", None)
582625

583626
def clear(self):
584627
"""Clear all accounts from the configuration."""

basilisk/config/main_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,20 @@
2121

2222
config_file_name = "config.yml"
2323

24+
MODEL_METADATA_CACHE_TTL_MIN_SECONDS = 60
25+
MODEL_METADATA_CACHE_TTL_MAX_SECONDS = 86400
26+
2427

2528
class GeneralSettings(BaseModel):
2629
"""General settings for BasiliskLLM."""
2730

2831
language: str = Field(default="auto")
32+
model_metadata_cache_ttl_seconds: int = Field(
33+
default=3600,
34+
ge=MODEL_METADATA_CACHE_TTL_MIN_SECONDS,
35+
le=MODEL_METADATA_CACHE_TTL_MAX_SECONDS,
36+
description="TTL for provider model-list refresh cache",
37+
)
2938
advanced_mode: bool = Field(default=False)
3039
log_level: LogLevelEnum = Field(default=LogLevelEnum.INFO)
3140
automatic_update_mode: AutomaticUpdateModeEnum = Field(

basilisk/model_catalog/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Catalog-driven sampling policy and model detail text for catalog-fed models."""

0 commit comments

Comments
 (0)