Provider 解析与 Fallback

对应源码

主题源码
配置模型和 presetnanobot/config/schema.py::ProviderConfig、ModelPresetConfig
Provider 注册表nanobot/providers/registry.py::ProviderSpec、find_by_name
Provider 构造和快照nanobot/providers/factory.py::make_provider、load_provider_snapshot
通用协议与 retrynanobot/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、apiBaseConfig.providers
模型选择provider + model + generation settingsModelPresetConfig
Provider 元数据backend、默认 base、关键字、env keyProviderSpec
运行实例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

重要字段:

  • name
  • keywords
  • env_key
  • display_name
  • backend
  • default_api_base
  • is_gateway
  • is_oauth
  • is_local
  • strip_model_prefix
  • model_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:

  1. 创建 primary;
  2. 解析 fallback preset;
  3. 为每个 fallback 创建 Provider;
  4. 包装成 fallback chain;
  5. 保留每个候选的 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
自动选错 Providerpreset 是否显式、Config._match_provider
apiBase 错误Provider config、ProviderSpec default
reasoning 参数 400ProviderSpec thinking style、adapter kwargs
fallback 未发生fallback 配置、错误是否可重试/可回退
/model 后 UI 不变runtime model event、WebUI coordinator
主 Agent 变了但 Subagent 没变SubagentManager.set_provider 调用链

源码与测试

  • nanobot/config/schema.py
  • nanobot/providers/registry.py
  • nanobot/providers/factory.py
  • nanobot/providers/base.py
  • nanobot/providers/fallback_provider.py
  • nanobot/providers/openai_compat_provider.py
  • tests/providers/
  • tests/agent/test_runtime_refresh.py
  • tests/agent/test_runner_fallback.py
  • tests/command/test_model_command.py

自检

  1. ProviderSpec 与 ProviderConfig 有什么区别?
  2. 为什么第一次配置建议显式 Provider?
  3. DeepSeek 为什么不需要单独的 deepseek_provider.py?
  4. fallback 为什么必须保留候选模型各自的 context 参数?
  5. 运行中切换模型为什么不应改变已经开始的 Turn?

下一步

进入“工程与安全”,理解并发、可靠性、测试和权限边界。