from functools import lru_cache from pathlib import Path from pydantic import Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_prefix="KBQA_", extra="ignore", ) app_name: str = "KBQA" debug: bool = False dashscope_api_key: str = "" dashscope_base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1" chat_model: str = "qwen3.5-flash" embedding_model: str = "text-embedding-v4" embedding_dim: int = 1024 database_url: str = "sqlite+aiosqlite:///./data/kbqa.sqlite3" milvus_uri: str = "./data/milvus/kbqa.db" raw_data_dir: Path = Path("./data/raw") chunk_size: int = Field(default=500, ge=1) chunk_overlap: int = Field(default=50, ge=0) retrieval_top_k: int = Field(default=5, ge=1, le=100) max_research_searches: int = Field(default=3, ge=1, le=10) max_sources: int = Field(default=8, ge=1, le=50) history_message_limit: int = Field(default=20, ge=1, le=200) max_upload_mib: int = Field(default=20, ge=1, le=1024) max_query_length: int = Field(default=10_000, ge=1, le=100_000) cors_origins: list[str] = [ "http://localhost:5173", "http://127.0.0.1:5173", ] run_live_tests: bool = False live_test_file: Path | None = None @field_validator("chunk_overlap") @classmethod def overlap_must_be_smaller_than_chunk(cls, value: int, info) -> int: chunk_size = info.data.get("chunk_size", 500) if value >= chunk_size: raise ValueError("chunk_overlap must be smaller than chunk_size") return value @lru_cache def get_settings() -> Settings: return Settings() def validate_live_settings(settings: Settings) -> None: missing: list[str] = [] if not settings.dashscope_api_key: missing.append("KBQA_DASHSCOPE_API_KEY") if not settings.dashscope_base_url: missing.append("KBQA_DASHSCOPE_BASE_URL") if missing: joined = ", ".join(missing) raise RuntimeError(f"Missing live model configuration: {joined}")