saap-plattform / backend /settings.py
Hwandji's picture
feat: initial HuggingFace Space deployment
4343907
"""
SAAP Configuration Settings - Production Ready with OpenRouter Integration
Environment-based configuration management for On-Premise deployment
"""
import os
from pathlib import Path
from typing import Optional, List
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings
from functools import lru_cache
import logging
class DatabaseSettings(BaseSettings):
"""Database configuration settings"""
# Database URL - supports SQLite, PostgreSQL, MySQL
database_url: str = Field(
default="sqlite:///./saap_production.db",
env="DATABASE_URL",
description="Database connection URL"
)
# Connection pool settings
pool_size: int = Field(default=10, env="DB_POOL_SIZE")
max_overflow: int = Field(default=20, env="DB_MAX_OVERFLOW")
pool_timeout: int = Field(default=30, env="DB_POOL_TIMEOUT")
pool_recycle: int = Field(default=3600, env="DB_POOL_RECYCLE")
# SQLite specific
sqlite_check_same_thread: bool = Field(default=False, env="SQLITE_CHECK_SAME_THREAD")
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"case_sensitive": False,
"extra": "allow" # Allow extra fields
}
@field_validator('database_url')
def validate_database_url(cls, v):
"""Ensure database URL is properly formatted"""
if not v.startswith(('sqlite:///', 'postgresql://', 'mysql://')):
raise ValueError('Unsupported database type. Use sqlite, postgresql, or mysql.')
return v
class ColossusSettings(BaseSettings):
"""colossus Server configuration"""
api_base: str = Field(
default="https://ai.adrian-schupp.de",
env="COLOSSUS_API_BASE",
description="colossus server base URL"
)
api_key: str = Field(
default="sk-dBoxml3krytIRLdjr35Lnw",
env="COLOSSUS_API_KEY",
description="colossus API key"
)
default_model: str = Field(
default="mistral-small3.2:24b-instruct-2506",
env="COLOSSUS_DEFAULT_MODEL"
)
timeout: int = Field(default=60, env="COLOSSUS_TIMEOUT")
max_retries: int = Field(default=3, env="COLOSSUS_MAX_RETRIES")
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"case_sensitive": False,
"extra": "allow" # Allow extra fields
}
class OpenRouterSettings(BaseSettings):
"""OpenRouter API configuration for cost-efficient models"""
# API Configuration
api_key: str = Field(
default="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",
env="OPENROUTER_API_KEY",
description="OpenRouter API key for cost-efficient LLM access"
)
base_url: str = Field(
default="https://openrouter.ai/api/v1",
env="OPENROUTER_BASE_URL"
)
enabled: bool = Field(default=True, env="OPENROUTER_ENABLED")
# Cost Optimization Settings
use_cost_optimization: bool = Field(default=True, env="OPENROUTER_USE_COST_OPTIMIZATION")
max_cost_per_request: float = Field(default=0.01, env="OPENROUTER_MAX_COST_PER_REQUEST")
fallback_to_free: bool = Field(default=True, env="OPENROUTER_FALLBACK_TO_FREE")
# Agent-Specific Model Configuration
jane_model: str = Field(default="openai/gpt-4o-mini", env="JANE_ALESI_MODEL")
jane_max_tokens: int = Field(default=800, env="JANE_ALESI_MAX_TOKENS")
jane_temperature: float = Field(default=0.7, env="JANE_ALESI_TEMPERATURE")
john_model: str = Field(default="anthropic/claude-3-haiku", env="JOHN_ALESI_MODEL")
john_max_tokens: int = Field(default=1200, env="JOHN_ALESI_MAX_TOKENS")
john_temperature: float = Field(default=0.5, env="JOHN_ALESI_TEMPERATURE")
lara_model: str = Field(default="openai/gpt-4o-mini", env="LARA_ALESI_MODEL")
lara_max_tokens: int = Field(default=1000, env="LARA_ALESI_MAX_TOKENS")
lara_temperature: float = Field(default=0.3, env="LARA_ALESI_TEMPERATURE")
# Free Model Fallbacks
fallback_model: str = Field(default="meta-llama/llama-3.2-3b-instruct:free", env="FALLBACK_MODEL")
analyst_model: str = Field(default="meta-llama/llama-3.2-3b-instruct:free", env="ANALYST_MODEL")
# Cost Tracking
enable_cost_tracking: bool = Field(default=True, env="ENABLE_COST_TRACKING")
cost_alert_threshold: float = Field(default=5.0, env="COST_ALERT_THRESHOLD")
log_performance_metrics: bool = Field(default=True, env="LOG_PERFORMANCE_METRICS")
save_cost_analytics: bool = Field(default=True, env="SAVE_COST_ANALYTICS")
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"case_sensitive": False,
"extra": "allow"
}
def get_agent_model_config(self, agent_name: str) -> dict:
"""Get model configuration for specific agent"""
configs = {
"jane_alesi": {
"model": self.jane_model,
"max_tokens": self.jane_max_tokens,
"temperature": self.jane_temperature,
"cost_per_1m": 0.15 # GPT-4o-mini
},
"john_alesi": {
"model": self.john_model,
"max_tokens": self.john_max_tokens,
"temperature": self.john_temperature,
"cost_per_1m": 0.25 # Claude-3-Haiku
},
"lara_alesi": {
"model": self.lara_model,
"max_tokens": self.lara_max_tokens,
"temperature": self.lara_temperature,
"cost_per_1m": 0.15 # GPT-4o-mini
}
}
return configs.get(agent_name.lower(), {
"model": self.fallback_model,
"max_tokens": 600,
"temperature": 0.7,
"cost_per_1m": 0.0 # Free model
})
class RedisSettings(BaseSettings):
"""Redis configuration for message queuing"""
host: str = Field(default="localhost", env="REDIS_HOST")
port: int = Field(default=6379, env="REDIS_PORT")
password: Optional[str] = Field(default=None, env="REDIS_PASSWORD")
database: int = Field(default=0, env="REDIS_DB")
max_connections: int = Field(default=50, env="REDIS_MAX_CONNECTIONS")
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"case_sensitive": False,
"extra": "allow" # Allow extra fields
}
class SecuritySettings(BaseSettings):
"""Security and authentication settings"""
secret_key: str = Field(
default="saap-development-secret-change-in-production",
env="SECRET_KEY",
min_length=32
)
# JWT Settings
jwt_algorithm: str = Field(default="HS256", env="JWT_ALGORITHM")
jwt_expire_minutes: int = Field(default=1440, env="JWT_EXPIRE_MINUTES") # 24 hours
# API Rate limiting
rate_limit_requests: int = Field(default=1000, env="RATE_LIMIT_REQUESTS")
rate_limit_window: int = Field(default=3600, env="RATE_LIMIT_WINDOW") # 1 hour
# CORS settings - Updated for network access
allowed_origins: str = Field(
default="http://localhost:5173,http://localhost:8080,http://localhost:3000,http://100.64.0.45:5173,http://100.64.0.45:8080,http://100.64.0.45:3000,http://100.64.0.45:8000",
env="ALLOWED_ORIGINS"
)
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"case_sensitive": False,
"extra": "allow" # Allow extra fields
}
@field_validator('allowed_origins')
def parse_allowed_origins(cls, v):
"""Parse comma-separated origins string into list"""
if isinstance(v, str):
return [origin.strip() for origin in v.split(',') if origin.strip()]
return v
def get_allowed_origins_list(self) -> List[str]:
"""Get allowed origins as a list"""
if isinstance(self.allowed_origins, str):
return [origin.strip() for origin in self.allowed_origins.split(',') if origin.strip()]
return self.allowed_origins
class LoggingSettings(BaseSettings):
"""Enhanced logging configuration with cost tracking"""
log_level: str = Field(default="INFO", env="LOG_LEVEL")
log_format: str = Field(
default="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
env="LOG_FORMAT"
)
# File logging
log_to_file: bool = Field(default=True, env="LOG_TO_FILE")
log_file_path: str = Field(default="logs/saap.log", env="LOG_FILE_PATH")
log_file_max_size: int = Field(default=10485760, env="LOG_FILE_MAX_SIZE") # 10MB
log_file_backup_count: int = Field(default=5, env="LOG_FILE_BACKUP_COUNT")
# Cost & Performance Logging
log_cost_metrics: bool = Field(default=True, env="LOG_COST_METRICS")
cost_log_path: str = Field(default="logs/saap_costs.log", env="COST_LOG_PATH")
performance_log_path: str = Field(default="logs/saap_performance.log", env="PERFORMANCE_LOG_PATH")
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"case_sensitive": False,
"extra": "allow" # Allow extra fields
}
class AgentSettings(BaseSettings):
"""Enhanced agent-specific configuration with multi-provider support"""
default_agent_timeout: int = Field(default=60, env="DEFAULT_AGENT_TIMEOUT")
max_concurrent_agents: int = Field(default=10, env="MAX_CONCURRENT_AGENTS")
agent_health_check_interval: int = Field(default=300, env="AGENT_HEALTH_CHECK_INTERVAL") # 5 minutes
# Performance settings
max_message_history: int = Field(default=1000, env="MAX_MESSAGE_HISTORY")
cleanup_old_messages_days: int = Field(default=30, env="CLEANUP_OLD_MESSAGES_DAYS")
# Multi-Provider Strategy
primary_provider: str = Field(default="colossus", env="PRIMARY_PROVIDER") # colossus, openrouter
fallback_provider: str = Field(default="openrouter", env="FALLBACK_PROVIDER")
auto_fallback_on_error: bool = Field(default=True, env="AUTO_FALLBACK_ON_ERROR")
fallback_timeout_threshold: int = Field(default=30, env="FALLBACK_TIMEOUT_THRESHOLD") # seconds
# Cost Efficiency Targets
target_response_time: float = Field(default=2.0, env="TARGET_RESPONSE_TIME") # seconds
target_cost_per_request: float = Field(default=0.002, env="TARGET_COST_PER_REQUEST") # $0.002
cost_vs_speed_priority: str = Field(default="balanced", env="COST_VS_SPEED_PRIORITY") # cost, speed, balanced
# Daily Cost Budgets
daily_cost_budget: float = Field(default=10.0, env="DAILY_COST_BUDGET") # $10/day
agent_cost_budget: float = Field(default=2.0, env="AGENT_COST_BUDGET") # $2/agent/day
warning_cost_threshold: float = Field(default=0.80, env="WARNING_COST_THRESHOLD") # 80%
# Model Selection Strategy
use_free_models_first: bool = Field(default=False, env="USE_FREE_MODELS_FIRST")
smart_model_selection: bool = Field(default=True, env="SMART_MODEL_SELECTION")
cost_learning_enabled: bool = Field(default=True, env="COST_LEARNING_ENABLED")
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"case_sensitive": False,
"extra": "allow" # Allow extra fields
}
@field_validator('primary_provider', 'fallback_provider')
def validate_provider(cls, v):
"""Validate provider names"""
allowed = ['colossus', 'openrouter']
if v not in allowed:
raise ValueError(f'Provider must be one of: {allowed}')
return v
@field_validator('cost_vs_speed_priority')
def validate_priority(cls, v):
"""Validate priority setting"""
allowed = ['cost', 'speed', 'balanced']
if v not in allowed:
raise ValueError(f'Priority must be one of: {allowed}')
return v
class SaapSettings(BaseSettings):
"""Main SAAP Application Settings with OpenRouter Integration"""
# Application Info
app_name: str = Field(default="SAAP - satware AI Autonomous Agent Platform", env="APP_NAME")
app_version: str = Field(default="1.0.0", env="APP_VERSION")
environment: str = Field(default="production", env="ENVIRONMENT")
debug: bool = Field(default=False, env="DEBUG")
# Server settings - Updated for network access
host: str = Field(default="100.64.0.45", env="HOST") # 🌐 Changed from 0.0.0.0
port: int = Field(default=8000, env="PORT")
reload: bool = Field(default=False, env="RELOAD")
# Component Settings
database: DatabaseSettings = DatabaseSettings()
colossus: ColossusSettings = ColossusSettings()
openrouter: OpenRouterSettings = OpenRouterSettings() # NEW!
redis: RedisSettings = RedisSettings()
security: SecuritySettings = SecuritySettings()
logging: LoggingSettings = LoggingSettings()
agents: AgentSettings = AgentSettings()
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"case_sensitive": False,
"extra": "allow" # Allow extra fields from .env that aren't explicitly defined
}
@field_validator('environment')
def validate_environment(cls, v):
"""Validate environment setting"""
allowed_envs = ['development', 'staging', 'production', 'testing']
if v.lower() not in allowed_envs:
raise ValueError(f'Environment must be one of: {allowed_envs}')
return v.lower()
def get_database_url(self) -> str:
"""Get database URL with environment-specific adjustments"""
if self.environment == 'testing':
return "sqlite:///./test_saap.db"
elif self.environment == 'development':
return "sqlite:///./saap_dev.db"
else:
return self.database.database_url
def is_production(self) -> bool:
"""Check if running in production"""
return self.environment == 'production'
def get_cors_origins(self) -> List[str]:
"""Get CORS allowed origins as list"""
return self.security.get_allowed_origins_list()
def get_log_config(self) -> dict:
"""Get logging configuration for uvicorn/fastapi"""
return {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": self.logging.log_format,
},
"cost": {
"format": "%(asctime)s - COST - %(message)s",
},
"performance": {
"format": "%(asctime)s - PERF - %(message)s",
}
},
"handlers": {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
},
"file": {
"formatter": "default",
"class": "logging.handlers.RotatingFileHandler",
"filename": self.logging.log_file_path,
"maxBytes": self.logging.log_file_max_size,
"backupCount": self.logging.log_file_backup_count,
} if self.logging.log_to_file else None,
"cost_file": {
"formatter": "cost",
"class": "logging.handlers.RotatingFileHandler",
"filename": self.logging.cost_log_path,
"maxBytes": self.logging.log_file_max_size,
"backupCount": self.logging.log_file_backup_count,
} if self.logging.log_cost_metrics else None,
"performance_file": {
"formatter": "performance",
"class": "logging.handlers.RotatingFileHandler",
"filename": self.logging.performance_log_path,
"maxBytes": self.logging.log_file_max_size,
"backupCount": self.logging.log_file_backup_count,
} if self.logging.log_cost_metrics else None
},
"loggers": {
"": {
"handlers": [h for h in ["default", "file"] if h],
"level": self.logging.log_level,
},
"saap.cost": {
"handlers": [h for h in ["cost_file"] if h],
"level": "INFO",
"propagate": False,
},
"saap.performance": {
"handlers": [h for h in ["performance_file"] if h],
"level": "INFO",
"propagate": False,
}
},
}
def get_openrouter_config_for_agent(self, agent_name: str) -> dict:
"""Get OpenRouter configuration for specific agent"""
return self.openrouter.get_agent_model_config(agent_name)
@lru_cache()
def get_settings() -> SaapSettings:
"""Get cached settings instance"""
return SaapSettings()
# Global settings instance
settings = get_settings()
# Create logs directory if needed
if settings.logging.log_to_file:
Path(settings.logging.log_file_path).parent.mkdir(parents=True, exist_ok=True)
if settings.logging.log_cost_metrics:
Path(settings.logging.cost_log_path).parent.mkdir(parents=True, exist_ok=True)
Path(settings.logging.performance_log_path).parent.mkdir(parents=True, exist_ok=True)
# Environment-specific logging setup
def setup_logging():
"""Setup logging configuration"""
import logging.config
log_config = settings.get_log_config()
logging.config.dictConfig(log_config)
logger = logging.getLogger(__name__)
logger.info(f"πŸš€ SAAP Configuration loaded:")
logger.info(f" Environment: {settings.environment}")
logger.info(f" Database: {settings.get_database_url()}")
logger.info(f" colossus: {settings.colossus.api_base}")
logger.info(f" OpenRouter: {settings.openrouter.enabled}")
logger.info(f" Redis: {settings.redis.host}:{settings.redis.port}")
logger.info(f" Debug Mode: {settings.debug}")
logger.info(f"🌐 Server Host: {settings.host}:{settings.port}") # Log the network settings
logger.info(f"πŸ”’ CORS Origins: {settings.get_cors_origins()}")
# Cost tracking logger
if settings.logging.log_cost_metrics:
cost_logger = logging.getLogger("saap.cost")
cost_logger.info("πŸ’° Cost tracking initialized")
performance_logger = logging.getLogger("saap.performance")
performance_logger.info("πŸ“Š Performance monitoring initialized")
if __name__ == "__main__":
# Test configuration
setup_logging()
logger = logging.getLogger(__name__)
logger.info("πŸ§ͺ Configuration Test:")
logger.info(f" App Name: {settings.app_name}")
logger.info(f" Database URL: {settings.get_database_url()}")
logger.info(f" Production Mode: {settings.is_production()}")
logger.info(f" OpenRouter Enabled: {settings.openrouter.enabled}")
logger.info(f"🌐 Network Settings:")
logger.info(f" Host: {settings.host}")
logger.info(f" Port: {settings.port}")
logger.info(f" CORS Origins: {settings.get_cors_origins()}")
# Test agent model configs
for agent in ['jane_alesi', 'john_alesi', 'lara_alesi']:
config = settings.get_openrouter_config_for_agent(agent)
logger.info(f" {agent}: {config['model']} (${config['cost_per_1m']}/1M tokens)")