218 lines
6.7 KiB
Python
218 lines
6.7 KiB
Python
"""LLM provider presets used by Settings and the runtime AI provider bridge."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from urllib.parse import urlsplit
|
|
|
|
from app.services.llm_model_catalog import fetch_model_catalog
|
|
|
|
OPENCODE_GO_MODEL_PROVIDER_APIS = {
|
|
"minimax-m3": "anthropic-messages",
|
|
"qwen3.8-max": "anthropic-messages",
|
|
"qwen3.8-flash": "anthropic-messages",
|
|
"qwen3.7-max": "anthropic-messages",
|
|
"qwen3.7-plus": "anthropic-messages",
|
|
"qwen3.6-plus": "anthropic-messages",
|
|
"grok-4.6": "openai-responses",
|
|
"gpt-5.6-luna": "openai-responses",
|
|
"muse-spark-1.3-contributor": "openai-responses",
|
|
"muse-spark-1.2-contributor": "openai-responses",
|
|
"minimax-m2.7": "anthropic-messages",
|
|
"minimax-m2.5": "anthropic-messages",
|
|
}
|
|
OPENCODE_GO_FALLBACK_MODELS = [
|
|
"minimax-m3",
|
|
"kimi-k3",
|
|
"glm-5.3",
|
|
"qwen3.8-max",
|
|
"gpt-5.6-luna",
|
|
"minimax-m2.7",
|
|
"minimax-m2.5",
|
|
"kimi-k2.6",
|
|
"kimi-k2.5",
|
|
"glm-5.1",
|
|
"glm-5",
|
|
"deepseek-v4-pro",
|
|
"deepseek-v4-flash",
|
|
"qwen3.6-plus",
|
|
"qwen3.5-plus",
|
|
"mimo-v2.5-pro",
|
|
"mimo-v2.5",
|
|
]
|
|
|
|
|
|
OPENAI_MODEL_PROVIDER_APIS = {
|
|
model: "openai-responses"
|
|
for model in [
|
|
"gpt-6-astra",
|
|
"gpt-5.6-sol",
|
|
"gpt-5.6-terra",
|
|
"gpt-5.6-luna",
|
|
"gpt-5.1",
|
|
"gpt-5.1-codex",
|
|
]
|
|
}
|
|
|
|
|
|
FALLBACK_LLM_PROVIDER_PRESETS: dict[str, dict[str, Any]] = {
|
|
"minimax": {
|
|
"provider": "minimax",
|
|
"label": "MiniMax",
|
|
"provider_api": "anthropic-messages",
|
|
"base_url": "https://api.minimaxi.com/anthropic",
|
|
"model": "MiniMax-M3",
|
|
"models": [
|
|
"MiniMax-M3",
|
|
"MiniMax-M2.7",
|
|
"MiniMax-M2.7-highspeed",
|
|
"MiniMax-M2.5",
|
|
"MiniMax-M2.5-highspeed",
|
|
"MiniMax-M2.1",
|
|
"MiniMax-M2.1-highspeed",
|
|
"MiniMax-M2",
|
|
],
|
|
"api_key_env": "MINIMAX_API_KEY",
|
|
"source": "fallback",
|
|
},
|
|
"openai": {
|
|
"provider": "openai",
|
|
"label": "OpenAI",
|
|
"provider_api": "openai-responses",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"model": "gpt-6-astra",
|
|
"models": ["gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-4.1"],
|
|
"model_provider_apis": OPENAI_MODEL_PROVIDER_APIS,
|
|
"api_key_env": "OPENAI_API_KEY",
|
|
"source": "fallback",
|
|
},
|
|
"anthropic": {
|
|
"provider": "anthropic",
|
|
"label": "Anthropic",
|
|
"provider_api": "anthropic-messages",
|
|
"base_url": "https://api.anthropic.com/v1",
|
|
"model": "claude-opus-5",
|
|
"models": ["claude-opus-5", "claude-sonnet-4-6"],
|
|
"api_key_env": "ANTHROPIC_API_KEY",
|
|
"source": "fallback",
|
|
},
|
|
"deepseek": {
|
|
"provider": "deepseek",
|
|
"label": "DeepSeek",
|
|
"provider_api": "openai-completions",
|
|
"base_url": "https://api.deepseek.com/v1",
|
|
"model": "deepseek-flash",
|
|
"models": ["deepseek-flash", "deepseek-v4-pro"],
|
|
"api_key_env": "DEEPSEEK_API_KEY",
|
|
"source": "fallback",
|
|
},
|
|
"alibaba": {
|
|
"provider": "alibaba",
|
|
"label": "Alibaba Qwen / DashScope",
|
|
"provider_api": "openai-completions",
|
|
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
"model": "qwen3.8-max",
|
|
"models": ["qwen3.8-max", "qwen3-max", "qwen-plus"],
|
|
"api_key_env": "DASHSCOPE_API_KEY",
|
|
"source": "fallback",
|
|
},
|
|
"moonshotai": {
|
|
"provider": "moonshotai",
|
|
"label": "Moonshot AI / Kimi",
|
|
"provider_api": "openai-completions",
|
|
"base_url": "https://api.moonshot.ai/v1",
|
|
"model": "kimi-k3",
|
|
"models": ["kimi-k3", "kimi-k2.5"],
|
|
"api_key_env": "MOONSHOT_API_KEY",
|
|
"source": "fallback",
|
|
},
|
|
"openrouter": {
|
|
"provider": "openrouter",
|
|
"label": "OpenRouter",
|
|
"provider_api": "openai-completions",
|
|
"base_url": "https://openrouter.ai/api/v1",
|
|
"model": "openai/gpt-5.1",
|
|
"models": ["openai/gpt-5.1", "anthropic/claude-sonnet-4.5", "qwen/qwen3-max"],
|
|
"api_key_env": "OPENROUTER_API_KEY",
|
|
"source": "fallback",
|
|
},
|
|
"opencode-go": {
|
|
"provider": "opencode-go",
|
|
"label": "OpenCode Go",
|
|
"provider_api": "openai-completions",
|
|
"base_url": "https://opencode.ai/zen/go/v1",
|
|
"model": "glm-5.1",
|
|
"models": OPENCODE_GO_FALLBACK_MODELS,
|
|
"model_provider_apis": OPENCODE_GO_MODEL_PROVIDER_APIS,
|
|
"api_key_env": "OPENCODE_GO_API_KEY",
|
|
"source": "fallback",
|
|
},
|
|
"ollama": {
|
|
"provider": "ollama",
|
|
"label": "Ollama Local",
|
|
"provider_api": "ollama-generate",
|
|
"base_url": "http://127.0.0.1:11434",
|
|
"model": "qwen2.5:7b",
|
|
"models": ["qwen2.5:7b", "llama3.1:8b", "mistral:7b"],
|
|
"api_key_env": "",
|
|
"source": "fallback",
|
|
},
|
|
}
|
|
|
|
|
|
def list_fallback_llm_provider_presets() -> list[dict[str, Any]]:
|
|
return [dict(value) for value in FALLBACK_LLM_PROVIDER_PRESETS.values()]
|
|
|
|
|
|
def get_fallback_llm_provider_preset(provider: str) -> dict[str, Any]:
|
|
key = provider.strip().lower()
|
|
if key not in FALLBACK_LLM_PROVIDER_PRESETS:
|
|
raise ValueError(f"Unsupported LLM provider preset: {provider}")
|
|
return dict(FALLBACK_LLM_PROVIDER_PRESETS[key])
|
|
|
|
|
|
def _opencode_go_model_provider_apis(model_ids: list[str]) -> dict[str, str]:
|
|
return {
|
|
model_id: OPENCODE_GO_MODEL_PROVIDER_APIS.get(model_id, "openai-completions")
|
|
for model_id in model_ids
|
|
}
|
|
|
|
|
|
async def refresh_llm_provider_preset(
|
|
provider: str,
|
|
api_key: str | None = None,
|
|
*,
|
|
base_url: str | None = None,
|
|
provider_api: str | None = None,
|
|
anthropic_version: str = "2023-06-01",
|
|
) -> dict[str, Any]:
|
|
fallback = get_fallback_llm_provider_preset(provider)
|
|
resolved_base_url = base_url or fallback["base_url"]
|
|
resolved_api = provider_api or fallback["provider_api"]
|
|
catalog = await fetch_model_catalog(
|
|
fallback["provider"],
|
|
resolved_base_url,
|
|
resolved_api,
|
|
api_key or "",
|
|
anthropic_version,
|
|
)
|
|
model_provider_apis = (
|
|
_opencode_go_model_provider_apis(catalog.models)
|
|
if fallback["provider"] == "opencode-go"
|
|
else fallback.get("model_provider_apis", {})
|
|
)
|
|
if (
|
|
fallback["provider"] == "openai"
|
|
and urlsplit(resolved_base_url).hostname != "api.openai.com"
|
|
):
|
|
model_provider_apis = {}
|
|
return {
|
|
**fallback,
|
|
"base_url": resolved_base_url,
|
|
"provider_api": resolved_api,
|
|
"model": catalog.models[0] if catalog.models else "",
|
|
"models": catalog.models,
|
|
"model_provider_apis": model_provider_apis,
|
|
"source": catalog.url,
|
|
}
|