xiaowei-system/scripts/learner.py

537 lines
17 KiB
Python
Executable File

#!/usr/bin/env python3
"""
学习层 — 反思·抽象·应用·探索
===============================
小唯的最高认知层,把经验转化为能力。
三层学习:
1. 事实学习: "xxx模型在yyy时段慢" → 织忆
2. 技能学习: "做调研的最佳流程是A→B→C" → skill
3. 元学习: "我缺少yyy能力" → 主动探索
用法:
learner.py reflect → 反思近期经验,提取教训
learner.py learn → 执行学习循环(产出 skill/配置)
learner.py plan → 生成学习计划(下次学什么)
learner.py status → 查看学习进度
"""
import json, os, re, sys, time, subprocess
from datetime import datetime, timezone
from collections import defaultdict, Counter
HOME = os.path.expanduser("~")
HERMES = HOME + "/.hermes"
D = HERMES + "/daemon"
LEARNER_DIR = HERMES + "/learner"
STATE_FILE = LEARNER_DIR + "/state.json"
SKILL_HEALTH = HERMES + "/skill-health.json"
OPT_REPORT = HERMES + "/optimization-report.json"
def log(msg):
ts = datetime.now().strftime("%H:%M:%S")
print(f"[LEARN] {ts} {msg}", flush=True)
def load_state():
os.makedirs(LEARNER_DIR, exist_ok=True)
if os.path.exists(STATE_FILE):
with open(STATE_FILE) as f:
return json.load(f)
return {
"version": 1,
"created_at": datetime.now(timezone.utc).isoformat(),
"total_cycles": 0,
"skills_created": 0,
"skills_archived": 0,
"configs_changed": 0,
"memories_added": 0,
"learned_items": [],
"in_progress": [],
"tracked_metrics": {
"avg_skill_score": [],
"health_score": [],
"model_stable_rate": [],
},
}
def save_state(state):
os.makedirs(LEARNER_DIR, exist_ok=True)
with open(STATE_FILE, "w") as f:
json.dump(state, f, indent=2, ensure_ascii=False)
def shell(cmd, timeout=10):
try:
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
return r.returncode, r.stdout.strip()[:500], r.stderr.strip()[:200]
except:
return -1, "", "timeout"
# ====== 采集经验 ======
def collect_experiences():
"""从各数据源采集近期经验"""
experiences = []
# 1. Daemon journal: 近期事件
jf = D + "/journal.jsonl"
if os.path.exists(jf):
with open(jf) as f:
for line in f:
try:
entry = json.loads(line)
ts = entry.get("timestamp", "")
# 只看最近24h
if ts:
try:
t = datetime.fromisoformat(ts)
if (datetime.now(timezone.utc) - t).total_seconds() > 86400:
continue
except:
pass
experiences.append({
"source": "daemon",
"type": entry.get("type", "unknown"),
"summary": entry.get("summary", ""),
"timestamp": ts,
})
except:
pass
# 2. Skill health: 技能质量趋势
if os.path.exists(SKILL_HEALTH):
with open(SKILL_HEALTH) as f:
try:
report = json.load(f)
s = report.get("summary", {})
experiences.append({
"source": "skill_health",
"type": "snapshot",
"summary": f"技能: {s.get('active',0)}活跃, 均分{s.get('avg_score',0)}, {s.get('needs_attention',0)}需关注",
})
except:
pass
# 3. Daemon context: 运行状态
cf = D + "/context.json"
if os.path.exists(cf):
with open(cf) as f:
try:
ctx = json.load(f)
experiences.append({
"source": "daemon_state",
"type": "state",
"summary": f"Daemon: {ctx.get('tick_count',0)}ticks, {ctx.get('solved_count',0)}已解决, {ctx.get('learned_count',0)}已学会",
})
except:
pass
# 4. 优化报告
if os.path.exists(OPT_REPORT):
with open(OPT_REPORT) as f:
try:
report = json.load(f)
experiences.append({
"source": "optimizer",
"type": "health",
"summary": f"健康分: {report.get('health_score', '?')}/100, 瓶颈: {len(report.get('bottlenecks', []))}",
})
except:
pass
return experiences
# ====== 模式提取 ======
def extract_patterns(experiences):
"""从经验中提取重复模式"""
patterns = []
# 按类型统计
type_counts = Counter(e["type"] for e in experiences)
# 告警模式
alerts = [e for e in experiences if e["type"] in ("alert", "process_down", "error")]
if len(alerts) >= 2:
patterns.append({
"type": "recurring_issue",
"confidence": min(len(alerts) * 20, 90),
"desc": f"近期出现 {len(alerts)} 次告警/异常",
"details": [a["summary"] for a in alerts[:3]],
"suggested_action": "检查看门狗日志,排查根因",
})
# 技能模式
skill_exps = [e for e in experiences if e["source"] == "skill_health"]
for s in skill_exps:
if "需关注" in s["summary"]:
# 提取数字
nums = re.findall(r'\d+', s["summary"])
if len(nums) >= 3 and int(nums[2]) > 50:
patterns.append({
"type": "skill_quality_gap",
"confidence": 80,
"desc": f"大量技能需要关注 ({nums[2]}个)",
"suggested_action": "运行 skill-manager.py archive 清理低分技能",
})
# 学习进度
solved_exps = [e for e in experiences if e["type"] in ("solve_auto", "learn", "solve_start")]
if solved_exps:
patterns.append({
"type": "learning_progress",
"confidence": 70,
"desc": f"近期解决了 {len(solved_exps)} 个问题/学会了新方案",
"suggested_action": "持续监控方案库的命中率",
})
return patterns
# ====== 差距分析 ======
def analyze_gaps(state):
"""分析能力差距"""
gaps = []
learned_names = {item["name"] for item in state.get("learned_items", [])}
# 检查已有系统的覆盖度
systems = {
"记忆": os.path.exists(HERMES + "/plugins/zhiyi/__init__.py") or os.path.exists(HERMES + "/skills/zhiyi"),
"技能管理": os.path.exists(HERMES + "/scripts/skill-manager.py"),
"优化": os.path.exists(HERMES + "/scripts/optimizer.py"),
"学习": os.path.exists(HERMES + "/scripts/learner.py"),
"配置保护": os.path.exists(HERMES + "/scripts/config-protector.sh"),
"ao团队": "npx" in os.popen("which npx 2>/dev/null || echo ''").read(),
"持久意识": os.path.exists(D + "/context.json"),
}
built = sum(1 for v in systems.values() if v)
total = len(systems)
coverage = built / total * 100
gaps.append({
"area": "system_coverage",
"coverage": f"{coverage:.0f}%",
"built": built,
"total": total,
"missing": [k for k, v in systems.items() if not v],
})
# 技能层面差距
if os.path.exists(SKILL_HEALTH):
with open(SKILL_HEALTH) as f:
try:
report = json.load(f)
except:
report = {}
s = report.get("summary", {})
gaps.append({
"area": "skill_quality",
"avg_score": s.get("avg_score", 0),
"d_count": s.get("grades", {}).get("D", 0),
"needs_attention": s.get("needs_attention", 0),
})
# 学习进度
gaps.append({
"area": "learning",
"items_learned": len(learned_names),
"cycles_completed": state.get("total_cycles", 0),
"in_progress": len(state.get("in_progress", [])),
})
return gaps
# ====== 学习计划 ======
def generate_plan(state, experiences, patterns, gaps):
"""生成下一步学习计划"""
plan = {
"generated_at": datetime.now(timezone.utc).isoformat(),
"immediate": [],
"short_term": [],
"long_term": [],
}
# 从差距生成学习项
for g in gaps:
if g["area"] == "skill_quality" and g.get("d_count", 0) > 20:
plan["short_term"].append({
"task": "清理D级技能",
"action": "skill-manager.py archive 批量归档低分技能",
"reason": f"{g['d_count']}个D级技能降低整体质量",
"effort": "20min",
})
if g["area"] == "learning" and g.get("items_learned", 0) == 0 and g.get("cycles_completed", 0) == 0:
plan["immediate"].append({
"task": "完成首次学习循环",
"action": "运行 learner.py learn 完成首次学习闭环",
"reason": "学习层刚建立,需要完成第一个循环验证",
"effort": "2min",
})
# 从模式生成学习项
for p in patterns:
if p["type"] == "skill_quality_gap" and not any(t["task"].startswith("清理") for t in plan["short_term"]):
plan["short_term"].append({
"task": "提升技能库质量",
"action": p["suggested_action"],
"reason": p["desc"],
"effort": "15min",
})
# 长期学习目标
long_term_topics = [
("家庭服务器互联", "连上192.168.123.11的Gitea/影音/照片服务"),
("语音交互", "部署STT模型实现语音输入"),
("本地LLM推理", "安装llama.cpp或vLLM跑本地模型"),
("持久意识增强", "让daemon能调用更多工具自主行动"),
]
learned_names = {item["name"] for item in state.get("learned_items", [])}
for topic, desc in long_term_topics:
if topic not in learned_names:
plan["long_term"].append({
"topic": topic,
"desc": desc,
"status": "not_started",
})
return plan
# ====== 执行学习 ======
def apply_learning(state, plan):
"""执行学习计划中的即时/短期项"""
results = []
for item in plan.get("immediate", []):
log(f" ▶ 执行: {item['task']}")
# 记录到学习记录
entry = {
"name": item["task"],
"type": "immediate",
"learned_at": datetime.now(timezone.utc).isoformat(),
"status": "completed",
"detail": item["action"],
}
state["learned_items"].append(entry)
state["total_cycles"] += 1
results.append({"task": item["task"], "result": "recorded"})
for item in plan.get("short_term", []):
log(f" 📋 计划: {item['task']} ({item['effort']})")
entry = {
"name": item["task"],
"type": "short_term",
"learned_at": datetime.now(timezone.utc).isoformat(),
"status": "planned",
"detail": item["action"],
}
state["in_progress"].append(entry)
results.append({"task": item["task"], "result": "planned"})
return results
# ====== 指标追踪 ======
def update_metrics(state):
"""更新跟踪指标"""
metrics = state.setdefault("tracked_metrics", {})
# 技能平均分趋势
if os.path.exists(SKILL_HEALTH):
with open(SKILL_HEALTH) as f:
try:
report = json.load(f)
metrics["avg_skill_score"].append({
"timestamp": datetime.now(timezone.utc).isoformat(),
"value": report.get("summary", {}).get("avg_score", 0),
})
except:
pass
# 健康分趋势
if os.path.exists(OPT_REPORT):
with open(OPT_REPORT) as f:
try:
report = json.load(f)
metrics["health_score"].append({
"timestamp": datetime.now(timezone.utc).isoformat(),
"value": report.get("health_score", 0),
})
except:
pass
# 模型稳定率趋势
mh = HERMES + "/model-health.json"
if os.path.exists(mh):
with open(mh) as f:
try:
data = json.load(f)
stable = data.get("stable", 0)
total = data.get("total_models", 1)
metrics["model_stable_rate"].append({
"timestamp": datetime.now(timezone.utc).isoformat(),
"value": round(stable / max(total, 1) * 100, 1),
})
except:
pass
# 限制历史长度
for key in metrics:
metrics[key] = metrics[key][-50:] # 保留最近50个
# ====== 命令入口 ======
def cmd_reflect():
experiences = collect_experiences()
patterns = extract_patterns(experiences)
print(f"\n{'='*50}")
print(f" 学习反思 | {len(experiences)}条经验")
print(f"{'='*50}")
print(f"\n📋 近期经验 ({len(experiences)}条):")
for e in experiences[-10:]:
print(f" [{e['source']}] {e['summary'][:80]}")
if patterns:
print(f"\n🔍 发现 {len(patterns)} 个模式:")
for p in patterns:
bar = "" * (p["confidence"] // 10) + "" * (10 - p["confidence"] // 10)
print(f" {bar} {p['confidence']}% {p['desc'][:60]}")
print(f"{p['suggested_action']}")
else:
print(f"\n✅ 未发现明显模式")
return experiences, patterns
def cmd_learn():
state = load_state()
experiences = collect_experiences()
patterns = extract_patterns(experiences)
gaps = analyze_gaps(state)
plan = generate_plan(state, experiences, patterns, gaps)
log(f"开始学习循环 #{state['total_cycles'] + 1}")
results = apply_learning(state, plan)
update_metrics(state)
save_state(state)
log(f"完成: {len(results)}")
for r in results:
print(f" {r['task']}: {r['result']}")
return state
def cmd_plan():
state = load_state()
experiences = collect_experiences()
patterns = extract_patterns(experiences)
gaps = analyze_gaps(state)
plan = generate_plan(state, experiences, patterns, gaps)
print(f"\n{'='*50}")
print(f" 学习计划")
print(f"{'='*50}")
learned = len({item["name"] for item in state.get("learned_items", [])})
in_progress = len(state.get("in_progress", []))
print(f"\n📊 进度: 已学{learned}项 / 进行中{in_progress}项 / 共{state['total_cycles']}")
if plan["immediate"]:
print(f"\n⚡ 立即执行:")
for i in plan["immediate"]:
print(f" {i['task']}: {i['reason']} ({i['effort']})")
if plan["short_term"]:
print(f"\n📋 短期计划:")
for i in plan["short_term"]:
print(f" {i['task']}: {i['reason']} ({i['effort']})")
if plan["long_term"]:
print(f"\n🎯 长期目标:")
for i in plan["long_term"]:
icon = "" if i["status"] == "completed" else ""
print(f" {icon} {i['topic']}: {i['desc']}")
return plan
def cmd_status():
state = load_state()
print(f"\n{'='*50}")
print(f" 学习状态")
print(f"{'='*50}")
print(f"\n📊 统计:")
print(f" 学习循环: {state['total_cycles']}")
print(f" 已学技能: {state['skills_created']}")
print(f" 归档技能: {state['skills_archived']}")
print(f" 配置变更: {state['configs_changed']}")
print(f" 记忆添加: {state['memories_added']}")
print(f"\n📈 趋势:")
metrics = state.get("tracked_metrics", {})
for key, values in metrics.items():
if values:
latest = values[-1]["value"]
trend = ""
if len(values) > 1:
prev = values[-2]["value"]
diff = latest - prev
trend = f" ({'+' if diff > 0 else ''}{diff:.1f})"
print(f" {key}: {latest}{trend} (共{len(values)}个采样)")
learned = state.get("learned_items", [])
if learned:
print(f"\n📚 已学 ({len(learned)}项):")
for item in learned[-5:]:
print(f" [{item['type']}] {item['name']} ({item['status']})")
in_progress = state.get("in_progress", [])
if in_progress:
print(f"\n🔄 进行中:")
for item in in_progress:
print(f" {item['name']}")
long_term = ["家庭服务器互联", "语音交互", "本地LLM推理", "持久意识增强"]
learned_names = {item["name"] for item in learned}
not_learned = [t for t in long_term if t not in learned_names]
if not_learned:
print(f"\n🎯 待探索:")
for t in not_learned:
print(f"{t}")
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "status"
if cmd == "reflect":
cmd_reflect()
elif cmd == "learn":
cmd_learn()
elif cmd == "plan":
cmd_plan()
elif cmd == "status":
cmd_status()
else:
print(f"未知: {cmd}")
print("可用: reflect, learn, plan, status")