mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-04-17 08:29:46 +00:00
376 lines
13 KiB
Python
376 lines
13 KiB
Python
"""
|
|
Provider Registry — single source of truth for LLM provider metadata.
|
|
|
|
Adding a new provider:
|
|
1. Add a ProviderSpec to PROVIDERS below.
|
|
2. Add a field to ProvidersConfig in config/schema.py.
|
|
Done. Env vars, config matching, status display all derive from here.
|
|
|
|
Order matters — it controls match priority and fallback. Gateways first.
|
|
Every entry writes out all fields so you can copy-paste as a template.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from pydantic.alias_generators import to_snake
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProviderSpec:
|
|
"""One LLM provider's metadata. See PROVIDERS below for real examples.
|
|
|
|
Placeholders in env_extras values:
|
|
{api_key} — the user's API key
|
|
{api_base} — api_base from config, or this spec's default_api_base
|
|
"""
|
|
|
|
# identity
|
|
name: str # config field name, e.g. "dashscope"
|
|
keywords: tuple[str, ...] # model-name keywords for matching (lowercase)
|
|
env_key: str # env var for API key, e.g. "DASHSCOPE_API_KEY"
|
|
display_name: str = "" # shown in `nanobot status`
|
|
|
|
# which provider implementation to use
|
|
# "openai_compat" | "anthropic" | "azure_openai" | "openai_codex" | "github_copilot"
|
|
backend: str = "openai_compat"
|
|
|
|
# extra env vars, e.g. (("ZHIPUAI_API_KEY", "{api_key}"),)
|
|
env_extras: tuple[tuple[str, str], ...] = ()
|
|
|
|
# gateway / local detection
|
|
is_gateway: bool = False # routes any model (OpenRouter, AiHubMix)
|
|
is_local: bool = False # local deployment (vLLM, Ollama)
|
|
detect_by_key_prefix: str = "" # match api_key prefix, e.g. "sk-or-"
|
|
detect_by_base_keyword: str = "" # match substring in api_base URL
|
|
default_api_base: str = "" # OpenAI-compatible base URL for this provider
|
|
|
|
# gateway behavior
|
|
strip_model_prefix: bool = False # strip "provider/" before sending to gateway
|
|
supports_max_completion_tokens: bool = False
|
|
|
|
# per-model param overrides, e.g. (("kimi-k2.5", {"temperature": 1.0}),)
|
|
model_overrides: tuple[tuple[str, dict[str, Any]], ...] = ()
|
|
|
|
# OAuth-based providers (e.g., OpenAI Codex) don't use API keys
|
|
is_oauth: bool = False
|
|
|
|
# Direct providers skip API-key validation (user supplies everything)
|
|
is_direct: bool = False
|
|
|
|
# Provider supports cache_control on content blocks (e.g. Anthropic prompt caching)
|
|
supports_prompt_caching: bool = False
|
|
|
|
@property
|
|
def label(self) -> str:
|
|
return self.display_name or self.name.title()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PROVIDERS — the registry. Order = priority. Copy any entry as template.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
PROVIDERS: tuple[ProviderSpec, ...] = (
|
|
# === Custom (direct OpenAI-compatible endpoint) ========================
|
|
ProviderSpec(
|
|
name="custom",
|
|
keywords=(),
|
|
env_key="",
|
|
display_name="Custom",
|
|
backend="openai_compat",
|
|
is_direct=True,
|
|
),
|
|
|
|
# === Azure OpenAI (direct API calls with API version 2024-10-21) =====
|
|
ProviderSpec(
|
|
name="azure_openai",
|
|
keywords=("azure", "azure-openai"),
|
|
env_key="",
|
|
display_name="Azure OpenAI",
|
|
backend="azure_openai",
|
|
is_direct=True,
|
|
),
|
|
# === Gateways (detected by api_key / api_base, not model name) =========
|
|
# Gateways can route any model, so they win in fallback.
|
|
# OpenRouter: global gateway, keys start with "sk-or-"
|
|
ProviderSpec(
|
|
name="openrouter",
|
|
keywords=("openrouter",),
|
|
env_key="OPENROUTER_API_KEY",
|
|
display_name="OpenRouter",
|
|
backend="openai_compat",
|
|
is_gateway=True,
|
|
detect_by_key_prefix="sk-or-",
|
|
detect_by_base_keyword="openrouter",
|
|
default_api_base="https://openrouter.ai/api/v1",
|
|
supports_prompt_caching=True,
|
|
),
|
|
# AiHubMix: global gateway, OpenAI-compatible interface.
|
|
# strip_model_prefix=True: doesn't understand "anthropic/claude-3",
|
|
# strips to bare "claude-3".
|
|
ProviderSpec(
|
|
name="aihubmix",
|
|
keywords=("aihubmix",),
|
|
env_key="OPENAI_API_KEY",
|
|
display_name="AiHubMix",
|
|
backend="openai_compat",
|
|
is_gateway=True,
|
|
detect_by_base_keyword="aihubmix",
|
|
default_api_base="https://aihubmix.com/v1",
|
|
strip_model_prefix=True,
|
|
),
|
|
# SiliconFlow (硅基流动): OpenAI-compatible gateway, model names keep org prefix
|
|
ProviderSpec(
|
|
name="siliconflow",
|
|
keywords=("siliconflow",),
|
|
env_key="OPENAI_API_KEY",
|
|
display_name="SiliconFlow",
|
|
backend="openai_compat",
|
|
is_gateway=True,
|
|
detect_by_base_keyword="siliconflow",
|
|
default_api_base="https://api.siliconflow.cn/v1",
|
|
),
|
|
|
|
# VolcEngine (火山引擎): OpenAI-compatible gateway, pay-per-use models
|
|
ProviderSpec(
|
|
name="volcengine",
|
|
keywords=("volcengine", "volces", "ark"),
|
|
env_key="OPENAI_API_KEY",
|
|
display_name="VolcEngine",
|
|
backend="openai_compat",
|
|
is_gateway=True,
|
|
detect_by_base_keyword="volces",
|
|
default_api_base="https://ark.cn-beijing.volces.com/api/v3",
|
|
),
|
|
|
|
# VolcEngine Coding Plan (火山引擎 Coding Plan): same key as volcengine
|
|
ProviderSpec(
|
|
name="volcengine_coding_plan",
|
|
keywords=("volcengine-plan",),
|
|
env_key="OPENAI_API_KEY",
|
|
display_name="VolcEngine Coding Plan",
|
|
backend="openai_compat",
|
|
is_gateway=True,
|
|
default_api_base="https://ark.cn-beijing.volces.com/api/coding/v3",
|
|
strip_model_prefix=True,
|
|
),
|
|
|
|
# BytePlus: VolcEngine international, pay-per-use models
|
|
ProviderSpec(
|
|
name="byteplus",
|
|
keywords=("byteplus",),
|
|
env_key="OPENAI_API_KEY",
|
|
display_name="BytePlus",
|
|
backend="openai_compat",
|
|
is_gateway=True,
|
|
detect_by_base_keyword="bytepluses",
|
|
default_api_base="https://ark.ap-southeast.bytepluses.com/api/v3",
|
|
strip_model_prefix=True,
|
|
),
|
|
|
|
# BytePlus Coding Plan: same key as byteplus
|
|
ProviderSpec(
|
|
name="byteplus_coding_plan",
|
|
keywords=("byteplus-plan",),
|
|
env_key="OPENAI_API_KEY",
|
|
display_name="BytePlus Coding Plan",
|
|
backend="openai_compat",
|
|
is_gateway=True,
|
|
default_api_base="https://ark.ap-southeast.bytepluses.com/api/coding/v3",
|
|
strip_model_prefix=True,
|
|
),
|
|
|
|
|
|
# === Standard providers (matched by model-name keywords) ===============
|
|
# Anthropic: native Anthropic SDK
|
|
ProviderSpec(
|
|
name="anthropic",
|
|
keywords=("anthropic", "claude"),
|
|
env_key="ANTHROPIC_API_KEY",
|
|
display_name="Anthropic",
|
|
backend="anthropic",
|
|
supports_prompt_caching=True,
|
|
),
|
|
# OpenAI: SDK default base URL (no override needed)
|
|
ProviderSpec(
|
|
name="openai",
|
|
keywords=("openai", "gpt"),
|
|
env_key="OPENAI_API_KEY",
|
|
display_name="OpenAI",
|
|
backend="openai_compat",
|
|
supports_max_completion_tokens=True,
|
|
),
|
|
# OpenAI Codex: OAuth-based, dedicated provider
|
|
ProviderSpec(
|
|
name="openai_codex",
|
|
keywords=("openai-codex",),
|
|
env_key="",
|
|
display_name="OpenAI Codex",
|
|
backend="openai_codex",
|
|
detect_by_base_keyword="codex",
|
|
default_api_base="https://chatgpt.com/backend-api",
|
|
is_oauth=True,
|
|
),
|
|
# GitHub Copilot: OAuth-based
|
|
ProviderSpec(
|
|
name="github_copilot",
|
|
keywords=("github_copilot", "copilot"),
|
|
env_key="",
|
|
display_name="Github Copilot",
|
|
backend="github_copilot",
|
|
default_api_base="https://api.githubcopilot.com",
|
|
strip_model_prefix=True,
|
|
is_oauth=True,
|
|
),
|
|
# DeepSeek: OpenAI-compatible at api.deepseek.com
|
|
ProviderSpec(
|
|
name="deepseek",
|
|
keywords=("deepseek",),
|
|
env_key="DEEPSEEK_API_KEY",
|
|
display_name="DeepSeek",
|
|
backend="openai_compat",
|
|
default_api_base="https://api.deepseek.com",
|
|
),
|
|
# Gemini: Google's OpenAI-compatible endpoint
|
|
ProviderSpec(
|
|
name="gemini",
|
|
keywords=("gemini",),
|
|
env_key="GEMINI_API_KEY",
|
|
display_name="Gemini",
|
|
backend="openai_compat",
|
|
default_api_base="https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
),
|
|
# Zhipu (智谱): OpenAI-compatible at open.bigmodel.cn
|
|
ProviderSpec(
|
|
name="zhipu",
|
|
keywords=("zhipu", "glm", "zai"),
|
|
env_key="ZAI_API_KEY",
|
|
display_name="Zhipu AI",
|
|
backend="openai_compat",
|
|
env_extras=(("ZHIPUAI_API_KEY", "{api_key}"),),
|
|
default_api_base="https://open.bigmodel.cn/api/paas/v4",
|
|
),
|
|
# DashScope (通义): Qwen models, OpenAI-compatible endpoint
|
|
ProviderSpec(
|
|
name="dashscope",
|
|
keywords=("qwen", "dashscope"),
|
|
env_key="DASHSCOPE_API_KEY",
|
|
display_name="DashScope",
|
|
backend="openai_compat",
|
|
default_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
),
|
|
# Moonshot (月之暗面): Kimi models. K2.5 enforces temperature >= 1.0.
|
|
ProviderSpec(
|
|
name="moonshot",
|
|
keywords=("moonshot", "kimi"),
|
|
env_key="MOONSHOT_API_KEY",
|
|
display_name="Moonshot",
|
|
backend="openai_compat",
|
|
default_api_base="https://api.moonshot.ai/v1",
|
|
model_overrides=(("kimi-k2.5", {"temperature": 1.0}),),
|
|
),
|
|
# MiniMax: OpenAI-compatible API
|
|
ProviderSpec(
|
|
name="minimax",
|
|
keywords=("minimax",),
|
|
env_key="MINIMAX_API_KEY",
|
|
display_name="MiniMax",
|
|
backend="openai_compat",
|
|
default_api_base="https://api.minimax.io/v1",
|
|
),
|
|
# Mistral AI: OpenAI-compatible API
|
|
ProviderSpec(
|
|
name="mistral",
|
|
keywords=("mistral",),
|
|
env_key="MISTRAL_API_KEY",
|
|
display_name="Mistral",
|
|
backend="openai_compat",
|
|
default_api_base="https://api.mistral.ai/v1",
|
|
),
|
|
# Step Fun (阶跃星辰): OpenAI-compatible API
|
|
ProviderSpec(
|
|
name="stepfun",
|
|
keywords=("stepfun", "step"),
|
|
env_key="STEPFUN_API_KEY",
|
|
display_name="Step Fun",
|
|
backend="openai_compat",
|
|
default_api_base="https://api.stepfun.com/v1",
|
|
),
|
|
# Xiaomi MIMO (小米): OpenAI-compatible API
|
|
ProviderSpec(
|
|
name="xiaomi_mimo",
|
|
keywords=("xiaomi_mimo", "mimo"),
|
|
env_key="XIAOMIMIMO_API_KEY",
|
|
display_name="Xiaomi MIMO",
|
|
backend="openai_compat",
|
|
default_api_base="https://api.xiaomimimo.com/v1",
|
|
),
|
|
# === Local deployment (matched by config key, NOT by api_base) =========
|
|
# vLLM / any OpenAI-compatible local server
|
|
ProviderSpec(
|
|
name="vllm",
|
|
keywords=("vllm",),
|
|
env_key="HOSTED_VLLM_API_KEY",
|
|
display_name="vLLM/Local",
|
|
backend="openai_compat",
|
|
is_local=True,
|
|
),
|
|
# Ollama (local, OpenAI-compatible)
|
|
ProviderSpec(
|
|
name="ollama",
|
|
keywords=("ollama", "nemotron"),
|
|
env_key="OLLAMA_API_KEY",
|
|
display_name="Ollama",
|
|
backend="openai_compat",
|
|
is_local=True,
|
|
detect_by_base_keyword="11434",
|
|
default_api_base="http://localhost:11434/v1",
|
|
),
|
|
# === OpenVINO Model Server (direct, local, OpenAI-compatible at /v3) ===
|
|
ProviderSpec(
|
|
name="ovms",
|
|
keywords=("openvino", "ovms"),
|
|
env_key="",
|
|
display_name="OpenVINO Model Server",
|
|
backend="openai_compat",
|
|
is_direct=True,
|
|
is_local=True,
|
|
default_api_base="http://localhost:8000/v3",
|
|
),
|
|
# === Auxiliary (not a primary LLM provider) ============================
|
|
# Groq: mainly used for Whisper voice transcription, also usable for LLM
|
|
ProviderSpec(
|
|
name="groq",
|
|
keywords=("groq",),
|
|
env_key="GROQ_API_KEY",
|
|
display_name="Groq",
|
|
backend="openai_compat",
|
|
default_api_base="https://api.groq.com/openai/v1",
|
|
),
|
|
# Qianfan (百度千帆): OpenAI-compatible API
|
|
ProviderSpec(
|
|
name="qianfan",
|
|
keywords=("qianfan", "ernie"),
|
|
env_key="QIANFAN_API_KEY",
|
|
display_name="Qianfan",
|
|
backend="openai_compat",
|
|
default_api_base="https://qianfan.baidubce.com/v2"
|
|
),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lookup helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def find_by_name(name: str) -> ProviderSpec | None:
|
|
"""Find a provider spec by config field name, e.g. "dashscope"."""
|
|
normalized = to_snake(name.replace("-", "_"))
|
|
for spec in PROVIDERS:
|
|
if spec.name == normalized:
|
|
return spec
|
|
return None
|