Spaces:
Running
Running
File size: 5,002 Bytes
4ef118d 592cb1d 4ef118d | 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 | """
Application configuration using Pydantic Settings.
Loads configuration from environment variables and .env files.
"""
import os
from pathlib import Path
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
def get_backend_dir() -> Path:
"""Get the backend directory path."""
# Try to get from environment first
if env_path := os.getenv("BACKEND_DIR"):
return Path(env_path)
# Fallback to script location
return Path(__file__).parent.parent
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
# Server Configuration
host: str = Field(default="127.0.0.1", alias="HOST")
port: int = Field(default=3002, alias="PORT")
# CORS Configuration
frontend_url: str = Field(default="http://localhost:3000", alias="FRONTEND_URL")
frontend_urls: str = Field(
default="http://localhost:3000",
alias="FRONTEND_URLS",
)
# SSE Configuration
sse_flush_ms: int = Field(default=50, alias="SSE_FLUSH_MS")
sse_heartbeat_ms: int = Field(default=10000, alias="SSE_HEARTBEAT_MS")
# Supabase Configuration
supabase_project_name: str = Field(default="", alias="SUPABASE_PROJECT_NAME")
supabase_url: str = Field(default="", alias="SUPABASE_URL")
supabase_password: str = Field(default="", alias="SUPABASE_PASSWORD")
supabase_service_role_key: str = Field(default="", alias="SUPABASE_SERVICE_ROLE_KEY")
supabase_db_url: str = Field(default="", alias="SUPABASE_DB_URL")
# Tavily API (for web search)
tavily_api_key: str = Field(default="", alias="TAVILY_API_KEY")
# Jina AI (for webpage reading)
jina_api_key: str = Field(default="", alias="JINA_API_KEY")
# Debug Flags
debug_stream: bool = Field(default=False, alias="DEBUG_STREAM")
debug_tools: bool = Field(default=False, alias="DEBUG_TOOLS")
debug_sources: bool = Field(default=False, alias="DEBUG_SOURCES")
# Context Message Limit
context_message_limit: int = Field(default=50, alias="CONTEXT_MESSAGE_LIMIT")
# Database Providers (backend-managed)
database_providers_json: str = Field(default="", alias="DATABASE_PROVIDERS")
db_access_key: str = Field(default="", alias="DB_PROVIDER_ACCESS_KEY")
database_provider: str = Field(default="", alias="DATABASE_PROVIDER")
database_url: str = Field(default="", alias="DATABASE_URL")
database_path: str = Field(default="", alias="DATABASE_PATH")
database_label: str = Field(default="", alias="DATABASE_LABEL")
# Session Summary Configuration
summary_lite_provider: str = Field(default="openai", alias="SUMMARY_LITE_PROVIDER")
summary_lite_model: str = Field(default="gpt-4o-mini", alias="SUMMARY_LITE_MODEL")
summary_agent_api_key: str = Field(default="", alias="SUMMARY_AGENT_API_KEY")
summary_lite_base_url: str | None = Field(default=None, alias="SUMMARY_LITE_BASE_URL")
# Memory Lite Model Configuration (Legacy / Long Term Memory)
memory_lite_provider: str = Field(default="openai", alias="MEMORY_LITE_PROVIDER")
memory_lite_model: str = Field(default="gpt-4o-mini", alias="MEMORY_LITE_MODEL")
memory_agent_api_key: str = Field(default="", alias="MEMORY_AGENT_API_KEY")
memory_lite_base_url: str | None = Field(default=None, alias="MEMORY_LITE_BASE_URL")
# Model configuration
@property
def allowed_origins(self) -> list[str]:
"""Parse frontend_urls into a list of allowed origins."""
return [origin.strip() for origin in self.frontend_urls.split(",") if origin.strip()]
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
env_ignore_empty=True,
extra="ignore",
)
@field_validator("sse_flush_ms", "sse_heartbeat_ms")
@classmethod
def validate_positive(cls, v: int) -> int:
"""Validate that numeric values are non-negative."""
return max(0, v)
# Global settings instance
_settings: Settings | None = None
def get_settings() -> Settings:
"""Get the global settings instance (singleton)."""
global _settings
if _settings is None:
if os.getenv("QURIO_ELECTRON", "0") == "1":
_settings = Settings(_env_file=None)
return _settings
# Search paths for env files (priority order)
src_dir = Path(__file__).parent
backend_dir = src_dir.parent
candidates = [
src_dir / ".env.local",
backend_dir / ".env.local",
src_dir / ".env",
backend_dir / ".env",
]
env_file = None
for candidate in candidates:
if candidate.exists():
env_file = str(candidate)
break
_settings = Settings(_env_file=env_file)
return _settings
def reload_settings() -> Settings:
"""Reload settings from environment variables."""
global _settings
_settings = None
return get_settings()
|