Provider resolution and fallback
Corresponding source
| Subject | Source |
|---|---|
| Configuration models and presets | nanobot/config/schema.py::ProviderConfig, ModelPresetConfig |
| Provider registry | nanobot/providers/registry.py::ProviderSpec, find_by_name |
| Provider construction and snapshots | nanobot/providers/factory.py::make_provider, load_provider_snapshot |
| Common protocol and retry | nanobot/providers/base.py::LLMProvider, LLMResponse |
| Fallback behavior | nanobot/providers/fallback_provider.py::FallbackProvider |
| WebUI settings synchronization | nanobot/webui/settings_api.py |
Learning objectives
After this lesson, you should be able to:
- distinguish Provider configuration, ProviderSpec, Provider instances, and model presets;
- explain explicit Provider selection and automatic detection;
- describe how a fallback chain is constructed;
- trace DeepSeek into the OpenAI-compatible adapter.
Prerequisites
- Complete the first DeepSeek request.
- Understand
LLMProviderandLLMResponse. - Read a Pydantic schema.
1. Four conceptual layers
| Layer | Example | Location |
|---|---|---|
| Configuration values | API key, apiBase | Config.providers |
| Model selection | provider + model + generation settings | ModelPresetConfig |
| Provider metadata | backend, default base, keywords, environment key | ProviderSpec |
| Runtime instance | OpenAICompatProvider and others | providers/factory.py |
Do not interpret "provider": "deepseek" as a Python class imported directly. Factory first looks up ProviderSpec, then selects a backend.
2. Named model preset
A preset contains:
- label;
- model;
- provider;
- maximum tokens;
- context window;
- temperature;
- reasoning effort.
agents.defaults.modelPreset selects the startup preset. Legacy direct fields under agents.defaults.provider/model still form an implicit default preset.
A named preset is preferred because it also supports:
/modelswitching;- WebUI model selection;
- fallback presets;
- consistent generation settings.
3. ProviderSpec
Location:
nanobot/providers/registry.py::ProviderSpec
Important fields:
namekeywordsenv_keydisplay_namebackenddefault_api_baseis_gatewayis_oauthis_localstrip_model_prefixmodel_overrides- thinking/reasoning adaptation fields
- capability flags such as transcription-only
Registry ordering can affect automatic matching and is therefore part of the implementation contract.
4. Provider resolution
Simplified path:
flowchart TD CFG["Config"] --> PRE["resolve_preset"] PRE --> EX{"Provider explicit and not auto?"} EX -->|Yes| SPEC["Find ProviderSpec by name"] EX -->|No| MATCH["Match by model / key / apiBase / gateway / local"] MATCH --> SPEC SPEC --> BACK["Create concrete Provider from backend"] BACK --> P["LLMProvider instance"]
For a first configuration, specify the Provider explicitly so you do not debug all of these at once:
- model keywords;
- multiple configured keys;
- Gateway;
- local endpoint;
- custom Provider.
5. Current DeepSeek path
ProviderSpec:
name = deepseek
keywords = ("deepseek",)
env_key = DEEPSEEK_API_KEY
backend = openai_compat
default_api_base = https://api.deepseek.com
thinking_style = thinking_typeRuntime path:
ModelPreset(provider=deepseek)
→ Config/Factory
→ ProviderSpec(deepseek)
→ OpenAICompatProvider
→ DeepSeek OpenAI-compatible Chat CompletionsWhen reasoningEffort is explicit, the adapter uses ProviderSpec to map it to DeepSeek’s wire field thinking.type. If omitted, Provider defaults remain in effect.
6. Custom Provider
ProvidersConfig accepts extra Provider configuration. A custom name with apiBase can use the generic OpenAI-compatible path.
However:
- only an OpenAI-compatible proxy fits the generic custom path;
- an Anthropic-compatible proxy should use its dedicated configuration;
- an
apiBasealone does not prove complete protocol compatibility; - tool calls, reasoning, usage, and streaming still require validation.
7. Fallback
agents.defaults.fallbackModels can reference:
- a named preset;
- inline model/Provider/settings.
Factory:
- creates the primary;
- resolves each fallback preset;
- creates a Provider for each fallback;
- wraps them in a fallback chain;
- retains each candidate’s generation and context parameters.
Fallback is not:
- broadcasting every request to every model;
- switching models for each ordinary Tool error;
- creating a new Session;
- a guarantee that all candidates have identical protocol semantics.
8. Runtime model refresh
AgentLoop retains a Provider snapshot loader and signature and can refresh after configuration or runtime-model changes:
- Provider;
- model;
- context window;
- preset;
- Subagent Provider.
A Turn in progress keeps the model snapshot from its start. Later Turns use the switched model.
9. Debugging path
| Symptom | Inspect |
|---|---|
| Provider name unrecognized | ProvidersConfig, Provider registry |
| automatic selection chooses wrong Provider | explicit preset, Config._match_provider |
| incorrect apiBase | Provider configuration, ProviderSpec default |
| reasoning argument returns 400 | ProviderSpec thinking style, adapter keyword arguments |
| fallback does not occur | fallback configuration, whether the error is retryable/fallback-eligible |
UI does not change after /model | runtime-model event, WebUI coordinator |
| parent Agent changes but Subagent does not | SubagentManager.set_provider call chain |
Source and tests
nanobot/config/schema.pynanobot/providers/registry.pynanobot/providers/factory.pynanobot/providers/base.pynanobot/providers/fallback_provider.pynanobot/providers/openai_compat_provider.pytests/providers/tests/agent/test_runtime_refresh.pytests/agent/test_runner_fallback.pytests/command/test_model_command.py
Self-check
- How do ProviderSpec and ProviderConfig differ?
- Why should a first configuration select the Provider explicitly?
- Why does DeepSeek not need a dedicated
deepseek_provider.py? - Why must fallback retain each candidate model’s own context parameters?
- Why must a runtime model switch not alter an already-started Turn?
Next step
Continue to Engineering and Security for concurrency, reliability, testing, and permission boundaries.