Spaces:
Sleeping
Sleeping
File size: 7,089 Bytes
59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 f71cdac 59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 2ae7490 f71cdac 2ae7490 f71cdac 2ae7490 f71cdac 59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 2ae7490 59ebe66 2ae7490 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | """
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."
)
@lru_cache(maxsize=1)
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}")
|