mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 15:37:55 +08:00
core/config+cli: empty models are bad; prompt model on provider switch
Reject providers with no models into bad_providers and warn with reasons on chat/providers. When /provider changes and the current model is not listed, prompt for a new model (keep it when still supported).
This commit is contained in:
+28
-4
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
import click
|
||||
import msgspec
|
||||
@@ -29,6 +29,8 @@ from plyngent.runtime import ProviderNotSupportedError, create_client
|
||||
from plyngent.tools import set_workspace_root
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
|
||||
from plyngent.config.store import ConfigStore
|
||||
|
||||
_DEFAULT_DB_FILENAME = "chat.db"
|
||||
@@ -43,6 +45,29 @@ def _load_config(config_path: Path | None) -> ConfigStore:
|
||||
raise click.ClickException(msg) from exc
|
||||
|
||||
|
||||
def _warn_bad_providers(bad: Mapping[str, object]) -> None:
|
||||
"""Surface ignored provider entries (parse errors, empty models, …)."""
|
||||
if not bad:
|
||||
return
|
||||
names = ", ".join(sorted(bad.keys()))
|
||||
click.secho(
|
||||
f"warning: ignored bad providers ({len(bad)}): {names}",
|
||||
fg="yellow",
|
||||
err=True,
|
||||
)
|
||||
for name in sorted(bad.keys()):
|
||||
entry = bad[name]
|
||||
reason = "invalid or incomplete entry"
|
||||
if isinstance(entry, dict):
|
||||
entry_map = cast("dict[str, object]", entry)
|
||||
raw_reason = entry_map.get("_reason")
|
||||
if isinstance(raw_reason, str) and raw_reason:
|
||||
reason = raw_reason
|
||||
elif "preset" not in entry_map and not any(k in entry_map for k in ("access_key_or_token", "url")):
|
||||
reason = "not a provider table"
|
||||
click.secho(f" - {name}: {reason}", fg="yellow", err=True)
|
||||
|
||||
|
||||
def _database_config(store: ConfigStore, *, quiet: bool = False) -> DatabaseConfig:
|
||||
raw = dict(store.database)
|
||||
# Prefer a durable file DB so sessions survive CLI restarts.
|
||||
@@ -163,8 +188,7 @@ async def _run_chat( # noqa: C901 — chat orchestration
|
||||
|
||||
store = _load_config(config_path)
|
||||
if store.bad_providers and not quiet:
|
||||
names = ", ".join(sorted(store.bad_providers.keys()))
|
||||
click.secho(f"warning: ignored bad providers: {names}", fg="yellow", err=True)
|
||||
_warn_bad_providers(store.bad_providers)
|
||||
|
||||
_setup_workspace_and_hooks(store, workspace, interactive=interactive)
|
||||
confirm_destructive: bool | None = False if yes else None
|
||||
@@ -397,7 +421,7 @@ def providers_cmd(config_path: Path | None) -> None:
|
||||
models = ", ".join(sorted(provider.models.keys())) or "(none listed)"
|
||||
click.echo(f"{name}\tpreset={tag}\tmodels={models}")
|
||||
if store.bad_providers:
|
||||
click.secho(f"bad: {', '.join(sorted(store.bad_providers.keys()))}", fg="yellow")
|
||||
_warn_bad_providers(store.bad_providers)
|
||||
|
||||
|
||||
@main.group("config")
|
||||
|
||||
@@ -526,11 +526,23 @@ def provider_cmd(state: ReplState, name: str | None) -> None:
|
||||
return
|
||||
try:
|
||||
pname, provider = select_provider(state.config.providers, preferred=name.strip())
|
||||
prev_model = state.model
|
||||
state.provider_name = pname
|
||||
state.provider = provider
|
||||
# Keep model if still listed; else first model on the new provider.
|
||||
if state.model not in provider.models and provider.models:
|
||||
state.model = next(iter(sorted(provider.models.keys())))
|
||||
if prev_model in provider.models:
|
||||
state.model = prev_model
|
||||
else:
|
||||
# Current model not on the new provider — pick one (prompt when interactive).
|
||||
try:
|
||||
state.model = select_model(provider, preferred=None, interactive=True)
|
||||
except click.ClickException as exc:
|
||||
click.echo(f"error: switched provider but model selection failed: {exc}")
|
||||
return
|
||||
if prev_model:
|
||||
click.secho(
|
||||
f"model {prev_model!r} is not available on {pname}; using {state.model!r}",
|
||||
fg="yellow",
|
||||
)
|
||||
state.rebuild_client()
|
||||
_await(state.persist_llm_selection())
|
||||
click.echo(f"switched provider to {pname} model={state.model}")
|
||||
|
||||
@@ -70,6 +70,12 @@ def _parse_providers(
|
||||
bad_providers[name] = raw_entry
|
||||
continue
|
||||
|
||||
# Usable providers must list at least one model (DeepSeek seeds defaults
|
||||
# when models is omitted; an explicit empty models={} is invalid).
|
||||
if not provider.models:
|
||||
bad_providers[name] = {**cast("dict[str, object]", raw_entry), "_reason": "no models"}
|
||||
continue
|
||||
|
||||
providers[name] = provider
|
||||
|
||||
return providers, bad_providers
|
||||
|
||||
Reference in New Issue
Block a user