llm-proxy-rotate / src /rotator_library /provider_config.py
bardd's picture
Upload 144 files
260d3dd verified
# SPDX-License-Identifier: LGPL-3.0-only
# Copyright (c) 2026 Mirrowel
# src/rotator_library/provider_config.py
"""
Centralized provider configuration for the rotator library.
This module handl- Known LiteLLM provider definitions (from scraped data)
- UI configuration (categories, notes, extra vars) for credential tool
- API base overrides for known providers
- Custom OpenAI-compatible provider detection and routing
"""
import os
import logging
from typing import Dict, Any, Set, Optional
from .litellm_providers import (
SCRAPED_PROVIDERS,
get_provider_route,
get_provider_api_key_var,
get_provider_display_name,
)
lib_logger = logging.getLogger("rotator_library")
# =============================================================================
# LiteLLM Provider UI Configuration
#
# Keys are route-based provider identifiers (e.g., "openai", "anthropic").
# Provider data (display_name, api_key_env_vars, etc.) comes from SCRAPED_PROVIDERS.
#
# This dict only contains UI-specific configuration:
# - category: Provider category for display grouping
# - note: (optional) Configuration notes shown to user
# - extra_vars: (optional) Additional env vars to prompt for [(name, label, default), ...]
# =============================================================================
LITELLM_PROVIDERS: Dict[str, Dict[str, Any]] = {
# =========================================================================
# POPULAR - Most commonly used providers
# =========================================================================
"openai": {
"category": "popular",
},
"anthropic": {
"category": "popular",
},
"gemini": {
"category": "popular",
},
"xai": {
"category": "popular",
},
"deepseek": {
"category": "popular",
},
"mistral": {
"category": "popular",
},
"codestral": {
"category": "popular",
},
"openrouter": {
"category": "popular",
"extra_vars": [
("OPENROUTER_API_BASE", "API Base URL (optional)", None),
],
},
"groq": {
"category": "popular",
},
"chutes": {
"category": "popular",
},
"nvidia_nim": {
"category": "popular",
"extra_vars": [
("NVIDIA_NIM_API_BASE", "NIM API Base (optional)", None),
],
},
"perplexity": {
"category": "popular",
},
"moonshot": {
"category": "popular",
"extra_vars": [
("MOONSHOT_API_BASE", "API Base URL (optional)", None),
],
},
"zai": {
"category": "popular",
},
"minimax": {
"category": "popular",
"extra_vars": [
("MINIMAX_API_BASE", "API Base URL (optional)", None),
],
},
"xiaomi_mimo": {
"category": "popular",
},
"nano-gpt": {
"category": "popular",
},
"synthetic": {
"category": "popular",
},
# =========================================================================
# CLOUD PLATFORMS - Aggregators & cloud inference platforms
# =========================================================================
"together_ai": {
"category": "cloud",
},
"fireworks_ai": {
"category": "cloud",
"extra_vars": [
("FIREWORKS_AI_API_BASE", "API Base URL (optional)", None),
],
},
"replicate": {
"category": "cloud",
},
"deepinfra": {
"category": "cloud",
},
"anyscale": {
"category": "cloud",
},
"baseten": {
"category": "cloud",
},
"predibase": {
"category": "cloud",
},
"novita": {
"category": "cloud",
},
"featherless_ai": {
"category": "cloud",
},
"hyperbolic": {
"category": "cloud",
},
"lambda_ai": {
"category": "cloud",
"extra_vars": [
("LAMBDA_API_BASE", "API Base URL (optional)", None),
],
},
"nebius": {
"category": "cloud",
},
"galadriel": {
"category": "cloud",
},
"friendliai": {
"category": "cloud",
},
"sambanova": {
"category": "cloud",
},
"cerebras": {
"category": "cloud",
},
"meta_llama": {
"category": "cloud",
},
"ai21": {
"category": "cloud",
},
"cohere_chat": {
"category": "cloud",
},
"alephalpha": {
"category": "cloud",
},
"huggingface": {
"category": "cloud",
},
"github": {
"category": "cloud",
},
"helicone": {
"category": "cloud",
"note": "LLM gateway/proxy with analytics.",
},
"heroku": {
"category": "cloud",
"extra_vars": [
(
"HEROKU_API_BASE",
"Heroku Inference URL",
"https://us.inference.heroku.com",
),
],
},
"morph": {
"category": "cloud",
},
"poe": {
"category": "cloud",
},
"llamagate": {
"category": "cloud",
},
"manus": {
"category": "cloud",
},
# =========================================================================
# ENTERPRISE / COMPLEX AUTH - Major cloud providers (may need extra config)
# =========================================================================
"azure": {
"category": "enterprise",
"note": "Requires Azure endpoint and API version.",
"extra_vars": [
("AZURE_API_BASE", "Azure endpoint URL", None),
("AZURE_API_VERSION", "API version", "2024-02-15-preview"),
],
},
"azure_ai": {
"category": "enterprise",
"extra_vars": [
("AZURE_AI_API_BASE", "Azure AI endpoint URL", None),
],
},
"vertex_ai": {
"category": "enterprise",
"note": "Uses Google Cloud service account. Enter path to credentials JSON file.",
"extra_vars": [
("VERTEXAI_PROJECT", "GCP Project ID", None),
("VERTEXAI_LOCATION", "GCP Location", "us-central1"),
],
},
"bedrock": {
"category": "enterprise",
"note": "Requires all three AWS credentials.",
"extra_vars": [
("AWS_SECRET_ACCESS_KEY", "AWS Secret Access Key", None),
("AWS_REGION_NAME", "AWS Region", "us-east-1"),
],
},
"sagemaker": {
"category": "enterprise",
"note": "Requires all three AWS credentials.",
"extra_vars": [
("AWS_SECRET_ACCESS_KEY", "AWS Secret Access Key", None),
("AWS_REGION_NAME", "AWS Region", "us-east-1"),
],
},
"databricks": {
"category": "enterprise",
"extra_vars": [
("DATABRICKS_API_BASE", "Databricks workspace URL", None),
],
},
"snowflake": {
"category": "enterprise",
"note": "Uses JWT authentication.",
"extra_vars": [
("SNOWFLAKE_ACCOUNT_ID", "Snowflake Account ID", None),
],
},
"watsonx": {
"category": "enterprise",
"extra_vars": [
("WATSONX_URL", "watsonx.ai URL (optional)", None),
],
},
"cloudflare": {
"category": "enterprise",
"extra_vars": [
("CLOUDFLARE_ACCOUNT_ID", "Cloudflare Account ID", None),
],
},
"oci": {
"category": "enterprise",
"note": "Oracle Cloud Infrastructure. Requires OCI SDK configuration.",
},
"sap": {
"category": "enterprise",
"note": "SAP Generative AI Hub. Requires AI Core configuration.",
},
# =========================================================================
# SPECIALIZED - Image, audio, embeddings, rerank providers
# =========================================================================
"stability": {
"category": "specialized",
"note": "Image generation provider.",
},
"fal_ai": {
"category": "specialized",
"note": "Image generation provider.",
},
"runwayml": {
"category": "specialized",
"note": "Image generation provider.",
},
"recraft": {
"category": "specialized",
"note": "Image generation and editing.",
"extra_vars": [
("RECRAFT_API_BASE", "API Base URL (optional)", None),
],
},
"topaz": {
"category": "specialized",
"note": "Image enhancement provider.",
},
"elevenlabs": {
"category": "specialized",
"note": "Text-to-speech and audio transcription.",
},
"deepgram": {
"category": "specialized",
"note": "Audio transcription provider.",
},
"voyage": {
"category": "specialized",
"note": "Embeddings and rerank provider.",
},
"jina_ai": {
"category": "specialized",
"note": "Embeddings and rerank provider.",
},
"clarifai": {
"category": "specialized",
},
"nlp_cloud": {
"category": "specialized",
},
"milvus": {
"category": "specialized",
"note": "Vector database provider.",
"extra_vars": [
("MILVUS_API_BASE", "Milvus Server URL", None),
],
},
"infinity": {
"category": "specialized",
"note": "Self-hosted embeddings/rerank server. API key is optional.",
"extra_vars": [
("INFINITY_API_BASE", "Infinity Server URL", "http://localhost:8080"),
],
},
# =========================================================================
# REGIONAL - Region-specific or specialized regional providers
# =========================================================================
"dashscope": {
"category": "regional",
"note": "Alibaba Cloud Qwen models.",
},
"volcengine": {
"category": "regional",
"note": "ByteDance cloud platform.",
},
"ovhcloud": {
"category": "regional",
"note": "European cloud provider.",
},
"nscale": {
"category": "regional",
"note": "EU sovereign cloud.",
},
# =========================================================================
# LOCAL / SELF-HOSTED - Run locally or on your own infrastructure
# =========================================================================
"lm_studio": {
"category": "local",
"note": "Local provider. API key is optional. Start LM Studio server first.",
"extra_vars": [
("LM_STUDIO_API_BASE", "API Base URL", "http://localhost:1234/v1"),
],
},
"hosted_vllm": {
"category": "local",
"note": "Self-hosted vLLM server. API key is optional.",
"extra_vars": [
("HOSTED_VLLM_API_BASE", "vLLM Server URL", None),
],
},
"xinference": {
"category": "local",
"note": "Local Xinference server. API key is optional.",
"extra_vars": [
("XINFERENCE_API_BASE", "Xinference URL", "http://127.0.0.1:9997/v1"),
],
},
"litellm_proxy": {
"category": "local",
"note": "Self-hosted LiteLLM Proxy gateway.",
"extra_vars": [
("LITELLM_PROXY_API_BASE", "LiteLLM Proxy URL", "http://localhost:4000"),
],
},
"langgraph": {
"category": "local",
"note": "Self-hosted LangGraph server.",
"extra_vars": [
("LANGGRAPH_API_BASE", "LangGraph URL", "http://localhost:2024"),
],
},
"ragflow": {
"category": "local",
"note": "Self-hosted RAGFlow server.",
"extra_vars": [
("RAGFLOW_API_BASE", "RAGFlow URL", "http://localhost:9380"),
],
},
"docker_model_runner": {
"category": "local",
"note": "Local Docker Model Runner. API key is optional.",
"extra_vars": [
(
"DOCKER_MODEL_RUNNER_API_BASE",
"Docker Model Runner URL",
"http://localhost:22088",
),
],
},
"lemonade": {
"category": "local",
"note": "Local proxy. API key is optional.",
"extra_vars": [
("LEMONADE_API_BASE", "Lemonade URL", "http://localhost:8000/api/v1"),
],
},
# NOTE: ollama, llamafile, petals, triton are in PROVIDER_BLACKLIST
# because they don't use standard API key authentication.
# Use "Add Custom OpenAI-Compatible Provider" for these.
# =========================================================================
# OTHER - Miscellaneous providers
# =========================================================================
"aiml": {
"category": "other",
"extra_vars": [
("AIML_API_BASE", "API Base URL (optional)", None),
],
},
"abliteration": {
"category": "other",
},
"amazon_nova": {
"category": "other",
},
"apertis": {
"category": "other",
},
"bytez": {
"category": "other",
},
"cometapi": {
"category": "other",
},
"compactifai": {
"category": "other",
},
"datarobot": {
"category": "other",
"extra_vars": [
("DATAROBOT_API_BASE", "DataRobot URL", "https://app.datarobot.com"),
],
},
"gradient_ai": {
"category": "other",
"extra_vars": [
("GRADIENT_AI_AGENT_ENDPOINT", "Gradient AI Endpoint (optional)", None),
],
},
"publicai": {
"category": "other",
"extra_vars": [
("PUBLICAI_API_BASE", "PublicAI URL", "https://platform.publicai.co/"),
],
},
"v0": {
"category": "other",
},
"vercel_ai_gateway": {
"category": "other",
},
"wandb": {
"category": "other",
},
}
# Category display order and labels
PROVIDER_CATEGORIES = [
("custom", "Custom (First-Party)"),
("custom_openai", "Custom OpenAI-Compatible"),
("popular", "Popular"),
("cloud", "Cloud Platforms"),
("enterprise", "Enterprise / Complex Auth"),
("specialized", "Specialized (Image/Audio/Embeddings)"),
("regional", "Regional"),
("local", "Local / Self-Hosted"),
("other", "Other"),
]
# =============================================================================
# Provider Blacklist
# =============================================================================
# Providers that are in LiteLLM but should be excluded from:
# - KNOWN_PROVIDERS (so _API_BASE for them creates a "custom" provider)
# - Credential tool UI (won't show up in provider selection)
#
# Reasons for blacklisting:
# - No standard API key authentication (requires OAuth, token files, etc.)
# - Not actual LLM providers (protocols, templates, etc.)
# - Legacy/deprecated APIs
# - Complex auth requiring multiple credentials
# - Non-standard API key patterns (proxy only supports *_API_KEY)
# =============================================================================
PROVIDER_BLACKLIST: Set[str] = {
# Not standard LLM providers / protocols
"a2a", # Pydantic AI agent-to-agent protocol
"my-custom-llm", # Template, not a real provider
"text-completion-openai", # Legacy text completion API
# Require special auth (token files, OAuth, etc.)
"github_copilot", # Requires token file configuration
"vercel_ai_gateway", # Requires OIDC token
# No API key authentication (use custom provider instead)
"ollama", # Local, no API key
"llamafile", # Local, no API key
"petals", # Distributed network, no API key
"triton", # NVIDIA Triton server, no API key
"lemonade", # Local, no API key
"oci", # OCI SDK auth only
# Complex multi-credential auth (proxy only supports API_KEY + API_BASE)
"azure", # Requires API key + endpoint + API version
"bedrock", # Requires AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + region
"sagemaker", # Same as bedrock
"vertex_ai", # File path credential + project + location
"sap", # Multiple service credentials required
"cloudflare", # Requires API key + account ID
"snowflake", # Requires JWT + account ID
"watsonx", # Requires API key + special URL parameter
# Non-standard API key patterns (uses *_TOKEN or *_KEY, not *_API_KEY)
"cometapi", # Uses COMETAPI_KEY
"friendliai", # Uses FRIENDLI_TOKEN
"huggingface", # Uses HF_TOKEN
}
def _build_known_providers_set() -> Set[str]:
"""
Build set of known provider routes from scraped LiteLLM data.
Uses routes as the primary identifier (authoritative from LiteLLM docs).
Only uses API key prefix as fallback when provider has no route.
Excludes providers in PROVIDER_BLACKLIST.
Returns:
Set of lowercase provider route identifiers known to LiteLLM.
"""
known = set()
for provider_key, info in SCRAPED_PROVIDERS.items():
# Skip blacklisted providers
if provider_key in PROVIDER_BLACKLIST:
continue
route = info.get("route", "").rstrip("/").lower()
if route:
# Provider has a route - use it as the canonical key
known.add(route)
else:
# No route - fall back to API key prefix
for api_key_var in info.get("api_key_env_vars", []):
prefix = _extract_api_key_prefix(api_key_var)
if prefix:
known.add(prefix)
break # Only need one fallback
return known
def _extract_api_key_prefix(api_key_var: str) -> Optional[str]:
"""Extract provider prefix from an API key environment variable name.
Examples:
OPENAI_API_KEY -> openai
HF_TOKEN -> hf
WATSONX_APIKEY -> watsonx
"""
if not api_key_var:
return None
api_key_var = api_key_var.upper()
if api_key_var.endswith("_API_KEY"):
return api_key_var[:-8].lower()
elif api_key_var.endswith("_TOKEN"):
return api_key_var[:-6].lower()
elif api_key_var.endswith("_APIKEY"):
return api_key_var[:-7].lower()
elif api_key_var.endswith("_KEY"):
return api_key_var[:-4].lower()
elif api_key_var.endswith("_JWT"):
return api_key_var[:-4].lower()
return None
# Pre-computed set of known provider names
KNOWN_PROVIDERS: Set[str] = _build_known_providers_set()
def get_provider_ui_config(provider_key: str) -> Dict[str, Any]:
"""Get UI configuration for a provider.
Returns the UI-specific config (category, note, extra_vars) if defined,
otherwise returns a default config.
"""
return LITELLM_PROVIDERS.get(provider_key, {"category": "other"})
def get_full_provider_config(provider_key: str) -> Dict[str, Any]:
"""Get combined provider config (scraped data + UI config).
Merges scraped provider data with UI configuration.
"""
scraped = SCRAPED_PROVIDERS.get(provider_key, {})
ui_config = LITELLM_PROVIDERS.get(provider_key, {"category": "other"})
return {**scraped, **ui_config}
class ProviderConfig:
"""
Centralized provider configuration handling.
Handles:
- API base overrides for known LiteLLM providers
- Custom OpenAI-compatible providers (unknown provider names)
Usage patterns:
1. Override existing provider's API base:
Set OPENAI_API_BASE=http://my-local-llm/v1
Request: openai/gpt-4 → LiteLLM gets model="openai/gpt-4", api_base="http://..."
2. Custom OpenAI-compatible provider:
Set MYSERVER_API_BASE=http://myserver:8000/v1
Request: myserver/llama-3 → LiteLLM gets model="openai/llama-3",
api_base="http://...", custom_llm_provider="openai"
"""
def __init__(self):
self._api_bases: Dict[str, str] = {}
self._custom_providers: Set[str] = set()
self._load_api_bases()
def _load_api_bases(self) -> None:
"""
Load all <PROVIDER>_API_BASE environment variables.
Detects whether each is an override for a known provider
or defines a new custom provider.
"""
for key, value in os.environ.items():
if key.endswith("_API_BASE") and value:
provider = key[:-9].lower() # Remove _API_BASE
self._api_bases[provider] = value.rstrip("/")
# Track if this is a custom provider (not known to LiteLLM)
if provider not in KNOWN_PROVIDERS:
self._custom_providers.add(provider)
lib_logger.info(
f"Detected custom OpenAI-compatible provider: {provider} "
f"(api_base: {value})"
)
else:
lib_logger.info(
f"Detected API base override for {provider}: {value}"
)
def is_known_provider(self, provider: str) -> bool:
"""Check if provider is known to LiteLLM."""
return provider.lower() in KNOWN_PROVIDERS
def is_custom_provider(self, provider: str) -> bool:
"""Check if provider is a custom OpenAI-compatible provider."""
return provider.lower() in self._custom_providers
def get_api_base(self, provider: str) -> Optional[str]:
"""Get configured API base for a provider, if any."""
return self._api_bases.get(provider.lower())
def get_custom_providers(self) -> Set[str]:
"""Get the set of detected custom provider names."""
return self._custom_providers.copy()
def convert_for_litellm(self, **kwargs) -> Dict[str, Any]:
"""
Convert model params for LiteLLM call.
Handles:
- Known provider with _API_BASE: pass api_base as override
- Unknown provider with _API_BASE: convert to openai/, set custom_llm_provider
- No _API_BASE configured: pass through unchanged
Args:
**kwargs: LiteLLM call kwargs including 'model'
Returns:
Modified kwargs dict ready for LiteLLM
"""
model = kwargs.get("model")
if not model:
return kwargs
# Extract provider from model string (e.g., "openai/gpt-4" → "openai")
provider = model.split("/")[0].lower()
api_base = self._api_bases.get(provider)
if not api_base:
# No override configured for this provider
return kwargs
# Create a copy to avoid modifying the original
kwargs = kwargs.copy()
if provider in KNOWN_PROVIDERS:
# Known provider - just add api_base override
kwargs["api_base"] = api_base
lib_logger.debug(
f"Applying api_base override for known provider {provider}: {api_base}"
)
else:
# Custom provider - route through OpenAI-compatible endpoint
model_name = model.split("/", 1)[1] if "/" in model else model
kwargs["model"] = f"openai/{model_name}"
kwargs["api_base"] = api_base
kwargs["custom_llm_provider"] = "openai"
lib_logger.debug(
f"Routing custom provider {provider} through openai: "
f"model={kwargs['model']}, api_base={api_base}"
)
return kwargs