feat(config): add actionable startup diagnostics and WebUI recovery (#5110)

This commit is contained in:
chengyongru
2026-07-28 18:52:05 +08:00
committed by GitHub
parent 76ab04ac48
commit 0c6c0438d4
19 changed files with 1425 additions and 64 deletions
+66 -7
View File
@@ -21,6 +21,15 @@ class ProviderSnapshot:
model_preset: str | None = None
@dataclass(frozen=True)
class _ProviderSetup:
model: str
provider_name: str
provider_config: ProviderConfig | None
spec: ProviderSpec | None
backend: str
def _resolve_model_preset(
config: Config,
*,
@@ -40,18 +49,20 @@ def _provider_extra_headers(
return headers or None
def _make_provider_core(
def _resolve_provider_setup(
config: Config,
*,
preset: ModelPresetConfig,
model: str | None = None,
) -> LLMProvider:
"""Create a plain LLM provider without failover wrapping."""
) -> _ProviderSetup:
"""Resolve and validate provider configuration without constructing a client."""
model = model or preset.model
provider_name = config.get_provider_name(model, preset=preset)
p = config.get_provider(model, preset=preset)
spec = find_by_name(provider_name) if provider_name else None
if provider_name and not spec and p:
if not provider_name:
raise ValueError(f"No provider is configured for model '{model}'.")
spec = find_by_name(provider_name)
if not spec and p:
if not p.api_base:
raise ValueError(f"Provider '{provider_name}' requires api_base in config.")
spec = create_dynamic_spec(
@@ -79,12 +90,57 @@ def _make_provider_core(
and not (p and p.api_base)
):
raise ValueError(f"Provider '{provider_name}' requires api_base in config.")
elif backend == "openai_compat" and not model.startswith("bedrock/"):
elif backend in {"anthropic", "openai_compat"} and not (
backend == "openai_compat" and model.startswith("bedrock/")
):
needs_key = not (p and p.api_key)
exempt = spec and (spec.is_oauth or spec.is_local or spec.is_direct)
if needs_key and not exempt:
raise ValueError(f"No API key configured for provider '{provider_name}'.")
return _ProviderSetup(
model=model,
provider_name=provider_name,
provider_config=p,
spec=spec,
backend=backend,
)
def validate_provider_setup(
config: Config,
*,
preset_name: str | None = None,
preset: ModelPresetConfig | None = None,
model: str | None = None,
) -> None:
"""Validate local provider/model settings without loading a provider client."""
resolved = _resolve_model_preset(config, preset_name=preset_name, preset=preset)
_resolve_provider_setup(
config,
preset=resolved,
model=model,
)
def _make_provider_core(
config: Config,
*,
preset: ModelPresetConfig,
model: str | None = None,
) -> LLMProvider:
"""Create a plain LLM provider without failover wrapping."""
setup = _resolve_provider_setup(
config,
preset=preset,
model=model,
)
model = setup.model
provider_name = setup.provider_name
p = setup.provider_config
spec = setup.spec
backend = setup.backend
if backend == "openai_codex":
from nanobot.providers.openai_codex_provider import OpenAICodexProvider
@@ -315,6 +371,9 @@ def load_provider_snapshot(
from nanobot.config.loader import load_config, resolve_config_env_vars
return build_provider_snapshot(
resolve_config_env_vars(load_config(config_path)),
resolve_config_env_vars(
load_config(config_path),
config_path=config_path,
),
preset_name=preset_name,
)