from __future__ import annotations from functools import lru_cache from pathlib import Path from pydantic import Field, SecretStr from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict # 运行时通过源码位置确定项目根目录,不能依赖执行命令时的当前目录。 # 仓库移动、CI Checkout 或其他开发者 Clone 后,该值会自动变化。 PROJECT_ROOT = Path(__file__).resolve().parents[1] class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=PROJECT_ROOT / ".env", env_file_encoding="utf-8", extra="ignore", ) app_env: str = "development" llm_provider: str = "demo" # SecretStr 会在 repr、日志和校验错误中自动隐藏真实凭证。 deepseek_api_key: SecretStr = SecretStr("") deepseek_base_url: str = "https://api.deepseek.com" deepseek_model_name: str = "deepseek-v4-flash" deepseek_answer_thinking: bool = True tavily_api_key: SecretStr = SecretStr("") web_search_max_results: int = Field(default=5, ge=1, le=10) embedding_provider: str = "hash" embedding_model_name: str = ( "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" )7 hash_embedding_dim: int = Field(default=256, ge=64, le=4096) milvus_uri: str = "http://localhost:19530" milvus_token: SecretStr = SecretStr("") milvus_collection: str = "agentic_rag_docs" sqlite_path: Path = Path("./data/shop.db") orders_source_path: Path = Path("./data/source/orders.csv") documents_path: Path = Path("./data/documents") max_retrieval_rounds: int = Field(default=2, ge=1, le=5) min_evidence_score: float = Field(default=0.35, ge=0.0, le=1.0) @field_validator( "sqlite_path", "orders_source_path", "documents_path", mode="after", ) @classmethod def resolve_repository_path(cls, value: Path) -> Path: """将仓库相对路径解析为绝对路径,同时保留外部绝对路径。""" if value.is_absolute(): return value.resolve() return (PROJECT_ROOT / value).resolve() @lru_cache(maxsize=1) def get_settings() -> Settings: return Settings()