Spaces:
Sleeping
Sleeping
| """ | |
| Centralized configuration for Grant Analyst. | |
| All runtime configuration loaded from environment variables with sensible defaults. | |
| Supports OpenAI as the primary LLM provider with provider validation. | |
| ENV VARS | |
| -------- | |
| # Environment | |
| ENV=dev|staging|prod # default: dev | |
| # Database | |
| MONGO_URI= # MongoDB connection string | |
| MONGO_DB=grant_analyst # Database name | |
| REDIS_URL= # Redis connection URL (optional) | |
| # LLM Provider | |
| LLM_PROVIDER=openai # Only OpenAI supported | |
| LLM_MODEL=gpt-5-mini # Fallback model | |
| LLM_MODEL_ROUTER=gpt-5-nano # Fast routing/classification | |
| LLM_MODEL_QA=gpt-5-mini # QA and analysis | |
| LLM_MODEL_TRANSLATE=gpt-5-mini # Translations | |
| # Auth | |
| OPENAI_API_KEY= # Required for OpenAI | |
| # Security | |
| ALLOWED_ORIGINS= # Comma-separated CORS origins | |
| # Behavior | |
| MAX_QUERY_CHARS=4000 # Max query length | |
| LLM_TEMPERATURE=0.2 # 0..2 | |
| LLM_MAX_OUTPUT_TOKENS=800 # model-dependent cap | |
| LLM_TIMEOUT_S=30 # HTTP timeout (seconds) | |
| LLM_DISABLE=0 # 1 disables external calls | |
| # Logging | |
| LOG_LEVEL=INFO # DEBUG|INFO|WARNING|ERROR | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from functools import lru_cache | |
| from typing import List, Optional | |
| class Settings: | |
| """Centralized settings loaded from environment variables.""" | |
| # Environment | |
| ENV: str | |
| # Database | |
| MONGO_URI: Optional[str] | |
| MONGO_DB: str | |
| REDIS_URL: Optional[str] | |
| # LLM Configuration | |
| LLM_PROVIDER: str | |
| LLM_MODEL_ROUTER: str | |
| LLM_MODEL_QA: str | |
| LLM_MODEL_TRANSLATE: str | |
| # Auth | |
| OPENAI_API_KEY: Optional[str] | |
| ANTHROPIC_API_KEY: Optional[str] | |
| # Security | |
| ALLOWED_ORIGINS: List[str] | |
| MAX_QUERY_CHARS: int | |
| # LLM Behavior | |
| TEMPERATURE: float | |
| MAX_OUTPUT_TOKENS: int | |
| TIMEOUT_S: float | |
| DISABLE_LLM: bool | |
| # Logging | |
| LOG_LEVEL: str | |
| def __init__(self) -> None: | |
| """Load all settings from environment variables.""" | |
| self.ENV = os.getenv("ENV", "dev") | |
| # Database | |
| self.MONGO_URI = os.getenv("MONGO_URI") | |
| self.MONGO_DB = os.getenv("MONGO_DB", os.getenv("MONGO_DB_NAME", "grant_analyst")) | |
| self.REDIS_URL = os.getenv("REDIS_URL") | |
| # LLM Provider & Models | |
| self.LLM_PROVIDER = os.getenv("LLM_PROVIDER", "openai") | |
| base_model = os.getenv("LLM_MODEL", "gpt-5-mini") | |
| self.LLM_MODEL_ROUTER = os.getenv("LLM_MODEL_ROUTER", os.getenv("LLM_MODEL", "gpt-5-nano")) | |
| self.LLM_MODEL_QA = os.getenv("LLM_MODEL_QA", base_model) | |
| self.LLM_MODEL_TRANSLATE = os.getenv("LLM_MODEL_TRANSLATE", base_model) | |
| # Auth | |
| self.OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | |
| self.ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") | |
| # Security - CORS | |
| allowed = os.getenv("ALLOWED_ORIGINS", "") | |
| if allowed: | |
| self.ALLOWED_ORIGINS = [o.strip() for o in allowed.split(",") if o.strip()] | |
| else: | |
| # Dev-safe default; override in prod | |
| self.ALLOWED_ORIGINS = ["*"] if self.ENV == "dev" else [] | |
| # Security - Limits | |
| self.MAX_QUERY_CHARS = int(os.getenv("MAX_QUERY_CHARS", "4000")) | |
| # LLM Behavior | |
| self.TEMPERATURE = float(os.getenv("LLM_TEMPERATURE", "0.2")) | |
| self.MAX_OUTPUT_TOKENS = int(os.getenv("LLM_MAX_OUTPUT_TOKENS", "800")) | |
| self.TIMEOUT_S = float(os.getenv("LLM_TIMEOUT_S", "30")) | |
| self.DISABLE_LLM = os.getenv("LLM_DISABLE", "0") == "1" | |
| # Logging | |
| self.LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") | |
| # Validate critical settings | |
| self._validate() | |
| def _validate(self) -> None: | |
| """Validate critical configuration.""" | |
| # Only OpenAI is fully supported | |
| if self.LLM_PROVIDER != "openai": | |
| raise ValueError( | |
| f"Unsupported LLM_PROVIDER: {self.LLM_PROVIDER}. " | |
| "Only 'openai' is currently supported." | |
| ) | |
| # Auth validation (skip if LLM is disabled) | |
| if not self.DISABLE_LLM: | |
| if self.LLM_PROVIDER == "openai" and not self.OPENAI_API_KEY: | |
| raise ValueError("OPENAI_API_KEY is required when LLM_PROVIDER=openai") | |
| if self.LLM_PROVIDER == "anthropic" and not self.ANTHROPIC_API_KEY: | |
| raise ValueError("ANTHROPIC_API_KEY is required when LLM_PROVIDER=anthropic") | |
| # Range validation | |
| if not (0.0 <= self.TEMPERATURE <= 2.0): | |
| raise ValueError("LLM_TEMPERATURE must be between 0 and 2") | |
| if self.MAX_OUTPUT_TOKENS <= 0: | |
| raise ValueError("LLM_MAX_OUTPUT_TOKENS must be > 0") | |
| if self.TIMEOUT_S <= 0: | |
| raise ValueError("LLM_TIMEOUT_S must be > 0") | |
| if self.MAX_QUERY_CHARS <= 0: | |
| raise ValueError("MAX_QUERY_CHARS must be > 0") | |
| # Security warning for production | |
| if self.ENV != "dev" and not self.ALLOWED_ORIGINS: | |
| import logging | |
| logging.warning( | |
| "ALLOWED_ORIGINS not set in production environment. " | |
| "CORS will be disabled for security." | |
| ) | |
| def get_settings() -> Settings: | |
| """Get cached settings instance (singleton pattern).""" | |
| return Settings() | |
| # Legacy compatibility: load_config() for existing code | |
| def load_config(): | |
| """ | |
| Legacy function for backward compatibility. | |
| Returns a dict-like object with config values. | |
| DEPRECATED: Use get_settings() instead. | |
| """ | |
| import warnings | |
| warnings.warn( | |
| "load_config() is deprecated. Use get_settings() instead.", DeprecationWarning, stacklevel=2 | |
| ) | |
| settings = get_settings() | |
| # Create a simple namespace object that acts like the old Config dataclass | |
| class LegacyConfig: | |
| def __init__(self, s: Settings): | |
| self.provider = s.LLM_PROVIDER | |
| self.model = s.LLM_MODEL_QA | |
| self.openai_api_key = s.OPENAI_API_KEY | |
| self.anthropic_api_key = s.ANTHROPIC_API_KEY | |
| self.model_router = s.LLM_MODEL_ROUTER | |
| self.model_translator = s.LLM_MODEL_TRANSLATE | |
| self.model_analyzer = s.LLM_MODEL_QA # Use QA model for analysis | |
| self.temperature = s.TEMPERATURE | |
| self.max_output_tokens = s.MAX_OUTPUT_TOKENS | |
| self.timeout_s = s.TIMEOUT_S | |
| self.disable_llm = s.DISABLE_LLM | |
| self.log_level = s.LOG_LEVEL | |
| return LegacyConfig(settings) | |
| # Quick self-test when run directly | |
| if __name__ == "__main__": | |
| try: | |
| s = get_settings() | |
| print("Settings loaded successfully:") | |
| print(f" ENV: {s.ENV}") | |
| print(f" LLM_PROVIDER: {s.LLM_PROVIDER}") | |
| print(f" LLM_MODEL_QA: {s.LLM_MODEL_QA}") | |
| print(f" MONGO_DB: {s.MONGO_DB}") | |
| print(f" ALLOWED_ORIGINS: {s.ALLOWED_ORIGINS}") | |
| except Exception as e: | |
| print(f"Config error: {e}") | |