跳转到内容

声明式多模型路由与回退

声明式多模型路由与回退

学习目标

理解 Nanobot 如何通过声明式 ProviderSpec 注册表实现 30+ LLM 提供商的自动匹配、路由与回退。

前置知识

下文假设你已理解上述概念,直接聚焦 Nanobot 的具体实现。

项目实践

ProviderSpec 声明式元数据

每个 LLM 提供商通过一个 ProviderSpec dataclass 描述其全部元数据:

@dataclass(frozen=True)
class ProviderSpec:
name: str # 配置字段名
keywords: tuple[str, ...] # 模型名匹配关键词
env_key: str # API Key 环境变量名
backend: str # 实现后端: openai_compat / anthropic / bedrock / ...
is_gateway: bool # 是否为网关(可路由任意模型)
is_local: bool # 是否为本地部署
is_oauth: bool # 是否基于 OAuth
is_direct: bool # 是否跳过 API Key 验证
detect_by_key_prefix: str # API Key 前缀检测
detect_by_base_keyword: str # api_base URL 关键词检测
default_api_base: str # 默认 API 地址
thinking_style: str # 推理模式控制风格
supports_prompt_caching: bool # 是否支持提示词缓存
# ...更多字段

匹配优先级链

路由匹配按照严格的优先级链进行:

关键优先级规则:

  1. 强制指定优先:如果配置中 provider 不为 "auto",直接使用该提供商
  2. 模型前缀精确匹配anthropic/claude → 匹配 anthropic 提供商
  3. 本地提供商回退:检查 api_base 中是否包含端口关键词(如 Ollama 的 11434
  4. 网关优先:OpenRouter、AiHubMix 等网关在回退链中优先于具体提供商
  5. OAuth 排除:OAuth 提供商(Codex、Copilot)不会作为隐式回退

Thinking Style 统一

不同提供商控制”推理模式”的方式各不相同,Nanobot 通过 thinking_style 字段统一抽象:

thinking_style注入方式适用提供商
""(空)无需 extra_body大多数提供商
thinking_type{"thinking": {"type": "enabled"}}DeepSeek、VolcEngine、BytePlus、Xiaomi MIMO
enable_thinking{"enable_thinking": true}DashScope (通义)
reasoning_split{"reasoning_split": true}MiniMax

模型特定参数覆盖

某些模型有强制参数,通过 model_overrides 声明:

model_overrides=(
("kimi-k2.5", {"temperature": 1.0}),
("kimi-k2.6", {"temperature": 1.0}),
),

这解决了 Moonshot 的 Kimi K2.5/K2.6 强制 temperature >= 1.0 的限制。

持久重试模式

Nanobot 支持两种重试模式:

  • standard:最多 3 次指数退避重试(1s → 2s → 4s)
  • persistent:无限重试,每次最多等待 60 秒,相同错误 10 次后停止

重试时还会:

  • 解析 Retry-After 响应头和 retry-after-ms
  • 从错误消息中提取 retry after Xs 等模式
  • 每次等待期间发送心跳消息到聊天频道

问题与规避

网关不理解带前缀的模型名

问题:AiHubMix 不理解 anthropic/claude-3 格式,需要裸模型名 claude-3

规避strip_model_prefix=True 标记的提供商会自动去掉 provider/ 前缀。

非瞬态错误不应重试

问题:欠费(insufficient_quota)等错误不会因重试而解决。

规避:Nanobot 区分瞬态和非瞬态错误,欠费类错误直接返回,不重试。

设计取舍

为什么用声明式注册表而非配置文件

优势

  • 新增提供商只需添加一个 ProviderSpec 条目
  • 注册表顺序即匹配优先级,一目了然
  • 所有元数据集中管理,避免分散在多个配置文件中

代价

  • 添加提供商需要改代码(虽然是单行)
  • 运行时无法动态添加

替代方案:将提供商配置放入 YAML/JSON 配置文件。Nanobot 选择代码注册表的原因是提供商元数据包含实现细节(如 backend 字段),与代码强耦合。

参考来源