import os from dataclasses import dataclass, field from pathlib import Path from dotenv import load_dotenv load_dotenv(Path(__file__).resolve().parent.parent / ".env") @dataclass(frozen=True) class Settings: # LLM 配置 llm_api_key: str = field( default_factory=lambda: os.getenv("LLM_API_KEY", ""), repr=False, ) llm_model: str = field(default_factory=lambda: os.getenv("LLM_MODEL", "")) llm_base_url: str | None = field( default_factory=lambda: os.getenv("LLM_BASE_URL") or None ) llm_timeout: float = 60.0 llm_max_retries: int = 2 # Jev typesafe_api_key: str = field( default_factory=lambda: os.getenv("TYPESAFE_API_KEY", ""), repr=False ) typesafe_model: str = field( default_factory=lambda: os.getenv("TYPESAFE_DEFAULT_MODEL") or "jev-latest" ) typesafe_base_url: str = field( default_factory=lambda: ( os.getenv("TYPESAFE_BASE_URL") or "https://api.typesafe.ai" ) ) typesafe_timeout: float = field( default_factory=lambda: float(os.getenv("TYPESAFE_TIMEOUT", "30")) ) typesafe_max_retries: int = field( default_factory=lambda: int(os.getenv("TYPESAFE_MAX_RETRIES", "2")) ) # 意图门控使用选中项自身的概率,阈值需按真实语料校准 # 新建、修改会改动大屏,阈值较严;问答判错代价低,阈值较宽 intent_min_probability: float = field( default_factory=lambda: float(os.getenv("INTENT_MIN_PROBABILITY", "0.7")) ) intent_qa_min_probability: float = field( default_factory=lambda: float(os.getenv("INTENT_QA_MIN_PROBABILITY", "0.5")) ) # 同一问题最多澄清的轮数,用尽后不再追问 intent_max_clarify_rounds: int = field( default_factory=lambda: int(os.getenv("INTENT_MAX_CLARIFY_ROUNDS", "1")) ) # 动作自动修正次数,不含首次生成,0 表示不重试,与用户澄清轮数无关 max_action_retries: int = field( default_factory=lambda: int(os.getenv("MAX_ACTION_RETRIES", "2")) ) # 物料字段 MCP 服务:只给模型查询字段定义,只监听本机,随应用启动 mcp_material_url: str = field( default_factory=lambda: ( os.getenv("MCP_MATERIAL_URL") or "http://127.0.0.1:8201/mcp" ) ) # 单次字段查询的超时(秒) mcp_tool_timeout: float = field( default_factory=lambda: float(os.getenv("MCP_TOOL_TIMEOUT", "5")) ) # 生成动作或修正建议时,最多几轮查询字段,之后必须给出结果 max_field_tool_rounds: int = field( default_factory=lambda: int(os.getenv("MAX_FIELD_TOOL_ROUNDS", "4")) ) # 没有任何对话的空会话保留时长(秒),超时后视为不存在 conversation_empty_ttl: int = field( default_factory=lambda: int(os.getenv("CONVERSATION_EMPTY_TTL", "7200")) ) # 每次请求带入模型的最近对话轮数 conversation_history_turns: int = field( default_factory=lambda: int(os.getenv("CONVERSATION_HISTORY_TURNS", "6")) ) # MongoDB 连接串,未配置时会话记录和检查点回退为进程内存实现 mongodb_uri: str | None = field( default_factory=lambda: os.getenv("MONGODB_URI") or None, repr=False ) mongodb_db: str = field( default_factory=lambda: os.getenv("MONGODB_DB") or "screen_agent" ) # 检查点保留时长(秒),超时由 MongoDB TTL 索引清理,0 表示永久保留 mongodb_checkpoint_ttl: int = field( default_factory=lambda: int(os.getenv("MONGODB_CHECKPOINT_TTL", "604800")) ) # 用户中心:请求携带的 token 交给它校验,换回 user_id user_center_url: str = field( default_factory=lambda: ( os.getenv("USER_CENTER_URL") or "http://127.0.0.1:8100" ).rstrip("/") ) user_center_timeout_seconds: float = field( default_factory=lambda: float(os.getenv("USER_CENTER_TIMEOUT_SECONDS", "5")) ) # 关闭后不校验 token,所有请求视为同一个本地调试用户,仅用于开发 auth_enabled: bool = field( default_factory=lambda: ( os.getenv("AUTH_ENABLED", "true").strip().lower() not in ("false", "0", "no", "off") ) ) # token 校验结果缓存时长(秒),登出或禁用后最多延迟这么久失效,0 表示不缓存 auth_cache_ttl_seconds: int = field( default_factory=lambda: int(os.getenv("AUTH_CACHE_TTL_SECONDS", "30")) ) def __post_init__(self): if self.user_center_timeout_seconds <= 0: raise ValueError("USER_CENTER_TIMEOUT_SECONDS 必须是正数") if self.auth_cache_ttl_seconds < 0: raise ValueError("AUTH_CACHE_TTL_SECONDS 必须是非负整数") if self.mongodb_checkpoint_ttl < 0: raise ValueError("MONGODB_CHECKPOINT_TTL 必须是非负整数") if self.conversation_empty_ttl <= 0: raise ValueError("CONVERSATION_EMPTY_TTL 必须是正整数") if self.conversation_history_turns < 0: raise ValueError("CONVERSATION_HISTORY_TURNS 必须是非负整数") if type(self.max_action_retries) is not int or self.max_action_retries < 0: raise ValueError("MAX_ACTION_RETRIES 必须是非负整数") if self.mcp_tool_timeout <= 0: raise ValueError("MCP_TOOL_TIMEOUT 必须是正数") if self.max_field_tool_rounds < 0: raise ValueError("MAX_FIELD_TOOL_ROUNDS 必须是非负整数") if not 0 <= self.intent_min_probability <= 1: raise ValueError("INTENT_MIN_PROBABILITY 必须在 0 到 1 之间") if not 0 <= self.intent_qa_min_probability <= 1: raise ValueError("INTENT_QA_MIN_PROBABILITY 必须在 0 到 1 之间") if self.intent_max_clarify_rounds < 0: raise ValueError("INTENT_MAX_CLARIFY_ROUNDS 必须是非负整数") settings = Settings()