import os import sys from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict # 代码根:源码运行时是仓库根;PyInstaller onedir 冻结后是 /_internal ROOT_DIR = Path(__file__).resolve().parent.parent FROZEN = bool(getattr(sys, "frozen", False)) # 可写目录(.env / data / profiles)。桌面端打包后由 Electron 用 ASSISTANT_HOME 指到 userData; # 源码运行默认仓库根;冻结后单独运行默认 exe 所在目录。 HOME_DIR = Path( os.environ.get("ASSISTANT_HOME") or (Path(sys.executable).resolve().parent if FROZEN else ROOT_DIR) ) DATA_DIR = HOME_DIR / "data" TASKS_DIR = DATA_DIR / "tasks" PROFILES_DIR = HOME_DIR / "profiles" ENV_FILE = HOME_DIR / ".env" # 出厂知识 YAML:源码运行读仓库 knowledge/;打包后随应用 resources 分发,Electron 用 KNOWLEDGE_DIR 指过来 KNOWLEDGE_DIR = Path( os.environ.get("KNOWLEDGE_DIR") or (HOME_DIR / "knowledge" if FROZEN else ROOT_DIR / "knowledge") ) MODELS_BY_PROVIDER: dict[str, list[str]] = { "deepseek": [ "deepseek-flash", "deepseek-v4-pro", ], "qwen": [ "qwen-vl-max", "qwen-vl-plus", "qwen-max", ], } class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=str(ENV_FILE), env_file_encoding="utf-8", extra="ignore", ) llm_provider: str = "deepseek" deepseek_api_key: str = "" dashscope_api_key: str = "" # 兜底 Key:主 Key 402 余额不足时自动切换(见 backend/browser/llm_keys.py) deepseek_api_key_fallback: str = "" dashscope_api_key_fallback: str = "" # 切到兜底后多久重试一次主 Key(小时) llm_primary_retry_hours: float = 1.0 llm_model: str = "deepseek-flash" llm_base_url: str = "https://api.deepseek.com/v1" api_port: int = 8788 # 兼容历史数据列名:当前作为拓客工作区隔离标识,旧学校任务与经验不混入。 school_id: str = "prospecting_workspace" # 运维模式:显示沉淀草稿面板等团队用的功能,交付给学校默认关闭 operator_mode: bool = False # 实验性回复直接父归属与只读预检;默认退出主拓客路径。 reply_relation_preflight_enabled: bool = False # learned 自动晋升:同一条知识在不少于 N 个不同任务里出现即自动生效(仅控件技巧 / 入口路径类) learned_auto_verify_min_obs: int = 2 # 历史学校审批回查入口默认退出;当前拓客任务不启动此循环。 tracking_enabled: bool = False tracking_poll_seconds: int = 60 # 试用制:草稿也注入提示词(降一级,标注待验证),用 Agent 的回执当裁判来升降级 trial_enabled: bool = True trial_max_inject: int = 8 # 单次最多注入几条待验证线索 trial_promote_after: int = 2 # 被证实几次升为已确认 trial_demote_after: int = 1 # 被证伪几次降为驳回(错的知识会主动帮倒忙,所以严) # 中心服务端(后台审核台)。不配就是单机模式,本地照常沉淀、自动晋升 hub_url: str = "" hub_token: str = "" hub_sync_seconds: int = 900 hub_timeout_seconds: float = 20.0 # PostgreSQL(docker-compose.yml 中的 postgres 服务) pg_host: str = "127.0.0.1" pg_port: int = 15433 pg_user: str = "assistant" pg_password: str = "assistant_dev_123" pg_database: str = "school_assistant" # 系统账号密码加密密钥(Fernet),见 backend/security.py account_enc_key: str = "" @property def available_models(self) -> list[str]: return MODELS_BY_PROVIDER.get(self.llm_provider, MODELS_BY_PROVIDER["deepseek"]) settings = Settings() DATA_DIR.mkdir(parents=True, exist_ok=True) TASKS_DIR.mkdir(parents=True, exist_ok=True) PROFILES_DIR.mkdir(parents=True, exist_ok=True)