diff --git a/backend_api_python/app/config/api_keys.py b/backend_api_python/app/config/api_keys.py index 7ee779a25..f0e4f87e2 100644 --- a/backend_api_python/app/config/api_keys.py +++ b/backend_api_python/app/config/api_keys.py @@ -99,6 +99,36 @@ def GROK_API_KEY(cls): from app.utils.config_loader import load_addon_config val = load_addon_config().get('grok', {}).get('api_key') return val if val else '' + + @property + def CUSTOM_API_KEY(cls): + """Custom LLM API key (for OpenAI-compatible custom endpoints)""" + env_val = os.getenv('CUSTOM_API_KEY', '').strip() + if env_val: + return env_val + from app.utils.config_loader import load_addon_config + val = load_addon_config().get('custom', {}).get('api_key') + return val if val else '' + + @property + def CUSTOM_API_URL(cls): + """Custom LLM API base URL (e.g., https://your-api.com/v1)""" + env_val = os.getenv('CUSTOM_API_URL', '').strip() + if env_val: + return env_val + from app.utils.config_loader import load_addon_config + val = load_addon_config().get('custom', {}).get('base_url') + return val if val else '' + + @property + def CUSTOM_MODEL(cls): + """Custom LLM model name""" + env_val = os.getenv('CUSTOM_MODEL', '').strip() + if env_val: + return env_val + from app.utils.config_loader import load_addon_config + val = load_addon_config().get('custom', {}).get('model') + return val if val else '' @property def TAVILY_API_KEYS(cls): diff --git a/backend_api_python/app/routes/settings.py b/backend_api_python/app/routes/settings.py index 879826d51..abe88cb1e 100644 --- a/backend_api_python/app/routes/settings.py +++ b/backend_api_python/app/routes/settings.py @@ -131,6 +131,7 @@ def _refresh_runtime_services() -> None: {'value': 'google', 'label': 'Google Gemini'}, {'value': 'deepseek', 'label': 'DeepSeek'}, {'value': 'grok', 'label': 'xAI Grok'}, + {'value': 'custom', 'label': 'Custom API (OpenAI-compatible)'}, ], 'description': 'Select your preferred LLM provider' }, @@ -263,6 +264,31 @@ def _refresh_runtime_services() -> None: 'description': 'xAI Grok API endpoint', 'group': 'grok' }, + # Custom API (OpenAI-compatible) + { + 'key': 'CUSTOM_API_URL', + 'label': 'Custom API URL', + 'type': 'text', + 'default': '', + 'description': 'Your custom API endpoint (OpenAI-compatible, e.g. https://api.example.com/v1)', + 'group': 'custom' + }, + { + 'key': 'CUSTOM_API_KEY', + 'label': 'Custom API Key', + 'type': 'password', + 'required': False, + 'description': 'API key for your custom endpoint', + 'group': 'custom' + }, + { + 'key': 'CUSTOM_MODEL', + 'label': 'Custom Model', + 'type': 'text', + 'default': '', + 'description': 'Model name to use (e.g. gpt-4o, claude-3-opus)', + 'group': 'custom' + }, # Common settings { 'key': 'OPENROUTER_TEMPERATURE', diff --git a/backend_api_python/app/services/llm.py b/backend_api_python/app/services/llm.py index 0d06a7741..05559e0bd 100644 --- a/backend_api_python/app/services/llm.py +++ b/backend_api_python/app/services/llm.py @@ -23,6 +23,7 @@ class LLMProvider(Enum): GOOGLE = "google" DEEPSEEK = "deepseek" GROK = "grok" + CUSTOM = "custom" # Provider configurations @@ -52,6 +53,11 @@ class LLMProvider(Enum): "default_model": "grok-beta", "fallback_model": "grok-beta", }, + LLMProvider.CUSTOM: { + "base_url": "", # User configured via CUSTOM_API_URL + "default_model": "", # User configured via CUSTOM_MODEL + "fallback_model": "", + }, } @@ -117,6 +123,7 @@ def get_api_key(self, provider: LLMProvider = None) -> str: LLMProvider.GOOGLE: APIKeys.GOOGLE_API_KEY, LLMProvider.DEEPSEEK: APIKeys.DEEPSEEK_API_KEY, LLMProvider.GROK: APIKeys.GROK_API_KEY, + LLMProvider.CUSTOM: APIKeys.CUSTOM_API_KEY, } return key_map.get(p, "") or "" diff --git a/scripts/generate-secret-key.sh b/scripts/generate-secret-key.sh old mode 100644 new mode 100755