xiaowei-system/scripts/self-evolve.py

254 lines
7.6 KiB
Python
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

#!/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/%//'")
try:
pct = int(out.strip()) if out.strip() else 0
except ValueError:
pct = 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()