muse-agent-example/工具/环境预检.py
zizi 0658d178ad 工程底座:单一生成入口、验证期写闸门与并行分片
- Makefile:新增 验收数据库(生成→检查→库层全量,前置校验隔离库连接串)、数据库分片、浏览器测试三个入口;
  格式写入 末尾就地重新生成;数据库测试 与 验收数据库 统一排除 浏览器/网络;快检 不再静默跳过类型门;
  前端旅程 预检补齐四个必需变量;pytest 目标改用仓内解释器(uv run 在嵌套检出会解析到外层环境)。
- 工具/验证锁.py:验证会话持共享锁,写入口用 --执行 在独占锁内落盘,生成/格式写入/索引生成走同一闸门。
- 工具/并行数据库测试.py:按文件分片并行,缺连接串在采集前拒绝。
- 工具/构建编排.py、环境预检.py、构建资源包.py、维护索引.py 与上述口径对齐。
- .gitignore / CI / README / AGENTS:忽略构建产物、CI 与 Makefile 单一口径、README 按实际实现陈述、AGENTS 补日常入口与验证纪律。
2026-09-18 01:14:50 +08:00

159 lines
6.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""核对已安装资源、显式配置和本地宿主;不会发起模型调用。"""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import tempfile
from pathlib import Path
from muse.共享.错误 import Muse错误, 环境缺失错误, 资源缺失错误, 配置错误
from muse.启动 import 构建
from muse.资源加载 import 核对资源, 读取能力目录
from muse.配置 import 读取配置
def 安全失败(动作: str, 错误: Exception) -> dict:
"""输出固定原因,不展示异常文本、命令、stdout/stderr 或配置上下文。"""
可重试 = False
详情 = {}
if isinstance(错误, subprocess.TimeoutExpired):
类别, 说明, 可重试 = "TIMEOUT", "所选检查超过时限", True
elif isinstance(错误, subprocess.CalledProcessError):
类别, 说明 = "PROCESS_FAILED", "所选检查进程非零退出"
if isinstance(错误.returncode, int):
详情 = {"exit_code": 错误.returncode}
elif isinstance(错误, PermissionError):
类别, 说明 = "PERMISSION_DENIED", "所选检查没有所需文件或执行权限"
elif isinstance(错误, FileNotFoundError):
类别, 说明 = "NOT_FOUND", "所选检查需要的文件或可执行程序不存在"
elif isinstance(错误, 资源缺失错误):
类别, 说明 = "RESOURCE_INVALID", "安装资源缺失或完整性核对失败"
elif isinstance(错误, 环境缺失错误):
类别, 说明 = "ENVIRONMENT_MISSING", "所选检查需要的环境未就绪"
elif isinstance(错误, 配置错误):
类别, 说明 = "CONFIG_INVALID", "所选检查的显式配置或版本要求不匹配"
elif isinstance(错误, Muse错误) and 错误.错误码 == "MUSE_DB_CONNECTION":
类别, 说明, 可重试 = "CONNECTION_FAILED", "数据库连接或会话初始化失败", True
elif isinstance(错误, Muse错误):
类别, 说明 = "REJECTED", "所选检查被运行合同拒绝"
elif isinstance(错误, (ValueError, KeyError, TypeError)):
类别, 说明 = "INVALID_DATA", "所选检查的数据格式、字段或版本无效"
elif isinstance(错误, OSError):
类别, 说明 = "IO_FAILED", "所选检查的文件、执行或连接操作失败"
else:
类别, 说明 = "INTERNAL_ERROR", "所选检查遇到未预期错误"
return {
"code": f"PREFLIGHT_{动作}_{类别}",
"action": 动作,
"message": 说明,
"retryable": 可重试,
"details": 详情,
}
class 预检失败(Exception):
def __init__(self, 报告: dict) -> None:
self.报告 = 报告
super().__init__(报告["message"])
def 检查Pi(node: Path, cli: Path, 预期版本: str) -> dict:
动作 = "HOST_NODE"
try:
if not node.is_file():
raise FileNotFoundError
结果 = subprocess.run(
[str(node), "--version"], capture_output=True, text=True, timeout=10, check=True
)
node_version = 结果.stdout.strip().removeprefix("v")
parts = tuple(int(v) for v in node_version.split("."))
if len(parts) != 3 or any(v < 0 for v in parts):
raise ValueError
if parts < (22, 19, 0):
raise 配置错误("Node版本未达到已声明的宿主要求")
动作 = "HOST_PI"
if not cli.is_file():
raise FileNotFoundError
with tempfile.TemporaryDirectory(prefix="muse-pi-preflight-") as 临时:
环境 = {
**os.environ,
"PI_CODING_AGENT_DIR": 临时,
"PI_OFFLINE": "1",
"PI_TELEMETRY": "0",
}
结果 = subprocess.run(
[str(node), str(cli), "--version"],
cwd=临时,
env=环境,
capture_output=True,
text=True,
timeout=15,
check=True,
)
实际 = 结果.stdout.strip()
if 实际 != 预期版本:
raise 配置错误("Pi实际版本与显式要求不符")
return {"node_version": node_version, "pi_version": 实际, "state": "version_verified"}
except Exception as 错误:
raise 预检失败(安全失败(动作, 错误)) from None
def 主() -> int:
解析 = argparse.ArgumentParser(description="检查固定资源和明确选择的运行依赖,不调用模型")
解析.add_argument("--配置", type=Path)
解析.add_argument("--数据库", action="store_true", help="明确执行所给配置的数据库连接检查")
解析.add_argument("--node", type=Path)
解析.add_argument("--pi", type=Path)
解析.add_argument("--pi-version")
参数 = 解析.parse_args()
if 参数.数据库 and 参数.配置 is None:
解析.error("数据库检查需要明确配置")
if any((参数.node, 参数.pi, 参数.pi_version)) and not all(
(参数.node, 参数.pi, 参数.pi_version)
):
解析.error("Pi检查必须同时给出node、pi和pi-version")
动作 = "RESOURCE"
try:
清单 = 核对资源()
能力 = 读取能力目录()
报告 = {
"resource_build_id": 清单["构建身份"],
"resource_label": 清单["发布身份"],
"package_version": 清单["代码版本"],
"resources_verified": len(清单["资源"]),
"roles": sorted(能力["role"]),
"operations": sorted(能力["operation"]),
"database": "not_checked",
"host": {"state": "not_checked"},
"model_calls": 0,
}
if 参数.配置:
动作 = "CONFIG"
配置 = 读取配置(参数.配置)
if 配置.资源发布身份 != 清单["构建身份"]:
raise 配置错误("配置必须固定当前资源构建身份;先无配置运行预检取得身份")
if 参数.数据库:
动作 = "DATABASE"
# 常驻连接池只在装配生命周期内打开;不进生命周期会以「连接池未就绪」必然失败。
装配 = 构建(配置)
with 装配.生命周期():
装配.要求数据库().检查连接()
报告["database"] = "connection_verified"
if 参数.node:
动作 = "HOST"
报告["host"] = 检查Pi(参数.node.resolve(), 参数.pi.resolve(), 参数.pi_version)
print(json.dumps(报告, ensure_ascii=False, indent=2))
return 0
except 预检失败 as 错误:
print(json.dumps(错误.报告, ensure_ascii=False))
except Exception as 错误:
print(json.dumps(安全失败(动作, 错误), ensure_ascii=False))
return 1
if __name__ == "__main__":
raise SystemExit(主())