Files
unslothai--unsloth/studio/backend/core/inference/providers.py
T
wehub-resource-sync e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:59:56 +08:00

372 lines
15 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""
Static registry of supported external LLM providers.
All providers expose OpenAI-compatible /v1/chat/completions endpoints
with Bearer token auth and SSE streaming.
"""
import re
from typing import Any
PROVIDER_REGISTRY: dict[str, dict[str, Any]] = {
"openai": {
"display_name": "OpenAI",
"base_url": "https://api.openai.com/v1",
"default_models": [
"gpt-5.5",
"gpt-5.4",
"gpt-5.4-mini",
"o3",
],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
# Scope the picker to the current generation. /v1/models returns many
# historical snapshots, fine-tunes, and non-chat models we don't want.
"model_id_allowlist": re.compile(r"^(gpt-5\.[345]|gpt-4\.5|o3)(?:[-.]|$)"),
# Hide dated snapshots and the retired plain gpt-5.3 id.
"model_id_denylist": re.compile(r"^(gpt-5\.3)$|-\d{4}-\d{2}-\d{2}$"),
},
"anthropic": {
"display_name": "Anthropic",
"base_url": "https://api.anthropic.com/v1",
"default_models": [
"claude-opus-4-7",
"claude-opus-4-6",
"claude-sonnet-4-6",
"claude-opus-4-5",
"claude-sonnet-4-5",
"claude-haiku-4-5",
],
# Hide YYYYMMDD-suffixed snapshot ids (e.g. claude-3-5-sonnet-20241022).
"model_id_denylist": re.compile(r"-\d{8}$"),
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": False,
"auth_header": "x-api-key",
"auth_prefix": "",
"extra_headers": {
"anthropic-version": "2023-06-01",
},
"openai_compatible": False,
"notes": "Native Anthropic Messages API. Uses x-api-key header and /v1/messages endpoint with SSE translation.",
},
"gemini": {
"display_name": "Google Gemini",
# Native Gemini REST endpoint -- does NOT speak OpenAI Chat Completions;
# translated in `_stream_gemini` (external_provider.py).
# https://ai.google.dev/gemini-api/docs
"base_url": "https://generativelanguage.googleapis.com/v1beta",
# Curated lineup (ListModels returns many historical/experimental ids).
# Excluded on purpose:
# - `gemini-2.0-flash*` (retired 2026-06-01; 404 on use)
# - `gemini-3-pro-preview` (shut down 2026-03-09; auto-redirects to
# `gemini-3.1-pro-preview`, so we surface 3.1 directly).
"default_models": [
"gemini-3.1-pro-preview",
"gemini-3.5-flash",
"gemini-3.1-flash-lite",
"gemini-3-flash-preview",
"gemini-pro-latest",
"gemini-flash-latest",
"gemini-flash-lite-latest",
"gemini-2.5-pro",
"gemini-2.5-flash",
"gemini-2.5-flash-lite",
"gemini-3-pro-image-preview",
"gemini-3.1-flash-image-preview",
"gemini-2.5-flash-image",
],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
# Native API takes the bare key on `x-goog-api-key`.
"auth_header": "x-goog-api-key",
"auth_prefix": "",
"openai_compatible": False,
"notes": (
"Native Gemini API. Translation lives in _stream_gemini. "
"API key from https://aistudio.google.com/apikey. "
"See https://ai.google.dev/gemini-api/docs for endpoint shapes."
),
# gemini-3-pro-preview was shut down 2026-03-09 and auto-aliased to
# gemini-3.1-pro-preview; drop it so users see one canonical card.
"model_id_deny_exact": ("gemini-3-pro-preview",),
# Chat-capable 3.5 / 3.1 / 3 / 2.5 families plus rolling *-latest
# aliases. Image-tier ids flow through the Nano Banana
# `responseModalities` path in `_stream_gemini`. Retired 2.0 ids
# excluded (they 404 on use).
"model_id_allowlist": re.compile(
r"^("
r"gemini-3\.5-(?:flash|pro)(?:-preview)?|"
r"gemini-3\.1-(?:flash|pro|flash-lite)(?:-preview)?(?:-customtools)?|"
r"gemini-3\.1-flash-image-preview|"
r"gemini-3-(?:flash|pro)(?:-preview)?|"
r"gemini-3-pro-image-preview|"
r"nano-banana-pro-preview|"
r"gemini-2\.5-pro|gemini-2\.5-flash|gemini-2\.5-flash-lite|"
r"gemini-2\.5-flash-image|"
r"gemini-pro-latest|gemini-flash-latest|gemini-flash-lite-latest"
r")$"
),
},
"deepseek": {
"display_name": "DeepSeek",
"base_url": "https://api.deepseek.com/v1",
"default_models": [
"deepseek-chat",
"deepseek-reasoner",
],
"supports_streaming": True,
"supports_vision": False,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
"notes": "OpenAI-compatible API. deepseek-chat = V3, deepseek-reasoner = R1 thinking mode.",
},
"mistral": {
"display_name": "Mistral AI",
"base_url": "https://api.mistral.ai/v1",
"default_models": [
"codestral-latest",
"devstral-latest",
"devstral-medium-latest",
"magistral-medium-latest",
"ministral-14b-latest",
"ministral-3b-latest",
"ministral-8b-latest",
"mistral-large-latest",
"mistral-medium-latest",
"mistral-small-latest",
"mistral-tiny-latest",
"mistral-vibe-cli-latest",
],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
"model_id_allowlist": re.compile(
r"^(codestral-latest|devstral-latest|devstral-medium-latest|"
r"magistral-medium-latest|ministral-(?:14b|3b|8b)-latest|"
r"mistral-(?:large|medium|small|tiny)-latest|"
r"mistral-vibe-cli-latest)$"
),
},
"kimi": {
"display_name": "Kimi",
"base_url": "https://api.moonshot.ai/v1",
# Surface only the two SoTA multimodal models (kimi-k2.6/k2.5);
# moonshot-v1-* and dated k2 previews are filtered by the allowlist.
# Docs: https://platform.kimi.ai/docs/models
# Listing/overview: https://platform.kimi.ai/docs/api/list-models
# https://platform.kimi.ai/docs/api/overview
"default_models": [
"kimi-k2.6",
"kimi-k2.5",
],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
"notes": "Moonshot API key. China: use base URL https://api.moonshot.cn/v1",
"model_id_allowlist": re.compile(r"^kimi-k2\.[56]$"),
# Reasoning-class: the API rejects custom temperature/top_p ("only 1
# is allowed"). Strip both so the server uses its required defaults.
"body_omit": ("temperature", "top_p"),
},
"qwen": {
"display_name": "Qwen",
"base_url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
"default_models": [
"qwen-plus",
"qwen-turbo",
"qwen-max",
"qwen2.5-72b-instruct",
],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
"notes": "DashScope API key. China mainland: override base URL to https://dashscope.aliyuncs.com/compatible-mode/v1",
},
"huggingface": {
"display_name": "Hugging Face",
"base_url": "https://router.huggingface.co/v1",
# Seed the picker before the live /v1/models call resolves; the remote
# listing (see model_list_mode) is the source of truth.
"default_models": [
"openai/gpt-oss-120b",
"deepseek-ai/DeepSeek-V3",
"meta-llama/Llama-3.3-70B-Instruct",
"Qwen/Qwen2.5-72B-Instruct",
],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
"notes": (
"HF token from huggingface.co/settings/tokens. Uses the "
"OpenAI-compatible router at /v1/chat/completions; /v1/models "
"returns the cross-provider chat catalog. See "
"https://huggingface.co/docs/inference-providers/index."
),
# Remote so users see live availability; loadModels() merges defaults
# so they stay visible if the remote call fails.
"model_list_mode": "remote",
# Scope to trusted first-party org repos (the response is otherwise
# hundreds of community fine-tunes, mirrors, fp8 variants).
"model_id_allowlist": re.compile(
r"^(openai|deepseek-ai|google|meta-llama|Qwen|moonshotai|mistralai|zai-org)/"
),
# Cap the post-filter list to first N matches (no server-side sort);
# default_models keeps flagship ids near the top.
"model_id_limit": 15,
},
"vllm": {
"display_name": "vLLM",
# User-supplied via provider_base_url; the route falls back to the
# payload's base_url when the registry entry has none.
"base_url": "",
"default_models": [],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
# Force /v1/chat/completions -- vLLM's /v1/responses rebuilds messages
# through the chat template, 400ing on strict-alternation templates
# (Gemma 3). The chat-completions path takes messages verbatim.
"notes": "Self-hosted vLLM server. Always routed to /v1/chat/completions.",
# Surfaced via the frontend's CUSTOM_PROVIDER_PRESETS, not the dropdown.
"hidden": True,
},
"custom": {
"display_name": "Custom",
# User-supplied via provider_base_url.
"base_url": "",
"default_models": [],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
"notes": (
"User-supplied OpenAI-compatible server. Routed to "
"/v1/chat/completions; /models is optional."
),
# Surfaced by the frontend's generic Custom option, not the dropdown.
"hidden": True,
},
"ollama": {
"display_name": "Ollama",
"base_url": "http://localhost:11434/v1",
"default_models": [],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
"notes": (
"Local Ollama server. OpenAI-compatible /v1/chat/completions; "
"no API key. Surfaced via CUSTOM_PROVIDER_PRESETS in the frontend."
),
"hidden": True,
},
"llama_cpp": {
"display_name": "llama.cpp",
"base_url": "http://localhost:8080/v1",
"default_models": [],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
"notes": (
"Local llama.cpp server (llama-server). OpenAI-compatible "
"/v1/chat/completions. Surfaced via CUSTOM_PROVIDER_PRESETS."
),
"hidden": True,
},
"openrouter": {
"display_name": "OpenRouter",
"base_url": "https://openrouter.ai/api/v1",
# Curated picker list (locked, not live /models).
"default_models": [
"openrouter/free",
"openai/gpt-4o",
"anthropic/claude-sonnet-4-5",
"google/gemini-2.5-flash",
"mistralai/mistral-large-2411",
"deepseek/deepseek-r1",
"mistralai/mistral-small-3.1-24b-instruct",
"perceptron/perceptron-mk1",
"inclusionai/ring-2.6-1t:free",
"google/gemini-3.1-flash-lite",
"baidu/cobuddy:free",
"openai/gpt-chat-latest",
"x-ai/grok-4.3",
"ibm-granite/granite-4.1-8b",
"openrouter/owl-alpha",
"poolside/laguna-xs.2:free",
"~google/gemini-pro-latest",
"~moonshotai/kimi-latest",
],
"supports_streaming": True,
"supports_vision": True,
"supports_tool_calling": True,
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
"extra_headers": {
"HTTP-Referer": "https://unsloth.ai",
"X-Title": "Unsloth Studio",
},
"notes": "Unified gateway to 300+ models across all major providers. HTTP-Referer and X-Title headers sent for attribution.",
"model_list_mode": "curated",
},
}
def get_provider_info(provider_type: str) -> dict[str, Any] | None:
"""Return the registry entry for a provider type, or None if unknown."""
return PROVIDER_REGISTRY.get(provider_type)
def get_base_url(provider_type: str) -> str | None:
"""Return the default base URL for a provider type."""
info = PROVIDER_REGISTRY.get(provider_type)
return info["base_url"] if info else None
def list_available_providers() -> list[dict[str, Any]]:
"""Return all registered providers (for the /registry endpoint).
Hidden entries are filtered out: they exist only for backend lookups and
are surfaced via ``CUSTOM_PROVIDER_PRESETS`` instead of the dropdown.
"""
result = []
for provider_type, info in PROVIDER_REGISTRY.items():
if info.get("hidden"):
continue
result.append(
{
"provider_type": provider_type,
"display_name": info["display_name"],
"base_url": info["base_url"],
"default_models": info["default_models"],
"supports_streaming": info["supports_streaming"],
"supports_vision": info.get("supports_vision", False),
"supports_tool_calling": info.get("supports_tool_calling", False),
"model_list_mode": info.get("model_list_mode", "remote"),
}
)
return result