from pydantic_settings import BaseSettings from pydantic import Field from typing import Optional class Settings(BaseSettings): # Telegram telegram_bot_token: str = Field(..., env="TELEGRAM_BOT_TOKEN") telegram_allowed_users: str = Field("", env="TELEGRAM_ALLOWED_USERS") # comma-separated user IDs # Ollama ollama_url: str = Field("http://ollama.chemavx.xyz", env="OLLAMA_URL") ollama_model: str = Field("qwen2.5:3b", env="OLLAMA_MODEL") ollama_embed_model: str = Field("qwen2.5:3b", env="OLLAMA_EMBED_MODEL") # Claude fallback (optional) anthropic_api_key: Optional[str] = Field(None, env="ANTHROPIC_API_KEY") claude_model: str = Field("claude-haiku-4-5", env="CLAUDE_MODEL") # Database db_path: str = Field("/data/researchowl.db", env="DB_PATH") # Scraping max_depth: int = Field(3, env="MAX_DEPTH") # recursion depth max_sources: int = Field(150, env="MAX_SOURCES") # hard cap max_pages_per_search: int = Field(5, env="MAX_PAGES_PER_SEARCH") request_timeout: int = Field(30, env="REQUEST_TIMEOUT") request_delay: float = Field(1.0, env="REQUEST_DELAY") # seconds between requests min_content_length: int = Field(200, env="MIN_CONTENT_LENGTH") # chars # Processing chunk_size: int = Field(800, env="CHUNK_SIZE") # tokens per chunk chunk_overlap: int = Field(100, env="CHUNK_OVERLAP") quality_threshold: float = Field(0.3, env="QUALITY_THRESHOLD") # 0-1, chunks below discarded # Ghost CMS ghost_url: Optional[str] = Field(None, env="GHOST_URL") ghost_api_key: Optional[str] = Field(None, env="GHOST_API_KEY") ghost_url_en: str = Field("", env="GHOST_URL_EN") ghost_api_key_en: str = Field("", env="GHOST_API_KEY_EN") # Alerts cost_alert_threshold: float = Field(0.15, env="COST_ALERT_THRESHOLD") # App log_level: str = Field("INFO", env="LOG_LEVEL") @property def allowed_user_ids(self) -> list[int]: if not self.telegram_allowed_users: return [] return [int(uid.strip()) for uid in self.telegram_allowed_users.split(",") if uid.strip()] class Config: env_file = ".env" settings = Settings()