from functools import lru_cache import logging from pathlib import Path from pydantic_settings import BaseSettings logger = logging.getLogger(__name__) class Settings(BaseSettings): # LLM access (OpenRouter / vLLM) llm_base_url: str = "https://openrouter.ai/api/v1" llm_api_key: str = "" llm_model_name: str = "qwen/qwen3.5-35b-a3b" llm_enable_thinking: bool = False vllm_engine: bool = False # Deepseek API (decompose step only, Package 6) dp_base_url: str = "https://api.deepseek.com" dp_api_key: str = "" dp_model_name: str = "deepseek-v4-flash" # Embeddings embedding_model: str = "qwen/qwen3-embedding-4b" embedding_base_url: str = "https://openrouter.ai/api/v1" embedding_api_key: str = "" # ChromaDB chroma_db_path: str = "./chroma_db" # Chunk PDF storage (extracted PDF pages) document_chunk_path: str = "./document_chunk" # SQLite databases (Package 3) prompts_db_path: str = "./data/prompts.db" history_db_path: str = "./data/history.db" # App configuration moved to settings for easier testing/configuration # Cross-origin settings and chunking parameters (Phase 1 plan) cors_origins: list[str] = ["http://localhost:5173", "http://localhost:3000"] chunk_size: int = 1000 chunk_overlap: int = 200 retrieval_n_results: int = 10 relevance_threshold: float = 7.0 llm_timeout: float = 60.0 # Q&A-pair chunking strategy (Package 8) default_chunking_strategy: str = "token" qa_vision_enabled: bool = True qa_max_chunk_tokens: int = 3000 qa_structure_model: str = "" qa_include_internal_refs: bool = True qa_cache_vision_results: bool = True # ASR Configuration (Phase 2 + Phase 5) # Provider: "dashscope" (batch + realtime) or "openrouter" (batch-only) asr_provider: str = "dashscope" # DashScope ASR (used when asr_provider=dashscope, or for realtime WebSocket) dashscope_api_key: str = "" asr_model_name: str = "qwen3-asr-flash" asr_realtime_model_name: str = "qwen3-asr-flash-realtime" # OpenRouter STT (used when asr_provider=openrouter) openrouter_api_key: str = "" asr_openrouter_model: str = "google/chirp-3" # Video upload (Phase 2) video_upload_dir: str = "./uploads" max_video_size_mb: int = 300 supported_video_formats: list[str] = [".mp4", ".webm", ".mov", ".avi", ".mkv"] # Phase 4 — Live audio capture toggles system_audio_enabled: bool = True mic_enabled: bool = True # Development helpers model_config = {"env_file": ".env", "env_file_encoding": "utf-8"} VALID_ASR_PROVIDERS = frozenset({"dashscope", "openrouter"}) @lru_cache def get_settings() -> Settings: s = Settings() logger.info( "Settings loaded: llm_model=%s embedding_model=%s asr_provider=%s", s.llm_model_name, s.embedding_model, s.asr_provider, ) if s.asr_provider not in VALID_ASR_PROVIDERS: raise ValueError( f"Invalid ASR_PROVIDER '{s.asr_provider}'. " f"Must be one of: {', '.join(sorted(VALID_ASR_PROVIDERS))}" ) return s