"""集中配置:全部来自环境变量,禁止硬编码密钥。 用法:所有模块统一 `from . import config` 后读取 config.XXX。 """ import os from pathlib import Path # 仓库根目录(本文件位于 /dwg_review/config.py) PROJECT_ROOT = Path(__file__).resolve().parent.parent def _resolve_path(value: str) -> Path: """相对路径按仓库根目录解析(避免依赖当前工作目录)。""" p = Path(value) return p if p.is_absolute() else (PROJECT_ROOT / p) def _read_env(name: str) -> str: """读取环境变量。 Windows 下新设置的系统/用户环境变量不会传播到**已启动**的进程, 因此当前进程读不到时,回退到注册表中的 Machine / User 级环境变量。 """ value = os.environ.get(name) if value: return value if os.name != "nt": return "" try: import winreg except ImportError: # pragma: no cover return "" locations = ( (winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"), (winreg.HKEY_CURRENT_USER, r"Environment"), ) for hive, subkey in locations: try: with winreg.OpenKey(hive, subkey) as key: value, _ = winreg.QueryValueEx(key, name) if value: return str(value) except OSError: continue return "" # --------------------------------------------------------------------------- # CAD MCP 基座(TCP JSON-RPC) # --------------------------------------------------------------------------- HOST = os.environ.get("CAD_MCP_HOST", "127.0.0.1") PORT = int(os.environ.get("CAD_MCP_PORT", "8080")) # 单次 TCP 调用超时(秒) CALL_TIMEOUT = float(os.environ.get("CAD_MCP_TIMEOUT", "30")) # 连接/超时类错误的重试次数(写类工具不重试) CALL_RETRIES = int(os.environ.get("CAD_MCP_RETRIES", "2")) # 帧大小上限(与基座 8MB 上限对齐,留余量) MAX_FRAME_BYTES = int(os.environ.get("MAX_FRAME_BYTES", str(4 * 1024 * 1024))) # --------------------------------------------------------------------------- # LLM(默认 DeepSeek,视觉模型 deepseek-flash) # --------------------------------------------------------------------------- API_KEY = _read_env("DEEPSEEK_API_KEY") BASE_URL = os.environ.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com") MODEL_NAME = os.environ.get("DEEPSEEK_MODEL", "deepseek-flash") LLM_TIMEOUT = float(os.environ.get("LLM_TIMEOUT", "120")) LLM_RETRIES = int(os.environ.get("LLM_RETRIES", "2")) # --------------------------------------------------------------------------- # 审查流程 # --------------------------------------------------------------------------- # 审查清单路径 CHECKLIST_PATH = _resolve_path(os.environ.get("REVIEW_CHECKLIST", "review_checklist.json")) # 最多审查多少个图框(0 = 全部) MAX_FRAMES = int(os.environ.get("MAX_FRAMES", "0")) # 每个图框取数据的条数上限 FRAME_ENTITY_LIMIT = int(os.environ.get("FRAME_ENTITY_LIMIT", "200")) # 缩放后等待 CAD 渲染的秒数 RENDER_WAIT = float(os.environ.get("RENDER_WAIT", "0.6")) # 审查阶段受控工具子循环的最大回合数 REVIEW_MAX_ITERS = int(os.environ.get("REVIEW_MAX_ITERS", "4")) # 报告输出目录 REPORT_DIR = _resolve_path(os.environ.get("REPORT_DIR", "reports")) # 批注回写使用的图层 ANNOTATE_LAYER = os.environ.get("ANNOTATE_LAYER", "AI_REVIEW") # --------------------------------------------------------------------------- # 截图(视觉 token 成本控制) # --------------------------------------------------------------------------- # 长边上限(超过才缩放),0 表示不缩放 IMAGE_MAX_EDGE = int(os.environ.get("IMAGE_MAX_EDGE", "1280")) # 缩放后的输出格式:png(默认,无损,适合 CAD 线稿)或 jpeg(体积更小) IMAGE_FORMAT = os.environ.get("IMAGE_FORMAT", "png").strip().lower() # JPEG 质量(仅 IMAGE_FORMAT=jpeg 时生效) IMAGE_JPEG_QUALITY = int(os.environ.get("IMAGE_JPEG_QUALITY", "80")) def has_api_key() -> bool: return bool(API_KEY.strip())