"""Application configuration. Loads settings and secrets from environment variables (and the local .env file) using pydantic-settings. Everything the rest of the app needs — API keys, model names, the SQLite path, generation tunables — lives here, so no other module ever has to touch os.environ directly. Usage: from src.config import settings settings.anthropic_api_key """ from __future__ import annotations import os from pydantic_settings import BaseSettings, SettingsConfigDict # pydantic-settings ranks real environment variables ABOVE the .env file. Some # shells/CI (including the dev environment this was built in) export an *empty* # ANTHROPIC_API_KEY, which would silently shadow the real value in .env. Drop # any of our secrets that are present-but-empty so .env can fill them in. This # is safe on Hugging Face Spaces, where secrets arrive as non-empty env vars and # therefore still take priority. _SECRET_ENV_VARS = ( "ANTHROPIC_API_KEY", "HF_TOKEN", "LANGFUSE_PUBLIC_KEY", "LANGFUSE_SECRET_KEY", "LANGFUSE_HOST", "TAVILY_API_KEY", ) for _key in _SECRET_ENV_VARS: if os.environ.get(_key, None) == "": del os.environ[_key] class Settings(BaseSettings): """Typed view over the .env file / environment variables. Secrets default to empty strings so that *importing* this module never fails just because a key is missing. Each assistant/tool checks that the key it needs is actually present at call time and raises a clear error if not. This keeps the smoke tests and partial setups working. """ model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore", # ignore unrelated env vars instead of erroring ) # --- Secrets (loaded from .env) --- anthropic_api_key: str = "" hf_token: str = "" langfuse_public_key: str = "" langfuse_secret_key: str = "" langfuse_host: str = "https://cloud.langfuse.com" tavily_api_key: str = "" # --- Model identifiers --- # Frontier assistant (and the eval judge) use Claude Sonnet 4.5. frontier_model: str = "claude-sonnet-4-5" # Output-moderation guardrail uses the cheaper/faster Haiku. moderation_model: str = "claude-haiku-4-5" # Open-source assistant. oss_model: str = "Qwen/Qwen2.5-1.5B-Instruct" # --- Generation tunables (shared by both assistants for fair comparison) --- max_tokens: int = 512 temperature: float = 0.7 # --- Storage --- # SQLite file backing the per-session chat memory (added in Phase 3). sqlite_path: str = "./data/sessions.db" # Single shared instance imported across the app. settings = Settings()