#!/usr/bin/env python # -*- coding: utf-8 -*- import os from typing import Dict, List, Optional from pydantic_settings import BaseSettings from app.utils.logger import logger class Settings(BaseSettings): """Application settings""" # API Configuration API_ENDPOINT: str = "https://chat.z.ai/api/chat/completions" AUTH_TOKEN: str = os.getenv("AUTH_TOKEN", "sk-your-api-key") # 认证token文件路径(可选) AUTH_TOKENS_FILE: Optional[str] = os.getenv("AUTH_TOKENS_FILE") # Token池配置 TOKEN_HEALTH_CHECK_INTERVAL: int = int(os.getenv("TOKEN_HEALTH_CHECK_INTERVAL", "300")) # 5分钟 TOKEN_FAILURE_THRESHOLD: int = int(os.getenv("TOKEN_FAILURE_THRESHOLD", "3")) # 失败3次后标记为不可用 TOKEN_RECOVERY_TIMEOUT: int = int(os.getenv("TOKEN_RECOVERY_TIMEOUT", "1800")) # 30分钟后重试失败的token def _load_tokens_from_file(self, file_path: str) -> List[str]: """ 从文件加载token列表 支持多种格式的混合使用: 1. 每行一个token(换行分隔) 2. 逗号分隔的token 3. 混合格式(同时支持换行和逗号分隔) """ tokens = [] try: if os.path.exists(file_path): with open(file_path, 'r', encoding='utf-8') as f: content = f.read().strip() if not content: logger.debug(f"📄 Token文件为空: {file_path}") return tokens # 智能解析:同时支持换行和逗号分隔 # 1. 先按换行符分割处理每一行 lines = content.split('\n') for line in lines: line = line.strip() # 跳过空行和注释行 if not line or line.startswith('#'): continue # 2. 检查当前行是否包含逗号分隔 if ',' in line: # 按逗号分割当前行 comma_tokens = line.split(',') for token in comma_tokens: token = token.strip() if token: # 跳过空token tokens.append(token) else: # 整行作为一个token tokens.append(line) logger.info(f"📄 从文件加载了 {len(tokens)} 个token: {file_path}") else: logger.debug(f"📄 Token文件不存在: {file_path}") except Exception as e: logger.error(f"❌ 读取token文件失败 {file_path}: {e}") return tokens @property def auth_token_list(self) -> List[str]: """ 解析认证token列表 从AUTH_TOKENS_FILE指定的文件加载token(如果配置了文件路径) """ # 如果未配置token文件路径,返回空列表 if not self.AUTH_TOKENS_FILE: logger.debug("📄 未配置AUTH_TOKENS_FILE,跳过token文件加载") return [] # 从文件加载token tokens = self._load_tokens_from_file(self.AUTH_TOKENS_FILE) # 去重,保持顺序 if tokens: seen = set() unique_tokens = [] for token in tokens: if token not in seen: unique_tokens.append(token) seen.add(token) # 记录去重信息 duplicate_count = len(tokens) - len(unique_tokens) if duplicate_count > 0: logger.warning(f"⚠️ 检测到 {duplicate_count} 个重复token,已自动去重") return unique_tokens return [] @property def longcat_token_list(self) -> List[str]: """ 解析 LongCat token 列表 从 LONGCAT_TOKENS_FILE 指定的文件加载 token(如果配置了文件路径) """ # 如果未配置token文件路径,返回空列表 if not self.LONGCAT_TOKENS_FILE: logger.debug("📄 未配置LONGCAT_TOKENS_FILE,跳过LongCat token文件加载") return [] # 从文件加载token tokens = self._load_tokens_from_file(self.LONGCAT_TOKENS_FILE) # 去重,保持顺序 if tokens: seen = set() unique_tokens = [] for token in tokens: if token not in seen: unique_tokens.append(token) seen.add(token) # 记录去重信息 duplicate_count = len(tokens) - len(unique_tokens) if duplicate_count > 0: logger.warning(f"⚠️ 检测到 {duplicate_count} 个重复LongCat token,已自动去重") return unique_tokens return [] # Model Configuration PRIMARY_MODEL: str = os.getenv("PRIMARY_MODEL", "GLM-4.5") THINKING_MODEL: str = os.getenv("THINKING_MODEL", "GLM-4.5-Thinking") SEARCH_MODEL: str = os.getenv("SEARCH_MODEL", "GLM-4.5-Search") AIR_MODEL: str = os.getenv("AIR_MODEL", "GLM-4.5-Air") GLM46_MODEL: str = os.getenv("GLM46_MODEL", "GLM-4.6") GLM46_THINKING_MODEL: str = os.getenv("GLM46_THINKING_MODEL", "GLM-4.6-Thinking") GLM46_SEARCH_MODEL: str = os.getenv("GLM46_SEARCH_MODEL", "GLM-4.6-Search") # Provider Model Mapping @property def provider_model_mapping(self) -> Dict[str, str]: """模型到提供商的映射""" return { # Z.AI models "GLM-4.5": "zai", "GLM-4.5-Thinking": "zai", "GLM-4.5-Search": "zai", "GLM-4.5-Air": "zai", "GLM-4.6": "zai", "GLM-4.6-Thinking": "zai", "GLM-4.6-Search": "zai", # K2Think models "MBZUAI-IFM/K2-Think": "k2think", # LongCat models "LongCat-Flash": "longcat", "LongCat": "longcat", "LongCat-Search": "longcat", } # Server Configuration LISTEN_PORT: int = int(os.getenv("LISTEN_PORT", "8080")) DEBUG_LOGGING: bool = os.getenv("DEBUG_LOGGING", "true").lower() == "true" SERVICE_NAME: str = os.getenv("SERVICE_NAME", "z-ai2api-server") ANONYMOUS_MODE: bool = os.getenv("ANONYMOUS_MODE", "true").lower() == "true" TOOL_SUPPORT: bool = os.getenv("TOOL_SUPPORT", "true").lower() == "true" SCAN_LIMIT: int = int(os.getenv("SCAN_LIMIT", "200000")) SKIP_AUTH_TOKEN: bool = os.getenv("SKIP_AUTH_TOKEN", "false").lower() == "true" # LongCat Configuration LONGCAT_PASSPORT_TOKEN: Optional[str] = os.getenv("LONGCAT_PASSPORT_TOKEN") LONGCAT_TOKENS_FILE: Optional[str] = os.getenv("LONGCAT_TOKENS_FILE") # Retry Configuration MAX_RETRIES: int = int(os.getenv("MAX_RETRIES", "5")) RETRY_DELAY: float = float(os.getenv("RETRY_DELAY", "1.0")) # 初始重试延迟(秒) # Browser Headers CLIENT_HEADERS: Dict[str, str] = { "Content-Type": "application/json", "Accept": "application/json, text/event-stream", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0", "Accept-Language": "zh-CN", "sec-ch-ua": '"Not;A=Brand";v="99", "Microsoft Edge";v="139", "Chromium";v="139"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "X-FE-Version": "prod-fe-1.0.70", "Origin": "https://chat.z.ai", } class Config: env_file = ".env" settings = Settings()