xiaowei-system/scripts/optimizer.py

496 lines
17 KiB
Python
Executable File
Raw 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
"""
自我优化系统 — 采集、分析、推荐
===================================
用法:
optimizer.py collect → 从各数据源采集指标
optimizer.py analyze → 分析瓶颈和模式
optimizer.py recommend → 输出优化建议
optimizer.py report → 完整报告(收集+分析+推荐)
"""
import json, os, re, sys, time
from datetime import datetime, timezone, timedelta
from collections import defaultdict
HOME = os.path.expanduser("~")
HERMES = HOME + "/.hermes"
D = HERMES + "/daemon"
REPORT_FILE = HERMES + "/optimization-report.json"
# ====== 采集器 ======
def collect_daemon_metrics():
"""从 daemon 日志中提取指标"""
metrics = {
"uptime_minutes": 0,
"total_ticks": 0,
"deep_thoughts": 0,
"alerts_sent": 0,
"solutions_applied": 0,
"learned_solutions": 0,
"errors": 0,
"crashes": 0,
"model_calls": 0,
"model_tokens": 0,
}
# 从 context.json 读取
ctx_file = D + "/context.json"
if os.path.exists(ctx_file):
with open(ctx_file) as f:
ctx = json.load(f)
metrics["uptime_minutes"] = ctx.get("uptime_seconds", 0) // 60
metrics["total_ticks"] = ctx.get("tick_count", 0)
metrics["deep_thoughts"] = ctx.get("deep_tick_count", 0)
metrics["alerts_sent"] = ctx.get("messages_sent", 0)
metrics["solutions_applied"] = ctx.get("solved_count", 0)
metrics["learned_solutions"] = ctx.get("learned_count", 0)
# 从 daemon.log 提取崩溃和错误
log_file = D + "/daemon.log"
if os.path.exists(log_file):
with open(log_file) as f:
content = f.read()
metrics["crashes"] = content.count("❌ 崩溃")
metrics["errors"] = content.count("Error") + content.count("error") + content.count("失败")
# 模型调用次数
metrics["model_calls"] = content.count("深度思考")
# 从日志提取 token 数
token_matches = re.findall(r'\((\d+)t\)', content)
metrics["model_tokens"] = sum(int(t) for t in token_matches)
return metrics
def collect_cron_metrics():
"""从 cron job 状态采集指标"""
metrics = {
"total_jobs": 0,
"ok_jobs": 0,
"failed_jobs": 0,
"no_agent_jobs": 0,
"agent_jobs": 0,
}
# 从 cron/jobs.json 读取
jobs_file = HERMES + "/cron/jobs.json"
if os.path.exists(jobs_file):
with open(jobs_file) as f:
try:
jobs = json.load(f)
# 处理不同格式
if isinstance(jobs, dict):
jobs = [v for v in jobs.values()]
elif isinstance(jobs, list):
pass
metrics["total_jobs"] = len(jobs)
for j in jobs:
if isinstance(j, dict):
status = j.get("last_status", "")
if status == "ok":
metrics["ok_jobs"] += 1
elif status and status != "ok":
metrics["failed_jobs"] += 1
if j.get("no_agent"):
metrics["no_agent_jobs"] += 1
else:
metrics["agent_jobs"] += 1
except:
pass
return metrics
def collect_skill_metrics():
"""从技能健康报告采集指标"""
metrics = {
"total_skills": 0,
"avg_score": 0,
"grade_distribution": {},
"needs_attention": 0,
"categories": 0,
}
health_file = HERMES + "/skill-health.json"
if os.path.exists(health_file):
with open(health_file) as f:
try:
report = json.load(f)
s = report.get("summary", {})
metrics["total_skills"] = s.get("active", 0)
metrics["avg_score"] = s.get("avg_score", 0)
metrics["grade_distribution"] = s.get("grades", {})
metrics["needs_attention"] = s.get("needs_attention", 0)
metrics["categories"] = s.get("categories", 0)
except:
pass
return metrics
def collect_model_metrics():
"""从模型健康报告采集指标"""
metrics = {
"total_models_tested": 0,
"stable_models": 0,
"unstable_models": 0,
"dead_models": 0,
"fastest_model": "",
"fastest_latency": 0,
}
health_file = HERMES + "/model-health.json"
if os.path.exists(health_file):
with open(health_file) as f:
try:
data = json.load(f)
metrics["total_models_tested"] = data.get("total_models", 0)
metrics["stable_models"] = data.get("stable", 0)
metrics["unstable_models"] = data.get("unstable", 0)
metrics["dead_models"] = data.get("dead", 0)
fastest = data.get("fastest_stable", [])
metrics["fastest_model"] = fastest[0] if fastest else ""
except:
pass
return metrics
def collect_disk_trend():
"""采集磁盘趋势过去N次看门狗记录"""
# 从看门狗日志提取
watchdog_log = HERMES + "/watchdog"
snapshots = []
# 从 daemon context 读取最后状态
ctx_file = D + "/context.json"
if os.path.exists(ctx_file):
with open(ctx_file) as f:
ctx = json.load(f)
last = ctx.get("last_state", {})
snapshots.append({
"disk_pct": last.get("disk_pct", 0),
"mem_pct": last.get("mem_pct", 0),
"timestamp": ctx.get("last_light_tick", ""),
})
# 从 daemon journal 中提取历史磁盘数据
journal_file = D + "/journal.jsonl"
if os.path.exists(journal_file):
with open(journal_file) as f:
for line in f:
try:
entry = json.loads(line)
if entry.get("type") == "startup":
snapshots.append({
"timestamp": entry.get("timestamp", ""),
"event": "startup",
})
except:
pass
return snapshots
# ====== 分析引擎 ======
def analyze_bottlenecks(metrics):
"""分析性能瓶颈"""
bottlenecks = []
# 模型瓶颈
model = metrics.get("model", {})
if model.get("stable_models", 0) < 2:
bottlenecks.append({
"area": "model",
"severity": "high",
"desc": f"可用模型不足 (稳定{model.get('stable_models',0)}/{model.get('total_models_tested',0)})",
"suggestion": "扩大模型测试范围,或检查 NewAPI 状态",
})
if model.get("dead_models", 0) > 10:
bottlenecks.append({
"area": "model",
"severity": "info",
"desc": f"大量模型不可用 ({model.get('dead_models',0)}个dead)",
"suggestion": "可能 NewAPI 后端负载高,特定时段再测",
})
# 技能瓶颈
skill = metrics.get("skill", {})
if skill.get("avg_score", 10) < 5:
bottlenecks.append({
"area": "skill",
"severity": "medium",
"desc": f"技能平均质量偏低 ({skill.get('avg_score',0)}/10)",
"suggestion": "运行 skill-manager.py fix 修复元数据,归档低分技能",
})
if skill.get("needs_attention", 0) > 30:
bottlenecks.append({
"area": "skill",
"severity": "low",
"desc": f"{skill.get('needs_attention',0)}个技能需关注",
"suggestion": "逐步清理或升级这些技能",
})
# Daemon 瓶颈
daemon = metrics.get("daemon", {})
if daemon.get("crashes", 0) > 0:
bottlenecks.append({
"area": "daemon",
"severity": "high",
"desc": f"Daemon 崩溃 {daemon.get('crashes',0)}",
"suggestion": "检查 daemon.log 定位崩溃原因",
})
if daemon.get("errors", 0) > 5:
bottlenecks.append({
"area": "daemon",
"severity": "medium",
"desc": f"Daemon 有 {daemon.get('errors',0)} 个错误",
"suggestion": "审查 daemon 日志中的错误模式",
})
# 效率分析
if daemon.get("deep_thoughts", 0) > 0 and daemon.get("model_tokens", 0) > 0:
avg_tokens = daemon["model_tokens"] / daemon["deep_thoughts"]
if avg_tokens > 1000:
bottlenecks.append({
"area": "efficiency",
"severity": "info",
"desc": f"深度思考平均 {int(avg_tokens)} tokens/次(偏高)",
"suggestion": "考虑精简深度思考的 system prompt",
})
return bottlenecks
def analyze_trends(daemon_metrics, model_metrics):
"""分析趋势"""
trends = []
# 如果 daemon 运行超过 1h检查稳定性
if daemon_metrics.get("uptime_minutes", 0) > 60:
error_rate = daemon_metrics.get("errors", 0) / max(daemon_metrics.get("uptime_minutes", 1), 1)
if error_rate < 0.1:
trends.append({"type": "positive", "desc": f"Daemon 稳定运行 {daemon_metrics['uptime_minutes']}分钟,错误率低"})
else:
trends.append({"type": "negative", "desc": f"Daemon 错误率 {error_rate:.2f}/分钟"})
# 模型稳定率
if model_metrics.get("total_models_tested", 0) > 0:
stable_rate = model_metrics.get("stable_models", 0) / model_metrics.get("total_models_tested", 1) * 100
trends.append({"type": "neutral", "desc": f"模型稳定率 {stable_rate:.0f}%{model_metrics.get('stable_models',0)}/{model_metrics.get('total_models_tested',0)}"})
return trends
# ====== 推荐系统 ======
def generate_recommendations(metrics, bottlenecks, trends):
"""基于分析结果生成可执行建议"""
recs = []
# 从瓶颈推导建议
for b in bottlenecks:
recs.append({
"priority": b["severity"],
"area": b["area"],
"action": b["suggestion"],
"expected_impact": "",
"effort": "5min" if b["severity"] == "low" else "15min" if b["severity"] == "medium" else "30min",
})
# 从趋势推导建议
skill = metrics.get("skill", {})
if skill.get("avg_score", 10) < 6:
recs.append({
"priority": "medium",
"area": "skill",
"action": "运行 skill-manager.py archive <name> 归档低分技能D级44个",
"expected_impact": "减少技能库噪音,提升检索质量",
"effort": "20min",
})
model = metrics.get("model", {})
if model.get("fastest_model"):
recs.append({
"priority": "info",
"area": "model",
"action": f"考虑将默认模型切换到 {model['fastest_model']}(当前最快稳定模型)",
"expected_impact": "提升响应速度",
"effort": "2min",
})
# 提示用户更新
recs.append({
"priority": "info",
"area": "system",
"action": "运行 optimizer.py collect 定期采集指标,积累数据后分析更准确",
"expected_impact": "更精准的优化建议",
"effort": "0min",
})
return recs
# ====== 报告生成 ======
def generate_report():
"""生成完整优化报告"""
print("📊 采集指标...")
daemon = collect_daemon_metrics()
cron = collect_cron_metrics()
skill = collect_skill_metrics()
model = collect_model_metrics()
metrics = {
"daemon": daemon,
"cron": cron,
"skill": skill,
"model": model,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
print("🔍 分析瓶颈...")
bottlenecks = analyze_bottlenecks(metrics)
trends = analyze_trends(daemon, model)
print("💡 生成建议...")
recommendations = generate_recommendations(metrics, bottlenecks, trends)
# 整合报告
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"metrics": metrics,
"bottlenecks": bottlenecks,
"trends": trends,
"recommendations": recommendations,
"health_score": calculate_health_score(metrics, bottlenecks),
}
# 保存
os.makedirs(os.path.dirname(REPORT_FILE), exist_ok=True)
with open(REPORT_FILE, "w") as f:
json.dump(report, f, indent=2, ensure_ascii=False)
# 打印
print_report(report)
return report
def calculate_health_score(metrics, bottlenecks):
"""计算系统健康分 (0-100)"""
score = 100
# 减分项
for b in bottlenecks:
if b["severity"] == "high":
score -= 20
elif b["severity"] == "medium":
score -= 10
elif b["severity"] == "low":
score -= 5
# 加分项
daemon = metrics.get("daemon", {})
if daemon.get("crashes", 0) == 0:
score += 5
if daemon.get("uptime_minutes", 0) > 60:
score += 5
# 技能加分
skill = metrics.get("skill", {})
if skill.get("avg_score", 0) > 5:
score += 5
return max(0, min(100, score))
def print_report(report):
"""打印人类可读报告"""
m = report["metrics"]
d = m["daemon"]
c = m["cron"]
s = m["skill"]
mo = m["model"]
print(f'\n{"="*50}')
print(f' 自我优化报告 | 健康分: {report["health_score"]}/100')
print(f'{"="*50}')
print(f'\n📡 Daemon:')
print(f' 运行 {d["uptime_minutes"]}分钟 | {d["total_ticks"]} ticks | {d["deep_thoughts"]}次思考')
print(f' 告警 {d["alerts_sent"]}次 | 解决问题 {d["solutions_applied"]}个 | 学会 {d["learned_solutions"]}')
print(f' 模型调用 {d["model_calls"]}次 | {d["model_tokens"]} tokens | 错误 {d["errors"]}')
print(f'\n⏰ Cron:')
print(f' {c["total_jobs"]}个任务 | {c["ok_jobs"]}成功 | {c["failed_jobs"]}失败 | {c["no_agent_jobs"]}个no_agent')
print(f'\n🛠️ 技能:')
print(f' {s["total_skills"]}个活跃 | 均分{s["avg_score"]}/10 | {s["needs_attention"]}个需关注')
g = s.get("grade_distribution", {})
print(f' 分布: A={g.get("A",0)} B={g.get("B",0)} C={g.get("C",0)} D={g.get("D",0)}')
print(f'\n🤖 模型:')
print(f' 测试{mo["total_models_tested"]}个 | 稳定{mo["stable_models"]}个 | 最快: {mo["fastest_model"]}')
if report["bottlenecks"]:
print(f'\n⚠️ 瓶颈 ({len(report["bottlenecks"])}个):')
for b in sorted(report["bottlenecks"], key=lambda x: {"high": 0, "medium": 1, "low": 2, "info": 3}[x["severity"]]):
icon = {"high": "🔴", "medium": "🟡", "low": "🟢", "info": ""}[b["severity"]]
print(f' {icon} [{b["area"]}] {b["desc"]}')
print(f'{b["suggestion"]}')
if report["trends"]:
print(f'\n📈 趋势:')
for t in report["trends"]:
icon = {"positive": "", "negative": "📉", "neutral": "➡️"}[t["type"]]
print(f' {icon} {t["desc"]}')
if report["recommendations"]:
print(f'\n💡 建议 ({len(report["recommendations"])}条):')
for r in sorted(report["recommendations"], key=lambda x: {"high": 0, "medium": 1, "low": 2, "info": 3}[x["priority"]]):
icon = {"high": "🔴", "medium": "🟡", "low": "🟢", "info": ""}[r["priority"]]
print(f' {icon} [{r["area"]}] {r["action"]} ({r["effort"]})')
print(f'\n{"="*50}\n')
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "report"
if cmd == "collect":
print(json.dumps({
"daemon": collect_daemon_metrics(),
"cron": collect_cron_metrics(),
"skill": collect_skill_metrics(),
"model": collect_model_metrics(),
}, indent=2, ensure_ascii=False))
elif cmd == "analyze":
metrics = {
"daemon": collect_daemon_metrics(),
"cron": collect_cron_metrics(),
"skill": collect_skill_metrics(),
"model": collect_model_metrics(),
}
bottlenecks = analyze_bottlenecks(metrics)
for b in bottlenecks:
print(f"[{b['severity']}] {b['area']}: {b['desc']}")
print(f"{b['suggestion']}")
elif cmd == "recommend":
metrics = {
"daemon": collect_daemon_metrics(),
"cron": collect_cron_metrics(),
"skill": collect_skill_metrics(),
"model": collect_model_metrics(),
}
bottlenecks = analyze_bottlenecks(metrics)
trends = analyze_trends(metrics["daemon"], metrics["model"])
recs = generate_recommendations(metrics, bottlenecks, trends)
for r in recs:
print(f"[{r['priority']}] [{r['area']}] {r['action']}")
else:
generate_report()