Alfred Ang
Upload folder using huggingface_hub
532ffe7 verified
Raw
History Blame Contribute Delete
9.87 kB
"""
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
]