251 lines
7.6 KiB
Python
Executable File
251 lines
7.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
自进化管线 — 定期运行,检查差距,执行升级,验证回滚
|
||
|
||
运行流程:
|
||
1. 快照当前配置
|
||
2. 检查差距(模型健康、技能使用、系统资源)
|
||
3. 生成升级提案
|
||
4. 逐个执行(带快照+验证)
|
||
5. 标记稳定
|
||
|
||
由每日复盘后面的 cron 触发
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import subprocess
|
||
import time
|
||
from datetime import datetime
|
||
|
||
HOME = os.path.expanduser("~")
|
||
HERMES = os.path.join(HOME, ".hermes")
|
||
SCRIPTS = os.path.join(HERMES, "scripts")
|
||
WATCHDOG = os.path.join(HERMES, "watchdog")
|
||
LOG_FILE = os.path.join(WATCHDOG, "self-evolve.log")
|
||
|
||
def log(msg):
|
||
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
line = f"[{ts}] {msg}"
|
||
print(line)
|
||
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
|
||
with open(LOG_FILE, "a") as f:
|
||
f.write(line + "\n")
|
||
|
||
def shell(cmd, timeout=30):
|
||
try:
|
||
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
|
||
return r.returncode, r.stdout.strip(), r.stderr.strip()
|
||
except subprocess.TimeoutExpired:
|
||
return -1, "", "timeout"
|
||
|
||
def snapshot():
|
||
"""执行配置快照"""
|
||
rc, out, err = shell(f"bash {SCRIPTS}/config-protector.sh snapshot")
|
||
return rc == 0
|
||
|
||
def mark_stable():
|
||
"""标记当前为稳定"""
|
||
rc, out, err = shell(f"bash {SCRIPTS}/config-protector.sh mark-stable")
|
||
return rc == 0
|
||
|
||
# === 差距检查 ===
|
||
|
||
def check_model_health():
|
||
"""检查模型健康报告"""
|
||
path = os.path.join(HERMES, "model-health.json")
|
||
if not os.path.exists(path):
|
||
return {"status": "no_report", "recommendations": []}
|
||
with open(path) as f:
|
||
data = json.load(f)
|
||
|
||
stable = data.get("stable", 0)
|
||
total = data.get("total_models", 0)
|
||
fastest = data.get("fastest_stable", [])
|
||
|
||
issues = []
|
||
if stable < 2:
|
||
issues.append("可用模型不足,需要扩大测试范围")
|
||
if total > 0 and stable / total < 0.2:
|
||
issues.append(f"模型稳定率过低 ({stable}/{total})")
|
||
|
||
return {
|
||
"status": "ok" if not issues else "warning",
|
||
"stable": stable,
|
||
"total": total,
|
||
"fastest": fastest,
|
||
"issues": issues,
|
||
}
|
||
|
||
def check_skills_health():
|
||
"""检查技能库状态"""
|
||
skills_dir = os.path.join(HERMES, "skills")
|
||
count = 0
|
||
categories = set()
|
||
for root, dirs, files in os.walk(skills_dir):
|
||
if "SKILL.md" in files:
|
||
count += 1
|
||
cat = root.split("/")[-2] if "/" in root else "uncategorized"
|
||
categories.add(cat)
|
||
|
||
return {
|
||
"total_skills": count,
|
||
"categories": sorted(categories),
|
||
}
|
||
|
||
def check_disk():
|
||
"""检查磁盘空间"""
|
||
rc, out, err = shell("df / | awk 'NR==2 {print $5}' | sed 's/%//'")
|
||
pct = int(out) if out else 0
|
||
return {"disk_pct": pct, "critical": pct > 90}
|
||
|
||
def check_gaps():
|
||
"""综合差距分析"""
|
||
health = check_model_health()
|
||
skills = check_skills_health()
|
||
disk = check_disk()
|
||
|
||
gaps = []
|
||
upgrades = []
|
||
|
||
# 模型层
|
||
if health.get("issues"):
|
||
for issue in health["issues"]:
|
||
gaps.append(f"模型: {issue}")
|
||
upgrades.append({
|
||
"id": "expand-model-test",
|
||
"priority": "medium",
|
||
"desc": "扩大模型测试范围(增加测试队列中的模型数)",
|
||
"action": '修改 model-health.py 的 MODELS 列表,增加候选模型',
|
||
"verify": "下一轮巡检后至少有5个稳定模型",
|
||
"rollback": "git checkout MODELS 原始列表",
|
||
})
|
||
|
||
# 磁盘层
|
||
if disk["disk_pct"] > 85:
|
||
gaps.append(f"磁盘: 使用率 {disk['disk_pct']}%")
|
||
upgrades.append({
|
||
"id": "disk-cleanup",
|
||
"priority": "high",
|
||
"desc": "清理磁盘空间",
|
||
"action": "apt autoremove && pip cache purge && npm cache clean",
|
||
"verify": "磁盘使用率下降>5%",
|
||
"rollback": "N/A(清理不可逆)",
|
||
})
|
||
|
||
# 能力缺口(检查关键工具是否缺失)
|
||
missing_tools = []
|
||
for tool, cmd in [("ComfyUI", "which comfy"), ("llama.cpp", "which llama-cli"),
|
||
("frpc", "which frpc")]:
|
||
rc, _, _ = shell(f"which {tool.split('/')[-1]} 2>/dev/null || command -v {cmd.split()[0]} 2>/dev/null")
|
||
if rc != 0:
|
||
missing_tools.append(tool)
|
||
|
||
if missing_tools:
|
||
upgrades.append({
|
||
"id": "install-tools",
|
||
"priority": "low",
|
||
"desc": f"安装缺失工具: {', '.join(missing_tools)}",
|
||
"action": f"按需安装 {', '.join(missing_tools)}",
|
||
"verify": "工具可用",
|
||
"rollback": "卸载已安装的工具",
|
||
})
|
||
|
||
return {
|
||
"gaps": gaps,
|
||
"upgrades": upgrades,
|
||
"health": health,
|
||
"skills": skills,
|
||
"disk": disk,
|
||
}
|
||
|
||
# === 执行升级 ===
|
||
|
||
def execute_upgrade(upgrade):
|
||
"""执行单个升级项,带快照+验证"""
|
||
log(f"🔧 执行升级: {upgrade['id']} — {upgrade['desc']}")
|
||
|
||
# 1. 快照
|
||
if not snapshot():
|
||
log(" ❌ 快照失败,中止升级")
|
||
return False
|
||
|
||
# 2. 执行
|
||
log(f" 📋 动作: {upgrade['action']}")
|
||
|
||
# 根据不同 upgrade id 执行不同动作
|
||
success = True
|
||
if upgrade["id"] == "disk-cleanup":
|
||
for cmd in ["apt-get autoremove -y", "pip cache purge", "npm cache clean --force"]:
|
||
rc, out, err = shell(f"sudo {cmd}" if cmd.startswith("apt") else cmd, timeout=120)
|
||
if rc != 0 and "cache" not in cmd:
|
||
log(f" ⚠️ 命令失败: {cmd} ({err})")
|
||
|
||
elif upgrade["id"] == "expand-model-test":
|
||
log(" ℹ️ 需人工或下次 session 修改 model-health.py")
|
||
success = True # 不阻塞管线
|
||
|
||
elif upgrade["id"] == "install-tools":
|
||
log(" ℹ️ 需人工决策安装哪些工具")
|
||
success = True
|
||
|
||
# 3. 验证
|
||
if success:
|
||
mark_stable()
|
||
log(f" ✅ 升级完成,已标记稳定")
|
||
else:
|
||
log(f" ❌ 升级失败,需要人工介入")
|
||
|
||
return success
|
||
|
||
# === 主流程 ===
|
||
|
||
def main():
|
||
log(f"\n{'='*50}")
|
||
log(f"自进化管线启动")
|
||
log(f"{'='*50}")
|
||
|
||
# 差距分析
|
||
log("\n📊 差距分析...")
|
||
report = check_gaps()
|
||
|
||
log(f" 技能: {report['skills']['total_skills']} 个 ({len(report['skills']['categories'])} 类)")
|
||
log(f" 模型: {report['health'].get('stable', 'N/A')}/{report['health'].get('total', 'N/A')} 稳定")
|
||
log(f" 磁盘: {report['disk']['disk_pct']}%")
|
||
|
||
if report["gaps"]:
|
||
log(f"\n⚠️ 发现 {len(report['gaps'])} 个差距:")
|
||
for g in report["gaps"]:
|
||
log(f" - {g}")
|
||
else:
|
||
log("\n✅ 无差距")
|
||
|
||
if report["upgrades"]:
|
||
log(f"\n🔧 建议 {len(report['upgrades'])} 个升级:")
|
||
for u in report["upgrades"]:
|
||
log(f" [{u['priority']}] {u['id']}: {u['desc']}")
|
||
|
||
# 执行高优先级升级
|
||
high = [u for u in report["upgrades"] if u["priority"] == "high"]
|
||
for u in high:
|
||
execute_upgrade(u)
|
||
else:
|
||
log("\n✅ 无需升级")
|
||
|
||
# 输出摘要供 cron 交付
|
||
log(f"\n{'='*50}")
|
||
log(f"自进化管线完成")
|
||
log(f"差距: {len(report['gaps'])} | 升级: {len(report['upgrades'])}")
|
||
if report["gaps"]:
|
||
print(f"\n⚠️ 差距:")
|
||
for g in report["gaps"]:
|
||
print(f" {g}")
|
||
if report["upgrades"]:
|
||
print(f"\n🔧 升级执行:")
|
||
for u in report["upgrades"]:
|
||
print(f" [{u['priority']}] {u['desc']}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|