Skip to content

Commit bebe343

Browse files
feat: Add dynamic model discovery from provider APIs
Replace static model name text inputs with dynamic dropdowns that fetch available models directly from each provider's API based on configured API keys. Supported providers: - OpenAI, Anthropic, Google Gemini, Groq, Mistral, DeepSeek, xAI - OpenRouter, SambaNova - Any OpenAI-compatible provider with custom api_base Changes: - Add python/helpers/model_discovery.py for API-based model fetching - Add python/api/settings_refresh_models.py endpoint - Convert model name fields from text to select with dynamic options - Add frontend handlers for provider change and custom model entry - Include 1-hour cache with force refresh capability - Preserve "Custom (enter manually)" option for unlisted models
1 parent ea4968a commit bebe343

File tree

6 files changed

+813
-20
lines changed

6 files changed

+813
-20
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from typing import Any
2+
3+
import models as models_module
4+
from python.helpers.api import ApiHandler, Request, Response
5+
from python.helpers.model_discovery import (
6+
get_models_for_provider,
7+
clear_cache,
8+
)
9+
10+
# Placeholder used for masked API keys in UI
11+
API_KEY_PLACEHOLDER = "************"
12+
13+
14+
class RefreshModels(ApiHandler):
15+
"""
16+
API endpoint to dynamically fetch model options from provider APIs.
17+
18+
Called when:
19+
- User changes the provider dropdown
20+
- User enters/updates an API key
21+
- User explicitly requests a refresh
22+
23+
Input:
24+
model_type: "chat" or "embedding"
25+
provider: Provider ID (e.g., "openai", "anthropic", "openrouter")
26+
api_keys: Dictionary of API keys (may contain placeholders)
27+
api_base: Optional custom API base URL for OpenAI-compatible providers
28+
force_refresh: Optional, if True bypasses cache
29+
clear_cache: Optional, if True clears all cache first
30+
31+
Returns:
32+
models: List of {value, label} options fetched from the provider's API
33+
"""
34+
35+
async def process(
36+
self, input: dict[Any, Any], request: Request
37+
) -> dict[Any, Any] | Response:
38+
model_type = input.get("model_type", "chat")
39+
provider = input.get("provider", "")
40+
api_keys_input = input.get("api_keys", {})
41+
api_base = input.get("api_base", "")
42+
force_refresh = input.get("force_refresh", False)
43+
should_clear_cache = input.get("clear_cache", False)
44+
45+
# Handle cache clear request
46+
if should_clear_cache:
47+
clear_cache()
48+
49+
if not provider:
50+
return {"models": [{"value": "__custom__", "label": "Custom (enter manually)"}]}
51+
52+
# Resolve actual API keys from environment when placeholders are passed
53+
api_keys = {}
54+
for prov, key in api_keys_input.items():
55+
if key == API_KEY_PLACEHOLDER or not key:
56+
# Get actual key from environment
57+
actual_key = models_module.get_api_key(prov)
58+
if actual_key and actual_key != "None":
59+
api_keys[prov] = actual_key
60+
else:
61+
# Use the provided key (user may have just entered a new one)
62+
api_keys[prov] = key
63+
64+
# Fetch models dynamically from provider API
65+
models = await get_models_for_provider(
66+
model_type=model_type,
67+
provider=provider,
68+
api_keys=api_keys,
69+
api_base=api_base if api_base else None,
70+
force_refresh=force_refresh,
71+
)
72+
73+
return {"models": models}

0 commit comments

Comments
 (0)