声明式多模型路由与回退
声明式多模型路由与回退
学习目标
理解 Nanobot 如何通过声明式 ProviderSpec 注册表实现 30+ LLM 提供商的自动匹配、路由与回退。
前置知识
- 多模型适配架构 — 多模型路由与故障转移
- 声明式 Provider 注册表 — 注册表驱动的路由模式
下文假设你已理解上述概念,直接聚焦 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 # 是否支持提示词缓存 # ...更多字段匹配优先级链
路由匹配按照严格的优先级链进行:
关键优先级规则:
- 强制指定优先:如果配置中
provider不为"auto",直接使用该提供商 - 模型前缀精确匹配:
anthropic/claude→ 匹配anthropic提供商 - 本地提供商回退:检查
api_base中是否包含端口关键词(如 Ollama 的11434) - 网关优先:OpenRouter、AiHubMix 等网关在回退链中优先于具体提供商
- 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 字段),与代码强耦合。