新增 Provider

对应源码

扩展环节源码
配置字段和 Provider 名称nanobot/config/schema.py、nanobot/providers/registry.py
Provider 构造分派nanobot/providers/factory.py::_make_provider_core、make_provider
通用响应、Tool Call、stream/retry 契约nanobot/providers/base.py::LLMProvider、LLMResponse、ToolCallRequest
设置页同步nanobot/webui/settings_api.py、webui/src/lib/provider-brand.ts
现有 Provider 测试模式tests/providers/

学习目标

读完后,你应当能够:

  • 先判断是否只需配置已有 custom Provider;
  • 为 OpenAI-compatible 服务完成最小注册;
  • 判断何时必须写新的 Provider backend;
  • 设计消息、Tool Call、stream、reasoning、usage 和错误归一化测试。

前置知识

  • 理解 Provider 解析与 Fallback;
  • 知道 OpenAI-compatible 只表示 API 形状相似,不保证所有字段和流事件相同;
  • 能阅读异步 HTTP/SDK 调用。

1. 先选最小层级

flowchart TD
    A["要接入一个模型服务"] --> B{"已有 Provider 可配置?"}
    B -->|"是"| C["只写 config/preset"]
    B -->|"否"| D{"OpenAI-compatible?"}
    D -->|"基本兼容"| E["ProviderSpec + ProvidersConfig"]
    D -->|"协议/认证/响应不同"| F["新 backend + factory 分支"]

DeepSeek 已存在于当前 registry,不需要新增 Provider。

2. 仅配置 custom

如果服务允许自定义 apiBase、API key 和 model,并能被 OpenAICompatProvider 正确处理,先尝试 provider: custom。

这一层不修改源码,适合本地 vLLM、企业网关或还未正式进入 registry 的 endpoint。只有需要自动识别、状态展示、默认 base 或特定请求适配时,才新增 ProviderSpec。

3. OpenAI-compatible Provider 的最小改动

官方开发文档给出的当前最小路径是两处:

nanobot/providers/registry.py

ProviderSpec(
    name="myprovider",
    keywords=("myprovider", "mymodel"),
    env_key="MYPROVIDER_API_KEY",
    display_name="My Provider",
    default_api_base="https://api.myprovider.example/v1",
)

nanobot/config/schema.py

在 ProvidersConfig 增加:

myprovider: ProviderConfig = Field(default_factory=ProviderConfig)

registry 随后为环境变量、auto detection、status 和部分 WebUI credential surface 提供统一元数据。

4. ProviderSpec 不是展示表

它会影响真实请求。常用字段包括:

  • backend
  • default_api_base
  • is_gateway
  • is_local
  • is_direct
  • is_oauth
  • detect_by_key_prefix
  • detect_by_base_keyword
  • strip_model_prefix
  • supports_max_completion_tokens
  • model_overrides
  • thinking_style
  • gateway_reasoning_style
  • reasoning_as_content
  • reasoning_effort_remap
  • reasoning history 处理字段。

PROVIDERS 的顺序也影响匹配优先级。源码注释明确要求 gateway 优先。新增关键词不能过宽,否则可能把其他模型误判为此 Provider。

5. 何时需要新 backend

以下情况通常超出 ProviderSpec:

  • 完全不同的认证协议;
  • 非 OpenAI 消息或 Tool schema;
  • 独特的 streaming event;
  • 原生 SDK 才能正确处理;
  • Tool Call、reasoning 或 usage 形状无法通过现有 adapter 归一;
  • 需要专门的请求签名、region/profile 或 OAuth 生命周期。

这时需要:

  1. 实现 LLMProvider 子类;
  2. 至少实现 chat() 和 get_default_model();
  3. 必要时覆盖 chat_stream();
  4. 在 ProviderSpec.backend 声明新 backend;
  5. 在 nanobot/providers/factory.py::_make_provider_core() 增加构造分支;
  6. 更新导出、配置、测试和文档。

6. 必须归一化的响应

Provider 最终要构造 LLMResponse。关键内容包括:

  • content
  • tool_calls
  • finish_reason
  • usage
  • reasoning_content
  • error status/code/retry metadata
  • stream stall 等恢复线索。

Runner 不应该知道上游把工具调用叫 function_call、content_block 还是其他名称。适配层的价值正是把差异收敛在 Provider 边界。

7. Tool Call 测试

至少覆盖:

  1. 发送 nanobot function schema;
  2. 解析一个完整 Tool Call;
  3. 流式 arguments 分片能正确拼接;
  4. 多个 Tool Call 保持稳定 id/name;
  5. tool result history 能再次发送;
  6. 非法 arguments 产生可诊断错误,不静默丢失。

只测试“你好 → 一段文本”不能证明 Provider 能用于 Agent。

8. reasoning 与 thinking

模型可能:

  • 用独立 reasoning_content;
  • 把 thinking 放 content blocks;
  • 要求请求中显式开关;
  • 拒绝 OpenAI reasoning_effort;
  • 要求历史保留或删除 reasoning 字段。

先用官方 API 文档和实际 fixture 确定 wire shape,再选择 ProviderSpec 字段。不能根据模型名字猜测 thinking 兼容性。

9. 错误和重试

Provider 应尽量填充结构化信息:

  • HTTP status;
  • provider error code/type;
  • retry-after;
  • error_should_retry。

这样 LLMProvider._run_with_retry() 和 FallbackProvider 才能区分:

  • 暂时性 429/5xx;
  • 认证失败;
  • 配额/账单;
  • 内容过滤;
  • context length;
  • malformed request。

把所有异常都改写成普通字符串,会损失重试和 fallback 决策。

10. 配置与 UI 同步

最小 OpenAI-compatible Provider 通常涉及:

  • ProviderSpec
  • ProvidersConfig
  • registry/config tests
  • configuration/provider docs

若要专属品牌、模型列表、OAuth 或特殊设置,还可能涉及:

  • nanobot/webui/settings_api.py
  • webui/src/lib/provider-brand.ts
  • 对应 Python/TypeScript tests
  • i18n 文案。

只在产品确实需要特殊 UI 时增加这些分支;通用 Provider 应尽量由 registry 驱动。

11. 测试参考

简单 registry Provider:

  • tests/providers/test_novita_provider.py
  • tests/providers/test_longcat_provider.py
  • tests/providers/test_mistral_provider.py

通用适配:

  • tests/providers/test_openai_compat_timeout.py
  • tests/providers/test_provider_retry.py
  • tests/providers/test_reasoning_content.py

原生 backend:

  • tests/providers/test_azure_openai_provider.py
  • tests/providers/test_bedrock_provider.py
  • tests/providers/test_openai_codex_provider.py

12. 完成条件

  • config 能解析;
  • factory 选择预期 backend;
  • API key/base/model 正确;
  • 非流式和流式文本正确;
  • Tool Call 往返正确;
  • reasoning/usage 正确;
  • 错误 metadata 可驱动重试/fallback;
  • 不发真实 key 的单元测试通过;
  • 最后再用一个最小真实请求 smoke test。

自检

  1. 一个 OpenAI-compatible endpoint 为什么可能仍不能直接用于 Agent?
  2. ProviderSpec 的顺序为什么重要?
  3. 哪些差异应在 Provider 内归一,而不是让 Runner 分支处理?
  4. 为什么必须测试 Tool Call,而不只测试文本回答?

下一篇:新增 Channel