feat(mcp): 基座换行分帧与 isError 映射;新增 dwg_review 审查 Agent
基座(cad_mcp_frame): - 新增 RecvOneFrame: 以 '\n' 为消息边界分帧,去掉单次 recv(2048) 限制(上限 8MB); 用 select 短探测兼容不发 '\n' 的旧客户端;clientSocket 设 SO_RCVTIMEO 防卸载卡死 - JSON-RPC id 支持 string/number;无 id 的通知不回包 - isError 映射: 协议级错误保留顶层 error,工具执行错误转 result.isError=true - 响应补 '\n' 作为消息边界 Agent(新增 dwg_review 包): - mcp_client: 唯一 TCP/JSON-RPC 封装(单飞锁/分帧/重试/兼容 error 与 isError) - cad_tools: 从 tools/list 动态建表,含 15 个只读白名单 - phases: planning -> evidence -> review(受控只读取证子循环)-> report - annotator: 批注回写 AI_REVIEW 层,支持 dry-run 与撤销 - config: 配置全部走环境变量;Windows 下回退读注册表以读到新设的系统变量 其它: 新增 verify_mcp_frame.py 验证脚本;.gitignore 补 Python/AutoCAD 锁文件规则; ai.memory 记录架构决策与实测结论
This commit is contained in:
1 parent
3b1cc88262
commit
ec5292debe
20 files changed
+1502
-20
No files matched your search
@@ -0,0 +1,100 @@
|
||||
"""集中配置:全部来自环境变量,禁止硬编码密钥。
|
||||
|
||||
用法:所有模块统一 `from . import config` 后读取 config.XXX。
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 仓库根目录(本文件位于 <root>/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"))
|
||||
# JPEG 质量
|
||||
IMAGE_JPEG_QUALITY = int(os.environ.get("IMAGE_JPEG_QUALITY", "80"))
|
||||
|
||||
|
||||
def has_api_key() -> bool:
|
||||
return bool(API_KEY.strip())
|
||||
Reference in new issue
Block a user