"""Application settings — Pydantic v2 style, no module-level singleton. The container (config/container.py) instantiates Settings once at startup and passes it down to adapters. This keeps tests free of singleton state. """ from pathlib import Path from typing import Optional from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """All runtime configuration, sourced from environment variables or a .env file. Fields use explicit ``env=`` aliases so the variable names are immediately visible and grep-able without needing to know Pydantic's casing rules. """ model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", # Allow unknown env vars — avoids breakage when the .env file has # variables that belong to other services (Discord bot, scripts, etc.). extra="ignore", ) # ------------------------------------------------------------------ # OpenRouter / LLM # ------------------------------------------------------------------ openrouter_api_key: str = Field(default="", alias="OPENROUTER_API_KEY") openrouter_model: str = Field( default="stepfun/step-3.5-flash:free", alias="OPENROUTER_MODEL" ) # ------------------------------------------------------------------ # Discord # ------------------------------------------------------------------ discord_bot_token: str = Field(default="", alias="DISCORD_BOT_TOKEN") discord_guild_id: Optional[str] = Field(default=None, alias="DISCORD_GUILD_ID") # ------------------------------------------------------------------ # Gitea issue tracker # ------------------------------------------------------------------ gitea_token: str = Field(default="", alias="GITEA_TOKEN") gitea_owner: str = Field(default="cal", alias="GITEA_OWNER") gitea_repo: str = Field(default="strat-chatbot", alias="GITEA_REPO") gitea_base_url: str = Field( default="https://git.manticorum.com/api/v1", alias="GITEA_BASE_URL" ) # ------------------------------------------------------------------ # File-system paths # ------------------------------------------------------------------ data_dir: Path = Field(default=Path("./data"), alias="DATA_DIR") rules_dir: Path = Field(default=Path("./data/rules"), alias="RULES_DIR") chroma_dir: Path = Field(default=Path("./data/chroma"), alias="CHROMA_DIR") # ------------------------------------------------------------------ # Database # ------------------------------------------------------------------ db_url: str = Field( default="sqlite+aiosqlite:///./data/conversations.db", alias="DB_URL" ) # ------------------------------------------------------------------ # API authentication # ------------------------------------------------------------------ api_secret: str = Field(default="", alias="API_SECRET") # ------------------------------------------------------------------ # Conversation / retrieval tuning # ------------------------------------------------------------------ conversation_ttl: int = Field(default=1800, alias="CONVERSATION_TTL") top_k_rules: int = Field(default=10, alias="TOP_K_RULES") embedding_model: str = Field( default="sentence-transformers/all-MiniLM-L6-v2", alias="EMBEDDING_MODEL" )