Provider 解析与 Fallback
对应源码
| 主题 | 源码 |
|---|---|
| 配置模型和 preset | nanobot/config/schema.py::ProviderConfig、ModelPresetConfig |
| Provider 注册表 | nanobot/providers/registry.py::ProviderSpec、find_by_name |
| Provider 构造和快照 | nanobot/providers/factory.py::make_provider、load_provider_snapshot |
| 通用协议与 retry | nanobot/providers/base.py::LLMProvider、LLMResponse |
| Fallback 行为 | nanobot/providers/fallback_provider.py::FallbackProvider |
| WebUI 设置同步 | nanobot/webui/settings_api.py |
学习目标
读完后,你应当能够:
- 区分 Provider 配置、ProviderSpec、Provider 实例和模型 preset;
- 解释显式 Provider 与 auto detection;
- 说明 fallback chain 如何构建;
- 找到 DeepSeek 进入 OpenAI-compatible adapter 的路径。
前置知识
- 完成 DeepSeek 第一次请求;
- 理解
LLMProvider、LLMResponse; - 能阅读 Pydantic schema。
1. 四层概念
| 层级 | 例子 | 位置 |
|---|---|---|
| 配置值 | API key、apiBase | Config.providers |
| 模型选择 | provider + model + generation settings | ModelPresetConfig |
| Provider 元数据 | backend、默认 base、关键字、env key | ProviderSpec |
| 运行实例 | OpenAICompatProvider 等 | providers/factory.py |
不要把 "provider": "deepseek" 理解成一个直接导入的 Python 类名。Factory 会先查 ProviderSpec,再选择 backend。
2. Named model preset
一个 preset 包含:
- label;
- model;
- provider;
- max tokens;
- context window;
- temperature;
- reasoning effort。
agents.defaults.modelPreset 选择启动 preset。旧的直接 agents.defaults.provider/model 仍组成隐式 default preset。
推荐 named preset,因为它还用于:
/model切换;- WebUI model selector;
- fallback preset;
- 统一 generation settings。
3. ProviderSpec
位置:
nanobot/providers/registry.py::ProviderSpec
重要字段:
namekeywordsenv_keydisplay_namebackenddefault_api_baseis_gatewayis_oauthis_localstrip_model_prefixmodel_overrides- thinking/reasoning 适配字段
- transcription-only 等能力标志。
Registry 顺序也会影响 auto matching,应当视为实现契约。
4. Provider 解析
简化路径:
flowchart TD CFG["Config"] --> PRE["resolve_preset"] PRE --> EX{"provider 显式且非 auto?"} EX -->|是| SPEC["按名称找 ProviderSpec"] EX -->|否| MATCH["按 model / key / apiBase / gateway / local 匹配"] MATCH --> SPEC SPEC --> BACK["根据 backend 创建具体 Provider"] BACK --> P["LLMProvider 实例"]
第一次配置应显式 Provider,避免同时排查:
- model keyword;
- 多个已配置 key;
- gateway;
- local endpoint;
- custom provider。
5. DeepSeek 当前路径
ProviderSpec:
name = deepseek
keywords = ("deepseek",)
env_key = DEEPSEEK_API_KEY
backend = openai_compat
default_api_base = https://api.deepseek.com
thinking_style = thinking_type运行路径:
ModelPreset(provider=deepseek)
→ Config/Factory
→ ProviderSpec(deepseek)
→ OpenAICompatProvider
→ DeepSeek OpenAI-compatible Chat Completions当 reasoningEffort 显式设置时,adapter 根据 ProviderSpec 将其映射为 DeepSeek 的 thinking.type wire 字段。省略时保留 Provider 默认行为。
6. 自定义 Provider
ProvidersConfig 允许 extra provider 配置。带 apiBase 的自定义名称可以使用通用 OpenAI-compatible 路径。
但:
- OpenAI-compatible proxy 才适合 generic custom;
- Anthropic-compatible proxy 应走对应专用配置;
- 仅有 apiBase 不代表协议完全兼容;
- tool call、reasoning、usage、stream 细节仍需验证。
7. Fallback
agents.defaults.fallbackModels 可以引用:
- named preset;
- inline model/provider/settings。
Factory:
- 创建 primary;
- 解析 fallback preset;
- 为每个 fallback 创建 Provider;
- 包装成 fallback chain;
- 保留每个候选的 generation 和 context 参数。
Fallback 不是:
- 同时向所有模型广播;
- 对每次普通 Tool error 换模型;
- 新建 Session;
- 自动保证协议语义完全相同。
8. Runtime model refresh
AgentLoop 保存 Provider snapshot loader 和 signature,可以在配置或 runtime model 变化后刷新:
- Provider;
- model;
- context window;
- preset;
- Subagent Provider。
进行中的 Turn 应保持它开始时的模型快照,后续 Turn 才使用切换后的模型。
9. 调试路径
| 现象 | 查看 |
|---|---|
| Provider 名称不识别 | ProvidersConfig、Provider registry |
| 自动选错 Provider | preset 是否显式、Config._match_provider |
| apiBase 错误 | Provider config、ProviderSpec default |
| reasoning 参数 400 | ProviderSpec thinking style、adapter kwargs |
| fallback 未发生 | fallback 配置、错误是否可重试/可回退 |
/model 后 UI 不变 | runtime model event、WebUI coordinator |
| 主 Agent 变了但 Subagent 没变 | SubagentManager.set_provider 调用链 |
源码与测试
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
自检
- ProviderSpec 与 ProviderConfig 有什么区别?
- 为什么第一次配置建议显式 Provider?
- DeepSeek 为什么不需要单独的
deepseek_provider.py? - fallback 为什么必须保留候选模型各自的 context 参数?
- 运行中切换模型为什么不应改变已经开始的 Turn?
下一步
进入“工程与安全”,理解并发、可靠性、测试和权限边界。