init
This commit is contained in:
173
api/models.py
Normal file
173
api/models.py
Normal file
@@ -0,0 +1,173 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
import aiohttp
|
||||
|
||||
from services.api_key_service import ApiKeyService
|
||||
from db_models import get_db
|
||||
from api.api_keys import validate_api_key
|
||||
|
||||
router = APIRouter(tags=["models"])
|
||||
|
||||
|
||||
@router.get("/models/{provider}")
|
||||
async def get_available_models(provider: str, db=Depends(get_db)):
|
||||
"""
|
||||
Get available models for a specific provider by fetching from their API.
|
||||
Falls back to a curated default list if the API key is missing or the request fails.
|
||||
"""
|
||||
# Curated defaults for each provider — used as fallback
|
||||
default_models = {
|
||||
"openai": [
|
||||
{"model_identifier": "gpt-4o", "display_name": "gpt-4o"},
|
||||
{"model_identifier": "gpt-4o-mini", "display_name": "gpt-4o-mini"},
|
||||
{"model_identifier": "gpt-4-turbo", "display_name": "gpt-4-turbo"},
|
||||
{"model_identifier": "gpt-4", "display_name": "gpt-4"},
|
||||
{"model_identifier": "o3-mini", "display_name": "o3-mini"},
|
||||
{"model_identifier": "gpt-3.5-turbo", "display_name": "gpt-3.5-turbo"}
|
||||
],
|
||||
"claude": [
|
||||
{"model_identifier": "claude-opus-4-5", "display_name": "claude-opus-4-5"},
|
||||
{"model_identifier": "claude-sonnet-4-5", "display_name": "claude-sonnet-4-5"},
|
||||
{"model_identifier": "claude-3-5-sonnet-20241022", "display_name": "claude-3-5-sonnet-20241022"},
|
||||
{"model_identifier": "claude-3-5-haiku-20241022", "display_name": "claude-3-5-haiku-20241022"},
|
||||
{"model_identifier": "claude-3-opus-20240229", "display_name": "claude-3-opus-20240229"}
|
||||
],
|
||||
"qwen": [
|
||||
{"model_identifier": "qwen3-max", "display_name": "qwen3-max"},
|
||||
{"model_identifier": "qwen3-plus", "display_name": "qwen3-plus"},
|
||||
{"model_identifier": "qwen3-flash", "display_name": "qwen3-flash"},
|
||||
{"model_identifier": "qwen-max", "display_name": "qwen-max"},
|
||||
{"model_identifier": "qwen-plus", "display_name": "qwen-plus"},
|
||||
{"model_identifier": "qwen-turbo", "display_name": "qwen-turbo"}
|
||||
],
|
||||
"deepseek": [
|
||||
{"model_identifier": "deepseek-chat", "display_name": "deepseek-chat"},
|
||||
{"model_identifier": "deepseek-reasoner", "display_name": "deepseek-reasoner"},
|
||||
{"model_identifier": "deepseek-v3", "display_name": "deepseek-v3"},
|
||||
{"model_identifier": "deepseek-r1", "display_name": "deepseek-r1"}
|
||||
]
|
||||
}
|
||||
|
||||
defaults = default_models.get(provider, [])
|
||||
|
||||
try:
|
||||
# Retrieve and decrypt API key
|
||||
decrypted_key = ApiKeyService.get_api_key(db, provider)
|
||||
if not decrypted_key:
|
||||
return {"provider": provider, "models": defaults}
|
||||
|
||||
# ---------- OpenAI ----------
|
||||
if provider == "openai":
|
||||
import openai
|
||||
async with openai.AsyncOpenAI(api_key=decrypted_key, timeout=10.0) as client:
|
||||
response = await client.models.list()
|
||||
|
||||
# Keep only chat / reasoning models, sorted newest-first by created timestamp
|
||||
chat_prefixes = ('gpt-', 'o1', 'o3', 'o4', 'chatgpt')
|
||||
models = []
|
||||
seen = set()
|
||||
for m in sorted(response.data, key=lambda x: x.created, reverse=True):
|
||||
if m.id not in seen and m.id.startswith(chat_prefixes):
|
||||
seen.add(m.id)
|
||||
models.append({"model_identifier": m.id, "display_name": m.id})
|
||||
|
||||
if models:
|
||||
return {"provider": provider, "models": models}
|
||||
|
||||
# ---------- Claude ----------
|
||||
elif provider == "claude":
|
||||
timeout = aiohttp.ClientTimeout(total=10)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
headers = {
|
||||
"x-api-key": decrypted_key,
|
||||
"anthropic-version": "2023-06-01"
|
||||
}
|
||||
async with session.get("https://api.anthropic.com/v1/models", headers=headers) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
models = []
|
||||
for m in data.get("data", []):
|
||||
model_id = m.get("id", "")
|
||||
if model_id.startswith("claude-"):
|
||||
models.append({"model_identifier": model_id, "display_name": model_id})
|
||||
if models:
|
||||
return {"provider": provider, "models": models}
|
||||
else:
|
||||
print(f"Claude models API returned {resp.status}: {await resp.text()}")
|
||||
|
||||
# ---------- Qwen ----------
|
||||
elif provider == "qwen":
|
||||
timeout = aiohttp.ClientTimeout(total=10)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
headers = {"Authorization": f"Bearer {decrypted_key}"}
|
||||
async with session.get("https://dashscope.aliyuncs.com/compatible-mode/v1/models", headers=headers) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
exclude_keywords = [
|
||||
'tts', 'vl', 'ocr', 'image', 'asr', '-mt-', '-mt',
|
||||
'math', 'embed', 'rerank', 'coder', 'translate',
|
||||
's2s', 'deep-search', 'omni', 'gui-'
|
||||
]
|
||||
models = []
|
||||
for m in data.get("data", []):
|
||||
model_id = m.get("id", "")
|
||||
if not model_id:
|
||||
continue
|
||||
if not (model_id.startswith("qwen") or model_id.startswith("qwq")):
|
||||
continue
|
||||
if any(kw in model_id for kw in exclude_keywords):
|
||||
continue
|
||||
models.append({"model_identifier": model_id, "display_name": model_id})
|
||||
if models:
|
||||
return {"provider": provider, "models": models}
|
||||
else:
|
||||
print(f"Qwen models API returned {resp.status}: {await resp.text()}")
|
||||
|
||||
# ---------- DeepSeek ----------
|
||||
elif provider == "deepseek":
|
||||
timeout = aiohttp.ClientTimeout(total=10)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
headers = {"Authorization": f"Bearer {decrypted_key}"}
|
||||
async with session.get("https://api.deepseek.com/v1/models", headers=headers) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
models = []
|
||||
for m in data.get("data", []):
|
||||
model_id = m.get("id", "")
|
||||
if model_id.startswith("deepseek"):
|
||||
models.append({"model_identifier": model_id, "display_name": model_id})
|
||||
if models:
|
||||
return {"provider": provider, "models": models}
|
||||
else:
|
||||
print(f"DeepSeek models API returned {resp.status}: {await resp.text()}")
|
||||
|
||||
# API fetch succeeded but returned empty list, or unknown provider — use defaults
|
||||
return {"provider": provider, "models": defaults}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error fetching models for {provider}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {"provider": provider, "models": defaults}
|
||||
|
||||
|
||||
@router.get("/providers")
|
||||
async def get_available_providers(db=Depends(get_db)):
|
||||
"""
|
||||
Get all providers that have valid API keys set
|
||||
"""
|
||||
try:
|
||||
available_providers = []
|
||||
for provider in ("openai", "claude", "qwen", "deepseek", "tavily"):
|
||||
decrypted_key = ApiKeyService.get_api_key(db, provider)
|
||||
if not decrypted_key:
|
||||
continue
|
||||
is_valid = await validate_api_key(provider, decrypted_key)
|
||||
if is_valid:
|
||||
available_providers.append({
|
||||
"provider": provider,
|
||||
"has_valid_key": True
|
||||
})
|
||||
|
||||
return {"providers": available_providers}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user