fix(config): make model preset switching atomic

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Xubin Ren
2026-05-12 20:06:22 +08:00
committed by Xubin Ren
co-authored by Cursor
parent 6f78267c82
commit c450d6fd3f
8 changed files with 420 additions and 39 deletions
+41 -17
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from nanobot.config.schema import Config
from nanobot.config.schema import Config, ModelPresetConfig
from nanobot.providers.base import LLMProvider
from nanobot.providers.registry import find_by_name
@@ -18,12 +18,26 @@ class ProviderSnapshot:
signature: tuple[object, ...]
def make_provider(config: Config) -> LLMProvider:
def _resolve_model_preset(
config: Config,
*,
preset_name: str | None = None,
preset: ModelPresetConfig | None = None,
) -> ModelPresetConfig:
return preset if preset is not None else config.resolve_preset(preset_name)
def make_provider(
config: Config,
*,
preset_name: str | None = None,
preset: ModelPresetConfig | None = None,
) -> LLMProvider:
"""Create the LLM provider implied by config."""
resolved = config.resolve_preset()
resolved = _resolve_model_preset(config, preset_name=preset_name, preset=preset)
model = resolved.model
provider_name = config.get_provider_name(model)
p = config.get_provider(model)
provider_name = config.get_provider_name(model, preset=resolved)
p = config.get_provider(model, preset=resolved)
spec = find_by_name(provider_name) if provider_name else None
backend = spec.backend if spec else "openai_compat"
@@ -57,7 +71,7 @@ def make_provider(config: Config) -> LLMProvider:
provider = AnthropicProvider(
api_key=p.api_key if p else None,
api_base=config.get_api_base(model),
api_base=config.get_api_base(model, preset=resolved),
default_model=model,
extra_headers=p.extra_headers if p else None,
)
@@ -77,7 +91,7 @@ def make_provider(config: Config) -> LLMProvider:
provider = OpenAICompatProvider(
api_key=p.api_key if p else None,
api_base=config.get_api_base(model),
api_base=config.get_api_base(model, preset=resolved),
default_model=model,
extra_headers=p.extra_headers if p else None,
spec=spec,
@@ -88,16 +102,21 @@ def make_provider(config: Config) -> LLMProvider:
return provider
def provider_signature(config: Config) -> tuple[object, ...]:
def provider_signature(
config: Config,
*,
preset_name: str | None = None,
preset: ModelPresetConfig | None = None,
) -> tuple[object, ...]:
"""Return the config fields that affect the primary LLM provider."""
resolved = config.resolve_preset()
p = config.get_provider(resolved.model)
resolved = _resolve_model_preset(config, preset_name=preset_name, preset=preset)
p = config.get_provider(resolved.model, preset=resolved)
return (
resolved.model,
resolved.provider,
config.get_provider_name(resolved.model),
config.get_api_key(resolved.model),
config.get_api_base(resolved.model),
config.get_provider_name(resolved.model, preset=resolved),
config.get_api_key(resolved.model, preset=resolved),
config.get_api_base(resolved.model, preset=resolved),
p.extra_headers if p else None,
p.extra_body if p else None,
getattr(p, "region", None) if p else None,
@@ -109,13 +128,18 @@ def provider_signature(config: Config) -> tuple[object, ...]:
)
def build_provider_snapshot(config: Config) -> ProviderSnapshot:
resolved = config.resolve_preset()
def build_provider_snapshot(
config: Config,
*,
preset_name: str | None = None,
preset: ModelPresetConfig | None = None,
) -> ProviderSnapshot:
resolved = _resolve_model_preset(config, preset_name=preset_name, preset=preset)
return ProviderSnapshot(
provider=make_provider(config),
provider=make_provider(config, preset=resolved),
model=resolved.model,
context_window_tokens=resolved.context_window_tokens,
signature=provider_signature(config),
signature=provider_signature(config, preset=resolved),
)