Spaces:
Runtime error
Runtime error
| """ | |
| Dynamic API Key and Model Management | |
| This module handles dynamic storage and retrieval of API keys and LLM models. | |
| - API keys are loaded from environment variables or secrets.toml | |
| - All model configurations (built-in + custom) are stored in SQLite database | |
| Author: Wong Xin Ping | |
| Updated: February 2026 | |
| """ | |
| import json | |
| import os | |
| from typing import Dict, Any, List, Optional | |
| from dotenv import load_dotenv | |
| # Import SQLite database operations | |
| from settings.api_database import ( | |
| get_all_models as db_get_all_models, | |
| get_all_custom_models as db_get_all_custom_models, | |
| get_builtin_models as db_get_builtin_models, | |
| add_custom_model as db_add_custom_model, | |
| delete_custom_model as db_delete_custom_model, | |
| model_exists as db_model_exists, | |
| migrate_from_json, | |
| migrate_from_old_schema, | |
| init_database, | |
| refresh_builtin_models as db_refresh_builtin_models, | |
| refresh_builtin_api_keys as db_refresh_builtin_api_keys, | |
| # API key config functions | |
| get_all_api_key_configs as db_get_all_api_key_configs, | |
| get_api_key_config as db_get_api_key_config, | |
| add_api_key_config as db_add_api_key_config, | |
| delete_api_key_config as db_delete_api_key_config, | |
| update_api_key_configured_status as db_update_api_key_configured_status, | |
| api_key_config_exists as db_api_key_config_exists | |
| ) | |
| # Load environment variables from .env file | |
| load_dotenv() | |
| # Legacy JSON file paths (for migration) | |
| LEGACY_CUSTOM_MODELS_FILE = "settings/config/custom_models.json" | |
| # Module-level cache (replaces st.session_state) | |
| _cache: Dict[str, Any] = {} | |
| # Try to import Streamlit secrets (optional) | |
| _st_secrets = None | |
| try: | |
| import streamlit as st | |
| _st_secrets = st.secrets | |
| except (ImportError, Exception): | |
| pass | |
| def _get_secret(key: str, default: str = "") -> str: | |
| """Safely get a secret from environment variables or Streamlit secrets""" | |
| # First try environment variables (from .env file) | |
| env_value = os.environ.get(key, "") | |
| if env_value: | |
| return env_value | |
| # Then try streamlit secrets if available | |
| if _st_secrets is not None: | |
| try: | |
| return _st_secrets.get(key, default) | |
| except Exception: | |
| pass | |
| return default | |
| def load_api_keys() -> Dict[str, str]: | |
| """Load all API keys from secrets.toml or environment variables based on database config""" | |
| # Initialize database to ensure api_keys table exists | |
| init_database() | |
| # Get all configured API key names from database | |
| api_key_configs = db_get_all_api_key_configs() | |
| # Load each configured API key from secrets/env | |
| api_keys = {} | |
| for config in api_key_configs: | |
| key_name = config["key_name"] | |
| key_value = _get_secret(key_name, "") | |
| api_keys[key_name] = key_value | |
| # Update configured status in database | |
| db_update_api_key_configured_status(key_name, bool(key_value)) | |
| # Cache for performance | |
| _cache['api_keys'] = api_keys | |
| return api_keys | |
| def save_api_keys(keys: Dict[str, str]) -> bool: | |
| """ | |
| Save API keys to cache. | |
| Note: Actual persistence should be done by editing secrets.toml or .env | |
| """ | |
| try: | |
| _cache['api_keys'] = keys | |
| return True | |
| except Exception as e: | |
| print(f"Error saving API keys: {e}") | |
| return False | |
| def get_api_key(provider: str) -> str: | |
| """Get API key for specific provider""" | |
| keys = load_api_keys() | |
| key_name = f"{provider.upper()}_API_KEY" | |
| return keys.get(key_name, "") | |
| def delete_api_key(key_name: str) -> bool: | |
| """Clear an API key from cache""" | |
| try: | |
| keys = load_api_keys() | |
| if key_name in keys: | |
| keys[key_name] = "" | |
| return save_api_keys(keys) | |
| return False | |
| except Exception as e: | |
| print(f"Error deleting API key: {e}") | |
| return False | |
| def _migrate_json_to_sqlite(): | |
| """Migrate custom models from JSON to SQLite (one-time migration)""" | |
| if os.path.exists(LEGACY_CUSTOM_MODELS_FILE): | |
| try: | |
| with open(LEGACY_CUSTOM_MODELS_FILE, 'r') as f: | |
| json_models = json.load(f) | |
| if json_models: | |
| migrated = migrate_from_json(json_models) | |
| if migrated > 0: | |
| print(f"Migrated {migrated} custom models from JSON to SQLite") | |
| # Rename old file to indicate migration complete | |
| backup_file = LEGACY_CUSTOM_MODELS_FILE + ".migrated" | |
| os.rename(LEGACY_CUSTOM_MODELS_FILE, backup_file) | |
| print(f"Renamed {LEGACY_CUSTOM_MODELS_FILE} to {backup_file}") | |
| except Exception as e: | |
| print(f"Error during migration: {e}") | |
| def load_custom_models() -> List[Dict[str, Any]]: | |
| """Load custom (non-built-in) LLM models from SQLite database""" | |
| # Check for and perform migration if needed | |
| _migrate_json_to_sqlite() | |
| # Load from SQLite | |
| models = db_get_all_custom_models() | |
| # Cache | |
| _cache['custom_models'] = models | |
| return models | |
| def load_builtin_models() -> List[Dict[str, Any]]: | |
| """Load built-in LLM models from SQLite database""" | |
| return db_get_builtin_models() | |
| def save_custom_models(models: List[Dict[str, Any]]) -> bool: | |
| """ | |
| Save custom models - now handled by individual add/remove operations. | |
| This function is kept for backward compatibility. | |
| """ | |
| _cache['custom_models'] = models | |
| return True | |
| def add_custom_model( | |
| name: str, | |
| provider: str, | |
| model_id: str, | |
| base_url: str = "", | |
| temperature: float = 0.2, | |
| api_provider: str = "", | |
| custom_api_key: str = "" | |
| ) -> bool: | |
| """Add a new custom model to SQLite database""" | |
| # Check if model already exists | |
| if db_model_exists(name): | |
| print(f"Model Display Name '{name}' already exists! Please use a different name.") | |
| return False | |
| # Add to database | |
| success = db_add_custom_model( | |
| name=name, | |
| model_id=model_id, | |
| provider=provider, | |
| base_url=base_url if base_url else "https://openrouter.ai/api/v1", | |
| temperature=temperature, | |
| api_provider=api_provider if api_provider else "OPENROUTER" | |
| ) | |
| if success: | |
| # Clear cache to force reload | |
| _cache.pop('custom_models', None) | |
| _cache.pop('all_models', None) | |
| return success | |
| def remove_custom_model(name: str) -> bool: | |
| """Remove a custom model from SQLite database""" | |
| success = db_delete_custom_model(name) | |
| if success: | |
| # Clear cache to force reload | |
| _cache.pop('custom_models', None) | |
| _cache.pop('all_models', None) | |
| return success | |
| def get_all_available_models() -> Dict[str, Dict[str, Any]]: | |
| """Get all available models (built-in + custom) with current API keys from SQLite""" | |
| # Get current API keys | |
| current_keys = load_api_keys() | |
| # Get all models from SQLite database | |
| all_models = db_get_all_models(include_builtin=True) | |
| updated_models = {} | |
| for model in all_models: | |
| # Resolve API key based on api_provider | |
| api_provider = model.get("api_provider", "OPENROUTER") | |
| resolved_key = current_keys.get(f"{api_provider}_API_KEY", "") | |
| # Create model config with resolved API key | |
| model_with_key = { | |
| "name": model["name"], | |
| "provider": model["provider"], | |
| "config": { | |
| "model": model["config"]["model"], | |
| "temperature": model["config"]["temperature"], | |
| "base_url": model["config"].get("base_url", "https://openrouter.ai/api/v1"), | |
| "api_key": resolved_key | |
| }, | |
| "api_provider": api_provider, | |
| "is_builtin": model.get("is_builtin", False) | |
| } | |
| updated_models[model["name"]] = model_with_key | |
| return updated_models | |
| def initialize_api_system(): | |
| """Initialize the API system on app startup""" | |
| # Initialize SQLite database (includes seeding built-in models and API key configs) | |
| init_database() | |
| # Migrate from old schema if needed | |
| migrate_from_old_schema() | |
| # Check for JSON migration | |
| _migrate_json_to_sqlite() | |
| # Refresh built-in models and API keys (updates display names and adds new models) | |
| db_refresh_builtin_models() | |
| db_refresh_builtin_api_keys() | |
| # Load API keys into cache | |
| load_api_keys() | |
| # ============ API Key Configuration Management ============ | |
| def get_all_api_key_configs() -> List[Dict[str, Any]]: | |
| """Get all API key configurations from database""" | |
| return db_get_all_api_key_configs() | |
| def add_api_key_config( | |
| key_name: str, | |
| display_name: str, | |
| base_url: str = "", | |
| description: str = "" | |
| ) -> bool: | |
| """Add a new API key configuration""" | |
| # Ensure key_name is in correct format | |
| if not key_name.endswith("_API_KEY"): | |
| key_name = f"{key_name.upper()}_API_KEY" | |
| if db_api_key_config_exists(key_name): | |
| print(f"API key '{key_name}' already exists!") | |
| return False | |
| success = db_add_api_key_config( | |
| key_name=key_name, | |
| display_name=display_name, | |
| base_url=base_url, | |
| description=description | |
| ) | |
| if success: | |
| # Clear cache to force reload | |
| _cache.pop('api_keys', None) | |
| return success | |
| def remove_api_key_config(key_name: str) -> bool: | |
| """Remove an API key configuration (only custom ones)""" | |
| success = db_delete_api_key_config(key_name) | |
| if success: | |
| # Clear cache to force reload | |
| _cache.pop('api_keys', None) | |
| return success | |
| def get_api_providers_for_dropdown() -> List[Dict[str, str]]: | |
| """Get list of API providers for dropdown selection in custom model form""" | |
| configs = db_get_all_api_key_configs() | |
| return [ | |
| { | |
| "key_name": c["key_name"], | |
| "display_name": c["display_name"], | |
| "base_url": c["base_url"] or "" | |
| } | |
| for c in configs | |
| ] | |