1 line
17 KiB
JSON
1 line
17 KiB
JSON
{"repo": "1thirteeng3/robo", "files": {"pandaemon/providers/registry.py": {"348158cac7cacb7cdeb4aae060c0a19908b599ce": {"t": 1785809913.289398, "n": 16074, "h": "0ca64510e4aadb8f", "c": "\"\"\"\nProvider Registry — single source of truth for LLM provider metadata.\n\nAdding a new provider:\n 1. Add a ProviderSpec to PROVIDERS below.\n 2. Add a field to ProvidersConfig in config/schema.py.\n Done. Env vars, prefixing, config matching, status display all derive from here.\n\nOrder matters — it controls match priority and fallback. Gateways first.\nEvery entry writes out all fields so you can copy-paste as a template.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Any\n\n\n@dataclass(frozen=True)\nclass ProviderSpec:\n \"\"\"One LLM provider's metadata. See PROVIDERS below for real examples.\n\n Placeholders in env_extras values:\n {api_key} — the user's API key\n {api_base} — api_base from config, or this spec's default_api_base\n \"\"\"\n\n # identity\n name: str # config field name, e.g. \"dashscope\"\n keywords: tuple[str, ...] # model-name keywords for matching (lowercase)\n env_key: str # LiteLLM env var, e.g. \"DASHSCOPE_API_KEY\"\n display_name: str = \"\" # shown in `pandaemon status`\n\n # model prefixing\n litellm_prefix: str = \"\" # \"dashscope\" → model becomes \"dashscope/{model}\"\n skip_prefixes: tuple[str, ...] = () # don't prefix if model already starts with these\n\n # extra env vars, e.g. ((\"ZHIPUAI_API_KEY\", \"{api_key}\"),)\n env_extras: tuple[tuple[str, str], ...] = ()\n\n # gateway / local detection\n is_gateway: bool = False # routes any model (OpenRouter, AiHubMix)\n is_local: bool = False # local deployment (vLLM, Ollama)\n detect_by_key_prefix: str = \"\" # match api_key prefix, e.g. \"sk-or-\"\n detect_by_base_keyword: str = \"\" # match substring in api_base URL\n default_api_base: str = \"\" # fallback base URL\n\n # gateway behavior\n strip_model_prefix: bool = False # strip \"provider/\" before re-prefixing\n\n # per-model param overrides, e.g. ((\"kimi-k2.5\", {\"temperature\": 1.0}),)\n model_overrides: tuple[tuple[str, dict[str, Any]], ...] = ()\n\n # OAuth-based providers (e.g., OpenAI Codex) don't use API keys\n is_oauth: bool = False # if True, uses OAuth flow instead of API key\n\n # Direct providers bypass LiteLLM entirely (e.g., CustomProvider)\n is_direct: bool = False\n\n # Provider supports cache_control on content blocks (e.g. Anthropic prompt caching)\n supports_prompt_caching: bool = False\n\n @property\n def label(self) -> str:\n return self.display_name or self.name.title()\n\n\n# ---------------------------------------------------------------------------\n# PROVIDERS — the registry. Order = priority. Copy any entry as template.\n# ---------------------------------------------------------------------------\n\nPROVIDERS: tuple[ProviderSpec, ...] = (\n # === Abacus API (API-compatible router gateway) ======\n ProviderSpec(\n name=\"abacus\",\n keywords=(\"abacus\",),\n env_key=\"ABACUS_API_KEY\",\n display_name=\"Abacus\",\n litellm_prefix=\"\",\n is_direct=True, # We built a custom provider for it\n ),\n # === Custom (direct OpenAI-compatible endpoint, bypasses LiteLLM) ======\n ProviderSpec(\n name=\"custom\",\n keywords=(),\n env_key=\"\",\n display_name=\"Custom\",\n litellm_prefix=\"\",\n is_direct=True,\n ),\n\n # === Gateways (detected by api_key / api_base, not model name) =========\n # Gateways can route any model, so they win in fallback.\n # OpenRouter: global gateway, keys start with \"sk-or-\"\n ProviderSpec(\n name=\"openrouter\",\n keywords=(\"openrouter\",),\n env_key=\"OPENROUTER_API_KEY\",\n display_name=\"OpenRouter\",\n litellm_prefix=\"openrouter\", # claude-3 → openrouter/claude-3\n skip_prefixes=(),\n env_extras=(),\n is_gateway=True,\n is_local=False,\n detect_by_key_prefix=\"sk-or-\",\n detect_by_base_keyword=\"openrouter\",\n default_api_base=\"https://openrouter.ai/api/v1\",\n strip_model_prefix=False,\n model_overrides=(),\n supports_prompt_caching=True,\n ),\n # AiHubMix: global gateway, OpenAI-compatible interface.\n # strip_model_prefix=True: it doesn't understand \"anthropic/claude-3\",\n # so we strip to bare \"claude-3\" then re-prefix as \"openai/claude-3\".\n ProviderSpec(\n name=\"aihubmix\",\n keywords=(\"aihubmix\",),\n env_key=\"OPENAI_API_KEY\", # OpenAI-compatible\n display_name=\"AiHubMix\",\n litellm_prefix=\"openai\", # → openai/{model}\n skip_prefixes=(),\n env_extras=(),\n is_gateway=True,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"aihubmix\",\n default_api_base=\"https://aihubmix.com/v1\",\n strip_model_prefix=True, # anthropic/claude-3 → claude-3 → openai/claude-3\n model_overrides=(),\n ),\n # SiliconFlow (硅基流动): OpenAI-compatible gateway, model names keep org prefix\n ProviderSpec(\n name=\"siliconflow\",\n keywords=(\"siliconflow\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"SiliconFlow\",\n litellm_prefix=\"openai\",\n skip_prefixes=(),\n env_extras=(),\n is_gateway=True,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"siliconflow\",\n default_api_base=\"https://api.siliconflow.cn/v1\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # VolcEngine (火山引擎): OpenAI-compatible gateway\n ProviderSpec(\n name=\"volcengine\",\n keywords=(\"volcengine\", \"volces\", \"ark\"),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"VolcEngine\",\n litellm_prefix=\"volcengine\",\n skip_prefixes=(),\n env_extras=(),\n is_gateway=True,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"volces\",\n default_api_base=\"https://ark.cn-beijing.volces.com/api/v3\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # === Standard providers (matched by model-name keywords) ===============\n # Anthropic: LiteLLM recognizes \"claude-*\" natively, no prefix needed.\n ProviderSpec(\n name=\"anthropic\",\n keywords=(\"anthropic\", \"claude\"),\n env_key=\"ANTHROPIC_API_KEY\",\n display_name=\"Anthropic\",\n litellm_prefix=\"\",\n skip_prefixes=(),\n env_extras=(),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"\",\n strip_model_prefix=False,\n model_overrides=(),\n supports_prompt_caching=True,\n ),\n # OpenAI: LiteLLM recognizes \"gpt-*\" natively, no prefix needed.\n ProviderSpec(\n name=\"openai\",\n keywords=(\"openai\", \"gpt\"),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"OpenAI\",\n litellm_prefix=\"\",\n skip_prefixes=(),\n env_extras=(),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # Github Copilot: uses OAuth, not API key.\n ProviderSpec(\n name=\"github_copilot\",\n keywords=(\"github_copilot\", \"copilot\"),\n env_key=\"\", # OAuth-based, no API key\n display_name=\"Github Copilot\",\n litellm_prefix=\"github_copilot\", # github_copilot/model → github_copilot/model\n skip_prefixes=(\"github_copilot/\",),\n env_extras=(),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"\",\n strip_model_prefix=False,\n model_overrides=(),\n is_oauth=True, # OAuth-based authentication\n ),\n # DeepSeek: needs \"deepseek/\" prefix for LiteLLM routing.\n ProviderSpec(\n name=\"deepseek\",\n keywords=(\"deepseek\",),\n env_key=\"DEEPSEEK_API_KEY\",\n display_name=\"DeepSeek\",\n litellm_prefix=\"deepseek\", # deepseek-chat → deepseek/deepseek-chat\n skip_prefixes=(\"deepseek/\",), # avoid double-prefix\n env_extras=(),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # Gemini: needs \"gemini/\" prefix for LiteLLM.\n ProviderSpec(\n name=\"gemini\",\n keywords=(\"gemini\",),\n env_key=\"GEMINI_API_KEY\",\n display_name=\"Gemini\",\n litellm_prefix=\"gemini\", # gemini-pro → gemini/gemini-pro\n skip_prefixes=(\"gemini/\",), # avoid double-prefix\n env_extras=(),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # Zhipu: LiteLLM uses \"zai/\" prefix.\n # Also mirrors key to ZHIPUAI_API_KEY (some LiteLLM paths check that).\n # skip_prefixes: don't add \"zai/\" when already routed via gateway.\n ProviderSpec(\n name=\"zhipu\",\n keywords=(\"zhipu\", \"glm\", \"zai\"),\n env_key=\"ZAI_API_KEY\",\n display_name=\"Zhipu AI\",\n litellm_prefix=\"zai\", # glm-4 → zai/glm-4\n skip_prefixes=(\"zhipu/\", \"zai/\", \"openrouter/\", \"hosted_vllm/\"),\n env_extras=((\"ZHIPUAI_API_KEY\", \"{api_key}\"),),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # DashScope: Qwen models, needs \"dashscope/\" prefix.\n ProviderSpec(\n name=\"dashscope\",\n keywords=(\"qwen\", \"dashscope\"),\n env_key=\"DASHSCOPE_API_KEY\",\n display_name=\"DashScope\",\n litellm_prefix=\"dashscope\", # qwen-max → dashscope/qwen-max\n skip_prefixes=(\"dashscope/\", \"openrouter/\"),\n env_extras=(),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # Moonshot: Kimi models, needs \"moonshot/\" prefix.\n # LiteLLM requires MOONSHOT_API_BASE env var to find the endpoint.\n # Kimi K2.5 API enforces temperature >= 1.0.\n ProviderSpec(\n name=\"moonshot\",\n keywords=(\"moonshot\", \"kimi\"),\n env_key=\"MOONSHOT_API_KEY\",\n display_name=\"Moonshot\",\n litellm_prefix=\"moonshot\", # kimi-k2.5 → moonshot/kimi-k2.5\n skip_prefixes=(\"moonshot/\", \"openrouter/\"),\n env_extras=((\"MOONSHOT_API_BASE\", \"{api_base}\"),),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"https://api.moonshot.ai/v1\", # intl; use api.moonshot.cn for China\n strip_model_prefix=False,\n model_overrides=((\"kimi-k2.5\", {\"temperature\": 1.0}),),\n ),\n # MiniMax: needs \"minimax/\" prefix for LiteLLM routing.\n # Uses OpenAI-compatible API at api.minimax.io/v1.\n ProviderSpec(\n name=\"minimax\",\n keywords=(\"minimax\",),\n env_key=\"MINIMAX_API_KEY\",\n display_name=\"MiniMax\",\n litellm_prefix=\"minimax\", # MiniMax-M2.1 → minimax/MiniMax-M2.1\n skip_prefixes=(\"minimax/\", \"openrouter/\"),\n env_extras=(),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"https://api.minimax.io/v1\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # === Local deployment (matched by config key, NOT by api_base) =========\n # vLLM / any OpenAI-compatible local server.\n # Detected when config key is \"vllm\" (provider_name=\"vllm\").\n ProviderSpec(\n name=\"vllm\",\n keywords=(\"vllm\",),\n env_key=\"HOSTED_VLLM_API_KEY\",\n display_name=\"vLLM/Local\",\n litellm_prefix=\"hosted_vllm\", # Llama-3-8B → hosted_vllm/Llama-3-8B\n skip_prefixes=(),\n env_extras=(),\n is_gateway=False,\n is_local=True,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"\", # user must provide in config\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # === Ollama (local, OpenAI-compatible) ===================================\n ProviderSpec(\n name=\"ollama\",\n keywords=(\"ollama\", \"nemotron\"),\n env_key=\"OLLAMA_API_KEY\",\n display_name=\"Ollama\",\n litellm_prefix=\"ollama_chat\", # model → ollama_chat/model\n skip_prefixes=(\"ollama/\", \"ollama_chat/\"),\n env_extras=(),\n is_gateway=False,\n is_local=True,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"11434\",\n default_api_base=\"http://localhost:11434\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n # === Auxiliary (not a primary LLM provider) ============================\n # Groq: mainly used for Whisper voice transcription, also usable for LLM.\n # Needs \"groq/\" prefix for LiteLLM routing. Placed last — it rarely wins fallback.\n ProviderSpec(\n name=\"groq\",\n keywords=(\"groq\",),\n env_key=\"GROQ_API_KEY\",\n display_name=\"Groq\",\n litellm_prefix=\"groq\", # llama3-8b-8192 → groq/llama3-8b-8192\n skip_prefixes=(\"groq/\",), # avoid double-prefix\n env_extras=(),\n is_gateway=False,\n is_local=False,\n detect_by_key_prefix=\"\",\n detect_by_base_keyword=\"\",\n default_api_base=\"\",\n strip_model_prefix=False,\n model_overrides=(),\n ),\n)\n\n\n# ---------------------------------------------------------------------------\n# Lookup helpers\n# ---------------------------------------------------------------------------\n\n\ndef find_by_model(model: str) -> ProviderSpec | None:\n \"\"\"Match a standard provider by model-name keyword (case-insensitive).\n Skips gateways/local — those are matched by api_key/api_base instead.\"\"\"\n model_lower = model.lower()\n model_normalized = model_lower.replace(\"-\", \"_\")\n model_prefix = model_lower.split(\"/\", 1)[0] if \"/\" in model_lower else \"\"\n normalized_prefix = model_prefix.replace(\"-\", \"_\")\n std_specs = [s for s in PROVIDERS if not s.is_gateway and not s.is_local]\n\n # Prefer explicit provider prefix — prevents `github-copilot/...codex` matching openai_codex.\n for spec in std_specs:\n if model_prefix and normalized_prefix == spec.name:\n return spec\n\n for spec in std_specs:\n if any(\n kw in model_lower or kw.replace(\"-\", \"_\") in model_normalized for kw in spec.keywords\n ):\n return spec\n return None\n\n\ndef find_gateway(\n provider_name: str | None = None,\n api_key: str | None = None,\n api_base: str | None = None,\n) -> ProviderSpec | None:\n \"\"\"Detect gateway/local provider.\n\n Priority:\n 1. provider_name — if it maps to a gateway/local spec, use it directly.\n 2. api_key prefix — e.g. \"sk-or-\" → OpenRouter.\n 3. api_base keyword — e.g. \"aihubmix\" in URL → AiHubMix.\n\n A standard provider with a custom api_base (e.g. DeepSeek behind a proxy)\n will NOT be mistaken for vLLM — the old fallback is gone.\n \"\"\"\n # 1. Direct match by config key\n if provider_name:\n spec = find_by_name(provider_name)\n if spec and (spec.is_gateway or spec.is_local):\n return spec\n\n # 2. Auto-detect by api_key prefix / api_base keyword\n for spec in PROVIDERS:\n if spec.detect_by_key_prefix and api_key and api_key.startswith(spec.detect_by_key_prefix):\n return spec\n if spec.detect_by_base_keyword and api_base and spec.detect_by_base_keyword in api_base:\n return spec\n\n return None\n\n\ndef find_by_name(name: str) -> ProviderSpec | None:\n \"\"\"Find a provider spec by config field name, e.g. \"dashscope\".\"\"\"\n for spec in PROVIDERS:\n if spec.name == name:\n return spec\n return None\n"}}}} |